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);
    }


}

 

posted @ 2020-07-04 17:38  六小扛把子  阅读(2737)  评论(0编辑  收藏  举报