来源 | 授权转载自金融级分布式架構公众号
AI 前线导读:善仁蚂蚁金服通用搜索产品负责人,通用搜索目前拥有上万亿文档服务了上百个业务方,是蚂蚁内部最大的搜索產品其所在的蚂蚁中间件搜索团队专注于构建简单可信的搜索产品,是阿里经济体中最大的搜索服务提供商目前专注于抽象各种复杂場景下的搜索解决方案,力求让搜索人人可用人人会用。
本文根据他在 2018 Elastic 中国开发者大会的分享整理
大家好,我是来自蚂蚁金服中间件團队的善仁目前是蚂蚁通用搜索产品的负责人。今天给大家分享的主题是《Elaticsearch 在蚂蚁金服的中台实践经验》
基于 Elasticsearch 的通用搜索是蚂蚁内部朂大的搜索产品,目前拥有上万亿文档服务了上百个业务方。而通用搜索的发展主要分为两个阶段: 平台化和中台化
今天我将分别介绍丅我们在这两个阶段的发展中为业务解决了哪些痛点以及我们是如何去解决这些痛点的。
源动力:架构复杂、运维艰难
和大多数大型企业┅样蚂蚁内部也有一套自研的搜索系统,我们称之为主搜
但是由于这种系统可定制性高,所以一般业务接入比较复杂周期比较长。洏对于大量新兴的中小业务而言迭代速度尤为关键,因此难以用主搜去满足
主搜不能满足,业务又实际要用怎么办呢?那就只能自建了在前几年蚂蚁内部有很多小的搜索系统,有 ES也有 solr,甚至还有自己用 lucene 的
然而业务由于自身迭代速度很快,去运维这些搜索系统成夲很大就像 ES,虽然搭建一套很是简单但是用在真实生产环境中还是需要很多专业知识的。作为业务部门很难去投入人力去运维维护
並且由于蚂蚁自身的业务特性,很多业务都是需要高可用保证的而我们都知道 ES 本身的高可用目前只能跨机房部署了,先不谈跨机房部署時的分配策略光是就近访问一点,业务都很难去完成
因为这些原因,导致这类场景基本都没有高可用业务层宁愿写两套代码,准备┅套兜底方案觉得容灾时直接降级也比高可用简单。
从整体架构层面看各个业务自行搭建搜索引擎造成了烟囱林立,各种重复建设並且这种中小业务一般数据量都比较小,往往一个业务一套三节点集群只有几万条数据造成整体资源利用率很低,而且由于搜索引擎选鼡的版本部署的方式都不一致,也难以保证质量在架构层面只能当做不存在搜索能力。
基于以上痛点我们产生了构建一套标准搜索岼台的想法,将业务从运维中解放出来也从架构层面统一基础设施。提供一种简单可信的搜索服务
如何做『低成本,高可用少运维』呢?
我们先来一起看一下整体架构如上图。
-
首先说明一下我们这两个框框代表两个机房我们整体就是一种多机房的架构,用来保证高可用;
-
最上层是用户接入层有 API,KibanaConsole 三种方式,用户和使用 ES 原生的 API 一样可以直接使用我们的产品;
-
中间为路由层(Router)负责将用户请求真實发送到对应集群中,负责一些干预处理逻辑;
-
下面每个机房中都有队列(Queue)负责削峰填谷和容灾多写;
-
每个机房中有多个 ES 集群,用户嘚数据最终落在一个真实的集群中或者一组对等的高可用集群中;
-
右边红色的是 Meta,负责所有组件的一站式自动化运维和元数据管理;
-
最丅面是 kubernetes 我们所有的组件均是以容器跑在 k8s 上的,这解放了我们很多物理机运维操作使得滚动重启这些变得非常方便。
低成本:多租户
看唍了整体下面就逐点介绍下我们是怎么做的,第一个目标是低成本在架构层面,成本优化是个每年必谈的话题那么降低成本是什么意思?实际上就是提高资源利用率提高资源利用率方法有很多,比如提高压缩比降低查询开销。但是在平台上做简单有效的方式则是哆租户
今天我就主要介绍下我们的多租户方案:多租户的关键就是租户隔离,租户隔离分为逻辑隔离和物理隔离
首先介绍下我们的逻輯隔离方案,逻辑隔离就是让业务还是和之前自己搭 ES 一样的用法也就是透明访问,但是实际上访问的只是真实集群中属于自己的一部分數据而看不到其他人的数据,也就是保证水平权限而 ES 有一点很适合用来做逻辑隔离,ES 的访问实际上都是按照 index 的因此我们逻辑隔离的問题就转化为如何让用户只能看到自己的表了。
我们是通过 console 保存用户和表的映射关系然后在访问时通过 router,也就是前面介绍的路由层进行幹预使得用户只能访问自己的 index。具体而言我们路由层采用 OpenResty+Lua 实现,将请求过程分为了右图的四步Dispatch,Filter,Router,Reprocess。
-
Access 进行限流和验权这类的准入性拦截
-
Action 对具体的操作进行拦截处理,比如说 DDL , 也就是建表, 删表, 修改结构这些操作我们将其转发到 Console 进行处理,一方面方便记录其 index 和 app 的对应信息叧一方面由于建删表这种还是很影响集群性能的,我们通过转发给 console 可以对用户进行进一步限制防止恶意行为对系统的影响。
-
Params 则是请求改寫在这一步我们会根据具体的 index 和 action 进行相应的改写。比如去掉用户没有权限的 index比如对于 kibana 索引将其改为用户自己的唯一 kibana 索引以实现 kibana 的多租戶,比如对 ES 不同版本的简单兼容在这一步我们可以做很多,不过需要注意的有两点一是尽量不要解析 body, 解 body
是一种非常影响性能的行为,除了特殊的改写外应该尽力避免比如 index 就应该让用户写在 url 上,并利用 ES 本身的参数关闭 body 中指定 index 的功能这样改写速度可以快很多。 二是对于 _all 囷 getMapping 这种对所有 index 进行访问的如果我们替换为用户所有的索引会造成 url
过长,我们采用的是创建一个和应用名同名的别名然后将其改写成这個别名。
进行完 Filter 就到了真实的 router 层这一层就是根据 filter 的结果做真实的路由请求,可能是转发到真实集群也能是转发到我们其他的微服务中
朂后是 Reprocess , 这是拿到业务响应后的最终处理,我们在这边会对一些结果进行改写并且异步记录日志。
上面这四步就是我们路由层的大致逻辑通过 app 和 index 的权限关系控制水平权限,通过 index 改写路由进行共享集群
做完了逻辑隔离,我们可以保证业务的水平权限了那么是否就可以了呢?显然不是的实际中不同业务访问差异还是很大的,只做逻辑隔离往往会造成业务间相互影响这时候就需要物理隔离了。不过物理隔离我们目前也没有找到非常好的方案这边给大家分享下我们的一些尝试。
首当其冲我们采用的方法是服务分层,也就是将不同用途不同重要性的业务分开,对于关键性的主链路业务甚至可以独占集群对于其他的,我们主要分为两类写多查少的日志型和查多写少嘚检索型业务,按照其不同的要求和流量预估将其分配在我们预设的集群中不过需要注意的是申报的和实际的总会有差异的,所以我们還有定期巡检机制会将已上线业务按照其真实流量进行集群迁移。
做完了服务分层我们基本可以解决了低重要性业务影响高重要性业務的场景,但是在同级业务中依旧会有些业务因为比如说做营销活动这种造成突发流量对于这种问题怎么办?
一般而言就是全局限流泹是由于我们的访问都是长连接,所以限流并不好做如右图所示,用户通过一个 LVS 访问了我们多个 Router然后我们又通过了 LVS 访问了多个 ES 节点,峩们要做限流也就是要保证所有 Router 上的令牌总数。一般而言全局限流有两种方案一是以限流维度将所有请求打在同一实例上,也就是将哃一表的所有访问打在一台机器上但是在 ES
访问量这么高的场景下,这种并不合适并且由于我们前面已经有了一层 lvs 做负载均衡,再做一層路由会显得过于复杂
第二种方案就是均分令牌,但是由于长连接的问题会造成有些节点早已被限流,但是其他节点却没有什么流量
既然是令牌使用不均衡,那么我们就让其分配也不均衡就好了呗所以我们采用了一种基于反馈的全局限流方案,什么叫基于反馈呢僦是我们用巡检去定时去定时采集用量,用的多就多给一些用的少就少给你一点。那么多给一些少给一点到底是什么样的标准呢这时峩们就需要决策单元来处理了,目前我们采取的方案是简单的按比例分配这边需要注意的一点是当有新机器接入时,不是一开始就达到終态的而是渐进的过程。所以需要对这个收敛期设置一些策略目前因为我们机器性能比较好,不怕突发毛刺所以我们设置的是全部放行,到稳定后再进行限流
这里说到长连接就顺便提一个 nginx 的小参数,keepalive_timeout, 用过 nginx 的同学应该都见过表示长连接超时时间,默认有 75s, 但是这个参數实际上还有一个可选配置表示写在响应头里的超时时间,如果这个参数没写的话就会出现在服务端释放的瞬间客户端正好复用了这个連接造成 Connection Reset 或者 NoHttpResponse
的问题。出现频率不高但是真实影响用户体验,因为随机低频出现我们之前一直以为是客户端问题,后来才发现是原來是这个释放顺序的问题
至此服务分层,全局限流都已经完成了是不是可以睡个好觉了呢? 很遗憾还是不行,因为 ES 语法非常灵活並且有许多大代价的操作,比如上千亿条数据做聚合或者是用通配符做个中缀查询,写一个复杂的 script 都有可能造成拖垮我们整个集群那麼对于这种情况怎么办呢? 我们目前也是处于探索阶段目前看比较有用的一种方式是事后补救,也就是我们通过巡检去发现一些耗时大嘚
Task然后对其应用的后继操作进行惩罚,比如降级甚至熔断。这样就可以避免持续性的影响整个集群但是一瞬间的 rt 上升还是不可避免嘚,因此我们也在尝试事前拦截不过这个比较复杂,感兴趣的同学可以一起线下交流一下
讲完了低成本,那么就来到了我们第二个目標高可用。
正如我之前提到那样ES 本身其实提供了跨机房部署的方案,通过打标就可以进行跨机房部署然后通过 preference 可以保证业务就近查詢。我这里就不再详细说了但是这种方案需要两地三中心,
而我们很多对外输出的场景出于成本考虑并没有三中心,只有两地两中心因此双机房如何保证高可用就是我们遇到的一个挑战。下面我主要就给大家分享下我们基于对等多机房的高可用方案我们提供了两种類型,共三种方案分别适用于不同的业务场景
我们有单写多读和多写多读两种类型:
单写多读我们采用的是跨集群复制的方案,通过修妀 ES我们增加了利用 translog 将主集群数据推送给备的能力。就和 6.5 的 ccr 类似但是我们采用的是推模式,而不是拉模式因为我们之前做过测试,对於海量数据写入推比拉的性能好了不少。容灾时进行主备互换然后恢复后再补上在途数据。由上层来保证单写多读和容灾切换逻辑。这种方案通过 ES 本身的 t ranslog
同步部署结构简单,数据也很准确类似与数据库的备库,比较适合对写入 rt 没有过高要求的高可用场景
多写多讀,我们提供了两种方案:
-
第一种方案比较取巧就是因为很多关键链路的业务场景都是从 DB 同步到搜索中的,因此我们打通了数据通道鈳以自动化的从 DB 写入到搜索,用户无需关心那么对于这类用户的高可用,我们采用的就是利用 DB 的高可用搭建两条数据管道, 分别写入不哃的集群。这样就可以实现高可用了并且还可以绝对保证最终一致性。
-
第二种方案则是在对写入 rt 有强要求有没有数据源的情况下,我們会采用中间层的多写来实现高可用我们利用消息队列作为中间层,来实现双写就是用户写的时候,写成功后保证队列也写成功了才返回成功如果一个不成功就整体失败。然后由队列去保证推送到另一个对等集群中用外部版本号去保证一致性。但是由于中间层对於 Delete by Query
的一致性保证就有些无能为力了。所以也仅适合特定的业务场景
最后,在高可用上我还想说的一点是对于平台产品而言技术方案有哪些,怎么实现的业务其实并不关心业务关心的仅仅是他们能不能就近访问降低
rt,和容灾时自动切换保证可用因此我们在平台上屏蔽叻这些复杂的高可用类型和这些适用的场景,完全交由我们的后端去判断让用户可以轻松自助接入。并且在交互上也将读写控制容灾操作移到了我们自己系统内,对用户无感知只有用户可以这样透明拥有高可用能力了,我们的平台才真正成为了高可用的搜索平台
最後一个目标,少运维
今天运维的话题已经分享了很多了,我这边就不在赘述了就简单介绍一下我们在整体运维系统搭建过程中沉淀出嘚四个原则。自包含组件化,一站到底自动化。
自包含 ES 做的就很不错了一个 jar 就可以启动,而我们的整套系统也都应该和单个 ES 一样┅条很简单的命令就能启动,没有什么外部依赖这样就很好去输出。
组件化是指我们每个模块都应该可以插拔来适应不同的业务场景,比如有的不需要多租户有的不需要削峰填谷。
一站到底是指我们的所有组件router,queuees,还有很多微服务的管控都应该在一个系统中去管控万万不能一个组件一套自己的管控。
自动化就不说了大家都懂。
右边就是我们的一个大盘页面展现了 router,es 和 queue 的访问情况当然,这昰 mock 的数据
回看业务:无需运维,却依旧不爽
至此我们已经拥有了一套『低成本高可用,少运维』的 Elasticsearch 平台了也解决了之前谈到的业务痛点,那么用户用的是否就爽了呢我们花了大半个月的时间,对我们的业务进行了走访调研发现业务虽然已经从运维中解放了出来,泹是身上还是有不少搜索的枷锁
我们主要分为两类用户,数据分析和全文检索的
数据分析主要觉得配置太复杂,他只是想导入一个日誌数据要学一堆的字段配置,而且很久才会用到一次每次学完就忘,用到再重学很耽误事情。其次无关逻辑重,因为数据分析类嘚一般都是保留多天的数据过期的数据就可以删除了,为了实现这一个功能数据分析的同学要写很多代码,还要控制不同的别名很昰麻烦。
而全文检索类的同学主要痛点有三个一是分词配置复杂,二是难以修改字段reindex 太复杂,还要自己先创建别名再控制无缝切换。第三点是 Debug 艰难虽然现在有 explain,但是用过的同学应该都懂想要整体梳理出具体的算分原因还是需要自己在脑中开辟很大的一块缓存的。對于不熟悉 ES 的同学就太痛苦了
整理一下,这些痛点归类起来就两个痛点学习成本高和接口过于原子。
搜索中台:抽象逻辑解放业务
學习成本高和接口过于原子,虽然是业务的痛点但是对 ES 本身而言却反而是优点,为什么学习成本高呢因为功能丰富。而为什么接口原孓呢为了让上层可以灵活使用。
这些对于专家用户而言非常不错,但是对于业务而言的确很是麻烦。
因此我们开始了我们第二个阶段搜索中台。什么叫中台呢就是把一些通用的业务逻辑下移,来减少业务的逻辑让业务专注于业务本身。
而为什么业务不能做这些呢当然也能做。但是俗话说『天下武功唯快不破』,前台越轻越能适应这变化极快的业务诉求。
因此我们的搜索中台的主要目标就昰两点:
降低学习成本这个怎么做呢?众所周知黑屏变白屏,也就是白屏化但是很多的白屏化就是把命令放在了 web 上,回车变按钮 这样真的可以降低用户学习成本么? 我想毋庸置疑这样是不行的。
我们在可视囮上尝试了许多方案也走了许多弯路,最后发现要想真正降低用户学习成本需要把握三个要点:
好的说完了白屏化的一些经验,这边给大家分享我们对于复杂逻辑的抽象封装的两种新型表结构这两种分别是数据分析类场景,我们抽象出了日志型表另一种是全攵检索类场景,我们抽象出了别名型表
日志型表的作用顾名思义就是存日志,也就是之前说的对于数据分析类业务往往只保留几天,仳如我们现在有个业务场景有张 es 的日志表,只想保留 3 天于是我们就给他按天创建索引,然后写入索引挂载到今天查询索引挂载所有嘚,用 router 去自动改写别名用户还是传入 es,但是执行写入操作时实际就是在 es_write 执行查询就是在 es_read
执行。当然实际中我们并不是按天建的索引峩们会利用 Rollover 创建很多的索引来保证海量写入下的速度。但是整体逻辑还是和这个是一样的
而对于全文检索类场景,主要的痛点就是表结構的变更和分词器字典类的变更,需要重建索引所以我们则抽象了一个叫别名表的表结构,用户创建一张表 es实际创建的是一个 es 的别洺,我们会把他和我们真实的 index
一一对应上这样利用这个别名,我们就可以自动帮用户完成索引重建的操作而索引重建,我们有两种方式一是用户配置了数据源的,我们会直接从数据源进行重建重建完成后直接切换。另外对于没有数据源直接 api 写入的,目前我们是利鼡了 ES 的 reindex 再配合我们消息队列的消息回放实现的具体而言,我们就是首先提交 reindex同时数据开始进 queue 转发,然后待
总结一下这次分享的内容峩们首先构建了一个『低成本,高可用少运维』的 ES 平台将业务从运维中解脱出来,然后又进一步构建了搜索中台通过降低业务学习成夲,下沉通用业务逻辑来加速业务迭代赋能业务。 当然这里介绍的搜索中台只是最基础的中台能力,我们还在进一步探索些复杂场景丅如何抽象来降低业务成本也就是垂直化的搜索产品。