苏宁 11.11:搜索引擎 Solr 在苏宁易购商品评价系
|
背景说明苏宁易购商品评价系统主要提供商品维度评价数量聚合、评价列表展示功能,并为其他业务系统提供商品评价数据支撑服务。功能涉及对亿级数据的数量聚合、排序、多维度查询等复杂的业务场景,关系型数据库的索引为 B-Tree 结构,适合数值区分度或离散度高的数据,而评价系统中单个商品评价可以达到数十万条,相同星级的评价数则为亿级,故不适宜使用关系型数据库。解决此类海量数据准实时聚合的技术选型有以倒排表作为索引结构的 Solr 和 Elasticsearch(底层都是 Apache Lucene)搜索引擎服务,还有以 bitmap 作为底层索引结构的实时分析统计数据库 Druid,但 Druid 只是支持数据统计功能并不保存原始数据,无法满足商品评价类的功能需求。系统建设之初对团队对 Solr 更为熟悉,故在 Solr 和 Elasticsearch 两者之间选择的 Solr 作为商品评价索引数据存储服务。 评价系统架构苏宁易购商品评价系统架构如下:
应用服务模块根据苏宁易购技术规范要求,应用系统架构划分为前中后台三个主模块:
数据存储层
外围系统
基础设施
监控
Solr 介绍搜索引擎基础原理搜索引擎的索引称为反向索引,俗称倒排表,把文本分词得到字典,保存字典项与文档 ID(Lucene 自身 doc ID 而非应用端文档 ID)的关系,在查询时根据字典查询到倒排表文档 ID 集合,再进行交并集操作即可得到结果,其主要结构是字典域、索引域和字段存储域。反向索引如下图:
在 Solr4.0 之后为了满足排序和关键字聚合的需求场景,Lucene 提供了 DocValues 特性,又被称为正排表,使用列式存储保存文档 ID 和字典项的关系,不再使用之前 FieldValue Cache 机制,提升性能并降低对内存的使用和虚拟机 Full GC 的风险。在 Elasticsearch 中所有字段都是默认开启此特性,但在 Solr 中需要使用者进行显式配置生效。DocValues 存储结构可分为两种,一种是原值,一种是字典项 ID。
doc[0] = 1005 doc[1] = 1006 doc[2] = 1005
doc[0] = "aardvark" doc[1] = "beaver" doc[2] = "aardvark" 假如“aardvark”在字典表中 ID 为 0,”beaver”在字典中 ID 为 1,则实际结构为: doc[0] = 0 doc[1] = 1 doc[2] = 0 字典表数据为: term[0] = "aardvark" term[1] = "beaver" 注:字典表所有 term 都是有序的,故 DocValues 可以直接用于排序。 Solr&Lucene 架构体系Solr 是一个高性能、基于 Lucene 的开源全文搜索服务,提供丰富的查询语言,同时实现了可配置、可扩展并对查询性能提供了优化,提供完善的功能管理界面;在 Lucene 基础上对易用性和可用性进行了大量封装如 Schema 配置化、请求分发处理机制、插件化机制、数据导入、分布式、监控指标采集等,架构体系如下:
苏宁商品评价系统结合自身业务特点采用了 Solr 主从节点和聚合查询节点的组合架构,而不是通用的 Solr Cloud 架构,主要考虑到两点:
Solr 特性应用多维度数量聚合商品详情页展示商品 / 供应商维度审核通过的好中差评数量、标签数量、个性化评价项等数量,使用的是 Solr facet 机制。
/solr/select?q=*:*&wt=json&indent=true&facet=true&facet.query=picVideoFlag:1&facet.query=againReviewFlag:1 多维度列表查询根据查询条件直接查询评价 ID 即可,限制查询字段并支持分页,因商品评价无需计算文档相似度且开启缓存可提升查询性能,故使用 filter query 替代 query 作为查询条件: /solr/select?q=*:*&fq=(auditStat:0+OR+auditStat:1)&start=0&rows=10&fl=commodityReviewId 需要注意的是 filter cache 是以单个 filter query 为键缓存结果,可以根据业务需求要求拆成多个 filter query,设置不同缓存策略以提升缓存利用率。 分组查询需要说明的是 Solr 分组关键字 group 的含义与关系型数据库的 group by 中的 group 并不相同,在 Solr 中指的是对查询的结果文档根据指定的字段进行分组,而非关系型数据库对字段分组,如苏宁商品评价需要展示每个评价的第一条商家回复内容,通过 Solr 查询商家回复并根据评价 ID 进行分组后取第一条记录即可(查询条件为评价 ID 列表),参数说明如下:
自定义排序在使用 Solr 对结果集排序一般有两种方式:
大部分排序都使用第二种方式,但此方式对性能会有影响,特别是在涉及到多个字段时且参与排序的文档数较多时,其内部执行过程需要获取每个匹配 doc 的字段值进行计算,即使字段开启 docValues 特性对存储、IO 和内存空间也有一定压力。 提升函数排序的性能也有两种方式:
facet.method 参数的选择Lucene 字段级 facet 有三种机制:
facet.method 参数指定在对字段进行聚合时使用上述哪种算法,根据上述实现机制说明可知对于值区分度低的字段,适合选择 enum 机制;对于有大量不同值的字段合适使用 fc 机制,若 Solr 开启了近实时搜索 (NRT) 特性,fcs 机制则是更好的选择,因其在生成新索引段时旧索引段缓存不用重新加载。 分布式聚合查询苏宁易购商品电商模型中 SPU 和 SKU 可以在商品上架时根据产品特性和销售情况动态调整组合,查询 SPU 商品评价时就会出现跨多个节点的场景,需要支持跨节点的分布式查询并对聚合结果集进行二次处理,如数量的累加、列表二次排序和分页等,为此搭建空数据的 Solr 节点作为聚合查询节点。业务方根据 SPU 和 SKU 关系得出节点编号和节点的地址,改写查询请求添加 shards 参数转发给聚合节点即可,示例如下: /solr/select?q=*:*&wt=json&indent=true&facet=true&facet.query=picVideoFlag:1&facet.query=againReviewFlag:1&shards=solr1:8080/,solr2:8080/,solr3:8080/ 此特性原理是使用多线程查询多个 Solr 节点,在内存中对结果进行合并,故使用此特性也有一定限制:
性能调优
配置示例如下: <query> <maxBooleanClauses>2000</maxBooleanClauses> <filterCache class="solr.FastLRUCache" size="8192" initialSize="4096" autowarmCount="80%" /> <enableLazyFieldLoading>true</enableLazyFieldLoading> <listener event="newSearcher" class="solr.QuerySenderListener"> <arr name="queries"> <!-- 预加载查询 --> </arr> </listener> <listener event="firstSearcher" class="solr.QuerySenderListener"> <arr name="queries"> <!-- 预加载查询 --> </arr> </listener> <useColdSearcher>false</useColdSearcher> <maxWarmingSearchers>1</maxWarmingSearchers> </query> 展望目前在 CDN 缓存和 Redis 缓存失效情况部分请求的压力还是回源到 Solr,对于 Solr 的直接依赖较大,且更新索引时偶尔还是会触发 Full GC,影响业务的稳定性。下一步需要对缓存架构重新设计,设计缓存异步更新和熔断机制,限制用户请求直接落到 Solr 服务上,提升接口 QPS、系统稳定性和评价展示生效实时性。 另外还在规划引入 Elasticsearch 作为后台业务索引,前后台索引进行分离,降低前后台耦合度和系统风险,更便于业务开发。 作者介绍胡正林,苏宁易购 IT 总部消费者平台研发中心高级架构师,十余年软件开发经验,熟悉大型分布式高并发系统架构和开发,目前主要负责易购各系统架构优化与大促保障工作。 架构大数据 |
时间:2018-11-15 22:58 来源: 转发量:次
声明:本站部分作品是由网友自主投稿和发布、编辑整理上传,对此类作品本站仅提供交流平台,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,不为其版权负责。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。