第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群

数据聚合

帮助我们对海量的数据做统计和分析,结合kibana还可以形成可视化的图形报表

聚合的种类

聚合的字段一定是不分词的,不能是text的

比如说按照酒店数据按照品牌做分组,这个就属于桶的聚合

按照品牌分组后想算算不同品牌的 酒店的价格的平均值,或最大最小值怎么样--度量的聚合

算完平均值,想给排序,或者看看不同品牌平均值的最大最小值--这就是对度量结果再次聚合了就是管道了

除了这几种还有很多聚合的种类,这里只学习常用的

总结

聚合就是对索引库的数据做统计、分析、计算 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第1张图片

DSL实现聚合

aggs就是聚合的一个函数了

size是分页值,给0,显示的文档数据就是0条了,因为我的目的是看聚合,而不是看文档

聚合里的size如果不指定的话默认是10,就是控制显示的结果,比如说聚合结果又100多种,但是设置了这个size就是只显示前10条数据

三要素:聚合名称、聚合类型、聚合字段

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第2张图片

聚合是对桶的聚合,是个数组,将来有很多个桶,默认排序规则是倒序排序 ,可以自定义排序规则 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第3张图片

修改排序规则 

 这里我们的聚合名称是brandAgg,将来如果想要定义多个聚合,就再加就好了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第4张图片

 可以限定文档的范围,只需要添加查询条件,我们加了酒店价格的范围,那么聚合范围也就少了,不用全查

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第5张图片

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第6张图片

DSL实现Metrics聚合(度量聚合)

聚合的嵌套,我们先对品牌做聚合,然后再他的基础上在对评分做聚合,评分的聚合aggs在第一个aggs里边

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第7张图片

 聚合的嵌套,在第一个统计聚合的里边加平均值聚合,这样桶里的数据就多了好多 

我们再对得出结果的平均值做个排序,看看哪个酒店的评价最高

对桶里的数据做排序,以前是对_count排序,现在是对平均值排序

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第8张图片

 注意,这种度量聚合嵌套,是在桶里边,定义的名字里边做的度量聚合

RestAPI实现聚合

request.source代表的是最大的json了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第9张图片

brand_agg当初我们用的就是terms类型,所以返回值也就是terms类型,根据聚合名称获取聚合结果 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第10张图片

 逐层解析json就可以了,我们可以先打印response

@Test
    void aggTest1() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(10));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 解析结果
        Aggregations aggregations = response.getAggregations();
        // 根据聚合名称获取聚合结果,本来就terms类型,用terms接收那就
        // 根据brand_agg去获取桶
        Terms terms = aggregations.get("brandAgg");
        // 获取数组桶的内容
        List buckets = terms.getBuckets();
        // 遍历每一个桶位
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println(keyAsString);
        }
    }

service 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第11张图片

 实现类

 利用command+option+m封装重复代码,使得更优雅

@Override
    public Map> filters() {
        // 查出城市 city, 品牌 brand, 星级 startName
        try {
            SearchRequest request = new SearchRequest("hotel");
            // 针对多个字段做聚合,封装一下,就优雅了
            buildAggregation(request);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 解析返回结果信息
            Aggregations aggregations = response.getAggregations();
            return buildResponse(aggregations);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
    private void buildAggregation(SearchRequest request) {
        request.source().aggregation(
                AggregationBuilders.terms("cityAgg").field("city")
        ).aggregation(
                AggregationBuilders.terms("brandAgg").field("brand")
        ).aggregation(
                AggregationBuilders.terms("startNameAgg").field("starName")
        );
    }
    
    private Map> buildResponse(Aggregations aggregations) {
        Map> map = new HashMap<>();
        List brandAggList = getAggByName(aggregations, "brandAgg");
        map.put("品牌", brandAggList);
        List cityAggList = getAggByName(aggregations, "cityAgg");
        map.put("城市", cityAggList);
        List startAggList = getAggByName(aggregations, "startNameAgg");
        map.put("星级", startAggList);
        return map;
    }

    private List getAggByName(Aggregations aggregations, String params) {
        // 分别获取
        Terms cityAgg = aggregations.get(params);
        List buckets = cityAgg.getBuckets();
        List list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            list.add(keyAsString);
        }
        return list;
    }

利用单元测试注册service 然后就可以调用了,因为没有参数更方便测试,不用postMan就可以

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第12张图片

对接前端接口

前端页面向服务端发送请求,查询品牌、城市、星级等字段的聚合结果

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第13张图片

修改原来的filters接口,将其添加一个查询query即可,这个query得和之前的查询一样,直接用就可以了

也就是说我们在做聚合的时候是要加一些过滤条件的,去限制聚合的范围,条件就是查询的条件一样的,到时候记得把查询的条件封装出来,复用

@Override
    public Map> filters(RequestParams requestParams) {
        // 查出城市 city, 品牌 brand, 星级 startName
        try {
            SearchRequest request = new SearchRequest("hotel");
            
            request.source().size(0);
            // 添加查询功能
            buildQueryBool(requestParams, request);
            
            
            
            // 针对多个字段做聚合,封装一下,就优雅了
            buildAggregation(request);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // json 有多个,那就是 jsonArr
            Aggregations aggregations = response.getAggregations();
            return buildResponse(aggregations);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

自动补全

根据用户输入的部分信息去自动补全或者提示 用户真正想要搜索的内容,是搜索的必备功能

拼音分词器

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第14张图片

GitHub - medcl/elasticsearch-analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin. 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第15张图片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第16张图片

按照拼音分词器 分词,我这里装了拼音分词器es起不来,就先不装了

拼音分词器有几个问题,第一,把一句话都形成了拼音,第二把每个字都形成了一个拼音,第三个就是没有汉字只有拼音 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第17张图片

自定义分词器

就是说由ik分词器分词完了交给拼音分词器就好了,因为拼音分词器不会分词,我交给你分啥词不就行了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第18张图片

 修改拼音分词器的配置。去拼音分词器官网上查看配置,比如如下两个,将其默认值改了即可,只要全拼,不要单个拼的

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第19张图片

 要不要保留中文,为true,filter就是对分好的词条做进一步处理,比如拼音第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第20张图片

下边这个name字段就能用我们的自定义分词器了去分词了 

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": {
        "py": { 
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,# 要不要保留中文
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
    mappings{
        properties:{
            "name":{
                "type": "text",
                "nalyzer": "my_analyzer"
             }
        }
    }
}

测试:

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第21张图片

插入文档 

POST /test/_doc/1
{
  "id": 1,
  "name": "狮子"
}
POST /test/_doc/2
{
  "id": 2,
  "name": "虱子"
}

GET /test/_search
{
  "query": {
    "match": {
      "name": "掉入狮子笼咋办"
    }
  }
}

查看结果发现把同音字的也搜索出来了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第22张图片

那是因为拼音分词器适合在创建倒排索引的时候使用而不是搜索的时候使用

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第23张图片

因为在搜索时也用了拼音分词器,那么久都凑所出来了,这不是我们希望的。

搜索的时候如果用户输入的是中文,那么久应该用中文去搜索,用户输入的是拼音,那么才拿拼音去搜索

也就是说创建索引、和搜索的时候要用不同的分词器,给它分开

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第24张图片

搜索结果如下,搜索发现成功了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第25张图片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第26张图片

自动补全查询

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第27张图片

 创建索引库,添加数据,数组的方式为1条,当我搜索S的时候会补全sony,当我们输入W的时候会补全WH_1000XM3,品牌和产品信息

PUT test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}
// 示例数据
POST test/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}
// 自动补全查询
POST /test/_search
{
  "suggest": {
    "title_suggest": { // 七个名字
      "text": "s", // 要输入查询的关键字,就是文档的前缀
      "completion": {
        "field": "title", // 补全字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第28张图片

 

查看酒店数据结构

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第29张图片

settings就是用来定义索引库的分词器的

定了了两个自定义分词器,一个是需要分词的加拼音,一个是用来做自动补全的不需要分词,注意需要新添加个一 个字段,再这个字段里引用才可以

// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {// 需要分词的
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": { // 自动补全的
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",// 创建索引时用这个,可以给分词 分词转拼音
        "search_analyzer": "ik_smart", // 搜索的时候用传统的分词器
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer", // 同理,再创建索引时用这个
        "search_analyzer": "ik_smart" // 在搜索时 用这传统的
      },
      "suggestion":{ // 新添加了suggestion这个字段,是用来做自动补全的,不分词直接转拼音
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第30张图片

重新导入批量数据,我们发现suggestion里就有了数据了品牌和商圈的数据 

我们发现suggesion里有/这种符号,其实是一个商圈,但是我们只能搜索j出来补全,搜索w是出不来的,我们可以对其切割一下

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第31张图片

 也就是说bussiness商圈有可能包含多个以斜杠分开的,这种情况给它做个切割,切割完后再扔进去

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第32张图片

再次执行批量插入操作,就会发现成功了  第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第33张图片

 查询,就会发现以h开头已经有自动补全了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第34张图片

 以上就实现了以拼音自动补全的功能

 RestApi实现

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第35张图片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第36张图片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第37张图片

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第38张图片

运行就会得到前缀是h的补全结果了 

 @Test
    void suggestionTest() throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().suggest(new SuggestBuilder().addSuggestion(
                "mySuggestion",
                SuggestBuilders.completionSuggestion("suggestion")
                .prefix("h")
                .skipDuplicates(true)
                .size(10)
        ));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Suggest suggest = response.getSuggest();
        // 泛型很长,其实就是CompletionSuggestion这个类型
        // Suggest.Suggestion> f = suggest.getSuggestion("f");
        CompletionSuggestion mySuggestion = suggest.getSuggestion("mySuggestion");
        // 得到结果后,我要去options,是个集合
        List options = mySuggestion.getOptions();
        // 取的text,是个对象,转成对象
        for (CompletionSuggestion.Entry.Option option : options) {
            String text = option.getText().toString();
            System.out.println(text);
        }
    }

实现酒店搜索框自动补全

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第39张图片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第40张图片

只需要把测试类那个拿过来,然后把结果放到集合里就可以了

集合直接就可以指定大小,因为我们知道的 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第41张图片

输入x 就会发现有自动补全了 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第42张图片

 我们可以直接用拼音去做搜索,比如xs

就会 出现这个结果等 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第43张图片

以上我们就实现了拼音搜索和拼音自动补全的相关功能了,当然也可以输入中文也会有自动补全的 

数据同步

mysql与ES数据同步问题

数据同步思路分析

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第44张图片

第一种方式:demo微服务暴露对外接口,admin服务去调用demo服务,是依次执行的,也就是说写入到数据库后才能去调接口,调接口才能去更新,更新完成了返回,数据耦合,这就是同步调用

原来是写完就结束了,现在是得调用demo的方法,更新ES,就是说我么写入mysql后,还得等待更新es的返回,太耦合了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第45张图片

方案二:异步通知,利用mq的方式

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第46张图片

方案三:监听binlog,默认是关闭的,如果开启后,mysql在做些的操作时,都会讲记录记录到binlog里;;利用canal这样的中间件去监听binlog ,一旦binlog发生变化就去通知对应的微服务

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第47张图片

因为要开启mysql的binlog对mysql的压力就增加了,并且还要引入新的中间件

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第48张图片

实现ES与数据库数据同步

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第49张图片

启动项目8099

需要几个队列呢?

增、删、改 都是事件,但是增和改都是写操作,有id就改,无就增,可以是一个业务,因此需要两个队列就行,也就是说消息的类型就是两类消息 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第50张图片

两个队列,一个监听增、改的消息,一个监听删除的消息 

消费者引入rabbitmq依赖,配置yaml

声明我们最好定义成一个常量

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第51张图片

 可以定义bean,也可以用声明的方式

消息发送者修改代码

@Resource
    private RabbitTemplate rabbitTemplate;
    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel){
        hotelService.save(hotel);
        // 发送消息,指定交换机、key,对象, 这里我们不发整个对象,只发送一个id就行,那边可以查出来
        // 省内存
        rabbitTemplate.convertAndSend(MQConstants.HOTEL_EXCHANGE, MQConstants.HOTEL_INSERT_KEY,  hotel.getId());

    }

    @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);
        rabbitTemplate.convertAndSend(MQConstants.HOTEL_EXCHANGE, MQConstants.HOTEL_INSERT_KEY,  hotel.getId());
    }

    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        hotelService.removeById(id);
        rabbitTemplate.convertAndSend(MQConstants.HOTEL_EXCHANGE, MQConstants.HOTEL_DELETE_KEY, id);

    }

demo里 新建一个包,mq.HotelListener,声明方法同时指定队列名字

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第52张图片

我这里是直接用注解声明的方式了,不用上边这种bean注入的方式,有点麻烦

这消费者多简单,不用单独去写bean了,队列、交换机、key,还有绑定关系就都指定了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第53张图片

消费者代码如下

监听控制台

@Resource
    private IHotelService hotelService;
    /**
     * 新增或修改业务
     * @param id
     * @return
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = MQConstants.HOTEL_INSERT_QUEUE),
            exchange = @Exchange(name = MQConstants.HOTEL_EXCHANGE, type = ExchangeTypes.TOPIC),
            key = MQConstants.HOTEL_INSERT_KEY
    ))
    public String listenerHotelInsertOrUpdate(Long id) {
        hotelService.insertById(id);
    }

    /**
     * 删除业务
     * @param id
     * @return
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = MQConstants.HOTEL_DELETE_QUEUE),
            exchange = @Exchange(name = MQConstants.HOTEL_EXCHANGE, type = ExchangeTypes.TOPIC),
            key = MQConstants.HOTEL_DELETE_KEY
    ))
    public String listenerHotelDelete(Long id) {
        hotelService.deleteByIdHotel(id);
    }

 业务层

@Override
    public void insertById(Long id) {
        try {
            Hotel hotel = getById(id);
            HotelDoc hotelDoc = new HotelDoc(hotel);
            IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteByIdHotel(Long id) {
        try {
            DeleteRequest request = new DeleteRequest("hotel", id.toString());
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

 酒店管理上修改一个价格,查看rabbitMQ发现确实接收到了,去黑马旅游网查询发现已经改了

由592修改成5920

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第54张图片

 找到队列并且点进去发现有个三角

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第55张图片

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第56张图片

 删除也是同样的,实现了异步通讯这样一个效果

集群

比如说索引库分成三个片,每个片有一个备份,是交叉备份,比较安全 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第57张图片

搭建ES集群

利用docker容器来模拟三台服务器,因为docker容器是相互隔离的嘛,所以是没有问题的

我们会在单机上利用docker容器运行多个es实例来模拟es集群。不过生产环境推荐大家每一台服务节点仅部署一个es的实例。

部署es集群可以直接使用docker-compose来完成,但这要求你的Linux虚拟机至少有4G的内存空间

4.1.创建es集群

首先编写一个docker-compose文件,内容如下:

version: '2.2'
services:
  es01:
    image: elasticsearch:7.12.1
    container_name: es01 --容器的名称
    environment:
      - node.name=es01 -- 节点的名称
      - cluster.name=es-docker-cluster --集群名称一样es就会自动把他们组成一个集群
      - discovery.seed_hosts=es02,es03 --集群的另外两个ip地址,我们用的docker容器,容器内互联
      - cluster.initial_master_nodes=es01,es02,es03--初始化的主节点
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"--jvm的最小最大内存
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: elasticsearch:7.12.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data02:/usr/share/elasticsearch/data
    ports:
      - 9201:9200
    networks:
      - elastic
  es03:
    image: elasticsearch:7.12.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic
    ports:
      - 9202:9200
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local

networks:
  elastic:
    driver: bridge

es运行需要修改一些linux系统权限,修改/etc/sysctl.conf文件

vi /etc/sysctl.conf

添加下面的内容:

vm.max_map_count=262144

然后执行命令,让配置生效:

sysctl -p

出现这么一行,证明配置生效了 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第58张图片

通过docker-compose启动集群:

docker-compose up -d

发现docker容器都启动了 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第59张图片

4.2.集群状态监控

kibana可以监控es集群,不过新版本需要依赖es的x-pack 功能,配置比较复杂。

这里推荐使用cerebro来监控es集群状态,官方网址:https://github.com/lmenezes/cerebro

课前资料已经提供了安装包:

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第60张图片

解压即可使用,非常方便。

解压好的目录如下:

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第61张图片

进入对应的bin目录:

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第62张图片

双击其中的cerebro.bat文件即可启动服务。监听的是9000这个端口

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第63张图片

访问http://localhost:9000 即可进入管理界面:

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第64张图片

输入你的elasticsearch的任意节点的地址和端口,点击connect即可:

比如9200或者9201或者9202

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第65张图片

最上边绿色的大条,代表集群处于绿色(健康状态)。每个节点的内存占用情况、磁盘空间、cpu占用情况等;实心的⭐️代表主节点,空心的⭐️代表从节点,以后可以成为主节点。

集群创建完了,将来我们创建索引库的时候,索引库就可以分片,并且放到不同节点上

4.3.创建索引库

1)利用kibana的DevTools创建索引库

在DevTools中输入指令:

 创建索引库后要想指定分片信息 只需要指定settings,我们之前在settings里配置过拼音分词器,还可以配置分片信息和副本信息,

副本信息就是给每个片加个副本,就是加个备份

PUT /itcast
{
    #就是这个settings
  "settings": {
    "number_of_shards": 3, // 分片数量
    "number_of_replicas": 1 // 副本数量
  },
  "mappings": {
    "properties": {
      // mapping映射定义 ...
    }
  }
}

用集群的创建索引库

点击create创建,索引库名字,几个分片,几个备份 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第66张图片

3个片,每个片有一个副本就是2份,所以是3*2 总共是6个片,其中实心的正方形是主分片,虚线框的是副本分片就是拷贝的一份

我们会发下每个分片的备份一定是在不同的机器上,确保了如果有任一一台机器宕机了 ,那么它的备份依然还在保存着避免出现数据故障

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第67张图片

最后一个节点是协调节点,就是路由加负载均衡 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第68张图片

事实上不用我们管,es的节点同时具备这四种,但是再实际开发中不能让一个节点干这四种事

比如主节点,管理

数据节点,做存储,对计算机硬件比较高

协调节点,对磁盘没什么要求

不同节点对硬盘要求是不一样的

我们要不同的节点去干不同的事,配置参数给他,node.master。。。就是一个主节点

如下图有三个协调节点,N个数据节点和3个备选主节点

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第69张图片

候选主节点,这就是主从的结构 ,会有一个问题,脑裂

集群脑裂问题

比如说网络故障,不是节点宕机了,是网络问题导致 2 3节点与node1节点连不上了,但是node1和其他部分数据节点还是可以连通的,node2 node3和其他部分节点也是连通的,

node2和node3认为1没了,他们俩个又选出一个主,这就出现了两个主,这样就会导致将来数据crud一部分会和node1,另一部分会和node3,一旦网络恢复,就会出现两边数据不一致的情况

为了避免,(3+1 )/2=2,

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第70张图片

 一旦有网络问题,node1还想当主,它就只有它自己的1票

而node2和node3选node3,有两票,他就升为大哥了,node1就降级从了

所以节点最好是基数,1,3,5等

我们用的事7.12就不会有这种问题了,默认配置了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第71张图片

集群分布式存储

协调节点是做请求路由的,当一个增删改查请求到协调节点的时候,他会把请求路由到一个数据节点,完成对应的业务操作

尝试插入几条数据,直接模拟请求插入节点是9200主

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第72张图片

我在9200插入3条,在9200查到3条数据,很合理吧 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第73张图片

 结果发现9201和9202都可以查到3条数据,那么问题来了,我到底把数据存到了那个分片节点呢?

用explane可以看见我们的数据到底存到了哪里,如下图id=3的在1号片

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第74张图片

 我们发现每个片上,刚好都插入了一条数据,我只插入了3条数据,而且我是在9200插入的,为什么呢?

说明协调节点确实是工作了

保存到不同的片上是好的,保证了数据负载均衡

hash运算,hash对一个数字做运算,然后对分片数量做取余运算,比如说我的分片是3

hash运算后对3取余,结果只会是0、1、2,

id在变那么算出的数也就再变,就均衡的负载到了每个分片了

将来查询也是,拿到id,做hash运算,然后取余,然后就去哪个分片上找,就能找到该文档

可是,如果此时有人把分片改了,那么用这个算法就找不到了,

因此:索引库一旦创建,分片数量一定不能改,一改就找不到了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第75张图片

不管是新增还是查询,只要是基于id,那么都是这个算法 

分析新增

比如说我们要做新增,3个集群有三个节点

深蓝色的是主分片,每个主分片都需要有副本,浅蓝色的就是副本分片

比如说有个请求,id是1,请求到达了node1,那么这个node1就会充当协调节点的一个角色,它会hash运算得到结果是2,那么他就把请求路由到2号分片,node3一看,哦,请求是来找我的呀,好吧那就我来新增,于是数据就新增到node3分片上了;写完后一看我是主呀,那我就会把数据同步给从分片,从分片一看,哦,从分片在node2(R-2)上呢呀,于是就会把数据同步过去了

这样,主分片和从分片就都保存了该条数据了。他们俩再把相应结果返回给协调分片就是node1,node1一看他们都存好了,就会把响应返回给用户了

增删改操作都是这套流程,只要是根据id操作的

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第76张图片

集群分布式查询

不知道id的查法,有两个阶段:

分散和聚集阶段;

有id就是算法查询 

但是我们刚刚是用match_all查询的,我不知道id,也不知道要查询的数据在哪个分片上 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第77张图片

 这种情况下协调节点又是怎么操作的呢?

分散阶段:协调节点会把把请求分发给每一个分片,我没有id没法确定去哪个分片查,那么只去每个分片都查一次咯,每个分片都查完后,结果就会全了,这就是分散阶段

聚集阶段:每个节点查到的数据都会返回给协调节点,协调节点会去汇总他们查到的结果

注意:协调节点可以是主节点的任意一个,也可以单独摘出一个节点作为协调节点,所以用虚线框表示

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第78张图片

 正是因为有协调节点的存在,我们访问9200有三条结果,就明白为什么了,虽然数据在9201,9200,9202上都有,很公开,你随便访问,因为我有协调

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第79张图片

集群故障转移

主节点故障转移

主节点是node1,node2和node3是候选主节点

比如说node1挂了,node2和node3就要选一个主出来,比如说选了node2

node2就要来看看集群的健康状态,分片的状态,因为node1挂了,他发现p-1有主分片,没有副本分片,p-2有主有副本分片,p-0只有副本分片没有主分片

他就发现1号片和0号片是不安全的,因为只有一份了,这是不安全的,这个时候集群的状态就不是健康的了,而是处于危险的边缘。主节点强迫症犯了,忍不了了,于是他就回去看看挂了的节点上边有什么分片,然后就会把这上边的分片迁移到健康的节点里去,从而确保任何一个分片都有两份,一个主一个副本,这样我们的集群就又处于健康的状态了,这就是故障转移

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第80张图片

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第81张图片

 故障转移两个方面:

  • 第一:主节点挂了,再选出一个主节点
  • 第二:如果是数据节点挂了,要把数据节点上的数据做迁移,确保数据的安全

演示一下,如下es01节点是主节点,并且是健康的,也是个数据节点,有0和1的片

 模拟-这里手动停止es01节点(模拟不知名情况下es01节点宕机了)

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第82张图片

打开控制台,完蛋了,发现"涉黄"了,集群不健康了,条变黄了,0号片和1号片灰了,这两个片没有地方放了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第83张图片

 重新选主:发现es03变成主了,下一步就是数据迁移了,稍微等一下会自动迁移,经过等待发现数据迁移成功了,现在每一个片都有两份了,就又变绿了

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第84张图片

再次查询发现,啊哈,数据没有丢失,一点问题都没有

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第85张图片

经过一段时间,我们把故障修复了,然后重新启动es01 

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第86张图片

 切回控制台等待一下发现,啊哈,es01又回来了,但是它不是老大了,是备选主节点了,主节点又把分片迁移回es01去了,重新做了一份banlance,确保数据是均衡的,一人两个分片,一主一备份,注意是重新分配迁移哈

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第87张图片

 发送请求,依然没有问题

第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第88张图片

 第八章-分布式搜索引擎-深入ES:聚合、自动补全、拼音词典、DB数据同步、ES集群_第89张图片

你可能感兴趣的:(微服务,elasticsearch,大数据,搜索引擎)