07_es分布式搜索引擎3

本文详细介绍了Elasticsearch中的数据聚合功能,包括Bucket聚合、Metrics聚合的实现,以及如何通过DSL和RESTAPI进行操作。同时涉及了拼音分词和自定义分词器,以及数据同步机制,如利用MQ进行MySQL与Elasticsearch之间的实时同步。
摘要由CSDN通过智能技术生成

一、数据聚合

1.聚合的分类

①聚合可以对文档数据的统计,分析,运算

②聚合的分类

  • 桶Bucket聚合:对文档按照字段分组
  • 度量Metric聚合:计算最大值,最小值,平均值
  • 管道pipeline聚合:以聚合的结果为基础聚合

③聚合的类型

  • keyword
  • 数值
  • 日期
  • 布尔

2.DSL实现Bucket聚合

案例:统计所有数据中酒店的品牌有几种,根据酒店品牌做聚合

得到的结果是从大到小

 

 ①结果排序

②限定聚合范围,加上query条件

// 只对200元以下的文档聚合

总结

①aggs表示聚合,与query同级。Query的作用是添加过滤条件。

②聚合三要素

 

③聚合的属性

size:聚合结果的数量

order:排序方式

field:聚合字段

3.DSL实现Metrics聚合

获取每个品牌(分类)的用户评分的min,max,avg,sum等值

 结果如下

4. RestAPI实现聚合

DSL构造

结果解析

代码

@Test
void test() throws IOException{
   SearchRequest request = new SearchRequest("hotel");
   request.source().size(0);//不显示具体的文档内容
   request.source().aggregation(AggregationBuilders
         .terms("brand_agg")
         .field("brand")
         .size(20));
   SearchResponse response = client.search(request, RequestOptions.DEFAULT);
   // 2.解析聚合结果
   Aggregations aggregations = response.getAggregations();
   // 根据名称得到Buckets
   Terms brandTerms = aggregations.get("brand_agg");
   // 获取桶
   List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
   // 遍历
   for (Terms.Bucket bucket : buckets) {
       String brandName = bucket.getKeyAsString();
      System.out.println(brandName);
   }

}

5.案例:在IUserService中定义方法,实现对品牌、城市、星级的聚合,展示给前端页面关键字

/**
 * 前端展示的聚合字段
 */
@Override
public Map<String, List<String>> filters() throws IOException {
   // 创建map对象
   Map<String, List<String>> map = new HashMap();
   // 创建request请求
   SearchRequest request = new SearchRequest("hotel");
   // 组织DSL size,agg,
   request.source().size(0);
   // 聚合
   buildAggregation(request);
   // 发送请求得到响应
   SearchResponse response = client.search(request, RequestOptions.DEFAULT);
   // 解析
   Aggregations aggregations = response.getAggregations();
   // 解析1:city
   List<String> cityList = getAggList(aggregations,"cityAgg");
   List<String> brandList = getAggList(aggregations,"brandAgg");
   List<String> starList = getAggList(aggregations,"starAgg");
   map.put("城市",cityList);
   map.put("品牌",brandList);
   map.put("星级",starList);
   return map;
}

/**
 * 代码抽取:获取数据
 * @param aggregations
 * @param aggName
 * @return
 */
private List<String> getAggList(Aggregations aggregations,String aggName) {
   Terms cityTerms = aggregations.get(aggName);
   final List<? extends Terms.Bucket> buckets = cityTerms.getBuckets();
   List<String> list = new ArrayList();
   for (Terms.Bucket bucket : buckets) {
      list.add(bucket.getKeyAsString());
   }
   return list;
}

/**
 * 代码抽取:聚合
 *
 * @param request
 */
private void buildAggregation(SearchRequest request) {
   request.source().aggregation(AggregationBuilders
         .terms("brandAgg")
         .field("brand")
         .size(100)
   );
   request.source().aggregation(AggregationBuilders
         .terms("cityAgg")
         .field("city")
         .size(100)
   );
   request.source().aggregation(AggregationBuilders
         .terms("starAgg")
         .field("starName")
         .size(100)
   );
}

二、自动补全

1.安装拼音分词器

要实现根据字母做补全,就必须对文档按照拼音分词。

elasticsearch的拼音分词插件GitHub - medcl/elasticsearch-analysis-pinyin: This Pinyin Analysis plugin is used to do conversion between Chinese characters and Pinyin.

安装方式分三步:

①  解压

②  上传到虚拟机中,elasticsearch的plugin目录

③  重启elasticsearch

④  测试

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

 

2.自定义分词器

以上的不够智能,需要改进。

拼音都是单个字或者全部一句话的首字母

①es分词器analyzer的组成:

  • character filters:删除字符、替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

 

②自定义分词器

  • 通过settings来配置自定义的analyzer(分词器)。创建索引库test,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",
        "analyzer": "my_analyzer"
      }
    }
  }
}

测试

POST /test/_analyze
{
  "text": "如家酒店",
  "analyzer": "my_analyzer"
}

结果中英文都有

所以创建倒排索引(添加)的时候可以使用拼音分词,搜索的时候不能使用,因为容易搜到同音字。两个要分别创建 

 ③字段在创建倒排索引时使用自定义拼音分词器(多个搜索的可能),字段在搜索的时候使用ik_smart分词器(中文只能匹配一种,拼音缩写是可能是多种)

总结

①如何自定义分词器?

创建索引库时,在settings中配置,可以包含三部分

character filter

tokenizer

filter

②拼音分词器注意事项?

为了避免搜索到同音字,搜索时不要使用拼音分词器

3. completion suggester查询实现自动补全

①es提供completion suggester查询实现自动补全功能。这个查询会匹配用户输入的内容开头词条并返回。

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

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

 查询

 

4.实现hotel索引库的自动补全、拼音搜索功能

实现思路如下:

  • 修改hotel索引库结构,设置自定义拼音分词器
  • 修改索引库的name、all字段,使用自定义分词器
  • 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器
  • 给HotelDoc类添加suggestion字段,内容包含brand、business
  • 重新导入数据到hotel库

注意:name、all是可分词的,自动补全的brand、business是不可分词的,要使用不同的分词器组合

①修改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"
      }
    }
  }
}

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

@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();
        if (this.business.contains("/")){
            // 如果多个商圈
            this.suggestion = new ArrayList<>();
            suggestion.add(this.brand);
            String[] arr = this.business.split("/");
            Collections.addAll(this.suggestion,arr);
        }else {
            this.suggestion = Arrays.asList(this.brand,this.business);
        }
    }
}

⑤具体实现

 

/**
 * 自动补全
 *
 * @param key
 * @return
 */
@Override
public List<String> getSuggestion(String key) {
   List<String> suggestionList = new ArrayList<>();
   // 创建请求
   SearchRequest request = new SearchRequest("hotel");
   // 编写DSL
   request.source().suggest(new SuggestBuilder().addSuggestion(
         "hotelSuggestion",
         SuggestBuilders.completionSuggestion("suggestion")
               .prefix(key)
               .skipDuplicates(true)
               .size(10)
   ));
   // 发送请求
   try {
      SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

      // 解析
      Suggest suggest = response.getSuggest();
      // 根据名称获取
      CompletionSuggestion suggestion = suggest.getSuggestion("hotelSuggestion");
      // 遍历
      for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
         String text = option.getText().string();
         suggestionList.add(text);

      }


   } catch (IOException e) {
      e.printStackTrace();
   }

   return suggestionList;
}

三、数据同步

数据同步问题分析

es的数据来自mysql,当mysql发生改变时,es的数据要同时改变。E  s与mysql之间要数据同步

异步通知

优点:低耦合,实现难度一般。

缺点:依赖mq的可靠性

 

案例:利用MQ实现mysql与elasticsearch数据同步

利用课前资料提供的hotel-admin项目作为酒店管理的微服务。当酒店数据发生增、删、改时,要求对elasticsearch中数据也要完成相同操作。

步骤:

  • 导入课前资料提供的hotel-admin项目,启动并测试酒店数据的CRUD
  • 声明exchange、queue、RoutingKey
  • 在hotel-admin中的增、删、改业务中完成消息发送
  • 在hotel-demo中完成消息监听,并更新elasticsearch中数据
  • 启动并测试数据同步功能

1. 声明exchange、queue、RoutingKey

当酒店发生增改,删除时发消息

消费者:hotel-demo声明exchange

①导入坐标

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

②配置文件

③声明队列交换机

 

MqConstants常量 

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

基于bean声明交换机对象

@Configuration
public class MqConfiguration {
   /**
    * 交换机定义
    *
    * @return
    */
   @Bean
   public TopicExchange topicExchange() {
      return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);
   }

   /**
    * 队列定义:insert和update
    */
   @Bean
   public Queue insertQueue() {
      return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
   }

   /**
    * 队列定义:delete
    */
   @Bean
   public Queue deleteQueue() {
      return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
   }

   /**
    * 绑定关系 insertQueue
    * bind队列--to交换机--with
    */
   @Bean
   public Binding insertQueueBinding() {
      return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
   }

   /**
    * 绑定关系 deleteQueue
    * bind队列--to交换机--with
    */
   @Bean
   public Binding deleteQueueBinding() {
      return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
   }

}

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

①复制MqConstants常量到hotel-admin

②同样导入队列amqp的maven坐标

③同样配置amqp地址

④消息发送的代码在controller

新增/修改

@Autowired
private RabbitTemplate rabbitTemplate;
用于构建发送和接收消息的客户端应用程序
@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
    hotelService.save(hotel);
    rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE,MqConstants.HOTEL_INSERT_KEY,hotel.getId());
}

发送消息rabbitTemplate.convertAndSend(交换机,新增RoutingKey,消息内容(酒店id))

删除

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

}

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

①创建监听类HotelListener,定义消费者,接收消息

@Component
public class HotelListener {
   @Autowired
   private IHotelService hotelService;
   /**
    * 监听新增或修改的业务
    * @param id
    */
   @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
   public void listenHotelInsertOrUpdate(Long id){
      hotelService.insertById(id);
   }

   /**
    * 监听删除业务
    * @param id
    */
   @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
   public void listenHotelDelete(Long id) {
      // 删除索引库
      hotelService.deleteById(id);
   }

}

新增或修改hotelService.insertById(id);

@Override
public void insertById(Long id) {
   try {
      // 0.根据id查询数据
      Hotel hotel = getById(id);
      HotelDoc hotelDoc = new HotelDoc(hotel);
      // 1.准备Request
      IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
      // 2.准备DSL
      request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
      // 3.发送请求
      restHighLevelClient.index(request,RequestOptions.DEFAULT);
   } catch (IOException e) {
      throw new RuntimeException(e);
   }
}

删除索引hotelService.deleteById(id);

@Override
public void deleteById(Long id) {
   try {
      // 1.准备request
      DeleteRequest request  = new DeleteRequest("hotel",id.toString());
      // 2.发送请求
      restHighLevelClient.delete(request,RequestOptions.DEFAULT);
   } catch (IOException e) {
      throw new RuntimeException(e);
   }
}

4.启动并测试数据同步功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值