Elasticsearch在SCRM的应用及优化

MCtalk 技术文章/2021.02.14 文|吕飞扬 网易智企资深后端开发工程师
网易互客作为一款网易云商旗下的SCRM产品,为企业提供了私域流量运营的能力。在业务中存在这样一个高频的搜索场景:用户可以根据自己的需求,灵活地选择筛选条件实施查询,或将用户行为数据实时地汇总到报表中。这对系统的即席查询和聚合分析能力带来了很大的挑战。我们选用Elasticsearch作为全文搜索引擎,本文将详细介绍Elasticsearch在项目中的落地及优化。
关于ES
Elasticsearch(以下简称ES)是建立在全文搜索引擎库Lucene基础上的搜索引擎,它隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的RESTful API,提供了分布式的全文搜索和分析能力。它的典型应用场景有如下几个:
- 日志实时分析:用户行为,慢查询,异常日志 ...
- 搜索服务:商品搜索,APP搜索,客户数据搜索 ...
- 数据分析监控:Metrics,行为数据 ...
ES索引(index)是一系列物理分片(shard)的集合,每个分片都有自己独立的索引(Lucence实现),并且ES自动确保备份。
ES的核心原理是倒排索引(Inverted index),倒排索引也常被称为反向索引,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。 在相同的文本匹配场景下,传统关系型往往需要对目标数据集进行遍历,性能比较差。ES利用倒排索引,如下图【Term Dictionary】-【Position】,存的是分词后的词组到文档id的映射,并在搜索词组过程中利用FST等数据结构做了搜索优化,所以在搜索某个特定词语比如Allen时,很容易定位到文档的id是12,15。
接下来在讲述ES具体应用之前,先简单介绍一下我们的业务背景。
背景介绍
网易互客是一款赋能销售获客转化的SCRM产品,它集全渠道获客,多触点沟通,私域互动转化和客户关系管理等功能于一身,帮助企业更好地触达客户、产生互动、提升客户管理效率。
作为一个客户关系平台,系统对数据自定义、多维度的智能化搜索有很强的依赖场景。 系统中包括客户、租户、内容在内的诸多子服务域都对外提供了灵活的搜索服务。其中比较有代表性的是客户服务,客户服务支持用户自定义任意字段,并在字段上提供了灵活的条件筛选。
举一个典型的业务场景:
以上的即席查询功能大量的被系统核心功能所依赖,比如:
- 数据展示:最基础的功能,将筛选结果展示在交互界面。
- 客户实时流转,智能标签:实时流转符合预置条件的客户状态,并进行智能打标。
- 多渠道触达:与筛选后的目标客户进行触达沟通。
- 用户数据报表:在数据筛选的基础上进行聚合分析,汇总用户行为数据。
经过调研市面上比较流行的搜索引擎,我们最终选择了ES作为我们的搜索分析引擎。
落地实践
在项目初期,由于业务迭代节奏比较快,所以我们选用了一种比较简单的实现方式。我们在ES设置一张大宽索引,租户每次新增的字段都直接同步到索引映射中,但很快发现不断膨胀的索引结构让索引性能急剧下降。而为每个租户分配一个索引也不现实,一方面是集群无法承载分片数爆炸,另一方面是如果企业注册进来但活跃度很低,为他分配同等的硬件资源将造成极大的浪费。
所以接下来我们主要做了以下几件事情:
1、字段分类:
通过调研用户的使用场景,根据ES支持的数据格式,我们将字段归为五类,分别是:long、double、text、keyword、date,用户可在基础服务中自定义这五种类型的字段。 结合系统所要支持的筛选条件,这里需要介绍一下text和keyword这两种类型,这两个类型在java中对应的都是String类型,之所以区分开,是因为我们有模糊匹配的场景。关于这两种字段索引和写入的区别,这里不再继续展开,感兴趣的读者可自行查阅ES官网。我们可以简单理解为,keyword类型能支持模糊匹配,而text只能查询到分词得到的词语。
2、租户隔离,预置字段
每个企业都有一个租户标识(tenant_id),在ES索引中,每个索引中也存在着同样的映射字段,以此作为租户数据的逻辑隔离。
系统在ES索引中会给5种类型的字段分别预置100个字段空间,用以满足企业的自定义字段场景,理论上每个企业可以拥有500个自定义字段,基本能覆盖绝大多数的业务场景。
更进一步地,ES单个分片(ES6.8中推荐单个分片大小保持在几个GB至几十GB之间)能够承载的数据有限,过大的数据量会拖垮性能。并且,ES在索引创建后,主分片的数量就不能再变更。在没有冷热数据区分的业务场景下,为防止不断增长的数据拖垮系统,我们自定义了一套路由策略,让租户们均匀地分散到多索引中,大租户甚至可以独享一个索引,实现数据在物理上也隔离开。
3、分词器
官方内置的分词器对中文分词效果不好,也无法满足我们某些业务场景,所以我们在分词器上也做了一些选择和定制:
- ik分词器:业界比较流行的中文分词组件
- 自定义分词器:主要是针对标签类型的自定义字段
4、查询优化
主要体现在两方面:
- ES _routing特性的使用
- 有节制地使用keyword字段
5、SDK包装
ES官方提供的jar包只包含了基本的查询、写入和聚合等功能,我们希望其他服务域也能快速接入ES,复用已有的优秀实践,让开发人员无痛上手,我们在官方jar包基础上包装了一层,形成一套契合我们业务场景的SDK。
通过以上的实践,我们初步达到了既定的需求目标,用户可以灵活地组合字段和筛选条件。
6、问题
在满足系统的聚合搜索的业务需求后,我们遇到了新的问题:
- 写入速率不匹配
- 失败及异常场景(版本冲突,拒绝任务等)弱感知,无法重试:造成数据不一致,搜索结果有误差。
以我们典型的写入场景为例,业务在执行完DB变更后,需要以线程池异步写入ES。但由于传统DB和ES索引方式的不同,导致DB写入速率会比ES高很多,在高并发场景下,异步线程池的写入瓶颈会反压到上层线程池,甚至拖垮工作线程。中间以MQ做缓冲也会导致写入不及时,无法做到准实时地搜索。
框架优化
ES作为一个分布式集群,针对上述的性能瓶颈,我们当然可以通过增加硬件配置、扩容内存、增加集群节点来解决。但是这也意味着硬件成本的提升,我们选择了尝试进一步提升ES的写入性能。
核心点是批量(bulk)写入,缓存一定时间(500ms)/一定数量的用户操作后,一次性写入。在用户无感知的情况下,牺牲一点实时性换取更大的吞吐量。
我们针对单个操作和批量操作的写入做了性能对比,通过压测:
- 单个写入场景:当并发达到22000以上时,ES响应出现429,说明集群到达瓶颈。
- 批量写入场景:最终并发量大约在40W左右,并且不存在失败的用例。
所以我们主要做了几个调整:
- 架构调整,新增ES写入服务:用来支持ES的批量写入及重试,方便其他服务域快速接入。
- ES SDK新增插件机制:操作转发,失败/异常处理基于这套机制实现。
- 引入MQ用于流量削峰:确保异常流量场景下平稳运行。
新的架构带来几个明显的好处:
- 写入性能提升:经过压测对比,大概有接近20倍的性能提升。
- 失败/异常业务感知,重试:减少异常概率,确保数据的最终一致性。
- 压力告警,日志监控:实时查看ES数据变更,感知流量瓶颈和操作异常。
- 操作合并:可在写入服务上继续优化,针对热点数据,合并写入操作,人为减少IO,避免压垮整个服务。
- 插件复用:开发者可自定义其他插件来辅助业务操作。
结语
以上就是ES在网易互客整个的落地及优化过程。通过在查询实时性和写入性能的平衡,最终在聚合搜索和业务写入上都取得了不错的效果,线上稳定运行,没有再出现数据误差。目前也已经有多个服务域接入了这套解决方案。
后续我们也将在ES上做更多的探索,比如可以利用XPack和自研权限来防止攻击、误操作。以上是本次分享的全部内容,欢迎关注我们,持续分享更多技术干货。