优化 Lucene 搜索速度的一点建议
Lucene 是一个全文检索的框架,具体干嘛的自己 Google,在公司项目中使用到了 Lucene 记录用户的操作日志来做用户的行为分析(好像 Lucene 本来不是干这个用的。。),开始的时候索引的数量少,搜索速度很快,随着日志的数量级增大(目前已到千万级),索引文件的大小已攀升到5个多G,搜索的速度已经明显变慢,后来在 apache 官网找到了相关文章介绍优化 Lecene 搜索速度的文章,记录一下此次优化的过程
没有代码谈什么优化,先上一段示例代码,传入一个完整的用户列表,根据用户在时间段内是否登录过 APP 来切分列表
官方文章中一些建议
确定你真的需要去优化 Lucene 搜索的速度
大意就是你的应用可能比较复杂,请确定程序慢是 Lucene 慢导致的,此文就是为了优化 Lucene 的搜索速度,这一点略过
确定你使用的是最新版本的 Lucene
我用的版本是 5.5.3,截止至此文发布的最新版本是 6.3.0,为了项目稳定,能不升级就不升级,这一点我没有去实践,所以不发表意见
使用本地的文件系统
意思是将索引文件放在本地,不要放在远程服务器上,如果一定要放在远程服务器,那就将远程的文件系统挂载设为只读模式,可能会提升一点搜索速度,我的索引文件就在本地,还没有做分布式,这一步也没有进行尝试,欢迎尝试过的朋友和大家分享一下心得
用更好的硬件,更快的系统
硬件不是我想换,想换就能换。。。系统不是我想装,想装就能装。。。
优化你的操作系统
。。。。。。以后再说吧
把 IndexReader 设为只读模式
据官方文档说这在多线程共享同一个 IndexReader 的应用中效果很明显,因为这样会消除线程之间相互争抢某些资源,但是我还没有找到在哪里可以设置,以后找到了再来更新文章。。。
在非 Windows 平台,使用 NIOFSDirectory 代替 FSDirectory
终于有一点可以进行优化了,公司项目部署在 Linux 环境下,果断将代码 IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(App.getIndexPath())));
改为 IndexReader reader = DirectoryReader.open(NIOFSDirectory.open(Paths.get(App.getIndexPath())));
,实际测试效果不是很明显,但是效果都是累积出来的,按照文档改。。
增加的你的内存,提高 JVM 的堆大小
索引文件越大,搜索时会使用更多的内存,如果你没有足够的内存或者 JVM 的堆大小不够,那么搜索的速度就会很慢,这一点同样目前无法实践
使用单例的 IndexSearcher
在你的程序中使用一个单例的 IndexSearcher 对象在线程之间共享,这一点没有去做,原因是我的索引是实时更新的,如果只使用一个单例对象的话,无法实时的获取最新的索引
测试性能的时候,忽略第一个 Query
第一个 Query 在搜索的时候要初始化缓存,由尤其是要以一个字段进行排序的时候
只有在必要的时候才重新打开 IndexSearcher
只有当要搜索最新的索引的时候再重新打开 IndexSearcher
减少合并因子(mergeFactor)
更小的 mergeFactor 意味着更少的 segments,并且会使搜索速度更快,但是它会降低建立索引的速度,所以你应该尝试不同的值,在索引和搜索之间找到一个平衡点,现在重建索引基本不可能,所以没有并没有实践
限制存储字段和 term vectors 的使用
Lucene 在建立索引的时候,每个字段都有两个属性,一个是 index(索引),另一个是 stored(存储),将不需要存储的字段的 stored属性设为 flase,也是大大提高搜索的速度,同样也是无法重建索引,没有实践
使用 FieldSelector
在检索的时候,FieldSelector 会去选择加载哪些字段以及如何去加载他们
如果非必要,不要去迭代结果集
迭代所有的结果集会很慢,有两个原因:一是当你需要100个以上的结果的时候,search() 方法会在返回的 Hits 对象中再次执行搜索,解决方法是使用 HitController 去替代;二是搜索的结果集会分布在上,这会增加 IO 开销,除非它很小,可以被加载到内存中,如果你不需要一个完整的 document,你可以从 FieldCache 中快速的访问一个字段
使用模糊查询的时候使用长度最小的前缀
模糊查询会让 CPU 执行很密集的字符串比较方法,更短的前缀会提高模糊搜索的速度
考虑使用过滤器
使用过滤器比用一条查询语句可以更高效的限制检索的结果,尤其是在索引数量很大的时候,查询和过滤器的区别是,查询对分数有影响,但是过滤器不会
找到程序的瓶颈在哪里
复杂的查询分析和结果集的再处理是 Lucene 搜索的隐藏瓶颈所在,可以使用 VisualVM 等工具来找到问题的所在
开始优化
看完了官方的建议之后,发现有用的建议其实并不太多(我水平太低,能用上的不多。。),但是有一点可以着重去做一下优化,就是在非必要的时候,不要去迭代结果集,在示例代码中可以看到这样一句
这句开始执行搜索操作,返回一个结果集,通过 results.totalHits
的值可以判断用户是否有访问记录,但是可以看到 searcher.getIndexReader().maxDoc()
这个值是随着索引文件的增大而不断变大的,之前取这个值是因为我要遍历所有的结果集,但是现在的需求是我只要知道用户有过访问记录就可以,而不需要知道他具体访问了哪些页面,按照文档的建议,使用 HitController 替代,于是改为以下代码
经过测试,速度有了很大幅度的提升,基本不超过 1ms,而之前没优化之前查询一次要两秒左右,对两万多个用户进行遍历的话,时间简直不敢想象。。。这次优化先到这里结束,之后如果再次优化会更新此文章,由于笔者菜鸟一枚,文中难免会有错误之处,欢迎大家读阅斧正。
Last updated
Was this helpful?