SlideShare uma empresa Scribd logo
1 de 48
Baixar para ler offline
音乐搜索的极致

         唐福林
tangfulin@gmail.com
  http://blog.fulin.org
目录

    项目简介

    需求描述

    搜索实现

    查询示例

    持续改进
项目简介 (1/3)
 
     中国移动
 
     12530
 
     咪咕
 
     Miniportal
 
     搜索
 
     Out source : edadao
项目简介 (2/3)
 
     时间: 2009 年 9 月 12 日到 10 月 22 日
 
     地点:成都,郫县,犀浦,移动音乐基
     地
 
     参与人员:
      
          需求提供: wangquanli@12530,  
          zhengchangsong@12530
      
          开发人员: mike,tangfulin,xww,wanghui
      
          特别贡献: dave
项目简介 (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
     
         流量:
     
         机器负载情况:
需求描述
 
     搜索字段:歌手,歌曲,专辑,歌词
 
     搜索方式:
         
               精确匹配
         
               前缀匹配
         
               分词匹配
         
               模糊匹配
         
               拼音全量匹配
         
               拼音首字母匹配
         
               拼音同音匹配
         
               拼音纠错匹配

 
     关键词提示:
     •
             搜索框下拉提示
     •
             纠错提示
需求-精确匹配
 
     规则:精确匹配或过滤所有特殊字符后精
     确匹配
 
     单个字段:
     
         歌手:阿唬 , 80 前后
     
         歌曲:爱情 BT 大讲堂
     
         专辑: Alive!
 
     多个字段联合:
     
         歌手名+歌曲名:刘德华 今天
     
         歌手名+专辑名:许茹芸 爱 . 旅行 . 一公里
需求-前缀匹配
 
     规则:过滤所有特殊字符后前缀匹配
 
     单个字段:
     
         歌手:刘德,张学
     
         歌曲:
     
         专辑:
需求-分词匹配
 
     规则:过滤所有特殊字符后分词匹配
 
     单个字段:
      
          歌手:德华,杰伦,学友
      
          歌曲:
      
          专辑:
      
          歌词:
 
     多个字段联合:先 Must ,再 Should
      
          歌手名+歌曲名+专辑名:
      
          歌手名+专辑名:
需求-模糊匹配
 
     规则:一定模糊度的词匹配(注:很
     慢)
 
     单个字段:
     
         歌手:刘大华
     
         歌曲: beautful, califonia
     
         专辑:
需求-拼音全量匹配
 
     规则:用户输入拼音匹配
 
     单个字段:
     
         歌手: liudehua, zhangxueyou
     
         歌曲:
     
         专辑:
需求-拼音首字母匹配
 
     规则:用户输入拼音首字母匹配
 
     单个字段:
     
         歌手: ldh, zxy
     
         歌曲:
     
         专辑:
需求-拼音同音匹配
 
     规则:用户拼音输入法,输入错误的同
     音字匹配
 
     单个字段:
     
         歌手:柳的话,两用器
     
         歌曲:
     
         专辑:
需求-拼音纠错匹配
 
     规则:用户拼音输入法输入错误的字,
     或直接输入错误的拼音
 
     n­l,h­f,z­zh,c­ch,s­sh,an­ang,en­eng,in­ing
 
     单个字段:
      
          歌手:牛德华, niudehua
      
          歌曲:
      
          专辑:
搜索实现
 
     建索引策略
     
        冗余字段
      
        中文将拼音,首字母也建进索引里
 
     搜索 Query 策略
     
         弃用多次查询的策略
     
         采用多个 Query 拼装成一个
         BooleanQuery ,设置不同的权值,
         一次查询的策略
搜索实现:建索引策略 (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 
搜索实现:建索引策略 (2/2)
 
     关键词: keyword
      
          所有的歌手名,歌曲名,专辑名,
          歌手+歌曲,歌手+专辑,都视为
          关键词
      
          单独一个索引文件
      
          供下拉提示和搜索无结果时的纠错
          提示使用
      
          也提供拼音全量,拼音首字母,拼
          音纠错等功能
搜索实现:搜索策略 (1/6)
 
       策略列表:
     1.   精确匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,精确匹配

     2.   过滤后的精确匹配:歌手,歌曲,专辑,过滤字段,去掉所有特殊字符,英文转成小
          写,精确匹配

     3.   拼音全量匹配:歌手,歌曲,专辑,拼音全量字段,去掉所有非英文字符,英文转成小
          写,精确匹配

     4.   同音纠错匹配:歌手,歌曲,专辑,拼音全量字段,只对含中文的搜索词使用,中文转
          拼音,英文转小写,去掉所有特殊字符,精确匹配

     5.   拼音首字母匹配:歌手,拼音首字母字段,中文转拼音首字母,英文转小写,去掉所有
          特殊字符,精确匹配

     6.   前缀匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,英文转小写,前缀匹
          配

     7.   分词 Must 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Must 连
          接,分词匹配
搜索实现:搜索策略 (2/6)
 
       策略列表(续):
     1.   分词 Should 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用
          Should 连接,分词匹配

     2.   合并分词 (must) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 must  连
          接),分词匹配

     3.   合并分词 (should) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 Should  连
          接),分词匹配

     4.   拼音纠错匹配查询(忽略掉鼻音等) : 歌手,歌曲,专辑,分词字段,去掉前后多余空
          格,英文转小写 .

     5.   中文模糊匹配 ,中文时模糊度: 0.65: 歌手,歌曲,专辑,分词字段,去掉前后多余空
          格,英文转小写 .

     6.   英文模糊匹配,英文模糊度: 0.85: 歌手,歌曲,专辑,分词字段,去掉前后多余空
          格,英文转小写 . 
搜索实现:搜索策略 (3/6)
 
     精度选择
     
         只搜索精确匹配结果
         
             精确匹配,过滤后精确匹配,前缀匹配
         
             拼音全量,首字母
         
             分词全部命中
     
         只搜索模糊匹配结果
         
             分词部分命中
         
             同音纠错,拼音纠错
         
             模糊匹配
         
             去掉精确匹配的结果
     
         搜索全部结果
搜索实现:搜索策略 (4/6)
 
     字段选择
     
         只搜索歌手
     
         只搜索歌曲
     
         只搜索专辑
     
         只搜索歌词
     
         搜索关键词字段
     
         搜索全部字段(暂时不包括关键词
         和歌词)
搜索实现:搜索策略 (5/6)
 
     设置权值
     
         将所有的策略置入一个有序列表中
     
         列表中相邻的两个策略之间权值相差常数倍(当前
         设置为 10 )。过大可能会导致 lucene 评分溢出,过
         小可能会导致不同策略命中的结果集重叠
     
         调整列表中策略的先后次序以调整结果集中各种命
         中的出现顺序
     
         二级权值:在搜索全部的时候,歌手 > 歌曲 > 专
         辑,所以需要在同一个策略内部再设置字段权值
     
         中文分词命中的权值设置: Lucene  默认打分策略
         中,并没有考虑命中的词的长度。为了优先显示长
         的词命中的结果,对分词 Query 中每个词根据长度
         设置不同的权值
搜索实现:搜索策略 (6/6)
 
     索引文件划分
     
         搜索歌曲索引
     
         搜索专辑索引
     
         搜索关键词索引
 
     排序策略
     
         编辑置顶
     
         Lucene  评分
     
         业务量(点击,订阅,播放等)
     
         关键词词频
搜索示例
 
         歌曲索引,全部字段,精确搜索
     
          搜 刘德华,结果条数: 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: 德华 ))))
搜索示例
 
         歌曲索引,全部字段,精确搜索
     
          搜 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)
搜索示例
 
         歌曲索引,全部字段,精确搜索
     
          搜 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)
搜索示例
 
         歌曲索引,全部字段,模糊搜索
     
          搜 刘德华,结果条数: 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)))
搜索示例
 
         歌曲索引,全部字段,模糊搜索
     
          搜 牛德华,结果条数: 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)))
搜索示例
 
         歌曲索引,全部字段,模糊搜索
     
          搜 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)))
搜索示例
 
         歌曲索引,歌手字段,精确搜索
     
          搜 杰伦,结果条数: 270
     
          QUERY:(((singer_name_notanslysis: 杰伦 ))^1000.0) (((singer_name_filtered: 杰伦 ))^100.0) 
          (((singer_name_filtered: 杰伦 *))^10.0) ((((+singer_name_anslysis: 杰伦 ))))
搜索示例
 
         歌曲索引,歌曲字段,模糊搜索
     
          搜 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)))
搜索示例
 
         关键词索引,搜索歌曲
     
          搜 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))
搜索示例
 
         关键词索引,搜索歌手
     
          搜 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))
搜索示例
 
         关键词索引,搜索全部字段
     
          搜 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))
搜索示例
 
         关键词索引,搜索全部字段
     
          搜 柳的话,结果:(拼音同音命中)
     
          刘德华     留得华

     
          刘德华 冰雨

     
          刘德华 中国人

     
          刘德华 幸福这么远那么甜

     
          刘德华 百分百好戏

     
          刘德华 笑着哭

     
          刘德华 谢谢你的爱

     
          刘德华 爱你一万年

     
          刘德华 情义俩心坚

     
          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)))
持续改进
 
     性能调优: resin ,内存, cache
 
     汉字转拼音:多音字,特殊符号
 
     拼音纠错:另一种思路,标准化 vs 排列组合
 
     关键词: Trie 树,按词频排序,加入歌词数据
 
     业务要求以频繁更新的业务量作为排序依据
 
     标签搜索:新需求
 
     搜索关键词,保持先后顺序
 
     自定义打分算法的尝试
 
     Lucene  升级到 2.9.1 , bug 1974 , explain  显示 0 或者 NaN
 
     显示时最佳片段截取, html 实体截断问题
 
     标红,按策略的山寨标红与 Lucene 自带标红的优劣比较及取舍
性能调优
 •
         目标:单台机器,百万数量级的索引, 1000 个并发
         下, 99% 0.5 秒内返回
 •
         优势:
         •
             索引更新不是很频繁,可以忽略不计
         •
             服务器性能不错, 8cpu , 8G 内存
     •
         劣势:
         •
             并发大,返回时间 0.5 秒要求太苛刻
         •
             一期代码有很多不合理的地方
         •
             项目时间紧张
         •
             使用了 resin 作为容器,有太多不可控因素
汉字转拼音
 •
         Pinyin4j  的词库
 •
         自己整理的多音字表
 •
         当前将所有多音字的组合都建在索引里
          •
              莫文蔚: mowenwei, mowenyu 
          •
              优点:保证能查到
          •
              缺点:输入 mowenyu  能查到莫文蔚,而且一个歌
               名中如果有好几个多音字,排列组合的数量比较
               可观
     •
         拼音首字母,而不是拼音声母
          •
              张学友: zxy ,不是 zhxy
拼音纠错
 •
         规则:
         •
             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
         •
             用户输入关键词,也经过同样的标准化后,在该字段进行
              查询
关键词
 •
     关键词当前使用 Lucene  的索引前缀查询的方式实现
 •
     也包括拼音,首字母,纠错等 Query
 •
     本来还有模糊查询的 Query ,但后来发现太影响查询
     速度了,于是就暂时去掉了
 •
     当前没有把歌词数据建到关键词索引中去。如果把歌
     词建进去,这个索引就太大了,必须要进行分拆
 •
     考虑使用 Trie 树:
     •
         多棵树,拼音,首字母,中文需要各自建树
     •
         模糊查询的问题
排序 vs 更新
 •
     业务需求希望能以歌曲的业务量(播放,下载等量)
     作为排序的一个依据
 •
     意味着需要频繁的更新索引,而且为了更新这样一个
     数字字段,需要将整个文档删除重新添加,不划算
 •
     打算重载 Lucene  的 Collection  类,自己实现排序字
     段值的加载,不从索引里面读取
 •
     问题: 2.4  与 2.9  在这个地方的实现上有很大的不
     同,没法无缝切换
标签搜索
 •
     固定维度的标签,编辑填写,非用户产生内容
 •
     如:奥运,免费,铃声,开心,悲伤等
 •
     关键是产品设计,非技术实现
 •
     参考: google  泡泡挑歌
保持搜索关键词的顺序
 •
         延后实现的一个需求
 •
         只命中跟用户输入的多个关键词之间的顺序一致的结果
 •
         如:
         •
             用户输入 “谢谢 爱”命中“谢谢你的爱”,但输入
              “爱 谢谢”不命中
         •
             用户输入 “眼睛 背叛 心”命中“你的眼睛背叛了
              你的心”,但输入“心 背叛 眼睛”不命中
     •
         实现:
         •
             分词命中,无法保留顺序信息
         •
             模糊查询,效率太差
         •
             ???
打分算法
    需求:
     •   产品人员对 Lucene 打分算法的不理解,要求单纯的以某一个
          依据来进行排序,如命中的词的个数
     •   拼多个 Query 查询的副作用:多个 Query 的评分累加得到的
          最后得分,会导致各个 Query 的命中结果重叠(想法:把
          累加改成取最大值?)
     •   一个想法:同样的分词命中,命中较长的词的结果排前面
    教训:
         •   没有金刚钻,别揽瓷器活!不要轻易的去改 Lucene 的评分算法
Lucene 版本的选择
 •
     首选 2.9.0
 •
     Bug 1974 :
     https://issues.apache.org/jira/browse/LUCENE­1974
 •
     换成了 2.4.1 
 •
     为了性能及长远打算,还是希望换回 2.9.1
 •
     Explain  函数调用返回评分 0  或 NaN 
摘要截取
 •
     Miniportal  空间有限,歌曲,专辑,甚至歌手名都可
     能需要截断
 •
     截断时需要考虑标红问题
 •
     截断时需要考虑 html  实体的问题
标红
 •
         Lucene  标红的优点与不足
         •
             优点:正统,可升级
         •
             缺点:不能满足需求,前缀标红,拼音标红等
     •
         山寨标红
         •
             按照每种 Query 进行相应的标红,最后合并
         •
更多讨论




http://blog.fulin.org

音乐搜索的极致

  • 1. 音乐搜索的极致 唐福林 tangfulin@gmail.com http://blog.fulin.org
  • 2. 目录  项目简介  需求描述  搜索实现  查询示例  持续改进
  • 3. 项目简介 (1/3)  中国移动  12530  咪咕  Miniportal  搜索  Out source : edadao
  • 4. 项目简介 (2/3)  时间: 2009 年 9 月 12 日到 10 月 22 日  地点:成都,郫县,犀浦,移动音乐基 地  参与人员:  需求提供: wangquanli@12530,   zhengchangsong@12530  开发人员: mike,tangfulin,xww,wanghui  特别贡献: dave
  • 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. 需求描述  搜索字段:歌手,歌曲,专辑,歌词  搜索方式:  精确匹配  前缀匹配  分词匹配  模糊匹配  拼音全量匹配  拼音首字母匹配  拼音同音匹配  拼音纠错匹配  关键词提示: • 搜索框下拉提示 • 纠错提示
  • 7. 需求-精确匹配  规则:精确匹配或过滤所有特殊字符后精 确匹配  单个字段:  歌手:阿唬 , 80 前后  歌曲:爱情 BT 大讲堂  专辑: Alive!  多个字段联合:  歌手名+歌曲名:刘德华 今天  歌手名+专辑名:许茹芸 爱 . 旅行 . 一公里
  • 8. 需求-前缀匹配  规则:过滤所有特殊字符后前缀匹配  单个字段:  歌手:刘德,张学  歌曲:  专辑:
  • 9. 需求-分词匹配  规则:过滤所有特殊字符后分词匹配  单个字段:  歌手:德华,杰伦,学友  歌曲:  专辑:  歌词:  多个字段联合:先 Must ,再 Should  歌手名+歌曲名+专辑名:  歌手名+专辑名:
  • 10. 需求-模糊匹配  规则:一定模糊度的词匹配(注:很 慢)  单个字段:  歌手:刘大华  歌曲: beautful, califonia  专辑:
  • 11. 需求-拼音全量匹配  规则:用户输入拼音匹配  单个字段:  歌手: liudehua, zhangxueyou  歌曲:  专辑:
  • 12. 需求-拼音首字母匹配  规则:用户输入拼音首字母匹配  单个字段:  歌手: ldh, zxy  歌曲:  专辑:
  • 13. 需求-拼音同音匹配  规则:用户拼音输入法,输入错误的同 音字匹配  单个字段:  歌手:柳的话,两用器  歌曲:  专辑:
  • 14. 需求-拼音纠错匹配  规则:用户拼音输入法输入错误的字, 或直接输入错误的拼音  n­l,h­f,z­zh,c­ch,s­sh,an­ang,en­eng,in­ing  单个字段:  歌手:牛德华, niudehua  歌曲:  专辑:
  • 15. 搜索实现  建索引策略  冗余字段  中文将拼音,首字母也建进索引里  搜索 Query 策略  弃用多次查询的策略  采用多个 Query 拼装成一个 BooleanQuery ,设置不同的权值, 一次查询的策略
  • 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. 搜索实现:建索引策略 (2/2)  关键词: keyword  所有的歌手名,歌曲名,专辑名, 歌手+歌曲,歌手+专辑,都视为 关键词  单独一个索引文件  供下拉提示和搜索无结果时的纠错 提示使用  也提供拼音全量,拼音首字母,拼 音纠错等功能
  • 18. 搜索实现:搜索策略 (1/6)  策略列表: 1. 精确匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,精确匹配 2. 过滤后的精确匹配:歌手,歌曲,专辑,过滤字段,去掉所有特殊字符,英文转成小 写,精确匹配 3. 拼音全量匹配:歌手,歌曲,专辑,拼音全量字段,去掉所有非英文字符,英文转成小 写,精确匹配 4. 同音纠错匹配:歌手,歌曲,专辑,拼音全量字段,只对含中文的搜索词使用,中文转 拼音,英文转小写,去掉所有特殊字符,精确匹配 5. 拼音首字母匹配:歌手,拼音首字母字段,中文转拼音首字母,英文转小写,去掉所有 特殊字符,精确匹配 6. 前缀匹配:歌手,歌曲,专辑,不分词字段,去掉前后多余空格,英文转小写,前缀匹 配 7. 分词 Must 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Must 连 接,分词匹配
  • 19. 搜索实现:搜索策略 (2/6)  策略列表(续): 1. 分词 Should 匹配:歌手,歌曲,专辑,(歌词),分词字段,分词,词之间使用 Should 连接,分词匹配 2. 合并分词 (must) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 must  连 接),分词匹配 3. 合并分词 (should) 匹配:歌手+歌曲+专辑 分词字段,分词,(当前使用 Should  连 接),分词匹配 4. 拼音纠错匹配查询(忽略掉鼻音等) : 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 5. 中文模糊匹配 ,中文时模糊度: 0.65: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 6. 英文模糊匹配,英文模糊度: 0.85: 歌手,歌曲,专辑,分词字段,去掉前后多余空 格,英文转小写 . 
  • 20. 搜索实现:搜索策略 (3/6)  精度选择  只搜索精确匹配结果  精确匹配,过滤后精确匹配,前缀匹配  拼音全量,首字母  分词全部命中  只搜索模糊匹配结果  分词部分命中  同音纠错,拼音纠错  模糊匹配  去掉精确匹配的结果  搜索全部结果
  • 21. 搜索实现:搜索策略 (4/6)  字段选择  只搜索歌手  只搜索歌曲  只搜索专辑  只搜索歌词  搜索关键词字段  搜索全部字段(暂时不包括关键词 和歌词)
  • 22. 搜索实现:搜索策略 (5/6)  设置权值  将所有的策略置入一个有序列表中  列表中相邻的两个策略之间权值相差常数倍(当前 设置为 10 )。过大可能会导致 lucene 评分溢出,过 小可能会导致不同策略命中的结果集重叠  调整列表中策略的先后次序以调整结果集中各种命 中的出现顺序  二级权值:在搜索全部的时候,歌手 > 歌曲 > 专 辑,所以需要在同一个策略内部再设置字段权值  中文分词命中的权值设置: Lucene  默认打分策略 中,并没有考虑命中的词的长度。为了优先显示长 的词命中的结果,对分词 Query 中每个词根据长度 设置不同的权值
  • 23. 搜索实现:搜索策略 (6/6)  索引文件划分  搜索歌曲索引  搜索专辑索引  搜索关键词索引  排序策略  编辑置顶  Lucene  评分  业务量(点击,订阅,播放等)  关键词词频
  • 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. 搜索示例  歌曲索引,全部字段,精确搜索  搜 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. 搜索示例  歌曲索引,全部字段,精确搜索  搜 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. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 刘德华,结果条数: 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. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 牛德华,结果条数: 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. 搜索示例  歌曲索引,全部字段,模糊搜索  搜 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. 搜索示例  歌曲索引,歌手字段,精确搜索  搜 杰伦,结果条数: 270  QUERY:(((singer_name_notanslysis: 杰伦 ))^1000.0) (((singer_name_filtered: 杰伦 ))^100.0)  (((singer_name_filtered: 杰伦 *))^10.0) ((((+singer_name_anslysis: 杰伦 ))))
  • 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. 搜索示例  关键词索引,搜索歌曲  搜 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. 搜索示例  关键词索引,搜索歌手  搜 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. 搜索示例  关键词索引,搜索全部字段  搜 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. 搜索示例  关键词索引,搜索全部字段  搜 柳的话,结果:(拼音同音命中)  刘德华 留得华  刘德华 冰雨  刘德华 中国人  刘德华 幸福这么远那么甜  刘德华 百分百好戏  刘德华 笑着哭  刘德华 谢谢你的爱  刘德华 爱你一万年  刘德华 情义俩心坚  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. 持续改进  性能调优: resin ,内存, cache  汉字转拼音:多音字,特殊符号  拼音纠错:另一种思路,标准化 vs 排列组合  关键词: Trie 树,按词频排序,加入歌词数据  业务要求以频繁更新的业务量作为排序依据  标签搜索:新需求  搜索关键词,保持先后顺序  自定义打分算法的尝试  Lucene  升级到 2.9.1 , bug 1974 , explain  显示 0 或者 NaN  显示时最佳片段截取, html 实体截断问题  标红,按策略的山寨标红与 Lucene 自带标红的优劣比较及取舍
  • 37. 性能调优 • 目标:单台机器,百万数量级的索引, 1000 个并发 下, 99% 0.5 秒内返回 • 优势: • 索引更新不是很频繁,可以忽略不计 • 服务器性能不错, 8cpu , 8G 内存 • 劣势: • 并发大,返回时间 0.5 秒要求太苛刻 • 一期代码有很多不合理的地方 • 项目时间紧张 • 使用了 resin 作为容器,有太多不可控因素
  • 38. 汉字转拼音 • Pinyin4j  的词库 • 自己整理的多音字表 • 当前将所有多音字的组合都建在索引里 • 莫文蔚: mowenwei, mowenyu  • 优点:保证能查到 • 缺点:输入 mowenyu  能查到莫文蔚,而且一个歌 名中如果有好几个多音字,排列组合的数量比较 可观 • 拼音首字母,而不是拼音声母 • 张学友: zxy ,不是 zhxy
  • 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. 关键词 • 关键词当前使用 Lucene  的索引前缀查询的方式实现 • 也包括拼音,首字母,纠错等 Query • 本来还有模糊查询的 Query ,但后来发现太影响查询 速度了,于是就暂时去掉了 • 当前没有把歌词数据建到关键词索引中去。如果把歌 词建进去,这个索引就太大了,必须要进行分拆 • 考虑使用 Trie 树: • 多棵树,拼音,首字母,中文需要各自建树 • 模糊查询的问题
  • 41. 排序 vs 更新 • 业务需求希望能以歌曲的业务量(播放,下载等量) 作为排序的一个依据 • 意味着需要频繁的更新索引,而且为了更新这样一个 数字字段,需要将整个文档删除重新添加,不划算 • 打算重载 Lucene  的 Collection  类,自己实现排序字 段值的加载,不从索引里面读取 • 问题: 2.4  与 2.9  在这个地方的实现上有很大的不 同,没法无缝切换
  • 42. 标签搜索 • 固定维度的标签,编辑填写,非用户产生内容 • 如:奥运,免费,铃声,开心,悲伤等 • 关键是产品设计,非技术实现 • 参考: google  泡泡挑歌
  • 43. 保持搜索关键词的顺序 • 延后实现的一个需求 • 只命中跟用户输入的多个关键词之间的顺序一致的结果 • 如: • 用户输入 “谢谢 爱”命中“谢谢你的爱”,但输入 “爱 谢谢”不命中 • 用户输入 “眼睛 背叛 心”命中“你的眼睛背叛了 你的心”,但输入“心 背叛 眼睛”不命中 • 实现: • 分词命中,无法保留顺序信息 • 模糊查询,效率太差 • ???
  • 44. 打分算法  需求: • 产品人员对 Lucene 打分算法的不理解,要求单纯的以某一个 依据来进行排序,如命中的词的个数 • 拼多个 Query 查询的副作用:多个 Query 的评分累加得到的 最后得分,会导致各个 Query 的命中结果重叠(想法:把 累加改成取最大值?) • 一个想法:同样的分词命中,命中较长的词的结果排前面  教训: • 没有金刚钻,别揽瓷器活!不要轻易的去改 Lucene 的评分算法
  • 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. 摘要截取 • Miniportal  空间有限,歌曲,专辑,甚至歌手名都可 能需要截断 • 截断时需要考虑标红问题 • 截断时需要考虑 html  实体的问题
  • 47. 标红 • Lucene  标红的优点与不足 • 优点:正统,可升级 • 缺点:不能满足需求,前缀标红,拼音标红等 • 山寨标红 • 按照每种 Query 进行相应的标红,最后合并 •