背景
上一代使用的阿里云的智能开放搜索OpenSearch,成本高,不够灵活、简单。
对比阿里云opensearch具体优势有:
●opensearch按照索引、索引数据量及TPS、QPS收费,费用较高,且超过QPS后会搜索异常,不能有效应对双十一这样的查询高峰
●提供界面操作,业务方自己可以灵活修改索引结构
●实时数据同步:无需业务方调接口同步数据
目标:降低用户使用搜索的门槛,让搜索人人可用,人人会用。
介绍
使用方法和opensearch类似,由于是内部系统,就不一一说明步骤了,重点介绍下数据链路
索引设计
索引通过可视化的方式设置各个字段的mapping,不支持自定义field mapping。可设置的选项包括是否索引,选择对应的分词器,是否全文检索等。
索引的实现原理
索引的实现不是直接基于es实现的,基于上面提到的三个设置分别说明其实现原理
- 是否索引:和es一致,映射到es的索引上就是对应的field mapping,index=true
- 选择分词器:这里选择了分词器后,并不会在es的索引对应的field mapping设置分词器,而是在数据写入时分词
- 是否全文检索:选择了之后,会加入到全文检索字段中
2和3的实现,请继续阅读下面的 数据写入
数据写入
写入方式
提功了两种方式的写入:
- 使用api写入:提供api接口,可以通过api将数据写入
- 基于canal二次开发,实时数据同步
写入时分词
分词没有使用es的分词功能,而是在负责数据写入的应用中完成的。
负责数据写入的应用中,引用或拷贝了开源的分词器,有标准分词器、ik分词器、拼音分词器等等。分词的操作直接在该应用中进行,分出的词会写入es索引中
举例:配置了分词和索引
举例某个索引的字段name配置了 分词器为标准分词
name=lazy dog
分词的结果为[ lazy, dog ]
,将分词结果拼接为lazy\tdog
,写入到es的索引字段analyze_name_own
中
analyze_name_own这个字段的index=true
,分词器是按照\t
分词的自定义分词器,索引结构如下
{
"mappings": {
"_doc": {
"name": {
"type": "keyword"
},
"analyze_name_own": {
"type": "text",
"analyzer": "按\t分词的分词器"
}
}
}
}
es中该文档内容如下
{
...
"name": "lazy dog",
"analyze_name_own": "lazy\tdog"
...
}
举例:配置了分词和索引,选择了全文检索
选择了全文检索后,会将所有选择了全文检索的字段分出的词,拷贝到字段analyze_all中,
name和nickname都选择了全文检索
name=lazy dog
分词的结果为[ lazy, dog ]
nickname=Brown-Foxes
分词的结果为[ brown, foxes ]
将name和nickname的分词结果使用\t拼接,得到lazy\tdog\tbrown\tfoxes
,写入到es索引的analyze_all
字段中
analyze_all这个字段的index=true
,分词器是按照\t
分词的自定义分词器,索引结构如下
{
"mappings": {
"_doc": {
"name": {
"type": "keyword"
},
"nickname": {
"type": "keyword"
},
"analyze_all": {
"type": "text",
"analyzer": "按\t分词的分词器"
}
}
}
}
es中该文档内容如下
{
...
"name": "lazy dog",
"nickname": "Brown-Foxes",
"analyze_all": "lazy\tdog\tbrown\tfoxes"
...
}
数据变更处理的流程
收到数据变更的消息后,会根据不同的索引数据同步配置,将数据写入到es。部分场景数据同步性能低下,经常出现堆积的情况。
慢查询原因主要如下:
- 收到更新消息时,部分文档需要计算
analyze_all
的值,会先从es中查询该文档,然后再更新 - 多表情况,部分场景更新使用
update_by_query
或update_by_script
或delete_by_script
操作的 - 顺序处理数据变更消息,消息过多时,无法弹性
- 其他一些设计…
数据查询
封装的查询语法
es集群并不直接对外使用,不能直接使用es的query-dsl语法查询,封装了一套查询语法,个别用例如下:
analyze(name)=\"hello world\" AND analyze(name,\"OR\")=\"elastic kafka\"
field6>=\"2019-01-01 00:00:00\"
field1 IN [3,6,....] AND field2 PREFIX \"abc\" AND field3 NOT IN [\"hello\", \"world\"]
keyword=\"boy\"
负责查询的应用,会将自定义语法的dsl翻译为es的dsl,然后将请求发送到es集群,返回查询结果。
将自定义语法的dsl翻译为es的dsl是使用antlr4实现的,demo可以参考es-sql-demo
分词的处理
数据写入时,字段的分词是做了处理的,因此查询时也要处理。
某个字段的全文检索
对某个字段的全文检索,转换为es query dsl时,需要使用该字段对应的analyze_xx_own
替换,
例如analyze(name)=\"hello world\"
转换之后为
{
"match": {
"analyze_name_own": "hello\tworld"
}
}
输入的
hello world
,会先分词得到hello\tworld
全文检索
用法keyword=\"boy\"
,转换为es query dsl时如下
{
"match": {
"analyze_all": "boy"
}
}
优缺点
相较于原生的es,使用确实简单,数据写入和索引结构通过可视化的控制台都可以配置。不需要理解底层的实现原理,只需要知道哪些字段是需要分词的,需要查询的就可以,基础的功能也都提供了。
缺点主要有3点吧
- 数据同步时的分词:
- 分词器不是可插拔的插件,而是编码的,也不支持对分词器的参数配置,导致个别业务场景,哪怕是修改个分词器的参数都需要单独开发分词器
- 分词器不可调试,没有提供调试分词器的入口
- 性能低问题,经常出现消息堆积
- 查询层面:
- 自定义的查询语法,仅覆盖率一小部分es的api,很多功能不可用
- 慢查询无法优化
- 设计层面:
- 索引结构不可自定义,很多es原生的功能无法使用