SpringCloud学习路线(12)——分布式搜索ElasticSeach数据聚合、自动补全、数据同步

一、数据聚合

聚合(aggregations): 实现对文档数据的统计、分析、运算。

(一)聚合的常见种类

  • 桶(Bucket)聚合: 用来做文档分组。
    • TermAggregation: 按照文档字段值分组
    • Date Histogram: 按照日期阶梯分组,例如一周一组,一月一组
  • 度量(Metric)聚合: 用以计算一些值,比如最大值、最小值、平均值等。
    • Avg: 求平均值
    • Max: 求最大值
    • Min: 求最小值
    • Stats: 同时求max、min、avg、sum等
  • 管道(pipeline)聚合: 其它聚合的结果为基础的聚合。

参与聚合的字段类型:

  • keyword
  • 数值
  • 日期
  • 布尔

(二)DSL实现聚合

1、桶聚合

当我们统计所有数据中的酒店品牌有几种,此时可以根据酒店品牌的名称做聚合。

(1)基本实现

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

(2)Bucket聚合结果排序

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

那么如何修改排序?

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

(3)限定聚合范围

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

复制代码
GET /hotel/_search
{
	"query": {
		"range": {
			"price": {
				"lte": 200	# 只对200元以下的文档聚合
			}
		}
	}
	"size": 0,	// 设置size为0,结果中不包含文档,只包含聚合结果
	"aggs": {	// 定义聚合
		"brandAgg": {	// 给聚合起个名字
			"terms": {	// 聚合的类型,按照品牌值聚合,所以选择term
				"field": "brand",	//参与聚合的字段,
				"order": {	# 排序
					"_count": "asc"
				},
				"size": 20	//	希望获取的聚合结果数量
			}
		}
	}
}

2、Metrics聚合

需求: 要求获取每个品牌的用户评分的min、max、avg等值。

复制代码
GET /hotel/_search
{
	"size": 0,
	"aggs": {	// 定义聚合
		"brandAgg": {	// 给聚合起个名字
			"terms": {	// 聚合的类型,按照品牌值聚合,所以选择term
				"field": "brand",
				"order": {	# 排序
					"scoreAgg.avg": "desc"
				},
				"size": 20
			},
			"aggs": {	#是brands聚合的子聚合,也就是对分组后对每组分别计算
				"score_stats": {	#聚合名称
					"stats":	{	#聚合类型,这里的stats可以同时计算min、max、avg等
						"field": "score"	#聚合字段,这里是score
					}
				}
			}
		}
	}
}

(三)RestClient实现聚合

1、桶聚合

java 复制代码
//1、创建request对象
SearchRequest request = new SearchRequest("hotel");
//2、DSL组装
request.source().size(0);
request.source().aggregation(
	AggregationBuilders.term("brand_agg").field("brand").size(20)
);

//3、发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

//4、解析结果
Aggregations aggregations = response.getAggregations();

//5、根据名称获取聚合结果
Terms brandTerms = aggregations.get("brand_agg");

//6、获取桶
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();

//7、遍历
for (Terms.Bucket bucket : buckets) {
	//获取品牌信息
	String brandName = bucket.getKeyAsString();
}

二、自动补全

(一)拼音分词器

1、离线安装拼音分词器


2、重启ES即可

(二)自定义分词器

1、直接使用拼音分词器的问题:

  • 拼音分词器不分词,只分拼音
  • 每一个字都形成了拼音
  • 没有汉字

2、分词器的组成

  • character filters: 在tokenizer之前对文本进行处理。例如删除字符、替换字符。
  • tokenizer: 将文本按照一定规则切割词条(term)。例如keyword、ik_smart
  • tokenizer filter: 将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理。

3、自定义实现结构

在创建索引库时, 通过settings来配置自定义的analyzer(分词器)

复制代码
PUT /test
{
	"settings": {	#设置配置
		"analysis": {	#解析组
			"analyzer": {	#自定义解析器
				"my_analyzer": {	#分词器名称,按照character 》 tokenizer 》 filter 顺序进行配置
					"tokenizer": "ik_max_word",
					"filter": "pinyin"
				}
			}
		}
	}
}

拓展自定义分词器

复制代码
PUT /test
{
	"settings": {	#设置配置
		"analysis": {	#解析组
			"analyzer": {	#自定义解析器
				"my_analyzer": {	#分词器名称,按照character 》 tokenizer 》 filter 顺序进行配置
					"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"	# 在搜索时使用中文分词器
		}
	}
}

(三)自动补全查询

ES 提供 Completion Suggester 查询来实现自动补全功能。

这个查询会匹配以用户输入内容开头的词条并返回。

为了提高补全查询的效率,对于文档中字段的类型有一些约束:

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

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

    #创建索引库
    PUT test
    {
    "maapings": {
    "properties": {
    "title": {
    "type": "completion"
    }
    }
    }
    }

    示例数据

    POST test/_doc
    {
    "title": ["Sony", "WH-1000XM3"]
    }
    POST test/_doc
    {
    "title": ["SK-II", "PITERA"]
    }
    POST test/_doc
    {
    "title": ["Nintendo", "switch"]
    }

查询示例

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

(四)RestClient实现自动补全

java 复制代码
//1、准备请求
SearchRequest request = new SearchRequest("hotel");

//2、请求参数
request.source().suggest(new SuggestBuilder().addSuggestion(
	"mySuggestion",	
	SuggestBuilders
		.completionSuggestion("title")
		.prefix("h")
		.skipDuplicates(true)
		.size(10)
));

//3、发送请求
client.search(request, RequestOptions.DEFAULT);

//4、解析结果
Suggest suggest = response.getSuggest();

//5、根据名称获取补全结果
CompletionSuggestion suggestion = suggest.getSuggestion("title_suggest");

//6、获取options并遍历
for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()){
	//获取option的text
	String text = option.getText().string();
}

三、数据同步

ES的数据来自数据库,而数据库数据发生改变时,ES也必须改变,这个就是ES与数据库的数据同步。

在微服务中,负责 数据操作业务 与 数据搜索业务 可能会出现在两个不同的微服务中,数据同步如何实现?

(一)数据同步思路

方式一:同步调用

新增数据 》 数据管理业务(直接写入数据库) 》 调用更新索引库接口 》 数据搜索服务(更新ES)

  • 优点: 实现简单,粗暴
  • 缺点: 数据耦合,业务耦合,性能下降。

方式二:异步通知(现阶段最为推荐的一种方式)

新增数据 》 数据管理业务(直接写入数据库,并给MQ发送消息) 》 MQ(搜索服务订阅) 》 数据搜索服务(更新ES)

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

方式三:监听binlog

新增数据 》 数据管理业务(直接写入mysql数据库,mysql数据库监听binlog库) 》 canal(中间件,通知搜索服务数据变更) 》 数据搜索服务(更新ES)

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

(二)实现ES与数据库数据同步

我们采用的是异步通知的方式进行数据同步

实现数据同步

  • 声明交换机,queue,RoutingKey
  • 在admin中的增删改业务中完成消息发送
  • 完成消息监听,并更新ES数据
相关推荐
初次攀爬者3 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
追风筝的人er4 天前
企业管理系统如何实现自定义首页与千人千面?RuoYi Office 给出了完整方案
vue.js·spring boot·spring cloud
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习