08|系统隔离:如何应对高并发流量冲击?
下载APP
关闭
渠道合作
推荐作者
08|系统隔离:如何应对高并发流量冲击?
2022-11-09 徐长龙 来自北京
《高并发系统实战课》
课程介绍
讲述:徐长龙
时长11:42大小10.69M
你好,我是徐长龙,今天我想跟你聊聊如何做好系统隔离。
我曾经在一家教育培训公司做架构师,在一次续报活动中,我们的系统出现了大规模崩溃。在活动开始有五万左右的学员同时操作,大量请求瞬间冲击我们的服务器,导致服务端有大量请求堆积,最终系统资源耗尽停止响应。我们不得不重启服务,并对接口做了限流,服务才恢复正常。
究其原因,我们习惯性地将公用的功能和数据做成了内网服务,这种方式虽然可以提高服务的复用性,但也让我们的服务非常依赖内网服务。当外网受到流量冲击时,内网也会受到放大流量的冲击,过高的流量很容易导致内网服务崩溃,进而最终导致整个网站无法响应。
事故后我们经过详细复盘,最终一致认为这次系统大规模崩溃,核心还是在于系统隔离性做得不好,业务极易相互影响。
改造前的系统部署结构
如果系统隔离性做得好,在受到大流量冲击时,只会影响被冲击的应用服务,即使某个业务因此崩溃,也不会影响到其他业务的正常运转。这就要求我们的架构要有能力隔离多个应用,并且能够隔离内外网流量,只有如此才能够保证系统的稳定。
拆分部署和物理隔离
为了提高系统的稳定性,我们决定对系统做隔离改造,具体如下图:
也就是说,每个内、外网服务都会部署在独立的集群内,同时每个项目都拥有自己的网关和数据库。而外网服务和内网必须通过网关才能访问,外网向内网同步数据是用 Kafka 来实现的。
网关隔离和随时熔断
在这个改造方案中有两种网关:外网网关和内网网关。每个业务都拥有独立的外网网关(可根据需要调整)来对外网流量做限流。当瞬时流量超过系统承受能力时,网关会让超编的请求排队阻塞一会儿,等服务器 QPS 高峰过后才会放行,这个方式比起直接拒绝客户端请求来说,可以给用户更好的体验。
外网调用内网的接口必须通过内网网关。外网请求内网接口时,内网网关会对请求的来源系统和目标接口进行鉴权,注册授权过的外网服务只能访问对其授权过的内网接口,这样可以严格管理系统之间的接口调用。
同时,我们在开发期间要时刻注意,内网网关在流量增大的时候要做熔断,这样可以避免外网服务强依赖内网接口,保证外网服务的独立性,确保内网不受外网流量冲击。并且外网服务要保证内网网关断开后,仍旧能正常独立运转一小时以上。
但是你应该也发现了,这样的隔离不能实时调用内网接口,会给研发造成很大的困扰。要知道常见外网业务需要频繁调用内网服务获取基础数据才能正常工作,而且内网、外网同时对同一份数据做决策的话,很容易出现混乱。
减少内网 API 互动
为了防止共享的数据被多个系统同时修改,我们会在活动期间把参与活动的数据和库存做推送,然后自动锁定,这样做可以防止其他业务和后台对数据做修改。若要禁售,则可以通过后台直接调用前台业务接口来操作;活动期间也可以添加新的商品到外网业务中,但只能增不能减。
通过缓存推送实现商品数据的同步
这样的实现方式既可以保证一段时间内数据决策的唯一性,也可以保证内外网的隔离性。
不过你要注意,这里的锁定操作只是为了保证数据同步不出现问题,活动高峰过后数据不能一直锁定,否则会让我们的业务很不灵活。
因为我们需要把活动交易结果同步回内网,而同步期间外网还是能继续交易的。如果不保持锁定,数据的流向不小心会成为双向同步,这种双向同步很容易出现混乱,系统要是因此出现问题就很难修复,如下图:
并发决策会导致数据无法决策同步
我们从图中可以看到,两个系统因为没有实时互动的接口,数据是完全独立的,但是在回传外网数据到内网时,库存如果在两个系统之间来回传递,就很容易出现同步冲突进而导致混乱。那怎么避免类似的问题呢?
其实只有保证数据同步是单向的,才能取消相互锁定操作。我们可以规定所有库存决策由外网业务服务决定,后台对库存操作时必须经过外网业务决策后才能继续操作,这样的方式比锁定数据更加灵活。而外网交易后要向内网同步交易结果,只能通过队列方式推送到内网。
事实上,使用队列同步数据并不容易,其中有很多流程和细节需要我们去打磨,以减少不同步的情况。好在我们使用的队列很成熟,提供了很多方便的特性帮助我们降低同步风险。
现在我们来看下整体的数据流转,如下图:
数据流转
后台系统推送数据到 Redis 或数据库中,外网服务通过 Kafka 把结果同步到内网,扣减库存需通知外网服务扣减成功后方可同步操作。
分布式队列控流和离线同步
我们刚才提到,外网和内网做同步用的是 Kafka 分布式队列,主要因为它有以下几个优点:
队列拥有良好吞吐并且能够动态扩容,可应对各种流量冲击场景;
可通过动态控制内网消费线程数,从而实现内网流量可控;
内网消费服务在高峰期可以暂时离线,内网服务可以临时做一些停机升级操作;
内网服务如果出现 bug,导致消费数据丢失,可以对队列消息进行回放实现重新消费;
Kafka 是分区消息同步,消息是顺序的,很少会乱序,可以帮我们实现顺序同步;
消息内容可以保存很久,加入 TraceID 后查找方便并且透明,利于排查各种问题。
两个系统之间的数据同步是一件很复杂、很繁琐的事情,而使用 Kafka 可以把这个实时过程变成异步的,再加上消息可回放,流量也可控,整个过程变得轻松很多。
在“数据同步”中最难的一步就是保证顺序,接下来我具体介绍一下我们当时是怎么做的。
当用户在外网业务系统下单购买一个商品时,外网服务会扣减本地缓存中的库存。库存扣减成功后,外网会创建一个订单并发送创建订单消息到消息队列中。当用户在外网业务支付订单后,外网业务订单状态会更新为“已支付”,并给内网发送支付成功的消息到消息队列中,发送消息实现如下:
可以看到,我们在发送消息的时候已经通过某些依据(如订单号、uid)算出这条消息应该投放到哪个分区内,Kafka 同一个分区内的消息是顺序的。
那为什么要保证消费顺序呢?其实核心在于我们的数据操作必须按顺序执行,如果不按顺序,就会出现很多奇怪的场景。
比如“用户执行创建订单、支付订单、退费”这一系列操作,消费进程很有可能会先收到退费消息,但由于还没收到创建订单和支付订单的消息,退费操作在此时就无法进行。
当然,这只是个简单的例子,如果碰到更多步骤乱序的话,数据会更加混乱。所以我们如果想做好数据同步,就要尽量保证数据是顺序的。
不过,我们在前面讲 Kafka 的优点时也提到了,队列在大部分时间是能够保证顺序性的,但是在极端情况下仍会有乱序发生。为此,我们在业务逻辑上需要做兼容,即使无法自动解决,也要记录好相关日志以方便后续排查问题。
不难发现,因为这个“顺序”的要求,我们的数据同步存在很大难度,好在 Kafka 是能够长时间保存消息的。如果在同步过程中出现问题,除了通过日志对故障进行修复外,我们还可以将故障期间的流量进行重放(重放要保证同步幂等)。
这个特性让我们可以做很多灵活的操作,甚至可以在流量高峰期,暂时停掉内网消费服务,待系统稳定后再开启,落地用户的交易。
除了数据同步外,我们还需要对内网的流量做到掌控,我们可以通过动态控制线程数来实现控制内网流量的速度。
好,今天这节课就讲到这里,相信你已经对“如何做好系统隔离”这个问题有了比较深入的理解,期望你在生产过程中能具体实践一下这个方案。
总结
系统的隔离需要我们投入大量的时间和精力去打磨,这节课讲了很多会对系统稳定性产生影响的关键特性,让我们整体回顾一下。
为了实现系统的隔离,我们在外网服务和内网服务之间设立了接口网关,只有通过网关才能调用内网接口服务。并且我们设定了在大流量冲击期间,用熔断内网接口的交互方式来保护内网。而外网所需的所有数据,在活动开始之前都要通过内网脚本推送到商城本地的缓存中,以此来保证业务的运转。
同时,外网成功成交的订单和同步信息通过分布式、可实时扩容和可回放的消息队列投递到了内网,内网会根据内部负载调整消费线程数来实现流量可控的消息消费。由此,我们实现了两个系统之间的同步互动。
我把这节课的关键知识画成了导图,供你参考:
思考题
用什么方法能够周期检查出两个系统之间不同步的数据?
欢迎你在留言区与我交流讨论,我们下节课见!
分享给需要的人,Ta购买本课程,你将得18元
生成海报并分享
赞 3
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
07|强一致锁:如何解决高并发下的库存争抢问题?
下一篇
09|分布式事务:多服务的2PC、TCC都是怎么实现的?
精选留言(7)
- LecKey2022-11-09 来自北京外网与没网的区别: 外网是一般是针对用户端,直接对接用户使用,需要做公网解析; 内网一般是公司技术服务节点,不需要公网解析且不对外服务,都是外网服务上做单独解析才能访问内网服务,访问内网会比公网获取数据速度更快,减少了一层解析。 一般大点的公司都会做外网和内网隔离,以保障服务安全和稳定。展开
作者回复: 你好,这个思考没毛病
共 2 条评论2 - ARM2023-01-08 来自北京“这个特性让我们可以做很多灵活的操作,甚至可以在流量高峰期,暂时停掉内网消费服务,待系统稳定后再开启,落地用户的交易” 那订单的状态流转会不会出问题,因为消息都在kafka没落库。比如我买东西,创建订单,我支付的时候,怎么显示已支付?退费怎么退费?
作者回复: 你好,ARM,所有操作都在外网服务决策,内网只是同步结果
- Layne2022-12-01 来自北京老师,在上面说到数据指定到Kafka某个分区上,这样会出现数据倾斜问题吧,导致Kafka的集群性能出问题吧?
作者回复: 你好,Layne,你说到了关键点,所以分区依据最好是能保证一定随机性,一般常见使用自增id或者某些比较分散的数据,如uid及订单id是snowflake算出来的,并且hash分区的算法足够发散
共 4 条评论 - RiseL2022-11-24 来自北京外网服务要保证内网网关断开后,仍旧能正常独立运转一小时以上,数据都是内网来的,内网挂了,外网功能还怎么正常运行呢
作者回复: 你好,risel,这就是隔离的关键,业务用数据都推送到业务内了
共 3 条评论 - 花花大脸猫2022-11-11 来自北京可以通过给数据打tag或者版本号,然后周期性的对比同一笔数据的tag或者版本号是否一致,如果不一致,以tag或者版本号新的那一条为准
作者回复: 你好,大脸猫,这个数据量会大一些并且全局要有tso服务才行
- LecKey2022-11-09 来自北京课后思考:每条数据都有唯一的数据标识(一般是自增id,或者有规律一串数字唯一id),而且一般都是小到大,根据这个最大值应该就能判断出来。 如果数据不同步应该找到对应数据节点做补偿操作
作者回复: 你好,加深下问题:那么更新操作同一条数据如何避免
共 5 条评论 - peter2022-11-09 来自北京请问:内外网这种划分方法是通用的还是你们公司自己独有的? 我读了一些专栏和书(数量不是很多),都没有提到内外网这种划分,所以对于本讲中提到的内外网非常不理解。请问,这种划分方法是互联网通用的方法吗?还是作者的培训公司自己的一种做法?
作者回复: 你好,peter,很高兴收到你的提问,内外网是通用的说法,基本对于内网api是没有鉴权可以随便调用的,只有大规模的互联网公司系统模块之间才会做鉴权隔离,外网是指对外给用户提供服务的入口,一般外网有统一入口,入口之外都是外网