翼度科技»论坛 编程开发 PHP 查看内容

万字详解PHP+Sphinx中文亿级数据全文检索实战(实测亿级数据0.1秒搜索耗时

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
Sphinx查询性能非常厉害,亿级数据下输入关键字,大部分能在0.01~0.1秒,少部分再5秒之内查出数据。
Sphinx


  • 官方文档:http://sphinxsearch.com/docs/sphinx3.html
  • 极简概括:
    由C++编写的高性能全文搜索引擎的开源组件,C/S架构,跨平台(支持Linux、Windows、MacOS),支持分布式部署,并可直接适配MySQL。
  • 解决问题:
    因为MySQL的 like %keyword% 不走索引,且全文索引不支持中文,所以需要借助其它组件。适用于不经常更新的数据的全文搜索。
  • 同类产品:
    ElasticSearch、Solr、Lucene、Algolia、XunSearch。
  • 使用思路:
    发送给Sphinx关键字,然后Sphinx返回id给PHP,PHP再调用MySQL根据id查询。也就是帮着MySQL找id的,MySQL走主键索引,查询性能很高。
  • API支持:
    Sphinx附带了三种不同的API,SphinxAPI、SphinxSE和SphinxQL。SphinxAPI是一个可用于Java、PHP、Python、Perl、C和其他语言的原生库。SphinxSE是MySQL的一个可插拔存储引擎,支持将大量结果集直接发送到MySQL服务器进行后期处理。SphinxQL允许应用程序使用标准MySQL客户端库和查询语法查询Sphinx。
  • SQL扩展
    Sphinx不仅限于关键字搜索。在全文搜索结果集之上,您可以计算任意算术表达式、添加WHERE条件、排序依据、分组依据、使用最小值/最大值/AVG值/总和、聚合等。本质上,支持成熟的SQL SELECT。
建表
  1. CREATE TABLE `articles` (
  2.   `id` int unsigned NOT NULL AUTO_INCREMENT,
  3.   `content` varchar(16000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  4.   PRIMARY KEY (`id`)
  5. ) ENGINE=MyISAM AUTO_INCREMENT=320974 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
复制代码
亿级大数据源准备


  • 决策分析:
    亿级的数据量,还都得是中文,如果是52个大小写字母和10个数字可以随机,中文随机效果差,最好是有能阅读通畅的数据源,而不是随机汉字数据源。
  • 尝试寻找:
    尝试看了txt版的《三国演义》,发现行数太少,所以转变思维,更大的数据量,只能是长篇小说,翻看了最长的小说,也才22万行,1行小说对应1条MySQL数据,数据量还是太少,虽然循环小说内容插入也行,但还是差点意思。
  • 资源整合:
    程序员天天玩的就是数据,这点小事难不倒我,去网盘找txt小说资源合集https://www.aliyundrive.com/s/LBBg4ZvWip2/folder/63a138e95845afd3baa947db96342937033c254f
    找到了如下35000本txt的小说。
  • 数据合并:
    下载了几千个txt小说,需要用命令把这些合并成一个文件,使用cat * > all.txt方便PHP程序逐行读取。整合后的单个txt文件9.57个G。
  • 数据入库:
    大数据文件一次加载会把内存撑爆,所以需要使用yield 生成(迭代)器去逐行读取文件。
  1.     set_time_limit(0);
  2.     $file = 'C:/Users/Administrator/Desktop/all.txt';
  3.     /**
  4.      * @function 逐行读取大文件
  5.      * @param    $file_name string 文件名
  6.      * @return   Generator|object
  7.      */
  8.     function readLargeFile($file_name) {
  9.         $file = fopen($file_name, 'rb');
  10.         if (! $file) {
  11.             return false;
  12.         }
  13.         while (! feof($file)) {
  14.             $line = fgets($file);
  15.             if ($line !== false) {
  16.                 yield $line;
  17.             }
  18.         }
  19.         fclose($file);
  20.     }
  21. // 使用生成器逐行读取大文件
  22.     $file_resource = readLargeFile($file);
  23.     if(! $file_resource) {
  24.         echo '文件读取错误';
  25.         die;
  26.     }
  27.     $db = \Illuminate\Support\Facades\DB::table('articles');
  28.     $arr = [];
  29.     foreach ($file_resource as $line) {
  30.         //获取单行编码
  31.         $from_charset = mb_detect_encoding($line, 'UTF-8, GBK, GB2312, BIG5, CP936, ASCII');
  32.         //转码操作,因为有些是ANSI,GBK的编码,最好转换成UTF-8
  33.         $utf8_str     = @iconv($from_charset, 'UTF-8', $line);
  34.         //遇见空行,直接过滤
  35.         if(in_array($utf8_str, ["\n", "\r", "\n\r", "\r\n"])) {
  36.             continue;
  37.         }
  38.         
  39.         $arr[] = ['content' => $utf8_str];
  40.         if(count($arr) >= 10000) {
  41.             $db->insert($arr);
  42.             $arr = [];
  43.         }
  44.     }
复制代码

  • 性能调优:
    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秒。
编译安装并配置Sphinx Server
  1. 虚拟机里的CentOS,记得要足够的空间,用来存储数据
  2. 一个亿的数据占了系统很大空间,系统存储空间所剩无几了,/usr/local地方没地方存,所以把Sphinx放到了/home下,因为/dev/mapper/centos-home挂载到了/home下。
  3. 可以事先安装mmsge
  4. wget https://files.cnblogs.com/files/JesseLucky/coreseek-4.1-beta.tar.gz
  5. tar zxf coreseek-4.1-beta.tar.gz
  6. cd /home/coreseek-4.1-beta/mmseg-3.2.14
  7. ./bootstrap
  8. ./configure --prefix=/home/mmseg
  9. make && make install
  10. 然后安装Sphinx
  11. cd /home/coreseek-4.1-beta/csft-4.1
  12. ./buildconf.sh
  13. 发现报错,不着急
  14. automake: warnings are treated as errors
  15. /usr/share/automake-1.13/am/library.am: warning: 'libstemmer.a': linking libraries using a non-POSIX
  16. /usr/share/automake-1.13/am/library.am: archiver requires 'AM_PROG_AR' in 'configure.ac'
  17. libstemmer_c/Makefile.am:2:   while processing library 'libstemmer.a'
  18. /usr/share/automake-1.13/am/library.am: warning: 'libsphinx.a': linking libraries using a non-POSIX
  19. /usr/share/automake-1.13/am/library.am: archiver requires 'AM_PROG_AR' in 'configure.ac'
  20. src/Makefile.am:14:   while processing library 'libsphinx.a'
  21. vim ./configure.ac +62
  22. 再AC_PROG_RANLIB的下方添加AM_PROG_AR
  23. 再次执行./buildconf.sh
  24. 发现有如下报错:
  25. configure.ac:231: the top level
  26. configure.ac:62: error: required file 'config/ar-lib' not found
  27. configure.ac:62:   'automake --add-missing' can install 'ar-lib'
  28. vim buildconf.sh +5
  29. 把--foreign改成--add-missing
  30. 再次执行./buildconf.sh
  31. 直到configure文件出现。
  32. ./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/
  33. make && make install
  34. 若发现报错:有ExprEval字样
  35. vim /home/coreseek-4.1-beta/csft-4.1/src/sphinxexpr.cpp
  36. 将所有的
  37. T val = ExprEval ( this->m_pArg, tMatch );
  38. 替换为
  39. T val = this->ExprEval ( this->m_pArg, tMatch );
  40. 再次执行make,发现还报错
  41. /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22292:对‘libiconv_open’未定义的引用
  42. /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22310:对‘libiconv’未定义的引用
  43. /home/coreseek-4.1-beta/csft-4.1/src/sphinx.cpp:22316:对‘libiconv_close’未定义的引用
  44. vim /home/coreseek-4.1-beta/csft-4.1/src/Makefile +249
  45. LIBS = -ldl -lm -lz -lexpat  -L/usr/local/lib -lrt  -lpthread
  46. 改成  
  47. LIBS = -ldl -lm -lz -lexpat -liconv -L/usr/local/lib -lrt  -lpthread
  48. make && make install
  49. 成功安装
复制代码
装好之后配置它,汉字是提示,记得运行环境要删掉
  1. cd /home/coreseek/etc
  2. 记得这个名字一定得是csft.conf,换成其它的也行,但是到后期增量索引合并时会报错
  3. cp sphinx-min.conf.dist csft.conf
  4. 配置内容就是有汉字的地方
  5. vim csft.conf
  6. source articles 起个名,叫articles
  7. {
  8.         type                    = mysql
  9.         sql_host                = 数据库IP
  10.         sql_user                = 数据库用户名
  11.         sql_pass                = 数据库密码
  12.         sql_db                  = 数据库
  13.         sql_port                = 3306  端口
  14.         sql_query_pre           = select names utf8mb4 加上这行
  15.         sql_query               = select id,content from articles Sphinx建索引的数据源
  16.         sql_attr_uint           = group_id   这行用不上可以去掉
  17.         sql_attr_timestamp      = date_added 这行用不上可以去掉
  18.         sql_query_info_pre      = select names utf8mb4 加上这行
  19.         sql_query_info          = select id,content from articles where id=$id 用Sphinx做什么SQL的查询逻辑
  20.         #sql_query_post          = update sphinx_index_record set max_id = (select max(id) from articles) where table_name = 'articles'; 这个是增量索引要用的东西,这里暂时用不上,后文会讲。
  21. }
  22. index articles 起个名,叫articles
  23. {
  24.         source                  = articles                         名字与source一致
  25.         path                    = /home/coreseek/var/data/articles 索引路径
  26.         docinfo                 = extern                           这行用不上可以去掉
  27.         charset_dictpath        = /home/mmseg/etc                  新增这行,表示分词读取词典文件的位置
  28.         charset_type            = zh_cn.utf-8                      设置字符集编码
  29. }
  30. 保存并退出,注意数据源要与索引是一对一的。
  31. 然后留意两个文件
  32. /home/coreseek/bin/indexer 是用来创建索引的
  33. /home/coreseek/bin/searchd 是启动服务端进程的。
复制代码
开启服务
  1. 开启服务
  2. /home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
  3. > ps aux | grep search
  4. root      83205  0.0  0.0  47732  1048 ?        S    02:01   0:00 /home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
  5. root      83206  0.5  0.0 116968  3728 ?        Sl   02:01   0:00 /home/coreseek/bin/searchd -c /home/coreseek/etc/csft.conf
  6. 9312端口
  7. > netstat -nlp | grep 9312
  8. tcp        0      0 0.0.0.0:9312            0.0.0.0:*               LISTEN      83206/searchd
复制代码
配置文件参数说明
  1. https://sphinxsearch.com/docs/current.html#conf-mem-limit
  2. indexer段-----------------------------
  3. mem_limit这是创建索引时所需内存,默认32M,可以适当调大,格式如下
  4. mem_limit = 256M
  5. mem_limit = 262144K # same, but in KB
  6. mem_limit = 268435456 # same, but in bytes
  7. https://sphinxsearch.com/docs/current.html#conf-query-log
  8. 在searchd段-----------------------------
  9. listen 9312
  10. 表示sphinx默认端口
  11. listen=9306
  12. mysql41,表示Sphinx服务器将监听在 9306 端口,并使用 MySQL 4.1 协议与客户端进行通信。
  13. 可以注释掉以下两行,使其不记录日志
  14. #log                    = /home/coreseek/var/log/searchd.log
  15. #query_log              = /home/coreseek/var/log/query.log
  16. read_timeout
  17. 网络客户端请求读取超时,以秒为单位。可选,默认为5秒。Searchd将强制关闭未能在此超时时间内发送查询的客户端连接。
  18. max_children
  19. 并发搜索数量,设置为0,表示不限制。
  20. max_matches
  21. 参数用于设置每次搜索操作返回的最大匹配项数
  22. seamless_rotate
  23. 在 Sphinx 服务器配置文件中,seamless_rotate 参数用于控制索引旋转操作的行为,确保索引更新过程中的查询不会被中断。“旋转”(rotate)是指在更新索引数据时,将旧的索引文件替换为新生成的索引文件的过程。
  24. 当设置为 1 或 true(默认值),seamless_rotate 使得 Sphinx 能够在后台加载新的索引数据,同时继续使用旧的索引数据处理查询请求,直到新的索引完全加载并准备好被使用。这个过程完成后,新的索引将无缝地接管,替换旧的索引,而不会干扰或中断正在进行的搜索操作。
  25. 如果将 seamless_rotate 设置为 0 或 false,则在索引旋转时,Sphinx 会暂停处理新的查询请求,直到新的索引文件完全加载并准备就绪。这可能会导致短暂的服务中断,因此,除非有特殊需求,一般建议保持 seamless_rotate 的默认设置(即启用无缝旋转),以确保索引更新过程中搜索服务的连续性和稳定性。
  26. preopen_indexes
  27. 是否在启动时强制预打开所有索引。可选,默认为1
  28. unlink_old
  29. 用于控制在索引旋转时是否删除旧的索引文件。当设置为 1 或 true 时(默认值),旧的索引文件将被删除,保持默认值就好。
复制代码
客户端配置

PHP SphinxClient手册:http://docs.php.net/manual/tw/class.sphinxclient.php
  1. cp /home/coreseek-4.1-beta/testpack/api/*.php /test
  2. vim /test/sphinxapi.php
  3. 把sphinxapi里面的SphinxClient()方法改为__construct(),防止稍高版本的PHP,产生类名和方法名一致的通知。
  4. 可以先测试,只要返回数组,就Server和Client都能正常工作。
  5. vim /test/test_coreseek.php
  6. 把里面的网络搜索改成晴空万里,把SPH_MATCH_ANY改成SPH_MATCH_PHRASE。
  7. sphinx文件有个limit选项,这个默认是20,所以最多显示出20个,可以去修改它。
  8. /usr/local/php5.6/bin/php /test/test_coreseek.php
复制代码
这是二次改过的test_coreseek.php,对于新手有很友好的提示。
  1. require ( "./sphinxapi.php" );
  2. $search = new SphinxClient ();
  3. //连接Sphinx服务器
  4. $search->SetServer ( '127.0.0.1', 9312);
  5. //设置超时秒数
  6. $search->SetConnectTimeout ( 3 );
  7. //查询出来的数据库ID存放位置
  8. $search->SetArrayResult ( true );
  9. //配置搜索模式
  10. $search->SetMatchMode (SPH_MATCH_PHRASE);
  11. //分页,参数1偏移量,从0开始,参数2限制条目
  12. $search->SetLimits(0, 200);
  13. //执行查询,参数1关键字
  14. $res = $search->Query ("黑色衣服", "索引名称");
  15. if($res === false) {
  16.     echo '查询失败';
  17.     return;
  18. }
  19. //获取主键
  20. $primary_keys = [];
  21. if(! empty($res['matches'])) {
  22.     foreach($res['matches'] as $v) {
  23.         $primary_keys[] = $v['id'];
  24.     }
  25. }
  26. //获取总数
  27. $all_count = $res['total_found'];
  28. //获取耗时
  29. $time = $res['time'];
  30. $ids = implode(',', $primary_keys);
  31. echo <<<RESULT
  32. 主键id: $ids
  33. 查询总数:$all_count
  34. 耗时: $time
  35. RESULT;
复制代码
多配置

这个也好办,直接在csft.conf配置文件内source段和index段复制粘贴,根据上文的两段文章,该创建索引的创建索引,该重启的重启。
不需要引入多个文件,就和MySQL一样,只需要一个/etc/my.cnf就行了,相加配置,接着往下续就行了。
新创建索引后不会生效,需要关闭searchd进程后重新启动。
集成到框架思路

方案1:
使用composer安装新的包,PHP8.0及以上不会报错。
记得要搜索sphinx client或sphinxapi,不要搜素sphinx,这会把SphinxQL的解决方案也给搜出来。
用这个包就行,composer require nilportugues/sphinx-search
用法与自带的完全一致,而且遇到PHP8不报错。
  1. Coreseek Fulltext 4.1 [ Sphinx 2.0.2-dev (r2922)]
  2. Copyright (c) 2007-2011,
  3. Beijing Choice Software Technologies Inc (http://www.coreseek.com)
  4. using config file '/home/coreseek/etc/csft.conf'...
  5. indexing index 'articles'...
  6. WARNING: Attribute count is 0: switching to none docinfo
  7. collected 109450000 docs, 13406.8 MB
  8. WARNING: sort_hits: merge_block_size=28 kb too low, increasing mem_limit may improve performance
  9. sorted 3133.8 Mhits, 100.0% done
  10. total 109450000 docs, 13406849231 bytes
  11. total 3018.247 sec, 4441931 bytes/sec, 36262.76 docs/sec
  12. total 322950 reads, 202.304 sec, 27.9 kb/call avg, 0.6 msec/call avg
  13. total 20920 writes, 25.975 sec, 997.1 kb/call avg, 1.2 msec/call avg
复制代码
方案2:
使用原生自带的包,PHP8.0及以上会报错。
sphinxapi.php放置到app/Libs/Others目录下,并添加自己的命名空间App\Libs\Others\SphinxApi。
app/Libs/helper.php存放了自定义封装的方法,并使用composer dump-autoload配置,跟随框架自动加载。
  1. 生成任意正整数个中文字符
  2. function generateRandomChinese($length) {
  3.     $result = '';
  4.     for ($i = 0; $i < $length; $i++) {
  5.         $result .= mb_convert_encoding('&#' . mt_rand(0x3e00, 0x9fa5) . ';', 'UTF-8', 'HTML-ENTITIES');
  6.     }
  7.     return $result;
  8. }
  9. 生成任意正整数个英文字符
  10. function generateRandomEnglish($length) {
  11.     $result = '';
  12.     for ($i = 0; $i < $length; $i++) {
  13.         $result .= chr(mt_rand(97, 122)); // 小写字母ASCII码范围: 97~122;大写字母:65~90
  14.     }
  15.     return $result;
  16. }
复制代码
Sphinx的不足之处


  • 查询结果遗漏:受分词器影响,实测查询结果比实际的存储要少一些,这会造成信息的损失。
  • 客户端代码陈旧:自带的的PHP Sphinx Api的代码,使用PHP8.0及以上会报错。
  • 不会自动添加增量索引:MySQL新增的数据,Sphinx并不会自动创建索引。
  • 不会自动改变索引:MySQL update的数据,Sphinx不会自动改变索引。
  • 不支持Canal监听bin log实时同步Sphinx索引。
鸣谢

感谢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

来源:https://www.cnblogs.com/phpphp/p/18104229
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具