华人澳洲中文论坛

热图推荐

    ES+Redis+MySQL,这个高可用架构设计太顶了!

    [复制链接]

    2023-2-28 21:43:41 17 0

    配景
    会员零碎是一种根底零碎,跟公司一切业务线的下单主流程亲密相干。假如会员零碎出毛病,会致使用户无奈下单,影响规模是全公司一切业务线。所以,会员零碎必需包管高机能、高可用,提供不乱、高效的根底办事。
    跟着同程和艺龙两家公司的合并,愈来愈多的零碎需求买通同程 APP、艺龙 APP、同程微信小顺序、艺龙微信小顺序等多平台会员体系。
    例如微信小顺序的穿插营销,用户买了一张火车票,此时想给他发酒店红包,这就需求查问该用户的一致会员瓜葛。
    由于火车票用的是同程会员体系,酒店用的是艺龙会员体系,只要查到对应的艺龙会员卡号后,能力将红包挂载到该会员账号。
    除了上述讲的穿插营销,还有许多场景需求查问一致会员瓜葛,例如定单核心、会员等级、里程、红包、常旅、实名,以及各类营销流动等等。
    所以,会员零碎的申请量愈来愈大,并发量愈来愈高,往年清明小长假的秒并发 tps 乃至超过 2 万多。
    在如斯大流量的冲击下,会员零碎是如何做到高机能和高可用的呢?这就是本文侧重要讲述的内容。
    ES 高可用计划
    | ES 双核心主备集群架构
    同程和艺龙两家公司融会后,全平台一切体系的会员总量是十多亿。在这么大的数据体量下,业务线的查问维度也对比繁杂。
    有的业务线基于手机号,有的基于微信 unionid,也有的基于艺龙卡号等查问会员信息。
    这么大的数据量,又有这么多的查问维度,基于此,咱们选择 ES 用来存储一致会员瓜葛。ES 集群在全部会员零碎架构中十分首要,那末如何包管 ES 的高可用呢?
    首先咱们知道,ES 集群自身就是包管高可用的,如下图所示:



    当 ES 集群有一个节点宕机了,会将其余节点对应的 Replica Shard 降级为 Primary Shard,持续提供办事。
    但即便是这样,还远远不敷。例如 ES 集群都部署在机房 A,当初机房 A 忽然断电了,怎么办?
    例如办事器硬件毛病,ES 集群大部份机器宕机了,怎么办?或者忽然有个十分抢手的抢购秒杀流动,带来了一波十分大的流量,间接把 ES 集群打死了,怎么办?面对这些状况,让运维兄弟冲到机房去解决?
    这个十分不理想,由于会员零碎间接影响全公司一切业务线的下单主流程,毛病恢复的时间必需十分短,假如需求运维兄弟人工染指,那这个时间就过长了,是绝对不克不及容忍的。
    那 ES 的高可用如何做呢?咱们的计划是 ES 双核心主备集群架构。



    咱们有两个机房,分别是机房 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 主集群隔分开来,不会由于某个营销流动的流量冲击而影响用户的下单主流程。
    如下图所示:



    | 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 了,此时更新申请来了,加了散布式锁,并删除了缓存。当更新申请实现操作后,查问申请的线程活过去了,此时它再履行更新缓存,就把脏数据写到缓存中了。
    发现没有?次要的问题关键就在于“删除缓存”和“更新缓存”产生了并发冲突,只有将它们互斥,就可以解决问题。
    如下图所示:



    实行了缓存计划后,经统计,缓存命中率 90%+,极大减缓了 ES 的压力,会员零碎总体机能失掉了很大晋升。
    | Redis 双核心多集群架构
    接上去,咱们看一下如何保障 Redis 集群的高可用。
    如下图所示:



    对于 Redis 集群的高可用,咱们采取了双核心多集群的模式。在机房 A 和机房 B 各部署一套 Redis 集群。
    更新缓存数据时,双写,只要两个机房的 Redis 集群都写胜利了,才前往胜利。查问缓存数据时,机房内就近查问,升高延时。这样,即便机房 A 总体毛病,机房 B 还能提供残缺的会员办事。
    高可用会员主库计划
    上述讲到,全平台会员的绑定瓜葛数据存在 ES,而会员的注册明细数据存在瓜葛型数据库。
    最先,会员使用的数据库是 SqlServer,直到有一天,DBA 找到咱们说,单台 SqlServer 数据库曾经存储了十多亿的会员数据,办事器已达到物理极限,不克不及再扩展了。根据当初的增长趋向,过不了多久,全部 SqlServer 数据库就崩了。
    你想一想,那是一种甚么样的灾害场景:会员数据库崩了,会员零碎就崩了;会员零碎崩了,全公司一切业务线就崩了。想一想就毛骨悚然,酸爽无比,为此咱们立刻开启了迁徙 DB 的任务。
    | MySQL 双核心 Partition 集群计划
    通过调研,咱们选择了双核心分库分表的 MySQL 集群计划,如下图所示:



    会员一共有十多亿的数据,咱们把会员主库分了 1000 多个分片,平分到每个分片大略百万的量级,足够使用了。
    MySQL 集群采取 1 主 3 从的架构,主库放在机房 A,从库放在机房 B,两个机房之间经过专线同步数据,提早在 1 毫秒内。
    会员零碎经过 DBRoute 读写数据,写数据都路由到 master 节点所在的机房 A,读数据都路由到当地机房,就近拜候,增加网络提早。
    这样,采取双核心的 MySQL 集群架构,极大进步了可用性,即便机房 A 总体都崩了,还能够将机房 B 的 Slave 降级为 Master,持续提供办事。
    双核心 MySQL 集群搭建好后,咱们进行了压测,测试上去,秒并发能达到 2 万多,均匀耗时在 10 毫秒内,机能达标。
    | 会员主库平滑迁徙计划
    接上去的任务,就是把会员零碎的底层存储从 SqlServer 切到 MySQL 上,这是个危险极高的任务。
    次要有下列几个难点:
    会员零碎是一刻都不克不及停机的,要在不断机的状况下实现 SqlServer 到 MySQL 的切换,就像是在给高速行驶的汽车换轮子。会员零碎是由得多个零碎和接口组成的,毕竟开展了 10 多年,因为历史缘故,遗留了少量老接口,逻辑扑朔迷离。这么多零碎,必需一个不落的整个梳理分明,DAL 层代码必需重写,并且不克不及出任何问题,不然将是灾害性的。数据的迁徙要做到无缝迁徙,不只是存量 10 多亿数据的迁徙,实时发生的数据也要无缝同步到 MySQL。此外,除了要保障数据同步的实时性,还要包管数据的正确性,以及 SqlServer 和 MySQL 数据的统一性。基于以上痛点,咱们设计了“全量同步、增量同步、实时流量灰度切换”的技术计划。
    首先,为了包管数据的无缝切换,采取实时双写的计划。由于业务逻辑的繁杂,以及 SqlServer 和 MySQL 的技术差别性,在双写 MySQL 的过程当中,纷歧定会写胜利,而一旦写失败,就会致使 SqlServer 和 MySQL 的数据纷歧致,这是毫不允许的。
    所以,咱们采用的战略是,在试运转期间,主写 SqlServer,而后经过线程池异步写 MySQL,假如写失败了,重试三次,假如仍然失败,则记日志,而后人工排查缘故,解决后,持续双写,直到运转一段时间,没有双写失败的状况。
    经过上述战略,能够确保在绝大部份状况下,双写操作的正确性和不乱性,即便在试运转期间泛起了 SqlServer 和 MySQL 的数据纷歧致的状况,也能够基于 SqlServer 再次全量构建出 MySQL 的数据。
    由于咱们在设计双写战略时,会确保 SqlServer 一定能写胜利,也就是说,SqlServer 中的数据是全量最残缺、最正确的。
    如下图所示:



    讲完了双写,接上去咱们看一下“读数据”如何灰度。总体思绪是,经过 A/B 平台逐渐灰度流量,刚开始 100% 的流量读取 SqlServer 数据库,而后逐渐切流量读取 MySQL 数据库,先 1%,假如没有问题,再逐渐放流量,终究 100% 的流量都走 MySQL数据库。
    在逐渐灰度流量的过程当中,需求有验证机制,只要验证没问题了,能力进一步缩小流量。
    那末这个验证机制如何实行呢?计划是,在一次查问申请里,经过异步线程,对比 SqlServer 和 MySQL 的查问后果是不是统一,假如纷歧致,记日志,再人工反省纷歧致的缘故,直到完全解决纷歧致的问题后,再逐渐灰度流量。
    如下图所示:



    所以,总体的实行流程如下:



    首先,在一个夜黑风高的深夜,流量最小的时分,实现 SqlServer 到 MySQL 数据库的全量数据同步。
    接着,开启双写,此时,假如有用户注册,就会实时双写到两个数据库。那末,在全量同步和实时双写开启之间,两个数据库还相差这段时间的数据,所以需求再次增量同步,把数据增补残缺,以防数据的纷歧致。
    剩下的时间,就是各种日志监控,看双写是不是有问题,看数据比对是不是统一等等。
    这段时间是耗时最长的,也是最容易产生问题的,假如有的问题对比重大,致使数据纷歧致了,就需求从头再来,再次基于 SqlServer 全量构建 MySQL 数据库,而后从新灰度流量。
    直到最初,100% 的流量整个灰度到 MySQL,此时就半途而废了,下线灰度逻辑,一切读写都切到 MySQL 集群。
    | MySQL 和 ES 主备集群计划
    做到这一步,觉得会员主库应该没问题了,可 dal 组件的一次重大毛病改动了咱们的设法。
    那次毛病很恐惧,公司得多运用衔接不上数据库了,创单量直线往下掉,这让咱们意想到,即便数据库是好的,但 dal 组件异样,仍然能让会员零碎挂掉。
    所以,咱们再次异构了会员主库的数据源,双写数据到 ES,如下所示:



    假如 dal 组件毛病或 MySQL 数据库挂了,能够把读写切到 ES,等 MySQL 恢复了,再把数据同步到 MySQL,最初把读写再切回到 MySQL 数据库。
    如下图所示:



    异样会员瓜葛治理
    会员零碎不单单要包管零碎的不乱和高可用,数据的精准和正确也一样首要。
    举个例子,一个散布式并发毛病,致使一位用户的 APP 账户绑定了他人的微信小顺序账户,这将会带来十分卑劣的影响。
    首先,一旦这两个账号绑定了,那末这两个用户下的酒店、机票、火车票定单是相互能够看到的。
    你想一想,他人能看到你订的酒店定单,你火不火,会不会投诉?除了能看到他人的定单,你还能操作定单。
    例如,一个用户在 APP 的定单核心,看到了他人订的机票定单,他感觉不是本人的定单,就把定单勾销了。
    这将会带来十分重大的客诉,大家知道,机票退订费用是挺高的,这不只影响了该用户的正常出行,还致使了对比大的经济损失,十分蹩脚。
    针对这些异样会员账号,咱们进行了具体的梳理,经过十分繁杂烧脑的逻辑辨认出这些账号,并对会员接口进行了深度优化治理,在代码逻辑层堵住了相干破绽,实现了异样会员的治理任务。
    如下图所示:



    瞻望:更精密化的流控和升级战略
    任何一个零碎,都不克不及包管百分之一百不出问题,所以咱们要有面向失败的设计,那就是更精密化的流控和升级战略。
    | 更精密化的流控战略
    热点管制。针对黑产刷单的场景,同一个会员 id 会有少量反复的申请,造成热点账号,当这些账号的拜候超过设定阈值时,实行限流战略。
    基于调用账号的流控规定。这个战略次要是避免调用方的代码 bug 致使的大流量。例如,调用方在一次用户申请中,循环得多次来调用会员接口,致使会员零碎流量暴增得多倍。所以,要针对每个调用账号设置流控规定,当超过阈值时,实行限流战略。
    全局流控规定。咱们会员零碎能抗下 tps 3 万多的秒并发申请量,假如此时,有个很恐惧的流量打过去,tps 高达 10 万,与其让这波流量把会员数据库、ES 整个打死,还不如把超过会员零碎接受规模以外的流量疾速失败,最少 tps 3 万内的会员申请能正常响应,不会让全部会员零碎整个解体。



    | 更精密化的升级战略
    基于均匀响应时间的升级。会员接口也有依赖其余接口,当调用其余接口的均匀响应时间超过阈值,进入准升级形态。
    假如接上去 1s 内进入的申请,它们的均匀响应时间都继续超过阈值,那末在接下的时间窗口内,自动地熔断。
    基于异样数和异样比例的升级。当会员接口依赖的其余接口产生异样,假如 1 分钟内的异样数超过阈值,或者每秒异样总数占经过量的比值超过阈值,进入升级形态,在接下的时间窗口以内,自动熔断。
    目前,咱们最大的痛点是会员调用账号的治理。公司内,想要调用会员接口,必需请求一个调用账号,咱们会记载该账号的使用场景,并设置流控、升级战略的规定。
    但在实际使用的过程当中,请求了该账号的共事,可能异动到其余部门了,此时他可能也会调用会员零碎,为了省事,他不会再次请求会员账号,而是间接沿用之前的账号过去调用,这致使咱们无奈判别一个会员账号的详细使用场景是甚么,也就无奈实行更精密的流控和升级战略。
    所以,接上去,咱们将会对一切调用账号进行一个个的梳理,这是个十分宏大且繁琐的任务,但无路如何,硬着头皮也要做好。
    文章来源:【同程艺龙技术核心】

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    返回列表 本版积分规则

    :
    注册会员
    :
    论坛短信
    :
    未填写
    :
    未填写
    :
    未填写

    主题34

    帖子40

    积分186

    图文推荐