赞
踩
Sphinx查询性能非常厉害,亿级数据下输入关键字,大部分能在0.01~0.1秒,少部分再5秒之内查出数据。
CREATE TABLE `articles` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(16000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=320974 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
cat * > all.txt
方便PHP程序逐行读取。整合后的单个txt文件9.57个G。yield 生成(迭代)器
去逐行读取文件。set_time_limit(0); $file = 'C:/Users/Administrator/Desktop/all.txt'; /** * @function 逐行读取大文件 * @param $file_name string 文件名 * @return Generator|object */ function readLargeFile($file_name) { $file = fopen($file_name, 'rb'); if (! $file) { return false; } while (! feof($file)) { $line = fgets($file); if ($line !== false) { yield $line; } } fclose($file); } // 使用生成器逐行读取大文件 $file_resource = readLargeFile($file); if(! $file_resource) { echo '文件读取错误'; die; } $db = \Illuminate\Support\Facades\DB::table('articles'); $arr = []; foreach ($file_resource as $line) { //获取单行编码 $from_charset = mb_detect_encoding($line, 'UTF-8, GBK, GB2312, BIG5, CP936, ASCII'); //转码操作,因为有些是ANSI,GBK的编码,最好转换成UTF-8 $utf8_str = @iconv($from_charset, 'UTF-8', $line); //遇见空行,直接过滤 if(in_array($utf8_str, ["\n", "\r", "\n\r", "\r\n"])) { continue; } $arr[] = ['content' => $utf8_str]; if(count($arr) >= 10000) { $db->insert($arr); $arr = []; } }
性能调优:
MyISAM引擎:
insert 发现1秒才1400行的插入速度,不行得调整。
改为批量插入,一次性插入10000行数据,并修改/etc/my.cnf 里面的max_allowed_packet,将1M改为512M
。
优化后,每秒2万的插入速度。
如果是InnoDB引擎
可以调整redo log 刷盘策略,set global innodb_flush_log_at_trx_commit=0
。
并添加缓冲池innodb_buffer_pool_size大小为系统总内存的70%
。
把max_allowed_packet,将1M改为512M
,就行了。
数量检查:
两万年后,用SELECT count(*) FROM articles
一看,数据量109 450 000,搞定。
性能测试:
SELECT count(*) FROM articles where content like ‘%晴空万里%’,找出来了1931个,花了242秒。
虚拟机里的CentOS,记得要足够的空间,用来存储数据 一个亿的数据占了系统很大空间,系统存储空间所剩无几了,/usr/local地方没地方存,所以把Sphinx放到了/home下,因为/dev/mapper/centos-home挂载到了/home下。 可以事先安装mmsge wget https://files.cnblogs.com/files/JesseLucky/coreseek-4.1-beta.tar.gz tar zxf coreseek-4.1-beta.tar.gz cd /home/coreseek-4.1-beta/mmseg-3.2.14 ./bootstrap ./configure --prefix=/home/mmseg make && make install 然后安装Sphinx cd /home/coreseek-4.1-beta/csft-4.1 ./buildconf.sh 发现报错,不着急 automake: warnings are treated as errors /usr/share/automake-1.13/am/library.am: warning: 'libstemmer.a': linking libraries using a non-POSIX /usr/share/automake-1.13/am/library.am: archiver requires 'AM_PROG_AR' in 'configure.ac' libstemmer_c/Makefile.am:2: while processing library 'libstemmer.a' /usr/share/automake-1.13/am/library.am: warning: 'libsphinx.a': linking libraries using a non-POSIX /usr/share/automake-1.13/am/library.am: archiver requires 'AM_PROG_AR' in 'configure.ac' src/Makefile.am:14: while processing library 'libsphinx.a' vim ./configure.ac +62 再AC_PROG_RANLIB的下方添加AM_PROG_AR 再次执行./buildconf.sh 发现有如下报错: configure.ac:231: the top level configure.ac:62: error: required file 'config/ar-lib' not found configure.ac:62: 'automake --add-missing' can install 'ar-lib' vim buildconf.sh +5 把--foreign改成--add-missing 再次执行./buildconf.sh 直到configure文件出现。 ./configure --prefix=/home/coreseek --with-mysql=/usr/local/mysql --with-mmseg=/home/mmseg --with-mmseg-includes=/home/mmseg/include/mmseg/ --with-mmseg-libs=/home/mmseg/lib/ make && make install 若发现报错:有ExprEval字样 vim /home/coreseek-4.1-beta/csft-4.1/src/sphinxexpr.cpp 将所有的 T val = ExprEval ( this->m_pArg, tMatch ); 替换为 T val = this->ExprEval ( this->m_pArg, tMatch ); 再次执行make,发现还报错 /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22292:对‘libiconv_open’未定义的引用 /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22310:对‘libiconv’未定义的引用 /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22316:对‘libiconv_close’未定义的引用 vim /home/coreseek-4.1-beta/csft-4.1/src/Makefile +249 把 LIBS = -ldl -lm -lz -lexpat -L/usr/local/lib -lrt -lpthread 改成 LIBS = -ldl -lm -lz -lexpat -liconv -L/usr/local/lib -lrt -lpthread make && make install 成功安装
装好之后配置它,汉字是提示,记得运行环境要删掉
cd /home/coreseek/etc 记得这个名字一定得是csft.conf,换成其它的也行,但是到后期增量索引合并时会报错 cp sphinx-min.conf.dist csft.conf 配置内容就是有汉字的地方 vim csft.conf source articles 起个名,叫articles { type = mysql sql_host = 数据库IP sql_user = 数据库用户名 sql_pass = 数据库密码 sql_db = 数据库 sql_port = 3306 端口 sql_query_pre = select names utf8mb4 加上这行 sql_query = select id,content from articles Sphinx建索引的数据源 sql_attr_uint = group_id 这行用不上可以去掉 sql_attr_timestamp = date_added 这行用不上可以去掉 sql_query_info_pre = select names utf8mb4 加上这行 sql_query_info = select id,content from articles where id=$id 用Sphinx做什么SQL的查询逻辑 #sql_query_post = update sphinx_index_record set max_id = (select max(id) from articles) where table_name = 'articles'; 这个是增量索引要用的东西,这里暂时用不上,后文会讲。 } index articles 起个名,叫articles { source = articles 名字与source一致 path = /home/coreseek/var/data/articles 索引路径 docinfo = extern 这行用不上可以去掉 charset_dictpath = /home/mmseg/etc 新增这行,表示分词读取词典文件的位置 charset_type = zh_cn.utf-8 设置字符集编码 } 保存并退出,注意数据源要与索引是一对一的。 然后留意两个文件 /home/coreseek/bin/indexer 是用来创建索引的 /home/coreseek/bin/searchd 是启动服务端进程的。
开启服务
开启服务
/home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
> ps aux | grep search
root 83205 0.0 0.0 47732 1048 ? S 02:01 0:00 /home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
root 83206 0.5 0.0 116968 3728 ? Sl 02:01 0:00 /home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
9312端口
> netstat -nlp | grep 9312
tcp 0 0 0.0.0.0:9312 0.0.0.0:* LISTEN 83206/searchd
https://sphinxsearch.com/docs/current.html#conf-mem-limit indexer段----------------------------- mem_limit这是创建索引时所需内存,默认32M,可以适当调大,格式如下 mem_limit = 256M mem_limit = 262144K # same, but in KB mem_limit = 268435456 # same, but in bytes https://sphinxsearch.com/docs/current.html#conf-query-log 在searchd段----------------------------- listen 9312 表示sphinx默认端口 listen=9306 mysql41,表示Sphinx服务器将监听在 9306 端口,并使用 MySQL 4.1 协议与客户端进行通信。 可以注释掉以下两行,使其不记录日志 #log = /home/coreseek/var/log/searchd.log #query_log = /home/coreseek/var/log/query.log read_timeout 网络客户端请求读取超时,以秒为单位。可选,默认为5秒。Searchd将强制关闭未能在此超时时间内发送查询的客户端连接。 max_children 并发搜索数量,设置为0,表示不限制。 max_matches 参数用于设置每次搜索操作返回的最大匹配项数 seamless_rotate 在 Sphinx 服务器配置文件中,seamless_rotate 参数用于控制索引旋转操作的行为,确保索引更新过程中的查询不会被中断。“旋转”(rotate)是指在更新索引数据时,将旧的索引文件替换为新生成的索引文件的过程。 当设置为 1 或 true(默认值),seamless_rotate 使得 Sphinx 能够在后台加载新的索引数据,同时继续使用旧的索引数据处理查询请求,直到新的索引完全加载并准备好被使用。这个过程完成后,新的索引将无缝地接管,替换旧的索引,而不会干扰或中断正在进行的搜索操作。 如果将 seamless_rotate 设置为 0 或 false,则在索引旋转时,Sphinx 会暂停处理新的查询请求,直到新的索引文件完全加载并准备就绪。这可能会导致短暂的服务中断,因此,除非有特殊需求,一般建议保持 seamless_rotate 的默认设置(即启用无缝旋转),以确保索引更新过程中搜索服务的连续性和稳定性。 preopen_indexes 是否在启动时强制预打开所有索引。可选,默认为1 unlink_old 用于控制在索引旋转时是否删除旧的索引文件。当设置为 1 或 true 时(默认值),旧的索引文件将被删除,保持默认值就好。
PHP SphinxClient手册:http://docs.php.net/manual/tw/class.sphinxclient.php
cp /home/coreseek-4.1-beta/testpack/api/*.php /test
vim /test/sphinxapi.php
把sphinxapi里面的SphinxClient()方法改为__construct(),防止稍高版本的PHP,产生类名和方法名一致的通知。
可以先测试,只要返回数组,就Server和Client都能正常工作。
vim /test/test_coreseek.php
把里面的网络搜索改成晴空万里,把SPH_MATCH_ANY改成SPH_MATCH_PHRASE。
sphinx文件有个limit选项,这个默认是20,所以最多显示出20个,可以去修改它。
/usr/local/php5.6/bin/php /test/test_coreseek.php
这是二次改过的test_coreseek.php,对于新手有很友好的提示。
require ( "./sphinxapi.php" ); $search = new SphinxClient (); //连接Sphinx服务器 $search->SetServer ( '127.0.0.1', 9312); //设置超时秒数 $search->SetConnectTimeout ( 3 ); //查询出来的数据库ID存放位置 $search->SetArrayResult ( true ); //配置搜索模式 $search->SetMatchMode (SPH_MATCH_PHRASE); //分页,参数1偏移量,从0开始,参数2限制条目 $search->SetLimits(0, 200); //执行查询,参数1关键字 $res = $search->Query ("黑色衣服", "索引名称"); if($res === false) { echo '查询失败'; return; } //获取主键 $primary_keys = []; if(! empty($res['matches'])) { foreach($res['matches'] as $v) { $primary_keys[] = $v['id']; } } //获取总数 $all_count = $res['total_found']; //获取耗时 $time = $res['time']; $ids = implode(',', $primary_keys); echo <<<RESULT 主键id: $ids 查询总数:$all_count 耗时: $time RESULT;
(new SphinxClient())->SetMatchMode (SPH_MATCH_ALL);
匹配模式 | 一句话概括 | 会被匹配 | 不会被匹配 |
---|---|---|---|
SPH_MATCH_ALL | 同时包含这些分词时会被匹配 | 白色的绳子晾着他的衣服 | 他的衣服 |
SPH_MATCH_ANY | 匹配任意一个分词就行 | 白色的纸 | 白云 |
SPH_MATCH_PHRASE | 相当于like ‘%关键词%’ | 白色衣服 | 白色的衣服 |
SPH_MATCH_EXTENDED | 支持Sphinx的表达式 | 下方有详解 | 白色的衣服 |
SPH_MATCH_EXTENDED2 | SPH_MATCH_EXTENDED的别名 | 下方有详解 | 白色的衣服 |
SPH_MATCH_BOOLEAN | 支持布尔方式搜索 | 下方有详解 | 白色的衣服 |
SPH_MATCH_FULLSCAN | 强制使用全扫描模式 | 下方有详解 | 白色的衣服 |
详解:SPH_MATCH_FULLSCAN
SPH_MATCH_FULLSCAN,强制使用全扫描模式。注意,任何查询项都将被忽略,这样过滤器、过滤器范围和分组仍然会被应用,但不会进行文本匹配。
当满足以下条件时,将自动激活SPH_MATCH_FULLSCAN模式来代替指定的匹配模式:
查询字符串为空(即:它的长度是0)。
Docinfo存储设置为extern。
在完全扫描模式下,所有索引的文档都被认为是匹配的。这样的查询仍然会应用过滤器、排序和分组,但不会执行任何全文搜索。这对于统一全文和非全文搜索代码或卸载SQL服务器很有用(在某些情况下,Sphinx扫描比类似的MySQL查询执行得更好)。
详解:SPH_MATCH_BOOLEAN
https://sphinxsearch.com/docs/current.html#boolean-syntax
详解:SPH_MATCH_EXTENDED
https://sphinxsearch.com/docs/current.html#extended-syntax
/home/coreseek/bin/indexer -c /home/coreseek/etc/csft.conf articles
,这里的article就是source段的名称。Coreseek Fulltext 4.1 [ Sphinx 2.0.2-dev (r2922)]
Copyright (c) 2007-2011,
Beijing Choice Software Technologies Inc (http://www.coreseek.com)
using config file '/home/coreseek/etc/csft.conf'...
indexing index 'articles'...
WARNING: Attribute count is 0: switching to none docinfo
collected 109450000 docs, 13406.8 MB
WARNING: sort_hits: merge_block_size=28 kb too low, increasing mem_limit may improve performance
sorted 3133.8 Mhits, 100.0% done
total 109450000 docs, 13406849231 bytes
total 3018.247 sec, 4441931 bytes/sec, 36262.76 docs/sec
total 322950 reads, 202.304 sec, 27.9 kb/call avg, 0.6 msec/call avg
total 20920 writes, 25.975 sec, 997.1 kb/call avg, 1.2 msec/call avg
开启searchd服务/home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
服务器配置:CentOS7.6 16核4G内存
实测亿级数据下搜索晴空万里只花费0.011秒,上文提到MySQL则需要242秒,性能差了22000倍。或许是受或分词器影响,Sphinx查询出来1898条,MySQL查询数据为1931条
多次测试:在109450000条数据中,从不同角度测试Sphinx SPH_MATCH_PHRASE匹配模式,相当于mysql where field like ‘%关键字%’:
类型 | 搜索关键字 | Sphinx搜索耗时(秒) | MySQL搜索耗时(秒) | Sphinx搜索数量 | MySQL搜索数量 |
---|---|---|---|---|---|
数字 | 123 | 0.005 | 305.142 | 3121 | 8143 |
中文单字 | 虹 | 0.013 | 223.184 | 67802 | 103272 |
英文单字母 | A | 0.031 | 339.576 | 136428 | 1017983 |
单中文标点 | 。 | 4.471 | 125.106 | 67088012 | 67096182 |
单英文标点 | . | 0 | 251.171 | 0 | 6697242 |
可打印特殊字符 | ☺ | 0 | 355.469 | 0 | 0 |
中文词语(易分词) | 黑色衣服 | 0.066 | 346.442 | 1039 | 1062 |
中文词语(不易分词) | 夏威夷 | 0.011 | 127.054 | 3636 | 3664 |
中文词语(热门) | 你好 | 0.022 | 126.979 | 102826 | 137717 |
中文词语(冷门) | 旖旎 | 0.010 | 345.493 | 4452 | 4528 |
英文单词 | good | 0.010 | 137.562 | 553 | 1036 |
中文短语 | 他不禁一脸茫然 | 1.742 | 218.272 | 0 | 0 |
英文短语 | I am very happy | 0.015 | 355.235 | 1 | 0 |
长文本 | 陈大人不急着回答,他先从柜台下面又抽出了一份文案,翻了好一阵之后才回答道:“瞧,果然如此,如今广州这边官职该放得都放出去了,只剩下消防营山字营的一个哨官之职。不出所料的话,督抚大人准会委你这个职务。 | 0.131 | 129.204 | 1 | 1 |
压测方式 :ab -c 1 -n 10~1000 192.168.3.180/test_coreseek.php
中文定值关键字为华盛顿,英文定值关键字为XYZ,30位随机中文或英文字符,由代码生成。
生成任意正整数个中文字符 function generateRandomChinese($length) { $result = ''; for ($i = 0; $i < $length; $i++) { $result .= mb_convert_encoding('&#' . mt_rand(0x3e00, 0x9fa5) . ';', 'UTF-8', 'HTML-ENTITIES'); } return $result; } 生成任意正整数个英文字符 function generateRandomEnglish($length) { $result = ''; for ($i = 0; $i < $length; $i++) { $result .= chr(mt_rand(97, 122)); // 小写字母ASCII码范围: 97~122;大写字母:65~90 } return $result; }
类型 | 请求量 (搜索次数) | 耗时(秒) |
---|---|---|
固定中文多次搜索 | 10 | 0.256 |
固定中文多次搜索 | 100 | 1.435 |
固定中文多次搜索 | 1000 | 11.604 |
随机30位中文字符多次搜索 | 10 | 0.517 |
随机30位中文字符多次搜索 | 100 | 2.305 |
随机30位中文字符多次搜索 | 1000 | 17.197 |
固定英文多次搜索 | 10 | 0.327 |
固定英文多次搜索 | 100 | 0.747 |
固定英文多次搜索 | 1000 | 8.510 |
随机30位英文字符多次搜索 | 10 | 0.077 |
随机30位英文字符多次搜索 | 100 | 0.766 |
随机30位英文字符多次搜索 | 1000 | 9.428 |
对于update很多数据,sphinx不会自动更新索引,所以可以选择在公司业务空闲时间重建索引。
对于insert,可以使用增量索引,创建一个表用于存储已经新增sphinx索引记录的最大值。
CREATE TABLE `sphinx_index_record` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Sphinx索引创建进度表id',
`table_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名',
`max_id` int unsigned NOT NULL COMMENT '最大id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
并insert一条数据:
INSERT INTO `test`.`sphinx_index_record` (`id`, `table_name`, `max_id`) VALUES (1, 'articles', 109450000);
$max_id = 从缓存中获取获取的索引位置,假设是100万;
$kw = 搜索关键字;
$id_s = sphinx($kw);
sql1是根据sphinx的
$sql1 = select * from table where id in $id_s;
sql2是对未添加sphinx索引的剩余表数据的操作
$sql2 = select * from table where id > $max_id like "%$kw%";
使用PHP的方式,或者mysql union的方式都行,让两个数据集合并,查询出的结果,通过接口返回。
这样兼顾新增的1000条数据也能被查询的到。
这块需要承接上文。
第一步:自动维护索引最大值,这一步可以在首次创建索引时,就可以完成。 vim /home/coreseek/etc/csft.conf 在source段最后,添加一行代码,这是让sphinx创建增量索引后,自动维护sphinx_index_record表数据。 source articles { ... ... sql_query_post = update sphinx_index_record set max_id = (select max(id) from articles) where table_name = 'articles'; } 第二步:新增增量索引配置 把source段复制出来,然后粘贴到下方,注意括号范围,不要嵌套,例如: source articles_add { type = mysql sql_host = 数据库IP sql_user = 数据库用户名 sql_pass = 数据库密码 sql_db = 数据库 sql_port = 3306 端口 sql_query_pre = select names utf8mb4 加上这行 -------------------------------修改这里start------------------------------------------- sql_query = select id,content from articles where id > (select max_id from sphinx_index_record where table_name = 'articles') Sphinx创建增量索引的数据源 -------------------------------修改这里end------------------------------------------- sql_attr_uint = group_id 这行用不上可以去掉 sql_attr_timestamp = date_added 这行用不上可以去掉 sql_query_info_pre = select names utf8mb4 加上这行 sql_query_info = select id,content from articles where id=$id 用Sphinx做什么SQL的查询逻辑 } 这里记得index段也新增一个配置。永远记住,source段与index段一对一的。 index articles_add 这里需要改 { source = articles_add 这里需要改 path = /home/coreseek/var/data/articles_add 这里需要改 #docinfo = extern charset_dictpath = /home/mmseg/etc charset_type = zh_cn.utf-8 } 执行创建索引功能。 /home/coreseek/bin/indexer -c /home/coreseek/etc/csft.conf articles_add 合并索引:语法indexer --merge 主索引名 增量索引名 /home/coreseek/bin/indexer --merge articles articles_add 如果不关闭searchd,可以添加 --rotate参数强制合并索引。 需要留意一下:如果主索引10个G,增量索引0.1G,则需要20.2G的临时空间去进行和合并。
这个也好办,直接在csft.conf配置文件内source段和index段复制粘贴,根据上文的两段文章,该创建索引的创建索引,该重启的重启。
不需要引入多个文件,就和MySQL一样,只需要一个/etc/my.cnf就行了,相加配置,接着往下续就行了。
新创建索引后不会生效,需要关闭searchd进程后重新启动。
方案1:
使用composer安装新的包,PHP8.0及以上不会报错。
记得要搜索sphinx client或sphinxapi,不要搜素sphinx,这会把SphinxQL的解决方案也给搜出来。
用这个包就行,composer require nilportugues/sphinx-search
用法与自带的完全一致,而且遇到PHP8不报错。
$sphinx = new \NilPortugues\Sphinx\SphinxClient();
$sphinx->setServer('192.168.3.180',9312);
$sphinx->SetConnectTimeout (3);
$sphinx->SetArrayResult (true);
$sphinx->SetMatchMode (SPH_MATCH_PHRASE);
$sphinx->SetLimits(0, 200);
$res = $sphinx->query ("黑色衣服", "articles");
print_r($res);
方案2:
使用原生自带的包,PHP8.0及以上会报错。
sphinxapi.php放置到app/Libs/Others目录下,并添加自己的命名空间App\Libs\Others\SphinxApi。
app/Libs/helper.php存放了自定义封装的方法,并使用composer dump-autoload配置,跟随框架自动加载。
封装成一个方法,方便调用,并放入helper.php下
function sphinx() {
$sphinx = new App\Libs\Others\SphinxApi();
$sphinx->SetServer (config('services.sphinx.ip'), config('services.sphinx.port'));
$sphinx->SetConnectTimeout (config('services.sphinx.timeout');
$sphinx->SetArrayResult (true);
return $sphinx;
}
//控制器随处调用
$sphinx = sphinx();
$sphinx->SetMatchMode (SPH_MATCH_PHRASE);
$sphinx->SetLimits(0, 200);
$res = $sphinx->Query ("黑色衣服", "索引名称");
...
感谢3位博主在C++编译安装报错时提供的解决方案:
夜里小郎君的博文:https://blog.csdn.net/b876143268/article/details/53771310
mingaixin的博文:https://www.cnblogs.com/mingaixin/p/5013356.html
晚晚的博文:https://www.cnblogs.com/caochengli/articles/3028630.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。