今天我要分享的主题是《基于ElasticSearch的搜索平台在哈啰出行的应用》。相信大家对ElasticSearch都有一定的了解。今天主要分享一下基于ElasticSearch的搜索在出行行业的应用,其中会涉及到一些 ES相关的技术细节。最后从大的方面来分享落地平台的一些经验和个人的一些感触,希望今天的分享能够给大家带来一些思考和启发。
建设背景
首先我想介绍一下我们团队的整体业务,不知道现在有多少人安装了我们哈啰出行的APP,没有安装我们的APP应该至少也用过我们哈啰出行的服务。
我猜想很多人对于哈啰出行的印象还停留在共享单车的时代。其实远远不止,目前哈啰出行的业务,不仅仅有共享单车,共享助力车,我们还有规模还比较不错的四轮业务,主要是顺风车和打车。我们还可以在哈啰出行的APP直接订火车票和酒店,还可以买景点门票。哈啰出行还造电动车售卖,它的无钥匙体验非常的好。另外还有租车等各种各样的生活服务类的业务,大家有兴趣可以去体验一下哈啰出行的业务。正是因为有各种各样的业务形态,才给了我们中台和平台一个非常广阔的成长空间。
我们团队目前负责的业务有四轮的匹配引擎,这个待会会讲到。然后两轮有智能调度引擎。可能很多人不知道调度是什么意思,调度的意思就是用算法的能力,把这个站点的单车搬往另外一个地方,投放到另外一个站点,用算法的能力获得一个全局收益的最大化。然后我们还有以平台的能力提供的搜索引擎和推荐引擎,我们还有语音识别服务,有超过200维的深度模型的在线排序打分服务。
当然我们这些能力都是依托于更底层的中台能力,比如我们的存储平台给我们提供存储服务。然后我们还依赖了模型平台,特征平台、计算平台、数据开发平台等等这些能力都是我们的一个底座。
2019年我刚来哈啰出行的时候还没有搜索平台,那个时候各个厂商已经都有自己的搜索平台。哈啰出行的各业务想要用搜索服务的时候,他就会去找运维去拉10台机器或者十几台机器,然后自己搭一个ES集群,然后往这个群里同步数据。然后可能还要做一些相关的性能优化,业务能够把这个工程搭起来就已经非常困难,更不要说提供一些算法能力。所以我们可以从这个图上面可以看到,2019年哈啰的 ES集群新增数量接近30个。我就想能不能把我的搜索经验用上,然后把这些业务经常用的这些功能抽象沉淀成平台,以平台的能力减少业务的重复开发,这样的话就提升业务的迭代效率。
同时把应用算法的同学也拉进来,为搜索和推荐,提供算法能力。这样就可以为业务提升工程效率和业务的算法效果。有了这个目标之后,我就开始着手构建搜索平台。建平台其实是非常不容易的一件事情。之前在阿里的时候我也尝试做过平台,但是失败了。然后我就总结了一下失败的经验,我理解是这样。
第一点就是业务团队对我们中台信任感缺失。一个现象是在互联网行业人员的流动性很强。大厂里面的一个常见的做法是起一个项目,然后项目上线之后也晋升了,晋升完了以后这个项目就没人管了,这个非常常见。
第二点业务团队基本上都有开发人力的冗余。他有能力去开发这个功能,他基本上就不会把这个事情交给外部团队,这个也是我们做平台所遇到的一个困境。所以我在做之前就整理了一个大概的思路:
我先做一个场景,先把一个场景给攻关下来。然后我拿着成功的案例,再拉拢更多的业务。更多的业务带来了各式各样的业务需求,他会给我提各种各样的需求,在满足他们需求的同时,我们平台的能力得到了一个很大的提升。
平台的能力提升之后,业务就更愿意接入平台,这样形成良性循环。
建设的过程与挑战
场景一:司乘匹配引擎
于是我们就开始找第一个场景,找到了四轮业务的顺风车场景。
乘客在哈啰APP上发单,乘客填入自己的起点、终点和出发时间,然后形成了一个海量的乘客订单池。接着司机要来找单了,他也会到APP上面去发一个单,然后这个订单包含起点、终点和出发时间。匹配引擎就会用司机的信息去海量的乘客订单里面去找最顺路的订单,然后按照顺路程度排序,供司机挑选。这个是我们非常常见的一个场景。
当然乘客也可以反过来找司机,乘客找到司机以后只能发邀请,不能去做别的。然后还有其他的业务形式,比如说我们司机可以看附近的乘客到哪个目的地的人最多,然后通过这种订单池的形式去选单,大家可以自己去体验一下。
原来的司乘匹配架构的核心问题,主要有两点。第一点原来是基于 PG数据库来实现,它是一个离线计算的过程。在司机发单的一刻,触发一次计算把当前最顺路的或者是最适合的乘客给匹配出来,然后存到缓存里面去。然后后续司机看到了就是缓存的结果,显然这个里面非常大的弊端,缓存之后新产生的一些乘客的订单,即使是再顺路,他也没有办法去让司机看到,这是最大的一个弊端。
然后我觉得第二个弊端是基于PG数据库的架构,横向扩展能力受到一定限制。所以我们就想到了能不能以ES用搜索的这种技术栈来解决当前的架构问题。
基于ES匹配架构升级:匹配引擎
解决这个问题我觉得首先核心的要解决几个问题,第一个问题是我们的数据同步怎么做?业务的数据是存储在数据库里面的,它不会直接往你的搜索引擎里面去写,这些数据库里面数据怎么在业务无感知的情况下做到一个秒级的实时同步,这是我们首先要解决的一个问题。
数据同步:技术方案落地
第二个问题就是业务有很多业务计算逻辑,比如说顺路度怎么算,过滤的逻辑怎么实现,然后这些逻辑能不能在我们ES集群面跑起来,这个也是一个问号。
然后第三个方面的话就是我们的一个稳定性该如何保证?我们知道新旧系统切换很容易出现问题,尤其是在我们这种核心交易链路里面稳定性非常重要。
我们首先解决数据同步的问题。
当时大部分公司会选择用Java开发,订阅kafka的数据,往搜索引擎写,这是最直接和简单的做法。
但是其实这里面要考虑的问题很多,为什么我当时选了Flink作为我们的技术选型?一方面阿里在2017年的时候,搜索引擎的数据同步已经切到Flink上了。
我主要是看中了Flink的两点,第一点的话就是分布式的高可用和高扩展的能力,高可用的话就是Flink他自己有failover的一个机制,在故障发生的时候,他可以自己做故障转移和故障恢复。他还有Exactly Once的能力,最后尽管我们只用到了at latest once。
第二个方面,我觉得Flink抽象能力很高,它上层的API table可以直接把业务的逻辑以SQL的形式提交上来的,这样的话就给了我们把这个场景推广到更多场景的一个可能。
解决技术选型问题之后,我们最后是怎么实现?批量的数据是通过批处理,从 PG数据库读取。然后实时的数据是数据库通过 binlog到kafka。然后消费kafka里面的数据,然后实时的写到ES里面去,是不是有了Flink这个银弹之后,我们什么都不用做就可以用了,其实也不是的。
在我们这个场景下面其实要解决很多的问题,首先在我的个人的理解Flink更擅长于处理log类型的数据, log和业务类型有什么差别?Log类型的数据它是append的模式,它的数据在Flink的表里面是不可更改的,而我们的业务场景它的数据是频繁的更新的,需要数据不停的更新。
技术挑战:Flink双流JOIN的难题
这个时候,我们看一下Flink的场景下面join会有哪些问题。左边的那些图就是Flink的基本原理,就是Flink join的时候它有很多窗口。然后右边上面这个图说的意思就是两个表的一个join过程。左表的数据来了之后,会跟右边的 state区域进行join,然后右边的数据来了以后会跟左边的state区进行join。如果join不上就放到state的区,这里就很明显有一个很大的问题,就是我们的窗口得开多大。然后我们这个业务数据有效期是非常长的,大概4~16天。你这个窗口放4~16天,然后不停的追加,内存可能很快就会爆掉。
然后第二个问题,state区里面它的数据是append模式的,同一条数据(主键相同)就会产生10条甚至20条数据。这样的话一旦join,就发生一个笛卡尔乘积的规模的写入,对ES的压力还是非常大的,那么怎么解决这个问题呢?
我们就想了一个办法,就是写入了数据insert的消息,我们就直接把它放到state区里面存起来,这个数据是有限的。然后进来之后它还会发生join,完了以后直接写到ES。然后对于更新的数据我们是单独把它拉出来,利用的是ES的一个部分更新的能力写入到ES集群。
然后左表的数据来了以后,我就更新左表的那些字段,右表的数据来了以后我就更新右表的一些字段,互不打扰,这样的话就解决了我们刚才那个问题。
同时在我们基本上用的都是Flink的上层API,这样的话想达到一个通用的目的。
做完这些之后是不是就可以上线了?当然不是。我们解决第二个问题,第二个问题就是业务的逻辑怎么办?
技术创新:定制化引擎插件
业务的逻辑我们是以ES的插件,然后发到我们ES集群里面去的。下面的公式是我们计算顺路度的一个核心的公式。可以去专利发明里面搜一下,里面有很多计算顺路程度的计算方法,然后我们把这个算法集成到我们ES集群里面,利用ES集群的分布式计算的算力去完成计算。计算完了之后用顺路度进行排序,这样的话我们就可以找到最顺路的订单。
之后我们还对这个插件的发布做了一些优化。我们知道ES的插件更新是需要重启ES集群的,对我们的稳定性伤害是非常大的,我们后来做到的就是业务插件发布是可以热发布的,不用重启ES集群。然后做完了这些之后,我们准备上线了,业务效果还不错。
上线的过程中也遇到一点问题,把流量切50%的时候还顺利,但切到100%的时候就出现了一些少量的超时,首先可以考虑的就是你的机器容量不足,能想到的最快的办法就是加机器。但是当时我们还发现一个细节,就是我们的CPU其实没有到100%。即使无法到100%,至少应该能到70到80%,但其实 CPU一直没有超过50%的,这里就给了我们很大的一个疑点,我们就围绕这个疑点去找这个问题,这里面肯定是有什么问题的。
我理解的性能优化的一个过程,最直接的办法就是去查监控,然后看你的瓶颈在哪里。
技术挑战:引擎性能调优
其中最常见的我们CPU上不去,一个就是锁,第二个的话就是等IO。但是当时我们遇到一个困难,就是我们的监控系统不太完善,监控系统的各项指标没有什么特别,或者根本看不出来。当时我们配套工具也不是太完善,就连压测工具都是我们自己去搭建的,压测环境是我们自己搭的,工具是我们自己找的,所以当时我甚至还怀疑过压测工具是不是不对,并用了两套压测方式来进行验证。最后发现都是CPU只能到50%,这就是一个很大的疑点。其实在做ES性能优化的时候,还比较难的一点是ES的配置项非常多,有很多的配置项跟性能都是密切相关的,包括你分片数量的设置,包括你的jvm,gc的这种设置对性能可能都会有影响,但是到底是哪个选项有问题,当时一直试了很多种方法,如果说监控系统看不出来,分析不出来,你只能是去尝试,然后去猜想和验证。
我觉得性能优化其实就是一个分析,猜想,然后验证这么一个过程。后来经过我们两天的一个寻找,最终还是发现一点东西, ES里面有一个配置项,他是做磁盘性能优化的,这个配置项如果是日志集群其实是没有任何问题的。但是在我们这个场景下就是我们的业务数据,业务数据里的点集的数据特别的大。
点集数据是什么?就是用户发的一个订单里面有个起点有个终点,经过高德导航的一个路径规划之后生成一条规划路径,这个路径的话大概是10米或者5米一个点,然后一个点的话是一个经度和一个纬度。然后经纬度的话基本上是一个5~7位的浮点数,这个数据的话存下来就跟非常的大,我们的很多IO都是CPU在等待磁盘里面读取点集的数据。找到之后我们就用 ES的mmap来优化数据加载过程,以及后来我们对这个路径进行了抽稀和压缩。路径的抽稀是什么意思?就是我们在做顺路度计算的时候,如果这两个点之间是很长的一段距离都是直线,那么直线中间的点都可以去掉,比如说这个点到另外一个点里面,直线有两公里或者三公里,这里面的点都可以删掉而不影响最终的计算结果。
这这个事情做完之后,我们的吞吐量就上去了,吞吐量直接提升了4倍,平均响应时间也降低了大概50%,然后CPU也终于能打满。在这里我就分享一下自己的一些感想:你自己在做性能优化,或者是遇到一个故障,或者是在做告警处理的时候,你有没有想过你的问题有没有一些合理的解释,就里面的所有的细节都不要放过,可能这个细节里面就隐藏了很多未知的坑,将来就是定时炸弹,今天不爆发,将来有一天会爆发。所以你不要放过任何一个细节,每一个细节都应该有它一个合理的逻辑解释。
最后在这里再聊一下稳定性,稳定性是我们老生常谈的问题,稳定性的话题基本上可以再开一个课堂,再讲50分钟都不为过。
稳定性方案
我理解的稳定性,个人总结的稳定性三板斧就是监控、告警加预案。监控就是你配置的监控大盘,告警发生之后,你有没有去看,有没有去认真的分析。然后告警风暴的时候,你有没有去调整告警的阈值,让他能够在出故障的时候出现,不该出现的时候不出现。然后预案在你出现真正的出现故障之后,你自己去能不能有一个开关或者是有一个配置,可以直接把故障给消除掉或者降级。
以前都是靠我个人的一些经验总结,后来发现谷歌发了一篇论文,把这些经验总结成为一个理论的形式。左边金字塔的图案就是谷歌的论文里面摘抄出来的,有兴趣的可以去搜一下。
在我们这个场景下面,我们以前是三板斧,后来我又特意加了一板斧叫四板斧,添加了一个人的因素,为什么我添加这么一个人的因素呢?
这里我可以给大家分享一个故事:去年720的时候,我们哈啰单车发生了大面积的故障,我相信应该很多人受到了影响,因为当时很多人上班都迟到被罚款。
然后故障恢复的时间特别长,为什么特别长呢?当时我听到一个有意思的事情,前天晚上有一个核心开发回家的时候,他说当天晚上回家的时候下雨了,担心下雨了淋湿笔记本电脑,就没把笔记本电脑带回去。结果第二天发生故障的时候,他被堵在出租车上,什么事情都做不了,然后导致故障恢复的时间特别长。我觉得这个是一个人很重要的一个因素。后来公司发生了大量的资损,对吧?这个资损不知道能买多少笔记本,实在是得不偿失。
我们还可以在网上看到,在火车站里面有个程序员,经常一坐下来就拿个笔记本出来开始处理生产故障。还有我们印象很深的,当年新浪微博鹿晗公布恋情,然后新浪的工程师在婚礼现场去扩容和处理故障的,那个事情相信大家都还记忆犹新。其实这里面就和人有很大的一个因素。无论你的理论再强,最后都是落实到人上面的。我们做这种在线系统,7x24小时OnCall应该所基本具备的一个素质,我觉得是这样的。
然后我们这个场景下面的监控,后来我们是怎么做的,我们可以看到这个坐标图,坐标图的横坐标的话从左到右的话,集群、单机和索引维度,我们的监控的力度是越来越细。然后从下往上看,就是我们从系统监控到业务监控,我们的监控是越来越具体,然后在做监控项的时候,其实也是有一些技巧,我们最核心的黄金指标有没有覆盖到。右下角 Table就是谷歌论文里面的一个黄金指标。常用的吞吐量,延迟和错误三个。
然后我们在做系统设计的时候也考虑到系统稳定性。我们选用了Flink作为我们的技术选型,同时我们的索引除了在线服务的以外,我们在我的后台里面点击一个重建索引的按钮之后,可以马上去重建拉起一条新的索引。因为在故障发生发生之后数据库里面的数据是全量的完整的,然后这个时候你可以快速的做一个故障恢复,把所有的读流量切到新建的一个索引。同时我们索引可以是多个索引并行的。新的索引有问题切回旧索引,旧索引有问题可以切回新的。这就是我们系统里面做的稳定性的设计。
基本上说完这些之后,出行的四轮出行的场景基本上就ok了。我们当时业务效果还是不错的,我们的完单量提升了接近50%,然后接单人数提升了接近40%。
后期的话我就做了一些搜索平台推广的事情,主要以技术分享的形式去让大家知道我搜索平台所具备的一个能力,然后去找业务去聊,让他了解我的搜索平台,并且让他接进来。
然后接下来的这些业务又给我提了很多需求,包括它的数据是存储在mysql库里面的,它的数据不是在kafka里面,是rocketmq的,以及它的数据binlog的格式也是不一样的,这些场景我们都做了都兼容了。所以我们后期接入的业务也越来越多,这样的话就形成了一个非常好的良性循环。
成果及未来展望
整个项目下来,我们的完单量提升了49.8%,接单人数提升了37%,取得了不错的成绩。
同时,我们也制定可搜索平台后期的计划。
未来的话主要是两个方面的,我们前期做的一些工作,还只是完成了搜索工程的一些事情,让搜索的工程用起来很容易很方便,但是叠加算法的能力还是靠我们人肉的开发的。比如说现在我们加一个意图识别的组件,或者是加一个拼写纠错的组件,我们还需要去拉NLP的同学,然后让他们给我们开发一个模型,在线去调用,这样的话其实是一个非常复杂的过程,我期望将来的话在我们平台上面有很多的算法组件,包括搜索前的包括搜索后的一个精排的组件,都可以随时的去挑选和使用。然后稳定性方面的话,我们正在做异地多活的话,今年年底应该会上线。
本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者使用。非商业目的转载或使用本文内容,敬请注明“内容转载自哈啰技术团队”。