一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
前言
承接上次的elasticsearch 搭配canal 处理数组字段。其实在刚开始的时候由于粗心,在canal的配置文件中并没有开启objField字段中的arrays配置,导致我原先期望的映射为数组的字段被映射成功字符串字段。于是引发了一个问题。例如我们在elasticsearch中有个商品的对象,它可以对应上多个标签,我们需要对一个商品的文档存储它所关联的标签id、中文标签、英文标签。由于某种原因这些本该作为数组存储的字段,被压缩成了用标点(例如分号)拼接的字符串。
{
"labels": "1;2;3",
"labels_cn": "历史;中国;精品课",
"labels_en": "history;china;class quality"
}
{
"label_cn_names": {
"type": "text"
},
"label_en_names": {
"type": "text"
},
"labels": {
"type": "text"
}
}
上面是文档存储的片段,可以发现1、存储是是字符串2、字段类型是text。
text在搜索时会被分词,我们的需求是比如我以标签id或者中文标签或英文标签去搜索。
31 =》 获取绑定31号标签id的对象
中国 =》 获取绑定中文标签是中国的对象
China =》 获取绑定英文标签是China的对象。
我的思考是如果使用匹配查询类似模糊的话,如果标签中容易出现误命中,比如英文下我搜索bag,而标签是“shcool and bag”也会被返回;中文下我搜索国,而标签是“中国”也会被返回。因为text类型在搜索时会被分析分词,而默认的标准分词器或者ik分词器,会在分词后出现误操作。对于这种被分割的字符串,同时我们又想完全匹配“数组”中每个单独的值。那么是不是应该从分词上着手,比如按照分隔符去分词,那每个分词即为数组的每个值,那么即使搜索时是通过分析器分词了,也可以完全匹配上每个分词的值。
自定义分词
首先我们简单的说明一下在elasticsearch中分析器由3部分组成: 1、0个或多个字符过滤器 2、一个分词器 3、0个或多个分词过滤器 我们要做的就是创建一个分词器,官方提供了一个模式分词器的案例。模式分词指的是通过正则将文本进行分隔。
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": ","
}
}
}
}
}
POST my-index-000001/_analyze
{
"analyzer": "my_analyzer",
"text": "comma,separated,values"
}
例如这是一个逗号的分词器。所有文本会按照逗号分隔。我们给我们的索引创建一个分号的分词器。 下面来看下比较,首先是没有用自定义分词器的
{
"query":{
"match":{
"labels_cn": "课"
}
}
}
--------------------
"hits": [
{
"_index": "book",
"_type": "_doc",
"_id": "HjPUJ4ABRHGvpe9lz-Be",
"_score": 0.2876821,
"_source": {
"book_name": "畅销书",
"labels": "1;2;3",
"labels_cn": "历史;中国;精品课",
"labels_en": "history;china;class quality"
}
}
]
可以看到有结果返回,可以理论上只有搜索精品课才能返回。这是由于labels_cn被分词了,例如你是用了ik,那么很有可能分词为精品、课。因为它会按照一个常见的汉语分词来进行划分,所以就会被误匹配。因为elasticsearch是基于倒排索引来进行查询的,而倒排索引在elasticsearch中是建立在分词机制下实现的,所以你的分词只要被匹配则会返回。
下面我们增加自定义分词器,然后再进行同样的查询
{
"query":{
"match":{
"labels_cn": "课"
}
}
}
------------
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
很神奇,没有返回。为了方便理解,我们测试下分词器的作用,使用_analyze API,这个方法是用于进行字符串内容的分析的,可以指定我们的分析器,那么内容就会按照我们的分析器进行分析,我们通过查看结果判断是否符合我们的预期。
{
"analyzer": "semicolon_analyzer",
"text": "历史;中国;精品课"
}
{
"tokens": [
{
"token": "历史",
"start_offset": 0,
"end_offset": 2,
"type": "word",
"position": 0
},
{
"token": "中国",
"start_offset": 3,
"end_offset": 5,
"type": "word",
"position": 1
},
{
"token": "精品课",
"start_offset": 6,
"end_offset": 9,
"type": "word",
"position": 2
}
]
}
看到文本只会按照分号进行分词。所以也只可能被完整的标签值匹配。下面输入完整的标签来搜索
{
"query":{
"match":{
"labels_cn": "精品课"
}
}
}
----------
"hits": [
{
"_index": "book1",
"_type": "_doc",
"_id": "HzPcJ4ABRHGvpe9lHeCW",
"_score": 0.18232156,
"_source": {
"book_name": "畅销书",
"labels": "1;2;3",
"labels_cn": "历史;中国;精品课",
"labels_en": "history;china;class quality"
}
}
]
可以看到符合我们的预期了。labels_cn字段内有字符被匹配就可以被查询出来。