sphinx应用项目实践踩坑记
2018年09月15日 13:08:51 Winner-雪花飘 阅读数:126
版权声明: https://blog.csdn.net/weixin_42233723/article/details/82713060
之前写的关于sphinx环境及使用的文章只是简单的做个小demo的测试,真正应用到项目中会遇到各种问题袭来,没有leader指导,只能自己摸索,应用到项目上线也是花了好几天的时间,这次记录只要是指出各个坑点,避免日后再踩坑。
环境搭建
docker 如何安装在这里就不详细说了,有了docker环境后使用 docker search sunfjun/coreseek ,笔者使用的docker源是阿里源 vi /etc/docker/daemon.json
{
“registry-mirrors”: [“https://78zjyej0.mirror.aliyuncs.com”]
}
1
2
3
这个镜像也是笔者找了很久才找到的,本来想下载软件环境重新安装走一遍的,可是www.coreseek.cn网站已经停止运营了,无法下载到coreseek,很多csdn下载coreseek都要积分,于是就找到了这个现成的容器,不过这个支持中文搜索的coreseek容器似乎环境版本有点低,只能下载sphinx-2.0.10-release.tar.gz,亲试过以上版本的会提示版本问题,下载地址{:target=_blank},sunfjun/coreseek作者项目说明地址
拉取镜像 docker pull sunfjun/coreseek
启动容器
启动容器必须要有一个 sphinx.conf,笔者刚开始在这里折腾了好久,该文件要映射到 /usr/local/etc/sphinx 目录,要不然会报 csrf.conf 找不到的错误,还有一点需要注意的是 sphinx.conf 必须是可使用的配置,因为启动容器会运行脚本 entrypoint.sh ,
#!/bin/bash
/usr/local/bin/indexer –all > /var/sphinx/log/indexer.log
echo “Starting Sphinx”
/usr/local/bin/searchd –nodetach
1
2
3
4
5
6
新建目录 mkdir -p /data/sphinx/conf /data/sphinx/data /data/sphinx/log ,刚开始不了解配置,最简单的方法是 copy sphinx 包里的 sphinx-min.conf.in 文件到 conf 目录 cp sphinx-min.conf.in /data/sphinx/conf/sphinx.conf ,将包里的 example.sql 导入到已有数据库,把配置里的数据库连接改为自己的数据库连接即可,把配置文件里的 data 目录和 log 目录改为 /var/sphinx/data /var/sphinx/log。
docker run -id –name sphinx -v /data/sphinx/conf:/usr/local/etc/sphinx -v /data/sphinx/data:/var/sphinx/data -v /data/sphinx/log:/var/sphinx/log -p 9312:9312 sunfjun/coreseek,配置如果没有什么大问题应该可以正常启动,可以使用 docker ps -a 查看容器有没有启动,使用 docker logs sphinx 查看失败原因
配置解析
#
Minimal Sphinx configuration sample (clean, simple, functional)
#
source src
{
type = mysql
sql_host = xxx
sql_user = xxx
sql_pass = xxx
sql_db = xxx
sql_port = 3306
#sql_query_pre = SET NAMES utf8
}
source release_src:src
{
sql_query_pre = replace into release_counter select 1, max(id) from sre_sql_release;
sql_query_pre = SET NAMES utf8
sql_query = select id,applicant,operation,reason,dbname,scrum_key,bug_key,content,is_deleted,status,CRC32(env) as env,UNIX_TIMESTAMP(createTime) as create_time,group_team_id,time_to_sec(createTime) as time_gap from release where id <= (select max_id from release_counter where id=1)
sql_attr_uint = is_deleted
sql_attr_uint = status
sql_attr_uint = env
sql_attr_timestamp = create_time
sql_attr_uint = group_team_id
sql_attr_uint = time_gap
sql_attr_multi = uint exec_time from query; SELECT release_id,exec_time from sql_exec_time
}
source delta_release:release_src
{
sql_query_pre = SET NAMES utf8
sql_query = select id,applicant,operation,reason,dbname,scrum_key,bug_key,content,is_deleted,status, CRC32(env) as env,UNIX_TIMESTAMP(createTime) as create_time,group_team_id,time_to_sec(createTime) as time_gap\
from release where id > (select max_id from release_counter where id=1)
}
index release
{
source = release_src
path = /var/sphinx/data/release
docinfo = extern
charset_type = zh_cn.utf-8
charset_dictpath = /usr/local/etc
min_word_len =1
min_prefix_len = 1
}
index release_delta:release
{
source = delta_release
path = /var/sphinx/data/release_delta
}
indexer
{
mem_limit = 32M
}
searchd
{
listen = 9312
log = /var/sphinx/log/searchd.log
query_log = /var/sphinx/log/query.log
read_timeout = 5
max_children = 30
pid_file = /var/sphinx/log/searchd.pid
max_matches = 1000
seamless_rotate = 1
preopen_indexes = 1
unlink_old = 1
workers = threads # for RT to work
binlog_path = /var/sphinx/data
compat_sphinxql_magics = 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
笔者在这里就不详细讲解配置,只讲解使用过程中遇到的坑点,参考资料 https://www.cnblogs.com/yjf512/p/3598332.html https://blog.csdn.net/beyond_boy/article/details/6462403
笔者应用的是增量索引,所以新建一个 release_counter 表来存储最大 id。
– 中文搜索
刚开始测试一直没有不能索引到中文,排查了好久才找到原因,不仅要配置 sql_query_pre = SET NAMES utf8, 还要配置中文字典路径
charset_type = zh_cn.utf-8
charset_dictpath = /usr/local/etc
1
2
我在测试时字典文件移到了 /usr/local/etc/data 目录,并且指向该目录,导致在部署环境生成另一个新的 sphinx 容器时出现找不到索引后缀为 .sph 的文件,容器启动失败,因为错误原因不明确也是排查了很长时间。索引中文必须配置以上三项!!!
继承
无论是源还是索引都使用的继承模式,这里有一个坑点,就是继承和增量索引配置影响中文搜索,也是花了一些时间去排查的。当子源配置了 sql_query_pre = replace into release_counter select 1, max(id) from sre_sql_release; 这个时,父源的 sql_query_pre = SET NAMES utf8 会被覆盖,导致不能正确索引到中文,所以我在子源多加了这个配置 sql_query_pre = SET NAMES utf8
属性、字段配置
sql_field_string 为设置全文索引字段,sql_attr_uint 、 sql_attr_timestamp 设置过滤字段,实测在 select 里面的字段中,在 sql_attr_* 配置的都是过滤字段,其它都是索引字段,如果两者都配置,属性优先,索引无效,在使用api的返回结果中有个字段是 field 指明索引字段,attr 指明过滤字段(api后文说),我刚开始没注意到这两个字段,能不能索引,能不能过滤也折腾了好久。这里有一点需要注意的是 sphinx 不能过滤字符串,设置的 sql_attr_string 并没有过滤的效果,刚开始不知道也折腾了好久,既然提供了配置为什么会没有效果呢?最后上网找资料,参考 https://blog.csdn.net/longxingzhiwen/article/details/76019644 ,该文中提到过滤字符串的方法,实测方法一不可行,方法二使用 crc32转化为数字 非常完美的解决了我的字符串过滤问题
时间段过滤
我项目里的需求是筛选出每天某个时间段里的数据,字符串过滤、timestamp、crc32都无法解决这个问题,也是找了好久才找到 time_to_sec 解决方法,time_to_sec 可以将时间转化为秒数,目前 sphinx 的 api 只可以过滤整形和范围过滤,范围是一个区间,需要边界值,如果是大小或者小于,只能自己加一个合理的边界值凑成两个边界参数了
跨表多值过滤
刚开始使用的 sql_joined_field 配置,后来才发现这个是索引配置,而过滤配置是sql_attr_multi , 参考文章 https://blog.csdn.net/websites/article/details/18802015 ,这里联表的返回的字段也要求是 uint 或 timestamp 所以如果碰到字符串也要通过转化的方式解决了。
索引长度问题
当设置字符是 utf-8 后,一两字母或者一个中文是索引不出来的,后来加了这个配置才生效,
min_word_len =1
min_prefix_len = 1
1
2
这是设置索引长度的问题,但是如果是说解释跟字符编码内部的长度联系起来则不了解了。
sphinxapi.php 使用
public function listFromSphinx($params)
{
$page = $params[‘page’] ?$params[‘page’]:1;
$pageSize = $params[‘page_size’] ? $params[‘page_size’] : 20;
$offset = ($page – 1) * $pageSize;
$sphinxClient = new SphinxClient();
$host = ENV_DATA[‘tool_sphinx’][‘host’];
$port = ENV_DATA[‘tool_sphinx’][‘port’];
$sphinxClient->SetServer($host, $port);//连接
$sphinxClient->SetConnectTimeout(5);//超时时间
$sphinxClient->SetMatchMode(SPH_MATCH_ALL);//匹配模式
$sphinxClient->SetSortMode(SPH_SORT_EXTENDED, ‘@id DESC’);//设置排序
$sphinxClient->SetLimits($offset, $pageSize);//分页
//全文索引
$queryKeys = [];
$keys = [‘applicant’, ‘operation’, ‘reason’, ‘dbname’, ‘scrum_key’, ‘bug_key’, ‘content’];
foreach ($keys as $key) {
if (!empty($params[$key])) {
array_push($queryKeys, $params[$key]);
}
}
//过滤
$sphinxClient->SetFilter(‘is_deleted’, [0]);
isset($params[‘status’]) && $params[‘status’] !== ” && $params[‘status’] >=0 && $sphinxClient->SetFilter(‘status’, [intval($params[‘status’])]);
$params[‘env’] && $sphinxClient->SetFilter(‘env’, [crc32($params[‘env’])]);
if (!empty($params[‘createTime’])) {
//日期区间
$arr = explode(‘~’, $params[‘createTime’]);
$startDate = $arr[0];
$endDate = date(‘Y-m-d’, strtotime(‘+1 day’, strtotime($arr[1])));
$sphinxClient->SetFilterRange(‘create_time’, strtotime($startDate), strtotime($endDate));
}
$params[‘group_team_id’] && $sphinxClient->SetFilter(‘group_team_id’, [$params[‘group_team_id’]]);
//执行时间
if ($params[‘execTime’]) {
$execTime = $params[‘execTime’] * 60;
$maxTime = 3600 * 6; //限制最大执行时间6个小时内的
$sphinxClient->SetFilterRange(‘exec_time’, $execTime, $maxTime);
}
//时间区间
if ($params[‘interval’]) {
$time_interval = explode(‘-‘, $params[‘interval’]);
if (count($time_interval) == 2) {
$min = strtotime(date(‘Y-m-d ‘).$time_interval[0]) – strtotime(date(‘Y-m-d’));
$max = strtotime(date(‘Y-m-d’).$time_interval[1]) – strtotime(date(‘Y-m-d’));
$sphinxClient->SetFilterRange(‘time_gap’, $min, $max);
}
}
$result = $sphinxClient->Query(implode(‘ ‘, $queryKeys), ‘sre_sql_release sre_sql_release_delta’);
if ($result === false) {
return [‘status’ => false, ‘msg’ => $sphinxClient->GetLastError()?:’sphinx返回错误’, ‘data’ => []];
} else {
$ids = count($result[‘matches’]) > 0 ? array_keys($result[‘matches’]):[];
return [‘status’ => true, ‘msg’ => ”, ‘data’ => $ids, ‘total’ => $result[‘total’]];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
将 sphinx 包里 api 目录里的 sphinxclient.php copy 到加入到项目中,添加命名空间并使用 use 引用到文件中,可能是版本太低,所以要把文件里的 SphinxClient() 构造方法改成 __construct(),不然一直返回 false。
$host = ENV_DATA[‘tool_sphinx’][‘host’];
$port = ENV_DATA[‘tool_sphinx’][‘port’];
1
2
这是我的项目里获取配置的方法,无需深究。
$sphinxClient->SetMatchMode(SPH_MATCH_ALL);,设置索引模式,这里是索引所有条件符合才返回,可参考 https://blog.csdn.net/myweishanli/article/details/42033825 ,索引是分词索引,也就是会把你的字符串拆分来索引匹配,而不是像 like 去包含某个字符串。
$sphinxClient->SetSortMode(SPH_SORT_EXTENDED, ‘@id DESC’);//设置排序,设置排序,id 是 sphinx 里的内置变量,所以加上 @ ,其它属性则不必,按我理解应该使用$sphinxClient->SetSortMode(SPH_SORT_ATTR_DESC, ‘@id’); 即可,可是没有生效,可参考 https://blog.csdn.net/slqgenius/article/details/51972102 。
$result = $sphinxClient->Query(implode(‘ ‘, $queryKeys), ‘release release_delta’); 将要索引的字段用空格分开,第二个参数使用空格分开引用多个索引
部署上线
sphinx 的执行命令在 /usr/local/bin 目录下,命令 /usr/local/bin/indexer –all –rotate 重建所有索引,命令 /usr/local/bin/indexer release_delta –rotate 只重建增量索引。要求写到 crontab 里,重建所有索引在每天晚上 0 点,重建增量索引要求每隔 2 秒执行一次(因为新建 数据后直接跳到列表页,所以要求要快)。这里提示一下 sphinx 里的 crontab 有问题,设置不成功,运维重建了镜像才生效。
##增量索引,2钟执行一次
* * * * * sleep 2;/usr/local/bin/indexer release_delta –rotate >/dev/nul
###全量索引,每天晚上12点执行
59 23 * * * /usr/local/bin/indexer –all –rotate >/dev/nul
1
2
3
4
因为 crontab 不支持秒设置,度娘了一下有两种方案,使用了 crontab sleep 2 的方法来解决这个问题,另一个方法是写在一个 shell 脚本里一直 while true 在 sleep 2,再执行那个脚本。
但是这个 2 秒还不能满足我的需求,新建跳转到列表页不能及时展示我的数据,只能 model 里触发事件去执行一次索引了。我的项目里是调用 python 的接口,python 连接到服务器再执行容器的命令去重建索引,这样就达到我的目的了。还有一个问题是担心重构索引太频繁担心太吃服务器资源。
public function init()
{
parent::init(); // TODO: Change the autogenerated stub
$this->on(self::EVENT_AFTER_INSERT, [RefreshIndexEvent::class, ‘refreshReleaseDelta’]);
$this->on(self::EVENT_AFTER_UPDATE, [RefreshIndexEvent::class, ‘refreshReleaseDelta’]);
$this->on(self::EVENT_AFTER_DELETE, [RefreshIndexEvent::class, ‘refreshReleaseDelta’]);
}
public static function refreshReleaseDelta()
{
…
$command = "docker exec -i {$container} /usr/local/bin/indexer release_delta --rotate";
...
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
总结
回想遇到的坑基本都在这里了,坑都是在实践的过程中才会发现的,仅仅是看文档的配置只是一个了解的层面而已。实践才是检验真理的惟一标准!!!
转载请注明:SuperIT » sphinx应用项目实践踩坑记