zoukankan      html  css  js  c++  java
  • ES分布式搜索引擎架构原理

    ES是啥?

    ES就是一个开源的搜索引擎    也是一个分布式文档数据库

    可以在极短的时间内存储、搜索和分析大量的数据。

    ES基本属性:

    字段

    ES中,每个文档,其实是以json形式存储的。而一个文档可以被视为多个字段的集合。

    映射

    每个类型中字段的定义称为映射。例如,name字段映射为String

    索引

    索引是映射类型的容器。 一个ES的索引非常像关系型世界中的数据库,是独立的大量文档集合

    ES各属性对应关系数据库

    关系数据库 ->   表名                ->  表结构    ->  一条记录       -> 一个字段

    ES        -> 索引index - 类型type(1-n) -> 映射apping  ->  文档document  -> 字段field 

    ES索引简单原理: 采用倒排索引

    Term(单词):一段文本经过分析器分析以后就会输出一串单词,这一个一个的就叫做Term

    Term Dictionary(单词字典):顾名思义,它里面维护的是Term,可以理解为Term的集合

    Term Index(单词索引):为了更快的找到某个单词,我们为单词建立索引

    Posting List(倒排列表):以前是根据ID查内容,倒排索引之后是根据内容查ID,然后再拿着ID去查询出来真正需要的东西。

    如果类比现代汉语词典的话,那么Term就相当于词语,Term Dictionary相当于汉语词典本身,Term Index相当于词典的目录索引)

    通过 单词索引 找到单词在单词字典中的位置,通过单词字典进而找到Posting List倒排列表,有了倒排列表就可以根据ID找到文档.

    (本质:通过单词找到对应的倒排列表,根据倒排列表中的倒排项进而可以找到文档记录)

    查询结果分析:

    took:本次操作花费的时间,单位为毫秒。

    timed_out:请求是否超时

    _shards:说明本次操作共搜索了哪些分片

    hits:搜索命中的记录

    hits.total : 符合条件的文档总数 hits.hits :匹配度较高的前N个文档

    hits.max_score:文档匹配得分,这里为最高分

    _score:每个文档都有一个匹配度得分,按照降序排列。

    _source:显示了文档的原始内容。

    ES聚合

    桶在概念上类似于 SQL 的分组(GROUP BY),而指标则类似于 COUNT() SUM() MAX() 等统计方法

    ES分布式架构原理

    shard就是ES索引存储具体数据的地方,一个索引对应多个shard

    多个shard存储在不同的机器上

    每个shard只放索引的一部分数据

    每个shard的副本replic放在其他机器上(shard的主体primary和副本replic分开存) 保证了一定程度的高可用

    ES写入数据原理:

    从客户端写入到shard的全过程:

    1. 客户端随机找一个ES集群节点当作协调节点,写数据
    2. 协调节点将数据 根据doc id的哈希路由,写入分配的shard并同步到从shard
    3. shard将数据写入内存buffer
    4. 内存buffer每一秒钟refresh一次将数据刷进OScache缓存,一份sagementfile,一份translog日志 (translog日志的作用 有点类似于redis的RDB文件,用于ES宕机恢复数据)  
    5. OScache缓存每5秒中刷入磁盘的translog日志文件
    6. 30分钟执行一次flush操作,执行一次commit,强制将内存buffer和OScache数据刷入新创建一个sagmentfile文件(磁盘) 落地到磁盘,多个sagementfile会有merge操作。

     数据搜索主要是从OScache中拿的,所以刚写入shard的数据要一秒后才能读到。

    写入一条document数据时会产生一个doc id,查的时候根据doc id进行哈希,路由到对应的shard  (doc id默认随机分配,也可以手动指定,例如订单id)

     

    ES删除数据原理:

    1.把被删除数据写入.del文件(磁盘),被.del文件标识的被认为已删除

    2.sagmentfile文件过多时ES会产生merge操作,将多个segmentfile合成一个,如果.del标识了删除的数据 merge后不会产生在新的segmentfile.

     

     

    ES根据doc id读取数据过程:

    选ES集群的任一台机器当作协调节点

    协调节点根据要查找的doc id 哈希路由到对应的节点的shard,

    查到结果返还给协调节点,协调节点返还给客户端。

     

     

    ES检索数据过程:

    客户端发送读取请求到任一台机器当作协调节点,协调节点发送给所有机器所有shard

    每个shard都会返回结果,协调节点拿到所有shard返回的匹配的结果,再次筛选最匹配的那些document,返还给客户端.

    每次查询完都会暂时将数据存入cache中,再次

     

     

    ES在数据量很大的情况下如何优化查询(保证搜索性能):

    主要思想:最大限度利用cache的高效率进行查询(磁盘查询效率太低)

    (1) 合理利用ES+Hbase/MySQL结合来查询(Hbase对海量数据在线存储)

    把重要的检索字段存成ES索引 (例如gid,订单id,订单金额,订单时间,订单说明...)

    其余不作为检索条件的字段存在Hbase/Mysql

    ES根据条件快速查出gid

    再用gidHbase/MySQL查出全部字段

    (2) 数据预热

    每隔一段时间将热门关键数据写个程序去查一下,主动从磁盘写入到cache

    (3) 冷热分离

    水平拆分,将很热的数据单独写进一个索引,将冷数据热数据拆成两个单独索引,放在不同机器上。

    (5) 单索引查,不要join多个索引查,效率非常低 (想办法导入ES的时候直接将要Join的表直接导成一个索引)

    (4) 分页性能 (深度分页,性能越差)

    查第100页的10条数据/1001-1010,由于ES分布式的存储,必须去每台机器每个shard都查1000条,全部返回到协调节点,合并排序后再取出1001-1010

    解决:合理利用Scroll游标

    scroll会一次性获取所有数据的快照,每次翻页通过游标移动获取下一页,分页性能大很多。、

    scroll不能乱跳,只能顺序向下翻

     

     

    ES生产集群部署架构

    模板:每个索引的数据大概有多少,每个索引大概分多少shard

    ES集群部署5台机器,每台664G,总内存320G,分8个shard

    ES集群每天增量2000万条,大约500M,每月大约6亿条 15G数据

     

     

    ES  Java-API使用(仅供参考)

        模板:
         Query:
        SearchRequest searchRequest = new SearchRequest("表名");
        searchRequest.types("doc");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();


        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termQuery( ?, ?));
        boolQueryBuilder.mustNot(QueryBuilders.termQuery( ?, ?));
        boolQueryBuilder.filter(QueryBuilders.rangeQuery( ?, ?).get(start).let(end));
        boolQueryBuilder.should(QueryBuilder.termsQuery( ?, ?));

        QueryBuilders.matchQuery("user", "kimchy");
        QueryBuilders.termsQuery("", "");
        QueryBuilders.rangeQuery("", "");

        searchSourceBuilder.query(boolQueryBuilder);
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(10);

         aggregation:
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("count数量聚合名").field("字段名").size(1000);
        aggregation.subAggregation(); //拼接新聚合
        aggregation.subAggregation(AggregationBuilders.avg("平均聚合").field("字段名"));

        AggregationBuilders.terms("count数量聚合").field("字段名").size(1000);
        AggregationBuilders.cardinality("唯一的数量").field("字段名");
        AggregationBuilders.sum("求和").field("字段名");

        searchSourceBuilder.aggregation(aggregation);


        searchRequest.source(searchSourceBuilder);


         SearchResponse:
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            logger.error("查询??", e);
            throw new CrmRuntimeException(ErrorCodeEnum.COMMON_FAIL, "查询出错");
        }
        SearchHits searchHits = searchResponse.getHits();
        SearchHit[] hits = searchHits.getHits();

        //Hits结果
        for (int i = 0; i < searchHits.length; i++) {
            Map<String, Object> sourceAsMap = searchHits[i].getSourceAsMap();
            UserDo userDo = new UserDo();
            userDo.setuCellphone(StringUtil.objectToString(sourceAsMap.get("u_cellphone")));
            userDo.setNickName(StringUtil.objectToString(sourceAsMap.get("nick_name")));
            int gid = StringUtil.objectToInteger(sourceAsMap.get("gid"));
            int number = gid + NUMBER_OFFSET;
            userDo.setNumber(number);
            userDo.setGid(gid);
            userDo.setAllSubmit(StringUtil.objectToInteger(sourceAsMap.get("all_submit")));
            userDo.setAvatarUrl(StringUtil.objectToString(sourceAsMap.get("avatar_url")));
            userDo.setVipType(StringUtil.objectToInteger(sourceAsMap.get("vip_type")));
            result.add(userDo);
        }

         } catch(
             Exception e)

         {
        log.error("全局搜索失败", e);
        throw new CrmRuntimeException(ErrorCodeEnum.COMMON_FAIL, "全局搜索失败");
            }

    //聚合结果
    Aggregations aggregations = searchResponse.getAggregations();
    Terms terms = aggregations.get("signin_rel_count");     //接受聚合结果
    List<? extends Terms.Bucket> buckets = terms.getBuckets();
    if(CollectionUtils.isNotEmpty(buckets))

    {
        for (Terms.Bucket bucket : buckets) {
            String salesmanId = (String) bucket.getKey();
            long docCount = bucket.getDocCount();
               ....
            Cardinality userCountAggregation = bucket.getAggregations().get("count");       //返回
            Terms groupBySalesgrpAggregations = bucket.getAggregations().get("group_by_salesgrp");   //获取bucket下的聚合函数

            Sum sumDiscountAggregation = groupBySalesgrpBucket.getAggregations().get("sum_discount");
            BigDecimal discount =
                    new BigDecimal(sumDiscountAggregation.getValue()).setScale(2, RoundingMode.HALF_UP);
        }


    }

     

  • 相关阅读:
    android学习(2) 多线程的理解
    activity学习(1) 生命周期理解
    webkit.net使用方法日记
    微信小程序入门一
    ES入门笔一
    node八-核心模块、包
    require、缓存
    node七-required、缓存
    Node六-模块化
    Node笔记五-进程、线程
  • 原文地址:https://www.cnblogs.com/ttaall/p/13235833.html
Copyright © 2011-2022 走看看