一.数据聚合

在ES中的数据聚合aggregations可以近似看做成mysql中的groupby分组,聚合可以实现文档数据的统计分析运算,常见聚合分类有以下几种:


1.DSL实现Bucket(桶)聚合

语法:

GET /hotel/_search
{
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": { // 定义聚合(带有"s"证明可以定义多个聚合)
    "xxx": { //给聚合起个名字
      "terms": { // 聚合的类型(不能为text)
        "field": "xxx", // 参与聚合的字段
        "size": xxx // 希望获取的聚合结果数量
      }
    }
  }
}

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序 修改结果排序方式:

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

默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可

示例:

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

2.DSL实现Metrics 聚合

例如我们要求获取每个品牌用户评分minmaxavg等值.
我们可以利用stats聚合:

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

二.RestAPI实现聚合

1.发送聚合DSL语句(其实本质就是一层一层按照DSL语句组成结构调用API,十分易懂也很简单):

在这里插入图片描述
2.聚合结果解析
在这里插入图片描述

java代码:

@Test
    void aggregation() 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=aggregations.get("brandAgg");
        List<? extends Terms.Bucket&gt; buckets = terms.getBuckets();

        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            System.out.println(key);
        }
    }

三.ES自动补全(联想)

用途:当用户搜索输入字符时,我们应该提示出与该字符(汉字,英文)有关的搜索

1.拼音分词

要实现根据字母补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件

下载地址:https://github.com/medcl/elasticsearch-analysis-pinyin

自定义分词器

elasticsearch分词器analyzer)的组成包含部分

在这里插入图片描述

我们可以创建索引库时,通过settings配置自定义analyzer(分词器):

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

拼音分词器适合在创建倒排索引时候使用,但不能在搜索时候使用(原因搜索时会把文本转换多个拼音和分词(如果添加了tokenizer分词的话),按照拼音搜索就会和倒排索引里面的拼音匹配(包括拼音一致但是词义不一致的词),这就会导致搜索产生歧义)。

因此字段创建倒排索引应该my_analyzer分词器;字段搜索时应该使用ik_smart分词器(使用ik_smart识别不了拼音,es默认使用创建索引库时指定的分词器)

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "ik_max_word", "filter": "py"
        }
      },
      "filter": {
        "py": { ... }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

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

在这里插入图片描述

查询语法如下

在这里插入图片描述
3.RestAPI实现自动补全

请求参数构造的API:
在这里插入图片描述
结果解析
在这里插入图片描述
案例:实现酒店搜索页面输入框自动补全

业务代码:

 public List<String> getSuggestions(String prefix) {
        try {
            SearchRequest request=new SearchRequest("hotel");

            request.source().suggest(new SuggestBuilder().addSuggestion("suggestions",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(prefix)
                            .skipDuplicates(true)
                            .size(10)
                    ));

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

            Suggest suggest = response.getSuggest();

            CompletionSuggestion completionSuggestion = suggest.getSuggestion("suggestions");

            List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();

            List<String> suggestionList=new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();

                suggestionList.add(text);
            }
            return suggestionList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

四.数据同步

数据同步问题分析

elasticsearch中的酒店数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearchmysql之间的数据同步。

方案一:同步调用(不推荐使用)

在这里插入图片描述

方案二:异步通知(推荐使用)

在这里插入图片描述

方案三:监听binlog(推荐使用)

在这里插入图片描述

总结:

方式一:同步调用

  • 优点:实现简单,粗暴

  • 缺点:业务耦合度高

方式二:异步通知

方式三:监听binlog

案例:利用MQ实现mysqlelasticsearch数据同步

1.在hoteldemo引入amqp依赖

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

2.编写RabbitMQ的连接信息(yaml文件中,省略)

3.编写常量RabbitMQ的交换机,队列,Routing_Key的值

package cn.itcast.hotel.constants;

public class RabbitMQConstants {
    /**
     * 交换机
     */
    public static final String HOTEL_EXCHANGE="hotel.topic";
    /**
     * 新增修改业务队列
     */
    public static final String HOTEL_INSERT_QUEUE="hotel.insert.queue";
    /**
     * 删除业务队列
     */
    public static final String HOTEL_DELETE_QUEUE="hotel.delete.queue";
    /**
     * 新增或修改业务ROUTING_KEY
     */
    public static final String HOTEL_INSERT_KEY="hotel.insert";
    /**
     * 修改业务的ROUTING_KEY
     */
    public static final String HOTEL_DELETE_KEY="hotel.delete";
}

4.定义交换机,队列,绑定交换机队列,以及声明Routing_Key

package cn.itcast.hotel.mq;

import cn.itcast.hotel.constants.RabbitMQConstants;
import cn.itcast.hotel.service.IHotelService;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class HotelListener {
    @Autowired
    private IHotelService iHotelService;
    /**
     * 监听新增或修改的消息
     * @param id
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = RabbitMQConstants.HOTEL_INSERT_QUEUE),
            exchange = @Exchange(name = RabbitMQConstants.HOTEL_EXCHANGE),
            key = RabbitMQConstants.HOTEL_INSERT_KEY
    ))
    public void listenHotelInsertOrUpdate(Long id){
        iHotelService.insertById(id);
    }

    /**
     * 监听删除消息
     * @param id
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = RabbitMQConstants.HOTEL_DELETE_QUEUE),
            exchange = @Exchange(name = RabbitMQConstants.HOTEL_EXCHANGE),
            key = RabbitMQConstants.HOTEL_DELETE_KEY
    ))
    public void listenHotelDelete(Long id){
        iHotelService.deleteById(id);
    }
}

5.在hotel-admin重复操作1,2,3

6.当执行增删操作,发送消息交换机中

	@PostMapping
    public void saveHotel(@RequestBody Hotel hotel) {
        hotelService.save(hotel);

        rabbitTemplate.convertAndSend(RabbitMQConstants.HOTEL_EXCHANGE, RabbitMQConstants.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(RabbitMQConstants.HOTEL_EXCHANGE, RabbitMQConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

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

7.在hotel-demo处理消息(业务层实现)

public void insertById(Long id) {
        try {
            Hotel hotel = getById(id);

            HotelDoc hotelDoc = new HotelDoc(hotel);

            IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());

            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);

            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    public void deleteById(Long id) {
        try {
            DeleteRequest request=new DeleteRequest("hotel").id(id.toString());

            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

五.elasticsearch集群

1.ES集群结构

单机的elasticsearch做数据存储,必然面临两个问题海量数据存储问题单点故障问题

在这里插入图片描述

2.ES集群的节点角色

elasticsearch中集群节点有不同职责划分

在这里插入图片描述

一个节点其实都包含了四种节点类型

elasticsearch中的每个节点角色都有自己不同职责,因此建议集群部署时,每个节点都有独立角色
在这里插入图片描述
3.ES集群脑裂

默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其它候选节点会选举一个成为主节点。当主节点与其他节点网络故障时,可能发生脑裂问题。

为了避免脑裂需要要求选票超过 ( eligible节点数量 + 1 )/ 2 才能当选为主,因此eligible节点数量最好是奇数对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题

4.ES集群分布式存储

新增文档时,应该保存不同分片,保证数据均衡,那么coordinating node(协调节点)如何确定数据该存储到哪个分片呢?

elasticsearch会通过hash算法计算文档应该存储到哪个分片:

在这里插入图片描述


新增文档流程

在这里插入图片描述


elasticsearch的查询分成两个阶段

在这里插入图片描述


5.ES集群的故障转移

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

原文地址:https://blog.csdn.net/m0_71484388/article/details/134713639

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_35894.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注