13 | 高可用架构案例(一):如何实现O2O平台日订单500万?

你好,我是王庆友。在上一讲中,我和你介绍了高可用系统的设计原则和常见手段。今天呢,我会通过一个实际的案例,告诉你具体如何落地一个高可用的架构,让你能够深入理解和运用这些高可用手段。

项目背景介绍

先说下项目的背景。这是一个小程序点餐平台,用户在小程序上点餐并支付完成后,订单会先落到订单库,然后进一步推送到门店的收银系统;收银系统接单后,推送给后厨系统进行生产;同时返回小程序取餐码,用户可以凭取餐码去门店取餐或收取外卖。

这个项目服务于一家大型的餐饮公司,公司在全国有大量的门店,他们准备搞一个长期的大型线上促销活动,促销的力度很大:用户可以在小程序上先领取优惠券,然后凭券再支付1元,就可以购买价值数十元的套餐。

结合以往的经验,以及这次的促销力度,我们预计在高峰时,前端小程序请求将会达到每秒10万QPS,并且预计首日的订单数量会超过500万。在这种高并发的情况下,我们为了保证用户的体验,系统整体的可用性要达到99.99%

你可以先了解一下这个点餐平台的具体架构:

这里呢,我具体说下系统主要的调用过程,以便于你更好地理解它:

  1. 小程序前端通过Nginx网关,访问小程序服务端;
  2. 小程序服务端会调用一系列的基础服务,完成相应的请求处理,包括门店服务、会员服务、商品服务、订单服务、支付服务等,每个服务都有自己独立的数据库和Redis缓存;
  3. 订单服务接收到新订单后,先在本地数据库落地订单,然后通过MQ同步订单给OMS履单中心;
  4. 门店的收银系统通过HTTP远程访问云端的OMS履单中心,拉取新订单,并返回取餐码给OMS,OMS再调用小程序订单服务同步取餐码;
  5. 小程序前端刷新页面,访问服务端获得取餐码,然后用户可以根据取餐码到门店取餐或等待外卖。

高可用系统改造措施

我在前面也介绍了,这次活动的促销力度很大,高峰期流量将达到平时的数十倍,这就要求系统能够在高并发的场景下,保证高可用性。

所以,基于访问量、日订单量和可用性的指标,我们对原有系统进行了一系列改造,最终顺利地实现了首日500万订单,以及在大促期间,系统4个9的可用性目标。这个500万的订单量,也创造了中国单商户线上交易的历史记录。

在下面的系统架构图中,我标出了具体的改造点,主要有10处,接下来我就给你分别具体介绍一下,你可以通过这些具体的改造措施,来真正理解高可用系统的设计手段。

前端接入改造

这里的前端有两个,C端的小程序和B端的门店收银系统。前端部分主要是对三个点进行改造,包括小程序端的CDN优化、Nginx负载均衡,以及收银端的通信线路备份。

  • 小程序端的CDN优化

用户点餐前,需要先浏览商品和菜单,这个用户请求的频率很高,数据流量大,会对服务端造成很大的压力。所以,针对这一点,我们通过CDN供应商,在全国各地构建了多个CDN中心,储存静态的商品数据,特别是图片,这样小程序前端可以就近访问CDN,流量无需通过小程序服务端,缓解了服务端的压力。

  • Nginx负载均衡

这个小程序点餐平台,之前是直接利用云服务商提供的LB,它只有简单的负载均衡能力。为了能应对这次的高并发流量,现在我们独立搭建了数十台的Nginx集群,集群除了负载均衡,还提供限流支持,如果QPS总数超过了10万,前端的访问请求将会被丢弃掉。

另外,Nginx在这里还有一个好处,就是可以实时提供每个接口的访问频率和网络带宽占用情况,能够起到很好的接入层监控功能。

补充说明:一台Nginx一般可以支持数万的并发,本来这里无需这么多台Nginx,这是因为云服务商对单个LB的接入有网络带宽的限制,所以我们要通过提升Nginx的数量,来保证接入有足够的带宽。

  • 收银端的通信线路备份

门店的收银系统会通过前置代理服务器,来访问云端的OMS系统,这个代理服务器部署在商户自己的IDC机房,原来只通过电信线路和云端机房打通。在这次改造中,我们增加了移动线路,这样当电信主线路出问题时,系统就可以快速地切换到移动线路。

应用和服务的水平扩展

首先,针对小程序服务端的部署,我们把实例数从十几台提升到了100台,水平扩展它的处理能力。在上面的架构图中,你可以看到,小程序服务端依赖了7个基础服务,每个基础服务也做了相应的水平扩展,由于应用和基础服务都是无状态的,因此我们很容易扩充。

这里的基础服务是Java开发的,原来是用虚拟机方式部署的,现在我们把基础服务全部迁移到了容器环境,这样在提升资源利用率的同时,也更好地支持了基础服务的弹性扩容。

订单水平分库

在大促情况下,下单高峰期,订单主库的写访问频率很高,一个订单会对应6~7次的写操作,包括了创建新订单和订单状态变更;订单的读操作,我们之前通过一主多从部署和读写分离,已经得到了支持。

但负责写入的主库只有一个实例,所以这次我们通过订单的水平分库,扩充了订单主库的实例数,改造后,我们有4个主库来负责订单数据写入。数据库的配置,也从原来的8核16G提升到了16核32G,这样我们通过硬件的垂直扩展,进一步提升了数据库的处理能力。

这里的订单水平分库在实现上比较简单,我们是通过订单ID取模进行分库,基于进程内的Sharding-JDBC技术,实现了数据库的自动路由。后面的课程中,我会专门介绍电商平台的订单水平分库,它会更加复杂,到时你可以做个比较,如果有需要的话,也可以在实际项目参考落地。

异步化处理

你可以看到,在前台订单中心和后台OMS之间,我们需要同步订单数据,所以这两者是紧密耦合的。不过这里,我们通过消息系统对它们进行了解耦。 一方面,前台下单要求比较快,后台OMS的订单处理能力比较弱(OMS库没有进行水平分库),通过消息的异步化处理,我们实现了对订单流量的削峰;另一方面,如果OMS有问题,以异步的方式进行数据同步,也不会影响前台用户下单。

还有在小程序服务端,在用户支付完成或者后台生成取餐码后,我们会以微信消息的方式通知用户,这个在代码中,也是通过异步方式实现的,如果微信消息发送不成功,用户还是可以在小程序上看到相关信息,不影响用户取餐。

主动通知,避免轮询

在原来的架构中,前台小程序是通过轮询服务端的方式,来获取取餐码;同样,商户的收银系统也是通过轮询OMS系统拉取新订单,这样的收银系统有上万个,每隔10s就会拉取一次。这种盲目轮询的方式,不但效率低,而且会对服务端造成很大的压力。

经过改造后,我们落地了消息推送中心,收银系统通过Socket方式,和推送中心保持长连接。当OMS系统接收到前台的新订单后,会发送消息到消息推送中心;然后,收银系统就可以实时地获取新订单的消息,再访问OMS系统拉取新订单。为了避免因消息推送中心出问题(比如消息中心挂掉了),导致收银系统拿不到新订单,收银系统还保持对OMS系统的轮询,但频率降低到了1分钟一次。

同理,小程序前端会通过Web Socket方式,和消息推送中心保持长连接。当OMS系统在接收到收银系统的取餐码后,会发送消息到消息推送中心。这样,小程序前端可以及时地获取取餐码信息。

缓存的使用

我们知道,缓存是提升性能十分有效的工具。这里的改造,就有两个地方使用了缓存。

  • 当收银系统向OMS拉取新订单时,OMS不是到数据库里查询新订单,而是把新订单先保存在Redis队列里,OMS通过直接查询Redis,把新订单列表返回给收银系统。
  • 在商品服务中,菜单和商品数据也是放在了Redis中,每天凌晨,我们通过定时任务,模仿前端小程序,遍历访问每个商品数据,实现对缓存的预刷新,进一步保证缓存数据的一致性,也避免了缓存数据的同时失效,导致缓存雪崩。

一体化监控

在前面各个节点可用性优化的基础上,我们也在系统的监控方面做了很多强化。除了常规的Zabbix做系统监控、CAT做应用监控、拉订单曲线做业务监控以外,我们还对系统实现了一体化的监控。

在这里,所有的节点都在一个页面里显示,包括Web应用、Redis、MQ和数据库,页面也会体现节点之间的上下游关系。我们通过采集节点的状态数据,实时监测每个节点的健康程度,并且用红黄绿三种颜色,表示每个节点的健康状况。这样,我们就可以非常直观地识别出,当前的哪些节点有问题。

监控的效果如下图所示,在下一讲中,我就会为你具体地介绍这个监控系统。

在实践中,这套监控系统也确实发挥了巨大的作用。很多时候,在系统问题还没有变得严重之前,我们就能够识别出来,并能进行主动干预。

比如说,小程序服务端的部分节点有时候会假死,这在Zabbix监控里往往看不出来,但在我们的监控页面中,这些节点就会飘红,我们就可以通过重启节点来快速恢复。还有好几次,系统有大面积的节点出问题了,我们通过节点的上下游关系,很容易地定位出了真正出现问题的地方,避免所有人一窝蜂地扑上去排查问题。

除了这里我介绍的优化措施以外,我们也为系统可能出问题的地方做了各种预案。比如说,我们保留了部分虚拟机上部署的基础服务实例,这样如果容器出现了问题,基础服务可以快速切回到虚拟机上的实例。

系统改造小结

到这里为止,系统主要的优化措施就介绍完了,但我们是如何知道要配置多少个节点,有没有达到预定的效果呢?

对于这个问题,我们的做法是,按照10万QPS和99.99%的可用指标要求,通过大量的压测来确定的。

  • 首先,我们对每个节点进行接口压测,做各种性能优化,确定好需要的机器数量;
  • 然后,我们利用JMeter,模拟小程序前端发起混合场景的调用,以此检验系统的抗压能力,以及在压力下,系统的可用性是否达到了预定的要求;
  • 最后,我们在生产环境中根据压测环境,按照服务器1:1的数量进行部署,保证性能不打折,最终这个小程序下单平台总的机器规模,也达到了数百台的量级。

这里,我想结合着上一讲和你介绍的架构原则,来让你更深刻地理解这次系统可用性的改造过程。

从正面保障的角度来看,我们首先在各个环节都避免了单点,包括远程通信线路,这样能保证任意一个节点出了问题,都有其他实例可以顶上去;其次,我们通过节点的垂直扩展和水平扩展,大幅度提升了系统的处理能力,包括应用、服务和数据库的扩展;我们也有效地利用了Redis缓存,对高频的订单和菜单数据的读取进行了优化。

柔性处理方面,我们通过异步处理,来优化系统的性能和避免大流量的直接冲击,包括使用消息系统解耦前台下单系统和后台OMS系统,以及通过及时的消息推送,避免前端盲目轮询服务端。

同时,我们在系统接入层,通过Nginx进行限流,为系统的可用性进行兜底,这样在流量超过预估时,能够有效地避免后端系统被冲垮。

最后,我们通过强有力的监控手段,可以实时全面地了解系统运行状况,随时为异常情况做好准备。

总结

今天,我与你分享了一个实际的O2O点餐平台,在面对高并发流量时,我们是如何对系统进行升级改造,保证系统的高可用的。相信你在上一讲理论的基础上,通过进一步结合实际的场景,能够深入地理解如何运用各种高可用的手段了。

高可用的处理方式有很多,我这里给你介绍的也只是一部分,希望你能够在实践中,结合具体的业务场景,灵活地落地高可用的设计。

不过,无论我们采取多么周密的措施,总会有些地方我们没有考虑到,系统可能会出现各种各样的问题,这个时候对系统进行全面的监控就非常重要了。下一讲我会就如何做好系统的监控,和你做详细的介绍。

最后,给你留一道思考题:你当前的系统有单点吗?这个单点有没有出过问题呢?

欢迎你在留言区与大家分享你的问题和思考,我们一起讨论。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

精选留言

  • J.Smile

    2020-03-20 23:16:03

    这讲最大的价值就是让普通公司的工程师见识到了大流量的应对架构设计,有了一个总体的轮廓和方向。
  • zeor

    2020-03-21 20:36:55

    老师您好 请问下单时怎么保证超卖 请指教具体方案和实现
    作者回复

    办法很多,这里举两个例子:
    悲观锁,
    select for update 提前锁定库存
    乐观锁
    库存记录有个字段标识它的版本,读库存的时候,获取版本信息,比如1。后面更新的时候,检查记录的版本是不是还是1,如果不是,则写失败,如果是,则写成功,同时更新版本为2。

    2020-03-21 22:57:20

  • 正在减肥的胖籽。

    2020-03-20 20:30:49

    下单后,订单放到redis中,如果redis数据写入失败?有做补偿吗?需要请教老师你们redis和数据库之间的数据怎么保证一致性?如果不保持一致性那其他系统是否就拉取不到订单?
    作者回复

    一种是把redis作为前置数据库,如果下单时,缓存写入失败,等于业务失败。当缓存写入后,如果缓存崩溃,这个问题不大,可以通过持久化缓存数据,重启后恢复。

    如果只是把redis当做缓存来用,我比较推荐写db的时候立即更新缓存或删除缓存,保证缓存和db数据的一致性。

    2020-03-21 18:25:30

  • 夜空中最亮的星

    2020-03-20 08:44:12

    这里讲,很棒,都是硬核
  • lyshrine

    2020-03-21 21:12:56

    请问老师,CAT的全程是什么?
    作者回复

    Central Application Tracking,一个开源的应用监控组件,国内用的比较广泛。

    2020-03-21 22:53:37

  • 每天晒白牙

    2020-03-20 09:42:30

    很期待老师说的监控提前预警的方案
    老师我请教个和监控报警相关的问题,就是我们自己通过 prometheus+grafana 搭建了一套简单的业务监控报警,主要通过在代码中埋点,现在报警短信有点多,但里面有些报警也并不是很严重,所以就忽略了,但有时又会错过严重的报警短信,从而影响线上问题,所以后面把报警的阈值提高了,但感觉这样又有风险。
    想听听老师对监控报警这的最佳实战,比如怎么实现监控报警的生命周期管理?怎么通过程序化的方式来分析报警,减少人力消耗等
    作者回复

    监控数据一般有收集,分析和告警的过程,你这里缺乏后端的监控数据的分析系统,需要结合各种规则做综合判断后才告警。

    2020-03-21 18:28:02

  • 旅途

    2020-05-26 19:27:30

    感谢老师的分享!很少见到这个量级web全链路改造的分析,收获很大,但是还是有几个问题不太明白
    1.将应用服务放入docker中,部署的设备有没有水平扩展或者扩容?还是只是用的原来的设备不过是docker部署了更多份?
    2.oms需要消费的消息会变多,oms也扩容了吧?
    3.机器规模已经达到了数百台量级,前端的请求分发,为什么没有先使用lvs进行四层以下的分发,是因为qps还不够大吗?
    作者回复

    1. 我们用的是云主机,供应商提供了K8s+docker环境,我们把服务部署在这个环节就可以,总体上资源利用率会更高。
    2. oms 也部署了更多机器
    3. lvs部署和运维起来比较复杂,别埋技术坑,nginx比较容易上手,效果也不错。

    2020-05-28 22:52:44

  • 探索无止境

    2020-04-13 00:06:28

    老师您好,有两个问题请教!
    第一个是文中提到小程序前端需要跟推送中心保持长连接,那如何有千万的用户,那服务端要保持上千万的长连接,这一块会不会有问题?或者需要做什么优化处理?
    第二个是收银系统如果是一个BS架构的客户端,那是不是就可以跟履单系统部署在同一个云厂商的服务器上,因为是同个云厂商的服务器,是不是就不需要考虑移动和电信的双线路了,不知道理解是否有问题?
    请老师指正!
    作者回复

    很好的思考。
    1. 小程序在完成下单支付后,会和推送中心建立长连接,这时它会有返回取餐码的预期,获取到取餐码后断掉连接,整个连接的时间一般在几秒。
    2. 门店的收银系统通过中心的代理服务器访问云端,这个代理服务器部署在商户自己的IDC机房,这个代理服务器和云厂商的有双线路。POS收银系统不是浏览器界面,是一个桌面应用。

    2020-04-13 11:16:23

  • 阿固

    2020-03-30 16:37:21

    老师好!对Nginx这块的部署有点拿捏不定,我想到两方案:
    方案一
    1.每个有公网IP的服务器上安装Nginx(upstream配置相同)
    2.在DNS域名商指定步骤1中的公网IP指向一个域名

    方案二
    1.内网部署Nginx,使用keepalived做高可用集群
    2.在DNS域名商指一个公网IP指向域名
    老师会从哪些角度考虑,能详细讲一下请求从域名到Nginx集群这一段的架构吗?
    作者回复

    Nginx配置细节,专业的运维会比较清楚,这里我不敢误导你。

    2020-03-30 19:34:40

  • 阿卡牛

    2020-03-30 11:39:49

    对于大型的分布式系统,没有一套好用的监控系统,就等于盲人摸象
  • 刺客

    2020-10-16 15:41:37

    硬核,非常棒,贴近实战,要多读几遍,有机会和老师做同事那将是人生幸事呀
    作者回复

    谢谢夸奖,坐标上海,有兴趣勾兑下,哈哈。

    2020-10-20 13:49:47

  • 小洛

    2020-03-27 16:14:01

    请教老师您几个问题:
    1、OMS履单中心的作用,因为我们公司的业务就是推送给消息给收银,这个消息就放在订单服务里面去做了,所以重新部署个OMS履单中心 对后续的业务架构有什么帮助吗?
    2、因为加了OMS履单中心,同步订单方面 看方案是采用了往MQ里面 写入订单数据 然后再落库吗?如果订单数据量很多几百万 那么OMS是不是要部署很多台机器去消费MQ然后落订单数据,是不是可以直接用订阅binlog 备份数据库就行了?
    作者回复

    是需要很多个消费者,这是业务功能,不是纯粹数据同步,不能订阅binlog,binlog太底层了意味强耦合。
    不是订单中心所有数据变动,oms都会关心,比如本来就是oms反馈取餐码给订单中心,oms自然不关心binlog。

    2020-03-27 22:10:39

  • Joshua

    2020-03-26 15:53:26

    请教几个问题:
    1. nginx和LB那里没看懂,是怎么一种结构关系呢,LB将请求分流到nginx?改造之后有多个LB了,还是改造后没有云服务商的LB了?
    2. 这么多工作,要在大促前提前多久开始进行?
    作者回复

    包括系统改造,压测,,做了有3个月。
    不使用云服务商提供的LB,流量从统一的口子进来后,由Nginx集群接管,再由各个Nginx路由到web应用。

    2020-03-27 22:20:08

  • Skysper

    2020-03-26 08:50:28

    数据库分库后 单库有MHA吗?没有就可能存在单点;另外redis 和mq等缓存和事件保障要怎么处理?
    作者回复

    每个数据库都是集群部署,1主1备2从

    2020-03-26 11:49:26

  • Geek_0e5f26

    2020-03-24 08:28:54

    老师 请教两个问题。问题一:Nginx集群,他们的物理架构是怎么样的,谁来做路由和负载均衡? 问题2:通过提升 Nginx 的数量,来保证接入有足够的带宽。这个带宽量是如何估算的?比如每个用户请求平均大小,再到响应数据平均大小,这个估算过程请您指点一下吧,最好请您分享一下参考值,谢谢老师!
    作者回复

    1. 这里的QPS是小程序前端的混合调用场景,包括首页访问,商品流量,加购物车,下单等,我们会大致分析各个接口的调用比例。
    2. 然后根据接口的请求和响应数据量,算出网络带宽。
    3.最后结合混合场景的压测数据,我们可以比较准确地知道带宽占用,最后算出需要部署的Nginx数量。
    Nginx的部署架构和云服务商的LB有关,我确认好再反馈给你。

    2020-03-24 20:56:07

  • 天天向善

    2020-03-23 20:46:02

    每秒10万,能不能再给一些数字,改造后没有用云lb,自建nginx集群,这个云上也是有vip是吗,这个流量是每台机器设多少固定带宽?还有小程序服务端与基础服务共100个实例,还是仅小程序服务端,基础服务当时用了多少实例,另外一个容器大约cpu与内存什么配置
    作者回复

    小程序服务端本身就100个实例,服务根据情况,实例数量不等。云上对外也是提供统一地址,如果我没记错的话,每台Nginx支持100M流量。

    2020-03-24 20:59:40

  • 川杰

    2020-03-22 14:46:43

    老师好,消息推送中心通过长连接的方式保持,但是每个长连接都有一定的资源消耗;如果上游的请求过多,这个资源消耗过大的问题怎么处理?
    作者回复

    这个只是简单连接,实际数据还是通过服务接口获取,几万个连接问题不大,也可以加机器增强处理能力。

    2020-03-23 11:42:40

  • 每天晒白牙

    2020-03-20 09:35:16

    感谢老师的实战经验分享,很干
  • 刘楠

    2020-03-20 07:57:38

    规模没这么大,只能想想,学习下
  • 深山小书童

    2020-03-20 06:59:38

    非常棒!一早起来读到干货满满的文章,心情美美哒