本文以Docker版Mastodon实例为主要环境,解决开启全文搜索后中文无法正常搜索的问题。如果有非Docker环境的实例在开启全文搜索后建议参考官方文档中的配置方法来优化中文分词。下文中提到的很多问题是仅限Docker版才有,请勿搞混。
下面所提到的都是在Mastodon当下最新版本(v4.5.5)的基础上所做的操作,后续版本如有变动,请以官方文档为准。
0. 启用全文搜索
Mastodon官方文档不是针对Docker环境书写的,这就对那些小型单节点的Docker版Mastodon实例用户造成了一定的困扰。目前使用Docker架设Mastodon小型单节点实例的最快的方法是使用官方GitHub站点上的docker-compose.yml文件。考虑对主机资源的占用,docker-compose.yml中默认并不开启全文搜索,即代码中第28-57行默认是注释掉的。
...
# es:
# restart: always
# image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29
# environment:
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
# - "xpack.license.self_generated.type=basic"
# - "xpack.security.enabled=false"
# - "xpack.watcher.enabled=false"
# - "xpack.graph.enabled=false"
# - "xpack.ml.enabled=false"
# - "bootstrap.memory_lock=true"
# - "cluster.name=es-mastodon"
# - "discovery.type=single-node"
# - "thread_pool.write.queue_size=1000"
# networks:
# - external_network
# - internal_network
# healthcheck:
# test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
# volumes:
# - ./elasticsearch:/usr/share/elasticsearch/data
# ulimits:
# memlock:
# soft: -1
# hard: -1
# nofile:
# soft: 65536
# hard: 65536
# ports:
# - '127.0.0.1:9200:9200'
...
假如之前把GitHub上给的.env.production.sample中的内容复制到了Mastodon配置文件.env.production中,也就是Elasticsearch (optional)字段保持了原样。此时我们只需要在docker-compose.yml中的上面这段代码的注释(即#号)去掉,然后再次上线整个docker compose,待Elasticsearch 7.17.29的镜像拉取成功后,你会发现Elasticsearch容器并没有像预先计划的那用启动,这是由于权限不足的问题。我们需要先用 docker compose down 将整个docker compose下线,然后针对映射到主机本地生成的文件夹进行权限调整:
chown -R 1000:1000 ./elasticsearch
之后再用 docker compose up -d 上线整个docker compose,全文搜索就开启了。
1. 安装中文分词和简繁转换插件
然而此时的全文搜索并没有针对中文进行优化,所以会导致搜中文时需要自行使用引号把每个词组划分开,否则Mastodon默认会以每个单字为搜索单位进行搜索。搜索结果自然也是杂乱无章的。想要解决这个问题,Mastodon官方文档给出中文搜索的优化方案。只不过这个方案是针对非Docker版节点的,我们不能直接拿来就用。
首先,官方文档介绍的要给Elasticsearch安装两个插件:
因为docker-compose.yml的缘故,docker compose每次下线的时候容器都会被删除掉,所以即便是进入Elasticsearch的容器安装了插件并重启容器完成插件安装,一旦使用docker compose down,则安装好插件的容器就会被删除,前功尽弃。
这时我们需要在Elasticsearch的官方文档中寻找解决方案。文档中的一篇文章详细介绍了在使用官方Docker镜像时,可以通过配置文件的方式来让Elasticsearch自动安装插件的方法。接下来我们就如法炮制:
# 首先我们在工作目录中建立一个插件相关的目录并进入
mkdir ./elasticsearch-plugins
cd elasticsearch-plugins
# 在该目录中我们创建一个配置文件,并把下面区块里的内容粘贴进去,这里我使用vim来创建配置文件
vim elasticsearch-plugins.yml
plugins:
- id: analysis-ik
location: file:///usr/share/elasticsearch/local-packages/elasticsearch-analysis-ik-7.17.29.zip
- id: analysis-stconvert
location: file:///usr/share/elasticsearch/local-packages/elasticsearch-analysis-stconvert-7.17.29.zip
这里你可以看到我使用了 file:// 协议,没错,接下来我们把插件下载到本地,这样就避免了每次新建容器的时候都要从网上下载的延迟和风险。
# 在elasticsearch-plugins中新建一个目录用来存放插件包并进入
mkdir ./local-packages
cd local-packages
# 在local-packages目录中下载匹配Elasticsearch版本(v7.17.29)的插件
wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.17.29.zip
wget https://release.infinilabs.com/analysis-stconvert/stable/elasticsearch-analysis-stconvert-7.17.29.zip
接下来我们要返回主工作目录去修改docker-compose.yml,将elasticsearch-plugins.yml文件和local-packages目录映射进Elasticsearch容器中去,并且针对Elasticsearch 7.x Docker镜像的一个bug进行修正。
首先我们先完成文件和目录的映射,我们在es区块下的volumes字段增加新的映射
...
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
volumes:
- ./elasticsearch:/usr/share/elasticsearch/data
# 新增下两行
- ./elasticsearch-plugins/elasticsearch-plugins.yml:/usr/share/elasticsearch/config/elasticsearch-plugins.yml
- ./elasticsearch-plugins/local-packages:/usr/share/elasticsearch/local-packages
# 新增完成
ulimits:
memlock:
soft: -1
hard: -1
...
接下来就要说一下刚才提到的那个bug,GitHub上有这个Issue。简而言之,当通过elasticsearch-plugins.yml来为Docker容器安装插件的时候,Elasticsearch会自动在容器中 /usr/share/elasticsearch/plugins/ 目录下新建一个名为 .elasticsearch-plugins.yml.cache 的缓存文件来记录已经安装的插件。当容器再次重启的时候,Elasticsearch 7.x版本则会扫描到这个缓存文件,并把它当成一个插件来执行。显而易见是无法执行的,于是就报错并且不停尝试重启,最终启动失败。
解决方法是在docker-compose.yml中增加启动时先删除该缓存文件的选项,让每次容器上线都是全新安装插件的状态。由于我们已经把插件包下载到本地,所以实际上并没有多少时间上的影响。代码如下:
...
es:
restart: always
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29
# 新增下一行
entrypoint: ["/bin/tini", "--", "bash", "-c", "rm -f /usr/share/elasticsearch/plugins/.elasticsearch-plugins.yml.cache && /usr/local/bin/docker-entrypoint.sh eswrapper"]
# 新增完成
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
- "xpack.license.self_generated.type=basic"
- "xpack.security.enabled=false"
- "xpack.watcher.enabled=false"
- "xpack.graph.enabled=false"
- "xpack.ml.enabled=false"
- "bootstrap.memory_lock=true"
- "cluster.name=es-mastodon"
- "discovery.type=single-node"
- "thread_pool.write.queue_size=1000"
...
新增这行命令是通过docker inspect对elasticsearch:7.17.29镜像进行检查,获取它的entrypoint指令,然后再在该指令上增加了删除.elasticsearch-plugins.yml.cache的指令后构建了新的entrypoint,以此覆盖镜像原本的entrypoint指令。
最后回到主目录调整完 ./elasticsearch-plugins 目录的权限后
chown -R 1000:1000 ./elasticsearch-plugins
至此插件安装完成。
2. 修改索引分词器选项以及增加简繁转换过滤器
根据官方文档,我们需要修改web容器中 app/chewy/ 目录下所有以 _index.rb 结尾的文件。我们仍旧利用Docker的映射功能,把修改好的文件映射到容器中的位置,来覆盖镜像原有的设置。首先我们要把这个目录拷贝出来。假设你的web容器的名字是 live-web-1,下面的指令可以按照实际容器名来修改。
# 假设当前为主工作目录,web容器名为live-web-1
docker cp -a live-web-1:/opt/mastodon/app/chewy ./elasticsearch-plugins/
这样一来elasticsearch-plugins目录下又多了一个chewy目录,进入该目录后能够看到五个以 _index.rb 结尾的文件以及一个名为concerns的目录。concerns目录及其内容不在修改范围内,我们主要查看的是剩下的这些以 _index.rb 结尾的文件。
由于五个文件内容各有不同(其中instances_index.rb一般不用修改),所以接下来我们只讲修改方法。使用vim打开每个文件,查找 tokenizer: 字段(一般都有好几个),然后将其后面带有引号中的值(一般是standard,但也有其他内容)全部修改为ik_max_word,即:
...
tokenizer: 'ik_max_word'
...
然后再在每个文件中寻找包含 content: 字段的 analyzer: 字段。 然后在 filter: 和 analyzer: 之间插入
char_filter: {
tsconvert: {
type: 'stconvert',
keep_both: false,
delimiter: '#',
convert_type: 't2s',
},
},
下面举个例子
...
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
type: 'stop',
stopwords: '_english_',
},
english_stemmer: {
type: 'stemmer',
language: 'english',
},
english_possessive_stemmer: {
type: 'stemmer',
language: 'possessive_english',
},
},
# 此段为新增
char_filter: {
tsconvert: {
type: 'stconvert',
keep_both: false,
delimiter: '#',
convert_type: 't2s',
},
},
# 新增完成
analyzer: {
verbatim: {
tokenizer: 'ik_max_word',
filter: %w(lowercase),
},
...
注意tags_index.rb文件中本身没有 filter: 部分,请直接插在 analyzer: 部分之前即可。
再在每个 analyzer: 中 content: 下添加 char_filter: %w(tsconvert), 来增加使用该过滤器,如
...
char_filter: {
tsconvert: {
type: 'stconvert',
keep_both: false,
delimiter: '#',
convert_type: 't2s',
},
},
analyzer: {
verbatim: {
tokenizer: 'ik_max_word',
filter: %w(lowercase),
},
content: {
tokenizer: 'ik_max_word',
filter: %w(
lowercase
asciifolding
cjk_width
elision
english_possessive_stemmer
english_stop
english_stemmer
),
# 新增下一行
char_filter: %w(tsconvert),
# 新增完成
},
hashtag: {
tokenizer: 'ik_max_word',
...
完成所有文件修改后返回主工作目录,重新调整 ./elasticsearch-plugins 目录的权限
chown -R 1000:1000 ./elasticsearch-plugins
然后再修改docker-compose.yml中web区块的volumes映射
...
healthcheck:
# prettier-ignore
test: ['CMD-SHELL',"curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1"]
ports:
- '127.0.0.1:3000:3000'
depends_on:
- db
- redis
- es
volumes:
- ./public/system:/mastodon/public/system
# 新增下一行
- ./elasticsearch-plugins/chewy:/opt/mastodon/app/chewy
# 新增完成
...
3. 重建索引
到这里所有需要调整的代码就算都完成了,接下来就是重建索引了。先用docker compose up -d来让所有容器都启动。然后用docker logs指令查看Elasticsearch容器是否已经正常启动。最后使用这条指令来重建索引(假设你的web容器名为live-web-1):
docker exec -it live-web-1 tootctl search deploy --reset-chewy
索引重建需要一段时间,待重建完成后用浏览器前往网页界面,在搜索框中尝试搜索中文,会发现显示的结果已经正常的分词后的搜索结果。
完
