会员系统[同程艺龙] — 高可用架构设计实践

魏小言
云原生领域优质创作者
博客专家认证
2022-04-18 10:50:28

目录

ES高可用方案

ES双中心主备集群架构

ES流量隔离三集群架构

ES集群深度优化提升

会员Redis缓存方案

Redis双中心多集群架构

高可用会员主库方案

MySQL双中心Partition集群方案

会员主库平滑迁移方案

MySQL和ES主备集群方案

异常会员关系治理

展望:更精细化的流控和降级策略

更精细化的流控策略

更精细化的降级策略

结语

会员系统[同程艺龙] — 高可用架构设计实践
会员系统是一种基础系统,跟公司所有业务线的下单主流程密切相关。如果会员系统出故障,会导致用户无法下单,影响范围是全公司所有业务线。所以,会员系统必须保证高性能、高可用,提供稳定、高效的基础服务。

随着同程和艺龙两家公司的合并,越来越多的系统需要打通同程APP、艺龙APP、同程微信小程序、艺龙微信小程序等多平台会员体系。

例如微信小程序的交叉营销,用户买了一张火车票,此时想给他发酒店红包,这就需要查询该用户的统一会员关系。因为火车票用的是同程会员体系,酒店用的是艺龙会员体系,只有查到对应的艺龙会员卡号后,才能将红包挂载到该会员账号。

除了上述讲的交叉营销,还有许多场景需要查询统一会员关系,例如订单中心、会员等级、里程、红包、常旅、实名,以及各类营销活动等等。所以,会员系统的请求量越来越大,并发量越来越高,今年五一小长假的秒并发TPS甚至超过2万多。在如此大流量的冲击下,会员系统是如何做到高性能和高可用的呢?这就是本文着重要讲述的内容。


ES高可用方案
ES双中心主备集群架构
同程和艺龙两家公司融合后,全平台所有体系的会员总量是十多亿。在这么大的数据体量下,业务线的查询维度也比较复杂。有的业务线基于手机号,有的基于微信unionid,也有的基于艺龙卡号等查询会员信息。这么大的数据量,又有这么多的查询维度,基于此,我们选择ES用来存储统一会员关系。ES集群在整个会员系统架构中非常重要,那么如何保证ES的高可用呢?

首先我们知道,ES集群本身就是保证高可用的,如下图所示:

当ES集群有一个节点宕机了,会将其他节点对应的Replica Shard升级为Primary Shard,继续提供服务。但即使是这样,还远远不够。

例如ES集群都部署在机房A,现在机房A突然断电了,怎么办?例如服务器硬件故障,ES集群大部分机器宕机了,怎么办?或者突然有个非常热门的抢购秒杀活动,带来了一波非常大的流量,直接把ES集群打死了,怎么办?

面对这些情况,让运维兄弟冲到机房去解决?这个非常不现实,因为会员系统直接影响全公司所有业务线的下单主流程,故障恢复的时间必须非常短,如果需要运维兄弟人工介入,那这个时间就太长了,是绝对不能容忍的。那ES的高可用如何做呢?我们的方案是ES双中心主备集群架构。

关注我 code 杂坛,了解更多......

我们有两个机房,分别是机房A和机房B。我们把ES主集群部署在机房A,把ES备集群部署在机房B。会员系统的读写都在ES主集群,通过MQ将数据同步到ES备集群。此时,如果ES主集群崩了,通过统一配置,将会员系统的读写切到机房B的ES备集群上,这样即使ES主集群挂了,也能在很短的时间内实现故障转移,确保会员系统的稳定运行。

最后,等ES主集群故障恢复后,打开开关,将故障期间的数据同步到ES主集群,等数据同步一致后,再将会员系统的读写切到ES主集群。如下图所示:

ES流量隔离三集群架构
双中心ES主备集群做到这一步,感觉应该没啥大问题了,但去年的一次恐怖流量冲击让我们改变了想法。那是一个节假日,某个业务上线了一个营销活动,在用户的一次请求中,循环10多次调用了会员系统,导致会员系统的TPS暴涨,差点把ES集群打爆。

这件事让我们后怕不已,它让我们意识到,一定要对调用方进行优先级分类,实施更精细的隔离、熔断、降级、限流策略。首先,我们梳理了所有调用方,分出两大类请求类型。第一类是跟用户的下单主流程密切相关的请求,这类请求非常重要,应该高优先级保障。第二类是营销活动相关的,这类请求有个特点,他们的请求量很大,TPS很高,但不影响下单主流程。

基于此,我们又构建了一个ES集群,专门用来应对高TPS的营销秒杀类请求,这样就跟ES主集群隔离开来,不会因为某个营销活动的流量冲击而影响用户的下单主流程。如下图所示:

关注我 code 杂坛,了解更多......

ES集群深度优化提升
讲完了ES的双中心主备集群高可用架构,接下来我们深入讲解一下ES主集群的优化工作。有一段时间,我们特别痛苦,就是每到饭点,ES集群就开始报警,搞得每次吃饭都心慌慌的,生怕ES集群一个扛不住,就全公司炸锅了。

那为什么一到饭点就报警呢?因为流量比较大,导致ES线程数飙高,CPU直往上窜,查询耗时增加,并传导给所有调用方,导致更大范围的延时。那么如何解决这个问题呢?通过深入ES集群,我们发现了以下几个问题: 

ES负载不合理,热点问题严重。ES主集群一共有几十个节点,有的节点上部署的shard数偏多,有的节点部署的shard数很少,导致某些服务器的负载很高,每到流量高峰期,就经常预警。

ES线程池的大小设置得太高,导致CPU飙高。我们知道,设置ES的threadpool,一般将线程数设置为服务器的CPU核数,即使ES的查询压力很大,需要增加线程数,那最好也不要超过“cpu core * 3 / 2 + 1”。如果设置的线程数过多,会导致CPU在多个线程上下文之间频繁来回切换,浪费大量CPU资源。

shard分配的内存太大,100G,导致查询变慢。我们知道,ES的索引要合理分配shard数,要控制一个shard的内存大小在50G以内。如果一个shard分配的内存过大,会导致查询变慢,耗时增加,严重拖累性能。

string类型的字段设置了双字段,既是text,又是keyword,导致存储容量增大了一倍。会员信息的查询不需要关联度打分,直接根据keyword查询就行,所以完全可以将text字段去掉,这样就能节省很大一部分存储空间,提升性能。

ES查询,使用filter,不使用query。因为query会对搜索结果进行相关度算分,比较耗CPU,而会员信息的查询是不需要算分的,这部分的性能损耗完全可以避免。

节约ES算力,将ES的搜索结果排序放在会员系统的JVM内存中进行。

增加routing key。我们知道,一次ES查询,会将请求分发给所有shard,等所有shard返回结果后再聚合数据,最后将结果返回给调用方。如果我们事先已经知道数据分布在哪些shard上,那么就可以减少大量不必要的请求,提升查询性能。

经过以上优化,成果非常显著,ES集群的CPU大幅下降,查询性能大幅提升。ES集群的CPU使用率: 

会员系统的接口耗时:

会员Redis缓存方案
一直以来,会员系统是不做缓存的,原因主要有两个:第一个,前面讲的ES集群性能很好,秒并发3万多,99线耗时5毫秒左右,已经足够应付各种棘手的场景。第二个,有的业务对会员的绑定关系要求实时一致,而会员是一个发展了10多年的老系统,是一个由好多接口、好多系统组成的分布式系统。

所以,只要有一个接口没有考虑到位,没有及时去更新缓存,就会导致脏数据,进而引发一系列的问题,例如:用户在APP上看不到微信订单、APP和微信的会员等级、里程等没合并、微信和APP无法交叉营销等等。那后来为什么又要做缓存呢?是因为今年机票的盲盒活动,它带来的瞬时并发太高了。虽然会员系统安然无恙,但还是有点心有余悸,稳妥起见,最终还是决定实施缓存方案。

ES近一秒延时导致的Redis缓存数据不一致问题的解决方案

在做会员缓存方案的过程中,遇到一个ES引发的问题,该问题会导致缓存数据的不一致。我们知道,ES操作数据是近实时的,往ES新增一个Document,此时立即去查,是查不到的,需要等待1秒后才能查询到。如下图所示:

ES的近实时机制为什么会导致Redis缓存数据不一致呢?具体来讲,假设一个用户注销了自己的APP账号,此时需要更新ES,删除APP账号和微信账号的绑定关系。而ES的数据更新是近实时的,也就是说,1秒后你才能查询到更新后的数据。而就在这1秒内,有个请求来查询该用户的会员绑定关系,它先到Redis缓存中查,发现没有,然后到ES查,查到了,但查到的是更新前的旧数据。最后,该请求把查询到的旧数据更新到Redis缓存并返回。就这样,1秒后,ES中该用户的会员数据更新了,但Redis缓存的数据还是旧数据,导致了Redis缓存跟ES的数据不一致。如下图所示:

面对该问题,如何解决呢?我们的思路是,在更新ES数据时,加一个2秒的Redis分布式并发锁,为了保证缓存数据的一致性,接着再删除Redis中该会员的缓存数据。如果此时有请求来查询数据,先获取分布式锁,发现该会员ID已经上锁了,说明ES刚刚更新的数据尚未生效,那么此时查询完数据后就不更新Redis缓存了,直接返回,这样就避免了缓存数据的不一致问题。如下图所示:

上述方案,乍一看似乎没什么问题了,但仔细分析,还是有可能导致缓存数据的不一致。例如,在更新请求加分布式锁之前,恰好有一个查询请求获取分布式锁,而此时是没有锁的,所以它可以继续更新缓存。

但就在他更新缓存之前,线程block了,此时更新请求来了,加了分布式锁,并删除了缓存。当更新请求完成操作后,查询请求的线程活过来了,此时它再执行更新缓存,就把脏数据写到缓存中了。发现没有?主要的问题症结就在于“删除缓存”和“更新缓存”发生了并发冲突,只要将它们互斥,就能解决问题。如下图所示:

关注我 code 杂坛,了解更多......

实施了缓存方案后,经统计,缓存命中率90%+,极大缓解了ES的压力,会员系统整体性能得到了很大提升。

Redis双中心多集群架构
————————————————
版权声明:本文为CSDN博主「魏小言」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34417408/article/details/124243522

...全文
359 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

16

社区成员

发帖
与我相关
我的任务
社区描述
code杂坛:提供初级同学进阶的深度与广度!关注一线 “ 互联网时讯、各技术栈、开源产品、面试技巧......“ 等最新动态
云原生架构设计模式 个人社区
社区管理员
  • 魏小言
  • flybirding10011
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

hi,欢迎加入 “code杂坛” 社区!

code杂坛:提供初级同学进阶的深度与广度!关注一线 “ 互联网时讯、各技术栈、开源产品、面试技巧......“ 等最新动态

在这里你可以:

  • 学习最新大厂技术知识
  • 掌握技能进阶各种技巧
  • 交到志同道合的朋友
  • 获取最新图书资讯
  • 参与活动免费赠书
  • 与我们的作者、译者互动!

 

加入我们成为 code杂坛 的同学

【欢迎联系】

微信公众号:code杂坛

试试用AI创作助手写篇文章吧