随着用户持续使用云音乐,红心歌曲、收藏歌单、关注艺人等用户私域数据资产也在不断积累,面向私域数据的检索诉求也越来越迫切;本文主要介绍云音乐本地私域数据检索功能的实现方案,包含本地轻量级搜索引擎的技术选型、整体技术方案以及搜索耗时的优化方案。
云音乐有着强大的推荐系统,用户在使用云音乐过程中,会通过红心标记喜欢的歌曲,会通过收藏标记感兴趣的歌单专辑,会通过关注持续获取喜爱艺人的信息,这些因用户行为而被关联的资源、状态数据都被属于用户自己的私域数据。私域数据会被App在本地记录,然后通过云端在不同的设备上进行数据同步。
随着用户持续使用云音乐,私域数据持续不断积累,私域数据搜索的诉求反馈也越来越多,比如找到自己众多歌单里五月天的歌曲,某首只记得若干关键字的红心歌曲,等等。目前云音乐已通过内置一个轻量级本地搜索引擎实现了该功能,相比由服务端完成用户私域数据检索,返回结果由客户端展示的方案,本地搜索引擎在保护用户数据隐私、节省云端存储成本、降低检索耗时、支持离线搜索上有着天然优势。
本文主要介绍该功能的技术方案,包含本地轻量级搜索引擎的技术选型、整体设计方案、性能优化和总结展望等。
本地私域数据搜索功能是通过在云音乐内置轻量级全文搜索引擎来实现的,为了更好地描述和理解后续的技术方案,先来回顾下搜索引擎的一些基础知识。
搜索引擎主要分为:爬取(Crawl)、解析(Analyze)、索引(Index)、检索(Search)和排序(Rank) 5个阶段。
全文搜索引擎是目前最广泛应用的主流搜索引擎,面向文本检索,以网页文字为主。全文检索下,当一条文档数据被存储时,解析器与分词器会将该文档数据划分成各自独立的词项,并为每个词项建立一个倒排索引。当进行查询时,查询数据也会被解析器与分词器进行词项划分,然后遍历倒排索引,找到匹配的已存储文档数据,最后基于文档数据与查询条件的相关性进行排序,返回最终查询结果。
通过对现有搜索引擎方案的调研,基于端侧集成成本考量,最终将引擎方案选定在 NSearch 和 SQLite FTS之间:
进行全文检索的核心是建倒排索引,建索引的核心是分词器,分词的效果直接影响了搜索的结果;通过对两套方案的分析,考虑到研发成本和对包大小的影响,若 SQLite FTS 能够很好的支持中文分词,则会是更优选项。
SQLite 作为目前移动端使用最为广泛的嵌入式数据库,SQLite3其实已经内嵌了离线全文搜索的扩展模块——FTS,包含 分词解析、倒排索引构建、文本匹配查询 等核心功能,并支持分词解析的三方插件扩展。FTS当前已发布了5个版本,现在大部分使用的主要为FTS4、FTS5,FTS5相比FTS4进行了诸多兼容性修复和存储优化,其详细差异参见官方介绍文档:FTS5 与 FTS3/4 的比较[1]。
在实际使用时,FTS可以理解成是一个表,为数据库应用程序提供全文本搜索功能,相比于普通表,FTS其实是种虚拟表模块。基于FTS5的全文检索使用SQL语法,使用时包含以下4个关键步骤(以歌曲检索为例):
// 创建歌曲信息表
CREATE TABLE IF NOT EXISTS songindex (
song_idINTEGER PRIMARY KEY,
name TEXT,
alias TEXT,
artist_name TEXT,
other TEXT
)
// 创建对应歌曲索引表的fts表
CREATE VIRTUAL TABLE IF NOT EXISTS songindexfts5 USING fts5 (
song_id UNINDEXED,
name,
alias,
artist_name,
other,
content='songindex',
content_rowid='song_id',
tokenize='simple'
)
// 使用trigger创建fts表更新器
CREATE TRIGGER IF NOT EXISTS triggerinsert AFTER INSERT ON songindex
BEGIN
INSERT INTO songindexfts5
(
rowid,
name,
alias,
artist_name,
other
)
VALUES
(
new.song_id,
new.name,
new.alias,
new.artist_name,
new.other
);
END;
// 进行全文搜索匹配
SELECT * FROM songindexfts5 WHERE songindexfts5 MATCH 'keyword'
更多详情及函数接口参见官方介绍文档:SQLite FTS5 扩展[2]。
分词器运行在 建索引 和 查询 两个阶段,承担 建索引分词 和 查询分词,是FTS的核心,没有分词器模块,FTS就没法工作;例如一段文本“网易云音乐”,可能被拆分为“网易、云音乐”,也可能本拆分为“网、易、云、音、乐”,最终检索结果也完全取决于分词器的拆分。
SQLite也提供了相关分词器插件,比如simple、icu、unicode61等,只有icu、unicode61支持中文;但unicode61按标点拆分,不可用;icu是按字拆分的,可以用,但检索结果比较乱,不符合中文检索的习惯和诉求,中文检索需要能够支持 字、字组、词、拼音、拼音首字母缩写等检索。
simple是微信开源的一个支持中文和拼音的SQLite FTS5 三方分词插件,在其原有中文分字能力上,支持通过cppjieba[3] 实现更精准的词组匹配。更多实现原理和细节可参考其开源介绍:simple: 一个支持中文和拼音搜索的 sqlite fts5插件[4]。
检索分词
查询分词
simple能够很好的支持 字、字组、词、拼音、拼音首字母缩写 等检索。
测试数据
测试结果
simple在中文检索上效果要远好于 ICU,更符合中文检索习惯。
测试数据
songName = "三里屯的夜" albumName = "署前街少年" artistName = "赵雷"
测试结果
综上调研分析可知,SQLite3 FTS5 + Simple 分词插件 是本地全文搜索引擎的最佳方案。
产品设计上,功能入口基于主搜页面做扩展,当用户输入搜索query触发云端搜索时,会同步进行本地私域数据搜索;本地搜索可搜索内容包含用户 创建/收藏歌单、红心歌曲、订阅艺人、已购专辑、最近收听 数据。
客户端内置一个轻量级全文搜索引擎进行数据检索,考量到排序策略需要不断迭代调优,对灵活性和动态性要求较高,基于云音乐跨端基建考量,排序跨端选型JS来实现,检索结果通过JS执行排序并返回最终展示结果给客户端做渲染展示。整体方案如下图所示。
搜索耗时是用户搜索体验和内容消费的关键影响指标,耗时越少,用户体验越好。
通过对搜索过程每个步骤环节的耗时分析(各步骤的耗时统计见下图),发现高耗时主要集中在以下3个环节:
资源数组组装耗时主要来自 SQLite查询串行执行、资源数据反序列化。优化方案上,根据实际业务逻辑,将SQL查询优化为多线程并发执行,并延迟数据反序列化时机到展示时执行。
优化后,复测 7k 条数据耗时由 2400+ms 下降到 810ms 左右(基于xiaomi8测试)。
客户端本地通过内置的JS脚本实现搜索结果排序,该JS脚本可动态发布更新。JS在与Native代码函数进行数据通信时,以Android系统为例,需要将java线程切换到native线程再切换到js线程,并且一次完整的流程上存在4次的线程切换以及4次的内存拷贝的情况。
针对这个问题,采用 JNI 和 C 调用 JSC 引擎来提升通信效率,方案落地细节参见文章 Android本地搜索优化[5],iOS优化思路一样,这里就不做过多论述。
优化后,本地检索JS数据传输耗时大幅度降低,复测 1w 条数据耗时由 1400+ms 下降到 310ms 左右(基于iPhoneX测试)。
SQlite FTS提供了bm25()函数来做文本匹配度计算,在返回检索结果的同时返回文本匹配值,可以替代JS脚本的文本匹配计算,继续减少耗时。
BM25 算法
BM25是信息索引领域用来计算query与文档相似度得分的经典算法。算法原理简单概括描述起来,就是先对搜索词query进行切分得到一组单词,然后求和每个单词的相关性得分,就得到了query和文档之间的分数,单词的相关性得分由三部分组成:
BM25算法公式如下图,详细介绍可参考文档 bm25算法介绍[6]。
调用SQL
// 在进行全文搜索匹配时调用,bm25()函数可以将查询结果按照字符匹配度进行排序
SELECT *, bm25(songindexfts5) as BM FROM songindexfts5
WHERE songindexfts5 MATCH 'keyword'
ORDER BY bm25(songindexfts5)
通过上述优化方案,总检索耗时下降了 **75%**,检索耗时的优化也有效促进了搜索业务指标的提升,通过将本地搜索结果展示数量由 1000条 提升至 4000条,结果有效点击率提升了 13%, 人均播放时长也提升了 17s。
优化后的各阶段耗时统计参见下图:
从图中可以看出,资源数据组装成为优化后的最大耗时占用,后续我们将持续进行拆解分析,优化数据查询耗时和JS数据传输耗时。
本文详细介绍了云音乐本地轻量级搜索引擎的技术实现方案和耗时优化方案,通过在云音乐私域数据搜索中的落地运用,对其技术能力和业务能力进行了有效验证。未来将通过自研分词器进一步优化分词效果提升检索准确性,优化SQLite数据查询耗时和JS数据传输效率进一步缩短检索耗时,并推进落地更多的业务场景,为用户提供更好更准确的检索服务。
受限于自身能力,文中如有不足之处还请大家斧正,欢迎一起学习交流。
FTS5 与 FTS3/4 的比较: https://sqlite.readdevdocs.com/fts5.html#appendix_a
[2]SQLite FTS5 扩展: https://sqlite.readdevdocs.com/fts5.html#overview_of_fts5
[3]cppjieba: https://www.wangfenjin.com/posts/simple-jieba-tokenizer/
[4]simple: 一个支持中文和拼音搜索的 sqlite fts5插件: https://www.wangfenjin.com/posts/simple-tokenizer/
[5]Android本地搜索优化: https://segmentfault.com/a/1190000043777969
[6]bm25算法介绍: https://zhuanlan.zhihu.com/p/79202151
更多岗位,可进入网易招聘官网查看 https://hr.163.com/