【DAY 07】Spring Cloud学习日记 ElasticSearch入门03

1. 数据聚合

数据聚合可以让我们方便的对ES中存储的数据进行分析,统计和运算,例如:

  • 每一个品牌的酒店的平均评分是多少
  • 不同地区的酒店的平均分是多少?

实际上数据聚合在MySQL的时候我们已经学习过了,在MySQL中常见的数据聚合函数如, Sum, Avg, Max, Min,等。

2 ES中的数据聚合

在ES中,数据聚合分为三类

  • 桶聚合:用来对文档进行分组
    • TermAggregation:按照文档的字段值进行分组,例如按照酒店的品牌进行分组,按照城市进行分组等
    • DateHistogram:按照日期阶梯分组,如一周为一组。
      在这里插入图片描述
  • 度量聚合:主要是以用力啊计算一些值,比如最小值,最大值和平均值等
    • Avg:平均值
    • Max:最大值
    • Min:最小值
    • Stats:同时求max,min,avg,sum
  • 管道:以其他聚合方式的结果进行聚合的方式,并不常用

注意:参加聚合的字段必须是keyword, 日期,布尔类型

3. DSL实现聚合

3.1 桶聚合

语法如下:

GET /hotel/_search
{
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": { // 定义聚合
    "brandAgg": { //给聚合起个名字
      "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
        "field": "brand", // 参与聚合的字段
        "size": 20 // 希望获取的聚合结果数量
      }
    }
  }
}

结果如图:
在这里插入图片描述

2.2 聚合结果排序

默认情况下,桶排序会根据桶内的文档数量降序排列,我们实际上也可以手动指定排序方式

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20
      }
    }
  }
}

2.3 限定聚合范围

默认情况下,桶聚合是对索引库中的所有文档做聚合。实际上,也可以根据搜索的结果,以搜索的结果为数据进行聚合,只需要添加query条件即可
我们可以限定要聚合的文档范围,只要添加query条件即可:

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200 // 只对200元以下的文档聚合
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

这次,聚合得到的品牌明显变少了:
在这里插入图片描述

2.4 度量聚合

上一小节中我们是对酒店的品牌进行桶聚合,现在我们可以利用度量聚合统计每一个品牌评分的max,avg,min等

如果我们要计算每一个品牌用户评分的max,avg,min等,就需要先对酒店的品牌做桶聚合,然后在每一个聚合内,使用度量聚合。
语法如下:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20
      },
      "aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
        "score_stats": { // 聚合名称
          "stats": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。

另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:

在这里插入图片描述

2.5 总结

aggs代表聚合,和query是同级关系,当同时出现aggs和query的时候,query的作用是

  • 限定聚合的文档范围,如果没有query,默认对全部文档进行聚合

聚合的三要素是:

  • 聚合名称
  • 聚合类型
  • 聚合字段

聚合可配置的字段有

  • order 聚合结果的排序
  • size 显示的结果条数
  • field:聚合的字段

4. 使用RestAPI实现聚合

聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。

聚合条件的语法:

聚合查询请求发送
在这里插入图片描述
结果解析
在这里插入图片描述

4.1 实例

4.1.1 需求分析

在这里插入图片描述
我们目前项目中过滤条件都是写死的,不会随着搜索结果的变化而变化,这样会出现以下的问题:

假设用户搜索的是“虹桥”,那么实际上城市过滤选项中就不应该再出现北京,深圳等城市,因为只有上海才有“虹桥”这个地方;再比如用户选择五星级酒店,那么诸如7天,如家就不应该再出现在品牌过滤项中。

也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。

我们的思路是将我们搜索到的结果使用桶聚合的方式,对搜索结果中包含的品牌,城市进行分组。

4.1.2 前端分析

在这里插入图片描述
通过观察前端页面可以发现,当我们设置过滤条件的时候前端发送了上面的这个请求。其返回值类型为map:

  • key是字符串,城市、星级、品牌、价格。
  • value是集合,例如多个城市的名称
4.1.3 代码实现

HotelController中添加一个方法,遵循下面的要求:

  • 请求方式:POST
  • 请求路径:/hotel/filters
  • 请求参数:RequestParams,与搜索文档的参数一致
  • 返回值类型:Map<String, List<String>>

代码:

    @PostMapping("filters")
    public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
        return hotelService.Filters(params);
    }

HotelServiceImpl中添加filter方法的实现

@Override
public Map<String, List<String>> filter(SearchParam searchParam) {
    try {
        Map<String, List<String>> map = new HashMap<>();
        SearchRequest request = new SearchRequest("hotel");

        buildBasicQuery(searchParam, request);

        request.source().size(0);
        request.source().aggregation(
                AggregationBuilders.terms("brandAgg")
                        .field("brand")
                        .size(30)
        );
        request.source().aggregation(
                AggregationBuilders.terms("cityAgg")
                        .field("city")
                        .size(30)
        );
        request.source().aggregation(
                AggregationBuilders.terms("starNameAgg")
                        .field("starName")
                        .size(30)
        );

        SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);

        List<String> name = parseRes(search, "brandAgg");
        map.put("brand", name);

        name = parseRes(search, "cityAgg");
        map.put("city", name);

        name = parseRes(search, "starNameAgg");
        map.put("starName", name);

        return map;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
private List<String> parseRes(SearchResponse search, String aggName) {
    Aggregations aggregations = search.getAggregations();
    Terms term = aggregations.get(aggName);
    List<String> list = new ArrayList<>();
    List<? extends Terms.Bucket> buckets = term.getBuckets();
    for (Terms.Bucket bucket : buckets) {
        String name = bucket.getKeyAsString();
        list.add(name);
    }

    return list;
}

5. 自动补全

我们发现在百度或者京东上搜索东西的时候,会根据拼音提示我们可能想要搜索的内容,这是如何实现的呢?
在这里插入图片描述
要实现上述的功能,需要使用一个新的分词器:拼音分词器

5.1 拼音分词器

如果要根据拼音做字母的补全,就必须对文档进行拼音分词,最常使用的拼音分词插件是https://github.com/medcl/elasticsearch-analysis-pinyin。

接下来我们测试一下拼音分词器的效果
测试用法如下:

POST /_analyze
{
  "text": "如家酒店还不错",
  "analyzer": "pinyin"
}

结果:
在这里插入图片描述
从结果中可以看出,默认的拼音分词器存在着下列的问题

  • 分词结果没有进行单词的划分,比如没有rujia, jiudian等
  • 分词结果中只有拼音,而没有保存中文。

我们的要求是分词结果中包含中文和拼音。如输入如家酒店,那么分词结果应该是,rujia, jiudian, rujiajiudian, rj, jd, rjjd, 如家酒店如家酒店

为了解决这个问题,我们将使用自定义分词器

5.2 自定义分词器

默认的拼音分词器不能够满足我们的需求,因此我们需要对拼音分词器做定制。首先我们来了解一下ES分词器的构造

ES分词器分为三部分

  • character filter:对文本进行预处理,例如删除一些特殊字符等
  • tokenizer:核心组件,将文本切割成字条
  • tokenizer filter:将tokenizer的结果进一步处理,比如大小写转换等

在这里插入图片描述
我们可以首先将输入的词使用ik分词器进行分词,然后将分词后的结果使用拼音分词器进行分词即可。

声明自定义分词器的语法如下:

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { // 自定义分词器
        "my_analyzer": {  // 分词器名称
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": { // 自定义tokenizer filter
        "py": { // 过滤器名称
          "type": "pinyin", // 过滤器类型,这里是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",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

我们注意到,我们使用了"search_analyzer": "ik_smart"这句代码,如果不加这个代码,会出现下面的问题

在这里插入图片描述

此时假设我输入的为狮子,那么狮子这个词会经过我们自定义的分词器,分解为狮子, shizi, sz;那么在进行匹配的时候,同音字虱子也会出来,这显然是不合理的。

因此我们需要指定我们在搜索的分词器为"search_analyzer": "ik_smart"
这样我们在输入狮子的时候,就只会根据中文分词,而不会进行拼音分词,从而避免了同音字的问题

5.3 自动补全

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。

  • 字段的内容一般是用来补全的多个词条形成的数组。

举个例子:
比如,一个这样的索引库:

// 创建索引库
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"]
}

查询的DSL语句如下:

// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

查询到的结果都是以s开头的所有匹配词条
在这里插入图片描述

5.4 使用RestAPI实现自动补全

发送自动补全查询的请求
在这里插入图片描述
对结果进行解析

在这里插入图片描述

5.5 实例:实现酒店搜索框的自动补全

完成酒店搜索框自动补全的功能,准备工作需要一下几个步骤:

  1. 修改hotel索引库结构,设置自定义拼音分词器

  2. 修改索引库的name、all字段,使用自定义分词器

  3. 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器

  4. 给HotelDoc类添加suggestion字段,内容包含brand、business

  5. 重新导入数据到hotel库

修改酒店映射结构
代码如下:

// 酒店数据索引库
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":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

这里我们重点来看suggest部分

"completion_analyzer": {
  "tokenizer": "keyword",
  "filter": "py"
}
"suggestion":{
  "type": "completion",
  "analyzer": "completion_analyzer",
  "search_analyzer": "keyword"
}

因为我们的suggestion里面存储的是酒店的名称和商圈,因此不需要进行分词,只需要转化成拼音即可。当用户搜索的时候,会自动根据用户输入的首个字符到suggestion里面去匹配,因此用户的输入也不需要分词或者转换成拼音。

修改实体类HotelDoc
为HotelDoc添加一个新的字段suggestion用来做自动补全,内容可以是酒店品牌、城市、商圈等信息。

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        // 组装suggestion
        if(this.business.contains("/")){
            // business有多个值,需要切割
            String[] arr = this.business.split("/");
            // 添加元素
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr);
        }else {
            this.suggestion = Arrays.asList(this.brand, this.business);
        }
    }
}

重新导入数据
直接执行之前编写的testAutoFill测试方法即可
在这里插入图片描述
分析前端页面

查看前端页面,可以发现当我们在输入框键入时,前端会发起ajax请求,其返回值是一个List<String> 集合,里面保存的是所有的自动补全的词条集合
在这里插入图片描述
代码编写
在的HotelController中添加新接口,接收新的请求:

@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
    return hotelService.getSuggestions(prefix);
}

HotelServiceImpl中实现该方法:

@Override
public List<String> getSuggestions(String prefix) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
            "suggestions",
            SuggestBuilders.completionSuggestion("suggestion")
            .prefix(prefix)
            .skipDuplicates(true)
            .size(10)
        ));
        // 3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Suggest suggest = response.getSuggest();
        // 4.1.根据补全查询名称,获取补全结果
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        // 4.2.获取options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        // 4.3.遍历
        List<String> list = new ArrayList<>(options.size());
        for (CompletionSuggestion.Entry.Option option : options) {
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

测试
在这里插入图片描述

6. 数据同步

我们在ES中使用的数据都是来自MySQL数据库的,因此当数据库中数据发生改变时,ES中的数据也应该同步的改变。
在这里插入图片描述

6.1 同步调用

当我们在hotel-admin微服务中对数据库中内容进行修改的时候,通过远程方法调用的方式调用hotel-demo中的相关代码去修改ES中的数据
在这里插入图片描述

  • 优点:实现简单,粗暴
  • 缺点:业务耦合度高,且耗时比较长

6.2 异步通知

结合我们之前学习的消息队列,当hotel-admin中有修改酒店数据相关操作时,可以将修改的信息发送到消息队列中,在hotel-demo中始终去监听这个消息队列,一旦有修改信息就读取,然后在ES中完成修改内容
在这里插入图片描述

  • 优点:低耦合,实现难度一般
  • 缺点:依赖mq的可靠性

此外,我们还可以使用数据库的Binlog功能实现异步通知流程如下:

  • 给mysql开启binlog功能
  • mysql完成增、删、改操作都会记录在binlog中
  • hotel-demo基于canal监听binlog变化,实时更新elasticsearch中的内容

在这里插入图片描述

  • 优点:完全解除服务间耦合
  • 缺点:开启binlog增加数据库负担、实现复杂度高

6.3 使用消息队列实现异步数据同步

具体步骤如下

  • 声明exchange、queue、RoutingKey

  • 在hotel-admin中的增、删、改业务中完成消息发送

  • 在hotel-demo中完成消息监听,并更新elasticsearch中数据

  • 启动并测试数据同步功能

消息队列
我们的消息队列结构如下图,包含两个消息队列分别负责插入更新操作和删除操作。一个交换机负责分发消息。
在这里插入图片描述
引入依赖
我们需要在两个微服务中都引入Spring AMQP的依赖

<!--amqp-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

声明交换机,队列,以及交换机和队列的绑定
在hotel-admin和hotel-demo中的cn.itcast.hotel.constatnts包下新建一个类MqConstants

package cn.itcast.hotel.constatnts;
public class MqConstants {
/**
 * 交换机
 */
public final static String HOTEL_EXCHANGE = "hotel.topic";
/**
 * 监听新增和修改的队列
 */
public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
/**
 * 监听删除的队列
 */
public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
/**
 * 新增或修改的RoutingKey
 */
public final static String HOTEL_INSERT_KEY = "hotel.insert";
/**
 * 删除的RoutingKey
 */
public final static String HOTEL_DELETE_KEY = "hotel.delete";
}

在hotel-demo中,定义配置类,声明队列、交换机:

@Configuration
public class MqConfig {
@Bean
public TopicExchange topicExchange(){
    return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);
}

@Bean
public Queue insertQueue(){
    return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
}

@Bean
public Queue deleteQueue(){
    return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
}

@Bean
public Binding insertQueueBinding(){
    return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
}

@Bean
public Binding deleteQueueBinding(){
    return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
}
}

在hotel-admin中发送消息

@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
    hotelService.save(hotel);
    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);
}

在hotel-demo中接收消息
首先需要在hotelServiceImpl中去实现根据id在ES添加或者删除酒店的操作

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

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

在hotel-demo中的cn.itcast.hotel.listener包新增一个类,在里面编写监听器来监听两个队列:

@Component
public class MqListener {
    @Resource
    private IHotelService service;

    @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
    public void insertListener(Long id) {
        service.insertById(id);
    }
    @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
    public void deleteListener(Long id) {
        service.deleteById(id);
    }
}

注意:这里监听器所在的那个类一定要添加@Component注解,否则监听器不生效

7. ES集群

单机模式下的ES存储数据,会面临两个问题:海量数据的存储问题和单点故障问题,为了解决上述的问题,ES提供了集群模式:

  • 海量数据存储:将索引库从逻辑上划分为N个分片,存储到多个ES节点
  • 单点故障问题:存储多分备份分片,存储在不同的节点上

7.1 ES集群相关概念

  • 集群:一组拥有相同的cluster name的ES节点
  • 节点:集群中的一个ES实例
  • 分片:索引库可以被拆分为不同的片存储到不同的节点上。主要用来解决索引库过大一个节点存储不下的问题
    • 主分片(Primary shard):相对于副本分片的定义。
    • 副本分片(Replica shard)每个主分片可以有一个或者多个副本,数据和主分片一样。
      在这里插入图片描述
      如果仅仅将数据分片而不做其他备份处理,那么一旦一个节点挂掉,那么数据就不完整了,因此还需要对分片进行备份,需要注意的是:备份分片一定不要和主分片在同一个节点上
      在这里插入图片描述
      我们在创建索引的时候,可以指定索引库划分的片数和每一片对应的副本的数量
      在这里插入图片描述

7.2 ES节点职责划分

在这里插入图片描述

默认情况下,ES中的每一个节点都具备上述的四种职责

真实情况下,往往根据节点的性能划分职责:

  • master节点:对CPU的要求较高,但是对内存要求低
  • data节点:需要大量的运算和聚合,对CPU和内存要求都很高
  • coordinating节点:对CPU和内存要求低
    在这里插入图片描述

7.3 小结

  • master eligible节点的作用:
    • 参与集群选主
    • 主节点可以管理集群状态,管理分片信息,处理请求
  • data节点作用:
    • 数据的CRUD
  • coordinator节点
    • 路由请求到其他节点
    • 将查询结果整合后返回给用户

7.4 集群的分布式存储和查询

我们向ES集群中任意一个节点插入数据,ES会根据下面的公式计算出这一条数据应该插入到哪一个位置:
在这里插入图片描述

因此,索引库一旦创建,就不可以再次修改划分的片数

因此插入一条新文档的流程如下
在这里插入图片描述
当我们在查询的时候,可以访问任何一个ES节点,都可以查询到数据,这是因为在查询的时候,ES会将受到的查询请求发送到每一个ES集群中,然后将查询后的结果汇总,最后返回到用户。因此从用户的角度来看,访问任何一个ES节点都可以得到全部的数据。

ES的查询分成两个阶段:

  • scatter phase:分散阶段,coordinating node会把请求分发到每一个分片

  • gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户
    在这里插入图片描述

7.5 集群故障转移

集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全,这个叫做故障转移。

例如一个集群结构如图:
在这里插入图片描述
突然,node1发生了故障:
在这里插入图片描述
宕机后的第一件事,需要重新选主,例如选中了node2:
在这里插入图片描述
node2成为主节点后,会检测集群监控状态,发现:shard-1、shard-0没有副本节点。
因此需要将node1上的数据迁移到node2、node3:
在这里插入图片描述

这种故障转移机制在ES中自动实现,不需要人为指定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值