More Related Content Similar to 基于Lucene的站内搜索 Beta (20) 基于Lucene的站内搜索 Beta1. 基于 lucene 的站内搜索实战
tangfulin <tangfulin@gmail.com>
www.imobile.com.cn
2009.07.26 @beta 技术沙龙
2. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
3. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
6. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
7. 手机之家的搜索 V1.0(1/3)
产品大全搜索
新闻 CMS 搜索
论坛搜索(默认只搜索标题)
论坛帖子及回复搜索
手机铃声,主题,电子书,软件搜索
二手交易搜索
其它版块的搜索
9. 手机之家的搜索 V1.0(3/3)
运行概况
Dev by chaoqian (http://www.longker.org/)
最初基于 Lucene 2.2 ,当前 2.4.1
开发时间: 07 年 12 月- 08 年 4 月
08 年 12 月随新版系统上线
部署在 Imobile-SV39-A49 上:
• X86_64 , 8 cpu @ 2.50GHz , 32G mem
• CentOS release 5.2, JDK64 1.6,
搜索: 35+ 万查询 / 天,高峰期 > 20 次 /s
更新: 平均 15 条 / 分钟
10. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
11. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
12. 需求背景 (1/3)
重建索引
索引字段变更
分词算法变更
搜索结果异常(记录重复,记录丢失)
索引文件意外损坏
V1.0 :半手工重建,重建过程需人工参与,重建过程中
不能正常更新
V1.5 :自动重建,重建过程中正常更新原来的索引
13. 需求背景 (2/3)
缩短更新周期(及时更新)
Google 更新手机之家内容的周期为 30 分钟左右
V1.0 索引更新周期为 10 - 15 分钟左右
V1.5 预期更新周期为 3 分钟,实际为 1-5 分钟
14. 需求背景 (3/3)
搜索大索引( V1.5 )
3300+ 万条记录的一个库, xml 原始文件 14G
V1.0 索引文件为 13 G ,无法快速更新
V1.5 索引文件为 3.9G (还是无法快速更新?)
V1.5 完整重建一次: 140 分钟
15. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
16. 目标
及时更新( 3 分钟)
快速重建( < 2 小时)
可配置(拥抱需求变化)
可监控(运维友好)
SLA :永远可写,永远可读,异常的时候唯一的表现是
更新延迟
高性能,能承受较大的流量,并发压力
17. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
18. 进度
2009 年 4 月 1 号 search 2.0 init
4 月 12 号,修改版本号为 1.5
6 月 1 号, search 1.5 在线上试运行
6 月 22 号,正式替换原来的搜索进程
19. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
20. 设计 (1/8)
分离索引与存储,二次读取
分离读与写
分离 update 和 rebuild
拆分大库和小库
new open 小库(小库滚动), reopen 大库
新索引预热
更多: http://blog.fulin.org/category/tech/lucene
21. 设计 (2/8) 分离索引与存储,二次读取
索引里只存储 id ,其他的字段只索引不存储。
优点:
保持索引的大小为一个可接受的范围
提高索引读取速度
提高索引 cache 效率
缺点:搜索时需要额外的请求来获取其它必须的字段
( lucene + db 方案)
衍生项目: blackdb , memcache 协议 + bdbJE 存储
22. 设计 (3/8) 分离读与写
优点:
降低编程复杂度
保证搜索服务的可用性,和可扩展性(可以将索引文件
分发到多台机器上,同时对外提供服务)
提升索引更新速度
缺点:
• 需要移动索引文件
• 需要额外的索引更新逻辑( reopen )
• 无法使用当前设计中的 lucene 的 real-time search
23. 设计 (4/8) 分离 update 和 rebuild
Rebuild 的同时, update 正常更新
Rebuild 需要将重建这段时间的更新计入新的索引中
Rebuild 完成后,通知 update 切换到新索引上来,并
继续更新
进程间通信,当前使用最原始的基于文件的方式
24. 设计 (5/8) 拆分大库和小库
保证及时更新的同时,减少索引频繁同步(由写索引同
步到读索引)带来的 io 压力
更新频率
• 小库(最近更新库) 1 分钟
• 大库(历史库) 1 天到 1 周,可配置
并行搜索,加速搜索速度
问题: group by
25. 设计 (6/8) 拆分大库和小库(续)
增加新记录:
增加到小库
更新记录:
从大库中删除(标记删除)
从小库中删除(物理删除)
增加到小库
删除记录
从大库中删除(标记删除)
从小库中删除(物理删除)
定期合并小库到大库,并清空小库
26. 设计 (7/8) 搜索端索引更新
小库每次同步到一个新的文件夹中
保留最近打开的 n (2) 份小库索引目录
检测到新的索引到达,关闭一个最旧的,打开新的,预
热后标识为可用
检测到新的小库到达, reopen 大库(为了逻辑上的简
单起见,大小库同步更新)
27. 设计 (8/8) 新索引预热
目的:消除新打开的索引上前几次搜索慢的问题
实现:
• 过去:遍历一遍新打开的索引,将数据都读入内存
• 引起 gc ,导致搜索暂停
• 对 lucene 内置的 cache 无贡献
• 现在:搜几个热门的词,并遍历结果集
• 将来:使用原来的 cache 填充新的 cache
预热完成后,再投入使用
28. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
29. 实现 (1/8)
IndexRebuilder
搜索管理后台
发出开始重建索引命令 IndexReceiver
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
xml data IndexUpdater
DAL 数据更新 配置文件
同步通知搜索
Searcher
搜索客户端调用
Searcher 的 API 端口
1985
30. 实现 (2/8)IndexReceiver
IndexRebuilder
搜索管理后台 IndexReceiver
发出开始重建索引命令
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
xml data IndexUpdater
DAL 数据更新 配置文件
同步通知搜索
Searcher
搜索客户端调用
Searcher 的 API 端口
1985
31. 实现 (2/8)IndexReceiver
By Java , Daemon 程序
监听端口,使用 SCGI 通讯协议
使用 Monkey 为底层 NIO 处理框架
接收客户端 post 过来的数据,并写入对应的目录
Update 和 rebuild 共用
为了保证原子性,先写入 .0.***.xml ,写入完成后,再 rename
Receiver 只负责写入, updater 和 rebuilder 稍后负责删除(备份)
对外提供服务,所以尽可能简单,单独进程
32. 实现 (3/8)IndexUpdater
IndexRebuilder
搜索管理后台
发出开始重建索引命令
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
IndexUpdater
xml data
DAL 数据更新 配置文件
同步通知搜索
Searcher
搜索客户端调用
Searcher 的 API 端口
1985
33. 实现 (3/8)IndexUpdater
By Java , Daemon 程序
多个索引共用,每个索引起一个线程
可根据需要随时停止或启动单个索引更新的线程
AddShutdownHook 退出前 close 所有打开的 IndexWriter
功能:
• 更新新数据到当前索引
• 合并大小库
• 拷贝当前索引的快照供搜索使用
• 切换 rebuild 出来的新索引
34. 实现 (4/8)IndexRebuilder
IndexRebuilder
搜索管理后台
发出开始重建索引命令
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
xml data
DAL 数据更新 配置文件
同步通知搜索
Searcher
搜索客户端调用
Searcher 的 API 端口
1985
35. 实现 (4/8)IndexRebuilder
By Java , Daemon 程序
多个索引共用,每个索引一个线程
重建索引时序图:
Receiver 接到通
知,停止抄送
updater 数据。
客户端发送开 客户端发送重 客户端发送结 重建过程结束
始重建标识 建索引的数据 束重建标识
Receiver 开始抄送
update 数据到 receiver
rebuild-update
Updater 接到
Updater 正常 通知,切换索
T0
T1
T2
T3
T4
T5
更新 引。完成后通 updater
知 receiver
Rebuilder Rebuilder Rebuilder 处理完 t0
开始工作 处理完重 到 当前的更新数据, rebuilder
建数据 通知 updater ,然后
自己退出
开始重建 重建数据 重建完成 时间线
接收完毕
36. 实现 (5/8)IndexRebuilder
切换索引过程
• Updater 删除自己当前的索引和未更新完的 xml 文件
• Updater 将 rebuilder 的索引和 rebuild-update 的 xml 文件 “据为己有”
• 通知 receiver 停止抄送
• 继续正常的数据更新过程
可以证明这个过程中,不会有数据丢失,也不会有数据重复
• 前提: updater 的 xml 文件和 rebuild-update 的 xml 文件是完全相
同的(包括文件名和数据)
• t3-t4 : 数据来自 rebuild-update
• t4-t5 : rebuild-update 中的数据可以被直接忽略
37. 实现 (6/8)Transfer
搜索管理后台
发出开始重建索引命令
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
xml data
DAL 数据更新 配置文件
同步通知搜索
Searcher Transfer
搜索客户端调用
Searcher 的 API 端口
1985
38. 实现 (6/8)Transfer
Bash 脚本,每个 indexId 一个进程,由 ControlCenter 或 monitor 启
动和停止
监控索引快照目录
快照目录下存在子目录,并且子目录中存在 copy.done.sign
( IndexUpdater 拷贝快照完成后 touch 的标识),则 rsync 子目录到
search
Rsync 的时候:先 rsync 大库快照,再 rsync 小库快照,都成功后再
rsync 一个 trans.done.sign
以 0 字节的 sign 文件作为标识,模拟两阶段提交,保证文件拷贝,
传输的原子性
39. 实现 (7/8)Searcher
搜索管理后台
发出开始重建索引命令
cron 发送重建数据
Rebuild
xml data
端口
1986
Update
xml data
DAL 数据更新 配置文件
同步通知搜索
Searcher
搜索客户端调用
Searcher 的 API 端口
1985
40. 实现 (7/8)Searcher
By Java , Daemon 程序
使用 Monkey 为底层 NIO 处理框架
使用 SCGI 通讯协议
启动后第一件事: warmUpAllIndex
当有新索引到达的时候,在后台打开,预热后,再投入使用
关闭旧的 IndexSearcher 实例的前提:当前没有线程还在使用它
• 方法:计数 ( get +1 , return -1 ,为 0 表示没有被使用)
Stat 统计
41. 实现 (8/8)Utils
Cleaner
• Bash 脚本,每个 indexId 一个进程
• 用来删除 Searcher 已经关闭或跳过的索引
Monitor
• 监控系统的各个进程是否存在,如不存在,则启动一个新的进程
ControlCenter
• Usage: ./controlCenter.sh {start|stop|restart} {all|receiver|updater|
rebuilder|searcher|trans|cleaner}
• OR: ./controlCenter.sh {start|stop|restart} {monitor|
logSlowSearch}
• OR: ./controlCenter.sh {mkdirs}
42. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
43. 性能简单测试结果
Apache bench
n 1000 c 10 ( 99% 545ms 100% 32838ms )
n 2000 c 20 ( 90% 1171ms 99% 4106ms )
库大小: 4.1G 条目数: 3300+w 更新周期: 1 分钟
总请求 3000 ,慢查询 ( 耗时大于 1 秒 ) 比例 4%
搜索词:手机之家论坛首页的板块名,随机
最长: 37s
> 10s : 22 (索引切换, gc )
2s10s : 38
1s2s : 62
44. 性能对比测试结果 (1/6)
Apache bench
库大小: 4.1G ,条目数: 3300+w ,更新周期: 1 分钟
搜索词:手机之家论坛板块名随机
对比条件
停止更新与正常更新
分配索引 1 倍大小的内存与 2 倍大小的内存
并发 10 个与 20 个
Document Length: 1529 bytes
Total transferred: 3447737 bytes (1722053 bytes)
HTML transferred: 3085737 bytes (1541053 bytes)
45. 性能对比测试结果 (2/6)
Time taken for tests 停止更新 正常更新
分配索引大小1倍内存 ab -n 2000 -c 20 21.85 64.17
分配索引大小1倍内存 ab -n 1000 -c 10 13.1 11.93
分配索引大小2倍内存 ab -n 2000 -c 20 22.8 22.89
分配索引大小2倍内存 ab -n 1000 -c 10 10.37 10.74
备注:单位为秒
46. 性能对比测试结果 (3/6)
Requests per second 停止更新 正常更新
分配索引大小1倍内存 ab -n 2000 -c 20 91.52 31.17
分配索引大小1倍内存 ab -n 1000 -c 10 76.33 83.83
分配索引大小2倍内存 ab -n 2000 -c 20 87.71 87.39
分配索引大小2倍内存 ab -n 1000 -c 10 96.45 93.13
47. 性能对比测试结果 (4/6)
Time per request 停止更新 正常更新
218.531 641.674
分配索引大小1倍内存 ab -n 2000 -c 20 10.927 32.084
131.015 119.288
分配索引大小1倍内存 ab -n 1000 -c 10 13.102 11.929
228.012 228.870
分配索引大小2倍内存 ab -n 2000 -c 20 11.401 11.444
103.676 107.375
分配索引大小2倍内存 ab -n 1000 -c 10 10.368 10.738
备注:
[ms] (mean)
(mean, across all concurrent requests)
48. 性能对比测试结果 (5/6)
Transfer rate 停止更新 正常更新
分配索引大小1倍内存 ab -n 2000 -c 20 154.06 52.47
分配索引大小1倍内存 ab -n 1000 -c 10 128.46 140.98
分配索引大小2倍内存 ab -n 2000 -c 20 147.63 146.82
分配索引大小2倍内存 ab -n 1000 -c 10 162.07 156.39
备注:
(Kbytes/sec)
49. 性能对比测试结果 (6/6)
耗时比例 停止更新 正常更新
50% 59 50% 162
95% 905 90% 1403
分配索引大小1倍内存 ab -n 2000 -c 20 100% 1525 98% 5738
50% 34 50% 24
99% 978 98% 750
分配索引大小1倍内存 ab -n 1000 -c 10 100% 1420 100% 3600
50% 61 50% 41
95% 906 95% 988
分配索引大小2倍内存 ab -n 2000 -c 20 99% 1519 99% 1678
50% 28 50% 27
99% 744 99% 698
分配索引大小2倍内存 ab -n 1000 -c 10 100% 905 100% 764
备注:百分比 毫秒
50. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
51. 部署上线
运行概况
updater , rebuilder 部署在 Imobile-SV25-B50 上
• IntelXeon 4 cpu @2.60GHz , 6G mem
• Slackware 12.1, JDK32 1.6
Search 部署在 Imobile-SV39-A49 上:
• X86_64 , 8 cpu @ 2.50GHz , 32G mem
• CentOS release 5.2, JDK64 1.6
搜索: 35+ 万查询 / 天,高峰期 > 20 次 /s
更新: 平均 15 条 / 分钟
52. 生产环境运行观察
Slow search 比例: >1s 1.x%; >2s 0.2%
机器负载 : 49(search): 1~3; 50(update): <1
内存消耗: search: Xms4096M Xmx4096M
Cpu 消耗 :多核之间平均比较分配
索引平均更新速度: ~1 分钟
索引延迟率(因各种原因导致延迟更新的比例)
53. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
54. 开发中的一些收获 (1/5)
FileChannel.transferTo 拷贝文件失败:
Fewer than the requested number of bytes are transferred if the
target channel is non-blocking and it has fewer than count bytes
free in its output buffer.
解决: check copied size ,断点续传
根据 lucene 索引文件更新的特性 ( 每次更新一个新版本的时
候,都会新建一个全新的文件 ) ,使用 cp -l 链接替代真实的
大文件拷贝动作
55. 开发中的一些收获 (2/5)
Kill 的问题
Never kill smart frog, don't kill 9
Java use Runtime.getRuntime().addShutdownHook to do the cleaning
things
Lucene IndexWriter need close!
server 程序,都应该考虑信号捕捉和处理的问题( java 程序容易忽
略这个问题)
56. 开发中的一些收获 (3/5)
更新太频繁导致的磁盘 IO 问题
– 同一台机器目录之间同步:没有问题
– 一拖二,一拖三
暂时解决
– 建索引的机器不提供搜索服务
– rsync 限速
57. 开发中的一些收获 (4/5)
GC 引起的服务暂停
多个索引共用 Search 进程
每个索引维持了多个 searcher
索引更新太过频繁: 30 秒
暂时解决
纵向拆分:将两个最大的库拆分到单独的进程
减少 searcher 个数到 2 个
谨慎的选择 gc 的类型,并调整 gc 的参数
G1 改进不明显,不稳定
58. 开发中的一些收获 (5/5)
Java server 程序的 trouble shooting
性能问题: jprofile
内存问题: jmx + jconsole
线程问题: Thread.currentThread().setName("diffDetect" +
indexId);
详细的 log : log4j
常用的脚本: logslowsearch.sh
开发调试阶段:方便的重新编译并重启脚本
线上服务阶段:完善的监控及报警机制
59. 目录
关于手机之家
过去:手机之家搜索 V1.0
现在:手机之家搜索 V1.5
需求背景
目标
进度
设计
实现
测试
上线,运维
经验分享
将来:手机之家搜索 V2.0
60. 持续改进
配置文件改动检测,自动重新加载
更智能的处理索引字段变更
分词算法改进
搜索关键字数据挖掘(搜索新词自动发现)
搜索建议,搜索联想功能
排序算法改进
全站整合搜索
准实时搜索
61. Lucene 3
Lucene 2.9
• Searchable.search(Weight, Filter, Collector): collector 终于可用了
• Added new MultiReaderHitCollector: 解决 group by
• near real-time search via IndexWriter.getReader(): 单机方案
Lucene 3.0
• Del deprecated apis: 大版本升级要谨慎
• Port to Java5: 应该会有性能的提升
Lucene 3.1
• Near Realtime Search (using a built in RAMDirectory): 还是单机方案
• Complete overhaul of FieldCache API/Implementation: 等到花儿也谢了
62. 定制功能
在某些情况下作为数据库的替代数据源
类似淘宝搜索,按多个字段筛选,过滤,排序
当前解决方案:使用 sql 从数据库中选取数据
问题:数据可能因为业务逻辑的设计而分散在多个不同的
库,表中,联表查询问题,并发压力问题
难点:非典型的复杂的查询条件,如 group by
63. 参考资料
Lucene
http://lucene.apache.org/
http://lucene.apache.org/java/2_4_1/api/index.html
中文分词
http://code.google.com/p/paoding/
http://code.google.com/p/imdict-chinese-analyzer/
http://code.google.com/p/mmseg4j/
Monkey ( Java 底层异步网络 IO 框架)
DAL
64. 关于我们
关于 imobile
网站首页 http://www.imobile.com.cn/
关于 http://www.imobile.com.cn/about.html
Longker ( V1.0 版本)
http://www.longker.org/
关于我( V1.5 版本)
http://www.fulin.org/
http://twitter.com/tangfl
65. 更多讨论
Java GC 的问题
巨大的索引库与频繁更新如何共存
全站整合搜索,后台技术实现与前台表现设计
更多