Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

音乐搜索的极致

5,244 views

Published on

中国移动 12530 咪咕客户端 搜索

Published in: Technology
  • Be the first to comment

音乐搜索的极致

  1. 1. 音乐搜索的极致 唐福林 tangfulin@gmail.com http://blog.fulin.org
  2. 2. 目录  项目简介  需求描述  搜索实现  查询示例  持续改进
  3. 3. 项目简介 (1/3)  中国移动  12530  咪咕  Miniportal  搜索  Out source : edadao
  4. 4. 项目简介 (2/3)  时间: 2009 年 9 月 12 日到 10 月 22 日  地点:成都,郫县,犀浦,移动音乐基 地  参与人员:  需求提供: wangquanli@12530,   zhengchangsong@12530  开发人员: mike,tangfulin,xww,wanghui  特别贡献: dave
  5. 5. 项目简介 (3/3)  部署情况:  位置:移动音乐基地,西区枢纽机房  机器:  建索引: 227.98  搜索: 227.221  Dual­Core AMD Opteron(tm) Processor 8218 2.6G * 8  8G mem  Red Hat Enterprise Linux Server release 5.3 (Tikanga)  Linux 2.6.18­128.el5PAE #1 SMP  i686 athlon  GNU/Linux  索引大小: 3 个索引目录共 1.2G  流量:  机器负载情况:
  6. 6. 需求描述  搜索字段:歌手,歌曲,专辑,歌词  搜索方式:  精确匹配  前缀匹配  分词匹配  模糊匹配  拼音全量匹配  拼音首字母匹配  拼音同音匹配  拼音纠错匹配  关键词提示: • 搜索框下拉提示 • 纠错提示
  7. 7. 需求-精确匹配  规则:精确匹配或过滤所有特殊字符后精 确匹配  单个字段:  歌手:阿唬 , 80 前后  歌曲:爱情 BT 大讲堂  专辑: Alive!  多个字段联合:  歌手名+歌曲名:刘德华 今天  歌手名+专辑名:许茹芸 爱 . 旅行 . 一公里
  8. 8. 需求-前缀匹配  规则:过滤所有特殊字符后前缀匹配  单个字段:  歌手:刘德,张学  歌曲:  专辑:
  9. 9. 需求-分词匹配  规则:过滤所有特殊字符后分词匹配  单个字段:  歌手:德华,杰伦,学友  歌曲:  专辑:  歌词:  多个字段联合:先 Must ,再 Should  歌手名+歌曲名+专辑名:  歌手名+专辑名:
  10. 10. 需求-模糊匹配  规则:一定模糊度的词匹配(注:很 慢)  单个字段:  歌手:刘大华  歌曲: beautful, califonia  专辑:
  11. 11. 需求-拼音全量匹配  规则:用户输入拼音匹配  单个字段:  歌手: liudehua, zhangxueyou  歌曲:  专辑:
  12. 12. 需求-拼音首字母匹配  规则:用户输入拼音首字母匹配  单个字段:  歌手: ldh, zxy  歌曲:  专辑:
  13. 13. 需求-拼音同音匹配  规则:用户拼音输入法,输入错误的同 音字匹配  单个字段:  歌手:柳的话,两用器  歌曲:  专辑:
  14. 14. 需求-拼音纠错匹配  规则:用户拼音输入法输入错误的字, 或直接输入错误的拼音  n­l,h­f,z­zh,c­ch,s­sh,an­ang,en­eng,in­ing  单个字段:  歌手:牛德华, niudehua  歌曲:  专辑:
  15. 15. 搜索实现  建索引策略  冗余字段  中文将拼音,首字母也建进索引里  搜索 Query 策略  弃用多次查询的策略  采用多个 Query 拼装成一个 BooleanQuery ,设置不同的权值, 一次查询的策略
  16. 16. 搜索实现:建索引策略 (1/2)  歌手: singer_name  singer_name_save: 保存字段, trim 后,原封不动  singer_name_filtered:  过滤字段,过滤所有的特殊字符,转小写  singer_name_analyzed:  分词字段  singer_name_notanalyzed:  不分词字段,前缀匹配使用  singer_name_full:  拼音全量字段  singer_name_first:  拼音首字母字段  歌曲: song_name   专辑: album_name 
  17. 17. 搜索实现:建索引策略 (2/2)  关键词: keyword  所有的歌手名,歌曲名,专辑名, 歌手+歌曲,歌手+专辑,都视为 关键词  单独一个索引文件  供下拉提示和搜索无结果时的纠错 提示使用  也提供拼音全量,拼音首字母,拼 音纠错等功能
  18. 18. 搜索实现:搜索策略 (1/6)  策略列表: 1. 精确匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,精确匹配 2. 过滤后的精确匹配:歌手,歌曲,专辑,过滤字段,去掉所有特殊字符,英文转成小 写,精确匹配 3. 拼音全量匹配:歌手,歌曲,专辑,拼音全量字段,去掉所有非英文字符,英文转成小 写,精确匹配 4. 同音纠错匹配:歌手,歌曲,专辑,拼音全量字段,只对含中文的搜索词使用,中文转 拼音,英文转小写,去掉所有特殊字符,精确匹配 5. 拼音首字母匹配:歌手,拼音首字母字段,中文转拼音首字母,英文转小写,去掉所有 特殊字符,精确匹配 6. 前缀匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,英文转小写,前缀匹 配 7. 分词 Must 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Must 连 接,分词匹配
  19. 19. 搜索实现:搜索策略 (2/6)  策略列表(续): 1. 分词 Should 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Should 连接,分词匹配 2. 合并分词 (must) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 must  连 接),分词匹配 3. 合并分词 (should) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 Should  连 接),分词匹配 4. 拼音纠错匹配查询(忽略掉鼻音等) : 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 5. 中文模糊匹配 ,中文时模糊度: 0.65: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 6. 英文模糊匹配,英文模糊度: 0.85: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 
  20. 20. 搜索实现:搜索策略 (3/6)  精度选择  只搜索精确匹配结果  精确匹配,过滤后精确匹配,前缀匹配  拼音全量,首字母  分词全部命中  只搜索模糊匹配结果  分词部分命中  同音纠错,拼音纠错  模糊匹配  去掉精确匹配的结果  搜索全部结果
  21. 21. 搜索实现:搜索策略 (4/6)  字段选择  只搜索歌手  只搜索歌曲  只搜索专辑  只搜索歌词  搜索关键词字段  搜索全部字段(暂时不包括关键词 和歌词)
  22. 22. 搜索实现:搜索策略 (5/6)  设置权值  将所有的策略置入一个有序列表中  列表中相邻的两个策略之间权值相差常数倍(当前 设置为 10 )。过大可能会导致 lucene 评分溢出,过 小可能会导致不同策略命中的结果集重叠  调整列表中策略的先后次序以调整结果集中各种命 中的出现顺序  二级权值:在搜索全部的时候,歌手 > 歌曲 > 专 辑,所以需要在同一个策略内部再设置字段权值  中文分词命中的权值设置: Lucene  默认打分策略 中,并没有考虑命中的词的长度。为了优先显示长 的词命中的结果,对分词 Query 中每个词根据长度 设置不同的权值
  23. 23. 搜索实现:搜索策略 (6/6)  索引文件划分  搜索歌曲索引  搜索专辑索引  搜索关键词索引  排序策略  编辑置顶  Lucene  评分  业务量(点击,订阅,播放等)  关键词词频
  24. 24. 搜索示例  歌曲索引,全部字段,精确搜索  搜 刘德华,结果条数: 329  QUERY:(((singer_name_notanslysis: 刘德华 ^9.0  song_name_notanslysis: 刘德华 ^4.0 album_name_notanslysis: 刘德 华 ))^10000.0) (((singer_name_filtered: 刘德华 ^9.0 song_name_filtered: 刘德华 ^4.0 album_name_filtered: 刘德华 ))^1000.0)  (((singer_name_filtered: 刘德华 *^9.0 song_name_filtered: 刘德华 *^4.0  album_name_filtered: 刘德华 *))^100.0) ((((((+singer_name_anslysis: 刘 德华 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 刘德 华 +song_name_anslysis: 德华 )^4.0) (+album_name_anslysis: 刘德华 +album_name_anslysis: 德华 ))))^10.0) ((((+singer_song_album: 刘德华 +singer_song_album: 德华 ))))
  25. 25. 搜索示例  歌曲索引,全部字段,精确搜索  搜 ldh ,结果条数: 401 (命中刘德华,刘大浩等)  QUERY:(((singer_name_notanslysis:ldh^9.0  song_name_notanslysis:ldh^4.0  album_name_notanslysis:ldh))^1000.00006)  (((singer_name_filtered:ldh^9.0 song_name_filtered:ldh^4.0  album_name_filtered:ldh))^100.00001) (((singer_name_full:ldh^9.0  song_name_full:ldh^4.0 album_name_full:ldh))^10.000001)  (((singer_name_first:ldh))^1.0000001) (((singer_name_filtered:ldh*^9.0  song_name_filtered:ldh*^4.0 album_name_filtered:ldh*))^0.10000001)  ((((((+singer_name_anslysis:ldh)^9.0) ((+song_name_anslysis:ldh)^4.0)  (+album_name_anslysis:ldh))))^0.010000001)  (((((+singer_song_album:ldh))))^0.0010)
  26. 26. 搜索示例  歌曲索引,全部字段,精确搜索  搜 liudehua ,结果条数: 324 (歌名《我不是刘德华》无法命中)  QUERY:(((singer_name_notanslysis:liudehua^9.0  song_name_notanslysis:liudehua^4.0  album_name_notanslysis:liudehua))^1000.00006)  (((singer_name_filtered:liudehua^9.0 song_name_filtered:liudehua^4.0  album_name_filtered:liudehua))^100.00001)  (((singer_name_full:liudehua^9.0 song_name_full:liudehua^4.0  album_name_full:liudehua))^10.000001)  (((singer_name_filtered:liudehua*^9.0 song_name_filtered:liudehua*^4.0  album_name_filtered:liudehua*))^1.0000001)  ((((((+singer_name_anslysis:liudehua)^9.0)  ((+song_name_anslysis:liudehua)^4.0)  (+album_name_anslysis:liudehua))))^0.10000001)  (((((+singer_song_album:liudehua))))^0.010000001)  (((singer_name_first:liudehua))^0.0010)
  27. 27. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 刘德华,结果条数: 44 (命中爱德华,杨德华等)  QUERY:­((((singer_name_notanslysis: 刘德华 ^9.0 song_name_notanslysis: 刘德华 ^4.0  album_name_notanslysis: 刘德华 ))^10000.0) (((singer_name_filtered: 刘德华 ^9.0  song_name_filtered: 刘德华 ^4.0 album_name_filtered: 刘德华 ))^1000.0)  (((singer_name_filtered: 刘德华 *^9.0 song_name_filtered: 刘德华 *^4.0  album_name_filtered: 刘德华 *))^100.0) ((((((+singer_name_anslysis: 刘德华 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 刘德华 +song_name_anslysis: 德华 )^4.0) (+album_name_anslysis: 刘德华 +album_name_anslysis: 德华 ))))^10.0)  ((((+singer_song_album: 刘德华 +singer_song_album: 德华 ))))) +(((((singer_song_album: 刘德华 ^9.0 singer_song_album: 德华 ^4.0)))^10000.0) (((((singer_name_anslysis: 刘德华 ^9.0 singer_name_anslysis: 德华 ^4.0)^9.0) ((song_name_anslysis: 刘德华 ^9.0  song_name_anslysis: 德华 ^4.0)^4.0) (album_name_anslysis: 刘德华 ^9.0  album_name_anslysis: 德华 ^4.0)))^1000.0) (((((singer_name_full:liudehua)^9.0)  ((song_name_full:liudehua)^4.0) (album_name_full:liudehua)))^100.0) ((())^10.0)  ((singer_name_anslysis: 刘德华 ~0.65^9.0 song_name_anslysis: 刘德华 ~0.65^4.0  album_name_anslysis: 刘德华 ~0.65)))
  28. 28. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 牛德华,结果条数: 840 (命中刘德华,爱德华,杨德华等)  QUERY:­((((singer_name_notanslysis: 牛德华 ^9.0 song_name_notanslysis: 牛德华 ^4.0  album_name_notanslysis: 牛德华 ))^10000.0) (((singer_name_filtered: 牛德华 ^9.0  song_name_filtered: 牛德华 ^4.0 album_name_filtered: 牛德华 ))^1000.0)  (((singer_name_filtered: 牛德华 *^9.0 song_name_filtered: 牛德华 *^4.0  album_name_filtered: 牛德华 *))^100.0) ((((((+singer_name_anslysis: 牛 +singer_name_anslysis: 德华 )^9.0) ((+song_name_anslysis: 牛 +song_name_anslysis: 德 华 )^4.0) (+album_name_anslysis: 牛 +album_name_anslysis: 德华 ))))^10.0)  ((((+singer_song_album: 牛 +singer_song_album: 德华 ))))) +(((((singer_song_album: 牛 singer_song_album: 德华 ^4.0)))^10000.0) (((((singer_name_anslysis: 牛 singer_name_anslysis: 德华 ^4.0)^9.0) ((song_name_anslysis: 牛 song_name_anslysis: 德华 ^4.0)^4.0) (album_name_anslysis: 牛 album_name_anslysis: 德华 ^4.0)))^1000.0)  (((((singer_name_full:niudehua)^9.0) ((song_name_full:niudehua)^4.0)  (album_name_full:niudehua)))^100.0) ((())^10.0) ((singer_name_anslysis: 牛德华 ~0.65^9.0  song_name_anslysis: 牛德华 ~0.65^4.0 album_name_anslysis: 牛德华 ~0.65)))
  29. 29. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 niudehua ,结果条数: 324  QUERY:­((((singer_name_notanslysis:niudehua^9.0 song_name_notanslysis:niudehua^4.0  album_name_notanslysis:niudehua))^1000.00006) (((singer_name_filtered:niudehua^9.0  song_name_filtered:niudehua^4.0 album_name_filtered:niudehua))^100.00001)  (((singer_name_full:niudehua^9.0 song_name_full:niudehua^4.0  album_name_full:niudehua))^10.000001) (((singer_name_filtered:niudehua*^9.0  song_name_filtered:niudehua*^4.0 album_name_filtered:niudehua*))^1.0000001)  ((((((+singer_name_anslysis:niudehua)^9.0) ((+song_name_anslysis:niudehua)^4.0)  (+album_name_anslysis:niudehua))))^0.10000001)  (((((+singer_song_album:niudehua))))^0.010000001)  (((singer_name_first:niudehua))^0.0010)) +(((((singer_song_album:niudehua^64.0)))^1000.0)  (((((singer_name_anslysis:niudehua^64.0)^9.0) ((song_name_anslysis:niudehua^64.0)^4.0)  (album_name_anslysis:niudehua^64.0)))^100.0) (((((singer_name_full:niudefua  singer_name_full:liudehua)^9.0) ((song_name_full:niudefua song_name_full:liudehua)^4.0)  (album_name_full:niudefua album_name_full:liudehua)))^10.0)  ((singer_name_anslysis:niudehua~0.85^9.0 song_name_anslysis:niudehua~0.85^4.0  album_name_anslysis:niudehua~0.85)))
  30. 30. 搜索示例  歌曲索引,歌手字段,精确搜索  搜 杰伦,结果条数: 270  QUERY:(((singer_name_notanslysis: 杰伦 ))^1000.0) (((singer_name_filtered: 杰伦 ))^100.0)  (((singer_name_filtered: 杰伦 *))^10.0) ((((+singer_name_anslysis: 杰伦 ))))
  31. 31. 搜索示例  歌曲索引,歌曲字段,模糊搜索  搜 li ,结果条数: 117  (命中 你 )  QUERY:­((((song_name_notanslysis:li))^10000.0) (((song_name_filtered:li))^1000.0)  (((song_name_full:li))^100.0) (((song_name_filtered:li*))^10.0)  ((((+song_name_anslysis:li))))) +(((((song_name_anslysis:li^4.0)))^100.0)  ((((song_name_full:ni)))^10.0) ((song_name_anslysis:li~0.85)))
  32. 32. 搜索示例  关键词索引,搜索歌曲  搜 liu ,结果:  流浪 流星 留恋 六月雪 浏阳河 流浪狗 流浪者之歌 留不住你的温柔  QUERY:+keyword_type:2 ­keyword_word_notanslysis:liu + (((keyword_word_filtered:liu*)^100.0) ((keyword_word_full:liu*)^100.0)  ((keyword_word_first:liu*)^100.0))
  33. 33. 搜索示例  关键词索引,搜索歌手  搜 liu ,结果:  刘德华 刘冠群 刘亦敏 刘韵 刘若英 刘基俊 刘益中 刘芳 刘庆  QUERY:+keyword_type:1 ­keyword_word_notanslysis:liu + (((keyword_word_filtered:liu*)^100.0) ((keyword_word_full:liu*)^100.0)  ((keyword_word_first:liu*)^100.0))
  34. 34. 搜索示例  关键词索引,搜索全部字段  搜 zhoujie ,结果:(有歌手+歌曲的命中)  周杰伦 周杰磊 周杰伦我求求你了 周杰伦传递祝福 周杰伦春节祝福 周杰伦情人节祝福  周杰伦 蒲公英的约定  周杰伦 最长的电影  周杰伦 阳光宅男  周杰伦 甜甜的  QUERY:+(keyword_type:1 keyword_type:2 keyword_type:3)  ­keyword_word_notanslysis:zhoujie +(((keyword_word_filtered:zhoujie*)^100.0)  ((keyword_word_full:zhoujie*)^100.0) ((keyword_word_first:zhoujie*)^100.0))
  35. 35. 搜索示例  关键词索引,搜索全部字段  搜 柳的话,结果:(拼音同音命中)  刘德华 留得华  刘德华 冰雨  刘德华 中国人  刘德华 幸福这么远那么甜  刘德华 百分百好戏  刘德华 笑着哭  刘德华 谢谢你的爱  刘德华 爱你一万年  刘德华 情义俩心坚  QUERY:+(keyword_type:1 keyword_type:2 keyword_type:3) ­keyword_word_notanslysis: 柳的话 +(((keyword_word_filtered: 柳的话 *)^100.0)  (((keyword_word_full:liudehua*)^100.0) ((keyword_word_full:liudihua*)^100.0)))
  36. 36. 持续改进  性能调优: resin ,内存, cache  汉字转拼音:多音字,特殊符号  拼音纠错:另一种思路,标准化 vs 排列组合  关键词: Trie 树,按词频排序,加入歌词数据  业务要求以频繁更新的业务量作为排序依据  标签搜索:新需求  搜索关键词,保持先后顺序  自定义打分算法的尝试  Lucene  升级到 2.9.1 , bug 1974 , explain  显示 0 或者 NaN  显示时最佳片段截取, html 实体截断问题  标红,按策略的山寨标红与 Lucene 自带标红的优劣比较及取舍
  37. 37. 性能调优 • 目标:单台机器,百万数量级的索引, 1000 个并发 下, 99% 0.5 秒内返回 • 优势: • 索引更新不是很频繁,可以忽略不计 • 服务器性能不错, 8cpu , 8G 内存 • 劣势: • 并发大,返回时间 0.5 秒要求太苛刻 • 一期代码有很多不合理的地方 • 项目时间紧张 • 使用了 resin 作为容器,有太多不可控因素
  38. 38. 汉字转拼音 • Pinyin4j  的词库 • 自己整理的多音字表 • 当前将所有多音字的组合都建在索引里 • 莫文蔚: mowenwei, mowenyu  • 优点:保证能查到 • 缺点:输入 mowenyu  能查到莫文蔚,而且一个歌 名中如果有好几个多音字,排列组合的数量比较 可观 • 拼音首字母,而不是拼音声母 • 张学友: zxy ,不是 zhxy
  39. 39. 拼音纠错 • 规则: • n­l,h­f,z­zh,c­ch,s­sh,an­ang,en­eng,in­ing (on­ong) • 当前实现: • 将用户输入的搜索词中的每个出现,依次替换成对应的纠 错,联合成一个 Should Query • 如: liudehua: niudehua, liudefua • 另一种思路:标准化 • 规定规则中的替换只能单向: n­>l,z­>zh 等 • 在索引中增加一个标准化拼音字段,如曾经最美,该字段 存储的值为 chengjingzhuimei • 用户输入关键词,也经过同样的标准化后,在该字段进行 查询
  40. 40. 关键词 • 关键词当前使用 Lucene  的索引前缀查询的方式实现 • 也包括拼音,首字母,纠错等 Query • 本来还有模糊查询的 Query ,但后来发现太影响查询 速度了,于是就暂时去掉了 • 当前没有把歌词数据建到关键词索引中去。如果把歌 词建进去,这个索引就太大了,必须要进行分拆 • 考虑使用 Trie 树: • 多棵树,拼音,首字母,中文需要各自建树 • 模糊查询的问题
  41. 41. 排序 vs 更新 • 业务需求希望能以歌曲的业务量(播放,下载等量) 作为排序的一个依据 • 意味着需要频繁的更新索引,而且为了更新这样一个 数字字段,需要将整个文档删除重新添加,不划算 • 打算重载 Lucene  的 Collection  类,自己实现排序字 段值的加载,不从索引里面读取 • 问题: 2.4  与 2.9  在这个地方的实现上有很大的不 同,没法无缝切换
  42. 42. 标签搜索 • 固定维度的标签,编辑填写,非用户产生内容 • 如:奥运,免费,铃声,开心,悲伤等 • 关键是产品设计,非技术实现 • 参考: google  泡泡挑歌
  43. 43. 保持搜索关键词的顺序 • 延后实现的一个需求 • 只命中跟用户输入的多个关键词之间的顺序一致的结果 • 如: • 用户输入 “谢谢 爱”命中“谢谢你的爱”,但输入 “爱 谢谢”不命中 • 用户输入 “眼睛 背叛 心”命中“你的眼睛背叛了 你的心”,但输入“心 背叛 眼睛”不命中 • 实现: • 分词命中,无法保留顺序信息 • 模糊查询,效率太差 • ???
  44. 44. 打分算法  需求: • 产品人员对 Lucene 打分算法的不理解,要求单纯的以某一个 依据来进行排序,如命中的词的个数 • 拼多个 Query 查询的副作用:多个 Query 的评分累加得到的 最后得分,会导致各个 Query 的命中结果重叠(想法:把 累加改成取最大值?) • 一个想法:同样的分词命中,命中较长的词的结果排前面  教训: • 没有金刚钻,别揽瓷器活!不要轻易的去改 Lucene 的评分算法
  45. 45. Lucene 版本的选择 • 首选 2.9.0 • Bug 1974 : https://issues.apache.org/jira/browse/LUCENE­1974 • 换成了 2.4.1  • 为了性能及长远打算,还是希望换回 2.9.1 • Explain  函数调用返回评分 0  或 NaN 
  46. 46. 摘要截取 • Miniportal  空间有限,歌曲,专辑,甚至歌手名都可 能需要截断 • 截断时需要考虑标红问题 • 截断时需要考虑 html  实体的问题
  47. 47. 标红 • Lucene  标红的优点与不足 • 优点:正统,可升级 • 缺点:不能满足需求,前缀标红,拼音标红等 • 山寨标红 • 按照每种 Query 进行相应的标红,最后合并 •
  48. 48. 更多讨论 http://blog.fulin.org

×