我上一次试图折腾 Sphinx 没成功,挂念了很久,前几天在首页看到有人留言询问全文搜索的事情,某 maintainer 甩了一个很容易搜到并且不好用的教程并且附带了一句“毕竟这个功能的用户量比较少”。
嗯 行吧 即使实际上每个人都需要,只要没有教程没人弄得起来,用户量都会很少的。
就像是中国人不需要用 Google 是一样的道理。
反正就下决心把这个东西研究清楚了
正文:
网上能搜到的大部分文章都是过期和不正确的,用的是很老版本的 Sphinx + Coreseek 插件。
老版本的 Sphinx 不支持中文分词,于是有国人做了一个插件来支持,这个方案主要问题在于,coreseek 已经凉了很久了 某份fork的最后更新是 7 years ago, 至于上游是什么年代的我已经找不到了,官网也早就挂了。
而且早期版本的 Sphinx 的 bug 和性能也不太好。
另外,流传最广的那篇教程似乎也不正确,它的 cron 里引用了 threads_merge 和 posts_merge 的索引,但是给的配置文件里没有定义这俩。即使你搭起来了旧版本的sphinx和coreseek,他也是没办法正常工作的。
Sphinx 后来的版本增加了原生的中文支持,但是做的比较一般,只支持 ngram 模式,就是单字拆分,而不是基于词典。Sphinx 新版本的配置文件区别不大。
Discuz 用的驱动 Sphinx 也已经过期了,搭起来新版本 Sphinx 之后搜不到任何东西,抓包看提示发现返回了一个 Client too old。不过好在这很容易解决,把 Sphinx 服务器包里附送的 php 驱动,改名字替换进 Discuz 里面就好了。
不过最新版本废弃掉了 SetMatchMode() 函数,好在服务器端依然支持,对照老版本驱动把 这个函数以及用到了的 _mode 变量抄过来,就能正常工作。
这样子能搭起来个能用的全文搜索,但是如之前所说,Sphinx 的中文分词做的不好,会有例如搜索 “论坛”,而出来的结果中,因为在不同的句子里分别包含了“论”字和“坛”字,而被加入搜索结果。如果你能忍这一点。到这里就已经能用了。
Sphinx3 转为闭源了,有一些开发者不满而 Fork 了一份叫做 Manticore,粗看了一下这个做的比 Sphinx 好,专门为中文分词做了不少支持 https://manual.manticoresearch.com/Creating_an_index/NLP_and_tokenization/CJK
而且因为是基于 Sphinx 改的,大致上还是兼容的,我还没部署上不过粗看了一眼 php 驱动,类名和函数名都跟原本的类似,可能只要把服务器搭建起来,对接 dz 的部分也能够丢进来直接用。
我正在研究如何搭建 Manticore,如果有成果会继续分享
看起来跟 Sphinx 差不多
Discuz 里全文搜索的调用非常集中,只有 search_forum.php:204 这几行,这意味着如果你熟悉任何其他的全文索索引擎,例如 elasticsearch 或者随便什么,把 Discuz 的调用接过来是很容易的,只需要改这一处把他对 Sphinx 的调用接过去就好了,也不一定非要撞死在 Sphinx 这一棵树上。
MySQL 也自带了全文搜索功能,中文分词同样是基于 ngram 机制,跟 Sphinx 一样差。
但是用它来做一个不需要外置依赖的默认全文搜索机制似乎总比没有好。
某 maintainer 就是我,这里自投罗网一下。给的教程确实是看过并且感觉相对看到的几篇其他教程算坑少的,但是毕竟没有亲手验证过,而且我也知道这块儿的功能比较坑,所以教程有问题的话我也认(毕竟我自认为除了认错速度还可以之外,也没啥其他优点了)。
搜索这个问题之前探讨过,确实没有找到什么极其好的解决方案,核心问题就是es维护太费力气,sphinx等小而美的分词都半死不活。 ngram 依赖的 MySQL 版本太高,而且还要改建表语句,所以一直犹犹豫豫没上。
另外受限的一点就是 Discuz! X 也没有多少人力去整(就几个人用爱发电,外带应用中心从中协调一部分。)。也欢迎您提供更好的解决方案代码到 X3.5 版本,以继续完善这一块儿,毕竟众人拾柴火焰高。
高就高嘛,还是上次说那个,现在已经没有人真的受制于虚拟主机了,哪怕用面板的也是自己起的面板自己可控的,哪怕你要求php8+mysql8,也都能跑得起来... 趁3.5本来就要改建表升上去吧真的... 不然错过这个机会又要好多年了
在我们发现 Discuz! Q 不太成功之后不太可能继续提高对服务器的要求,尤其是前段时间接待了一些 MySQL 5.6 的用户之后。
可能要改搜索还是会选择接 ES 或者 Sphinx / Manticore ,不知道您这边测试 Sphinx / Manticore 进度如何了,如果能用了可以考虑 PR 上来。
而且 MySQL 的 ngram 在部分场景下还有反向优化,而且 ngram 类的索引还影响写性能,这就更推进我们把全文搜索功能和数据库功能拆分了。
https://www.zengjianfeng.com/2017/06/150.html
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
很感谢DZ的陪伴我渡过快乐时光, 表示很关注 全文搜索功能,我很庆幸自己当年选择DZ 坚持走下去。。。
要不然,自己早已脱离互联网圈了 说多了都是感慨
全文检索这个功能赶紧弄起来啊。折腾了好久也没有搭起来
阶段性记录
manticore 基本是可行的,有内置中文分词相关处理,虽然有些时候结果依然比较扭曲,例如搜索某个它不认识的四字中文词,有可能会因为某篇文章里在四处不同的段落里分别出现这个词而被搜出来。不如 Discuz 自带那个结果好, 但也还算能用了。
manticore 官方提供的 php 客户端直接兼容 Discuz,https://github.com/manticoresoftware/manticoresearch/blob/master/api/sphinxapi.php 覆盖进 source/class/class_sphinx.php
就可以,无需任何改动。
注意官方有两个 php 客户端,还有个 manticoresearch-php,它跟 Discuz 不兼容,别下错。
另外,这个驱动是 LGPL 许可证的,我不清楚它是否适合打包进 Discuz 一起分发。以前的老版本驱动应该也是,但是流氓公司直接打包来并且删了版权信息,换成了 (C)2001-2099 Comsenz Inc.
source/module/search/search_forum.php
中,if($srchtype == 'fulltext' && $_G['setting']['sphinxon']) {
这句是bug,https://www.discuz.net/thread-3495437-1-1.html 这篇文里第 7 条描述的改法是对的。这个改动适合直接合并进上游。
如果你的论坛是GBK的:
https://www.discuz.net/thread-3495437-1-1.html 这篇文里提到的 SET NAMES gbk
不正确,这里必须用 utf8,即使你的站点是 GBK 的。我之前浪费了大量时间没折腾成主要是因为这个 (我的站因为在等 X3.5 一直没升级 UTF-8 ,升 UTF-8 丢一次用户安全提问,3.5 来了再升 utf8mb4 又丢一次) 这句话不是指数据库本身的编码,而是 mysql 发给客户端 (sphinx indexer) 的编码。而不管你论坛什么编码 sphinx 总是期望一个 UTF-8 编码的文本来解析的。查询地方在 search_forum.php 里面,也需要转换成 UTF-8 再去查询,这部分那篇文章里写的是对的。这个改动也适合直接合并进上游,不过考虑到 3.5 不打算支持 GBK 了那就也无所谓。
其他的配置论坛上那篇文里大多是对的
然后一个值得留意的事是,manticore 官方的 main+delta 模式中,推荐有一个 updated_at 字段和 deleted 字段,用于追踪编辑和删除,这样索引是能完美同步的,updated_at 时间在上次构建索引之后的,会被列入 killlist 把原本的结果去掉。然而,Discuz 并没有提供帖子编辑时间这样的功能,在论坛中看到的 本帖最后由 xxx 于 yyy 编辑
是真的插入了一段文本在正文里。。。。 所以我不推荐使用那个帖子计划任务的章节里写的 merge 的方式,而是周期性的去重做索引,不然编辑和删除就永远同步不过去了。
参考:
https://manual.manticoresearch.com/Adding_data_from_external_storages/Main_delta
https://play.manticoresearch.com/maindelta/
要是 Discuz 有真正的trace编辑和删除功能就好了,那样就可以正常用回去main + delta + merge 模式,性能会高很多,准确率也好。
记得高中时候折腾两个站点之间共享某个板块的帖子,也是受限于这一点没办法完美同步。
这是我写的 conf 文件,跟论坛上那篇大同小异,原理一样,除了中文分词改用 manticore 自带的。
参考:https://manual.manticoresearch.com/Creating_an_index/NLP_and_tokenization/Supported_languages
searchd {
binlog_path =
listen = 9312
log = /var/log/manticore/searchd.log
max_packet_size = 128M
pid_file = /var/run/manticore/searchd.pid
query_log_format = sphinxql
query_log = /var/log/manticore/query.log
}
common {
}
source shared {
type = mysql
sql_host =
sql_user =
sql_pass =
sql_db =
sql_query_pre = SET NAMES utf8
sql_query_pre = CREATE TABLE IF NOT EXISTS sph_counter (counter_id INTEGER PRIMARY KEY NOT NULL, max_doc_id INTEGER NOT NULL)
sql_attr_uint = tid
sql_attr_uint = digest
sql_attr_uint = displayorder
sql_attr_uint = authorid
sql_attr_uint = special
sql_attr_timestamp = lastpost
}
source threads : shared {
sql_query_range = SELECT MIN(tid),MAX(tid) FROM pre_forum_thread
sql_query = SELECT tid AS id,tid,subject,digest,displayorder,authorid,lastpost,special FROM pre_forum_thread \
WHERE tid>=$start AND tid<=$end
sql_query_post_index = REPLACE INTO sph_counter set counter_id = 1, max_doc_id = $maxid
}
source threads_minute : shared
{
sql_query = SELECT tid AS id,tid,subject,digest,displayorder,authorid,lastpost,special FROM pre_forum_thread \
WHERE tid > (SELECT max_doc_id FROM sph_counter WHERE counter_id = 1)
}
source posts: shared {
sql_query_range = SELECT MIN(pid),MAX(pid) FROM pre_forum_post
sql_query = SELECT p.pid AS id, p.tid, p.subject, p.message, t.digest, t.displayorder, t.authorid, t.lastpost, t.special \
FROM pre_forum_post AS p LEFT JOIN pre_forum_thread AS t USING(tid) \
WHERE pid>=$start AND pid<=$end
sql_query_post_index = REPLACE INTO sph_counter set counter_id = 2, max_doc_id = $maxid
}
source posts_minute : shared
{
sql_query = SELECT p.pid AS id, p.tid, p.subject, p.message, t.digest, t.displayorder, t.authorid, t.lastpost, t.special \
FROM pre_forum_post AS p LEFT JOIN pre_forum_thread AS t USING(tid) \
WHERE pid > (SELECT max_doc_id FROM sph_counter WHERE counter_id = 2)
}
index threads {
type = plain
source = threads
path = threads
charset_table = chinese
morphology = icu_chinese
stopwords = zh
}
index threads_minute : threads
{
source = threads_minute
path = threads_minute
}
index posts : threads
{
source = posts
path = posts
}
index posts_minute : threads
{
source = posts_minute
path = posts_minute
}
indexer --rotate threads posts
, 每分钟执行 indexer --rotate threads_minute posts_minute
while :
do
if (( $(date +%s) >= next )); then
indexer --rotate threads posts
next=$(date -d "tomorrow 04:00" +%s)
fi
indexer --rotate threads_minute posts_minute
sleep $(( 60 - $(date +%-S) ))
done
source/class/class_sphinx.php
source/module/search/search_forum.php
, 对应原帖第7步。这个改动适合直接合并进上游。GBK要额外加个转码。刚才仔细找了一下,还有个 post_message 的钩子,感觉如果插件化的话可以和这个钩子融合解决更新索引的问题,当然还是要配合定期重新刷新避免回调失败的情况。
感谢反馈,我们按照这个优化下试试看。
顺便备注一下曾经讨论过的其他已知全文搜索方案:
那看来内嵌新版本的库并改 Manticore 问题不大。可以考虑先替换了库保证 X3.5 可以跑起来,另外找点测试网站看看 Manticore 运行的效果,后续版本再考虑接入 ES 之类的,甚至可以改成插件扩展方便第三方接入其他库。
您看下 https://gitee.com/laozhoubuluo/DiscuzX/commits/fix/v3.5/sphinx_search
我感觉替换这个库相当于升级了客户端(因为 Manticore 相当于是从 Sphinx v2 的 Fork 版本,详见 https://github.com/sphinxsearch/sphinx 仓库),感觉问题不大(当然 FID 的 filter 是个问题,如果完全按照老教程走的用户需要调整索引脚本添加 FID 的索引)。如果不放心的话就 fork 一个 Manticore 或者 sphinxnew ,完了给个 option 可以切换版本就行。
全文搜索倒是不怕,ICU dictionary based / ngram-based 倒也能承担大部分需求了。
就怕有一些比较特殊的用法,要不让 @湖中沉 帮忙放个问卷在后台做下用户调研,约谈一下用户了解下他们是怎么搞的?
也是,这个需求在折腾 Sphinx 的站长这个级别里面可能这种优化也不算太小众,感觉还是后台发问下稳妥,或者直接留下老版本的库并且老版本也不查询 TID 索引保证老配置能完美继承。
很方便考据,manticore 是 sphinx 的 fork,repo 里维持了旧 sphinx 的完整 commit 记录,只要翻一下就知道了。那个api文件自古以来是 GPL 的,2015年的时候切到了 LGPL 至今。
切换的时候的那一个 commit
https://github.com/manticoresoftware/manticoresearch/commit/8c3df903f594288cd17e7475e43af63087d0e46e#diff-8d37cd1805f5224d4b7ce2153250b36e127002f352bbbc0353b44fb6ce46c95e
关于编辑时间戳的问题昨天没看到,补充下。
那就太好了,等编辑时间戳正式上线之后就可以让楼主看看怎么对接 sphinx 了,免得重复索引。
这个调整应该对老版本不影响,老版本还是可以定期重做索引解决。
发现一个坑,Discuz 走 sphinx 的那个分支代码,没设置板块的filter,无论搜索选项里板块选哪些,实际总会搜所有。
post 导入的 filter 里面好像也没有加入 FID ,我 PR 草稿里面加了 FID 查询,您这边测试一下看看能否用吧。
另外吐槽一句,这库写的真是不修边幅,TAB 错乱的我强迫症都犯了,考虑到 LGPL 还不太好下手改(其实是我懒)。
https://gitee.com/laozhoubuluo/DiscuzX/commits/fix/v3.5/sphinx_search
不行,并且变得奇怪了。
论坛那边的修改,没直接起3.5,就改了这一句
push 到 github 和 dockerhub 了。
mycard/discuz-sphinx,参考下面的 docker-compose.yml 直接起就好了
留个 github id 给你加 collaborator 吧
https://code.mycard.moe/mycard/docker-manticore
我做好了一个 dockerfile,配置文件和 cron 都打包进去了
推荐的 docker-compose.yml 如下
sphinx:
restart: always
image:
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
memlock:
soft: -1
hard: -1
volumes:
- /etc/localtime:/etc/localtime
environment:
sql_host:
sql_user:
sql_pass:
sql_db:
之后后台填主机名 sphinx 端口 9313 就可以
然后我要确认一下,如果我把这套整理到标准的 github -> dockerhub 流程,并且写好 README,官方是否会在推荐的安装流程下直接引用我的 repo 和镜像。如果会的话我就整理过去。如果我无论怎么做官方都一定要自己维护 repo 而不引荐任何三方东西的话那我就懒得整理了。
这份配置跟之前的相同,还没有包括 fid 的部分,我去起个带 fid 的 dz 试一下。
关于引用第三方 repo 、镜像的问题,需要让 @湖中沉 帮忙确认一下能否做,这个我说了不算的,毕竟我也只是个开发者。
个人感觉如果是免费的第三方内容(不绑定或者引荐收费服务)是可以引用的。
https://github.com/mycard/discuz-sphinx
https://hub.docker.com/r/mycard/discuz-sphinx
先挪到这儿了,如果不接受的话我再回滚。
收到,回头试试
备注一份测试环境启动脚本(测试环境无 Docker Compose),免得忘了。
docker run \
--detach \
--name discuz-sphinx \
--publish 9312:9312 \
--restart unless-stopped \
-e sql_host=10.6.1.21 \
-e sql_user=root \
-e sql_pass=123 \
-e sql_db=20220130 \
mycard/discuz-sphinx:latest
我们和 @湖中沉 讨论了一下,可以在后续上线的文档站的相关章节提供文档的放置位置(现阶段放在 https://gitee.com/Discuz/DiscuzX/wikis/ 的技术文档章节以及 www.dismall.com 论坛教程版,后续新文档站上线之后迁移),技术文档可以按照 Docker Compose -> Docker -> 手动安装(可以只提供配置文件和大概流程)组织,可以引用您的 Docker 和 GitHub 仓库,但是我们也有几个注意事项。
这东西搜索结果还是 有点糟糕,开了几天用户反馈不好,搜不到想找的东西了。就只好又关掉了............
大概猜一个原因,原始的mysql是like,我搜abcd就必须有abcd才会进入结果,而那些全文搜索引擎不是这样。
如果它认为 ab 是一个词,c是一个词,d是一个词。那么只要包含他们其中之一的就都会进结果,(也可能是全部都有才会进,但是至少一定是不要求连续的abcd)。
然后它又有个评分模式,我不知道这个确切是什么行为,是不是说连续abcd评分最高,然后分别有ab,c,d的评分就低一些,然后只有ab,c没有d的就更低。如果是这样的话,按评分做个排序就还ok的,但是discuz自己有排序功能,按最后时间排序了。导致搜出来第一屏里发现若干只有c的。但是我搜的是abcd。
还是不对,我仔细检查了一下结果,它里面确实包含每一个关键字,但是是分散的.... 就是我搜 abcd 结果里面,ab,c,d,全都有了。但是 abcd 的并不会排到前面所以还是搜不到。
要怎么精确的搜必须连续的 abcd 呢,排除分开的。或者把连续的排在分开的前面
我查了一下按照 weight 排序,更复杂点感觉可以把 lastmod 字段也放进去,不过感觉这么写也够用了
$s->setSortMode(SPH_SORT_EXTENDED, "@weight DESC, @id DESC");
详细聊一下外面教程上那个 search_forum.php 的改动,我可能猜到当年那么写的意图了。
首先基于这样一个假设:
sphinx 搜索结果它是比 mysql 直接查要烂的。mysql like 查询能保证结果是正确的,有的就一定在里面,没有的就一定不在。而 sphinx 不能。
于是最追求质量的解决方案可能是不上 sphinx 直接用 mysql 抗全文搜索。
其次是把非全文的搜索用 mysql,全文的给 sphinx。
最性能的是全文和非全文都给 sphinx。
按照现行 discuz 的代码,可以看出它是想做最后一种,sphinx 里分别建立了全文和非全文的两种索引,凡是开了 sphinx 就所有搜索都走 sphinx 了。
我前面根据论坛上建议的修改,也建议说直接改成那这样子,这是欠缺思考的。
看了 sphinx 的结果比 mysql 差之后,我认为那个改动可能是个补丁,就是通过一个错误的判断,把标题搜索丢回给了 mysql,这样会降低一些性能,换取非全文搜索的质量。
但是这种简单粗暴的改法显然是,不太合适的,他并没有在后台提供相应的解释,还是要求用户分别建立全文和非全文两套索引,然后非全文索引永远不会使用。代码里也有很多垃圾的部分来混淆。
可能的解决方案:
上策 正面刚 sphinx 搜索质量,调到不比 mysql 差,然后像最初设计的一样把所有搜索丢给 sphinx,性能最好。
中策 给用户解释这件事,让用户自己选择配置两个索引还是一个,然后根据后台用户有没有填上对应的索引名字来决定丢给 sphinx 还是 mysql。
下策 设计成 sphinx 就只负责全文,非全文的代码和索引配置正式删掉。
其实我觉得,只要把搜索的一句话拆分成多个断句,用php实现空格自动分开的话,效果也不比全文搜索差了。。。标题多打上关键词就好
什么时间,discuz全文搜索功能可以上线就好了,虽然现在有全文搜索,但只能搜索到标题,帖子内容和文章内容是搜不到的,比较鸡肋蛋疼。快点上线啊,这个做不好吗?
登录 后才可以发表评论