如何设计一个小而美的秒杀系统?

现现在,春节抢红包的活动已经逐渐变成你们过年的新风俗。亲朋好友的相互馈赠,微信、微博、支付宝等各大平台种类繁多的红包让你们收到手软。鸡年春节,公司的老总们也想给 15 万的全国员工发福利,因而咱们构建了一套旨在支撑 10 万每秒请求峰值的抢红包系统。经实践证实,春节期间咱们成功的为全部的小伙伴提供了高可靠的服务,红包总发放量近百万,抢红包的峰值流量达到 3 万/秒,最快的一轮抢红包活动 3 秒钟全部红包所有抢完,系统运行零故障。html

红包系统面临的挑战

红包系统,相似于电商平台的秒杀系统,本质上都是在一个很短的时间内面对巨大的请求流量,将有限的库存商品分发出去,并完成交易操做。好比 12306 抢票,库存的火车票是有限的,但瞬时的流量很是大,且都是在请求相同的资源,这里面数据库的并发读写冲突以及资源的锁请求冲突很是严重。如今,咱们将分析实现这样一个红包系统,须要面临以下的一些挑战:git

首先,到活动整点时刻,咱们有 15 万员工同时涌入系统抢某轮红包,瞬间的流量是很大的,而目前咱们整个链路上的系统和服务基础设施,都没有承受过如此高的吞吐量,要在短期内实现业务需求,在技术上的风险较大。github

其次,公司是第一次开展这样的活动,咱们很难预知你们参与活动的状况,极端状况下可能会出现某轮红包没抢完,须要合并到下轮接着发放。这就要求系统有一个动态的红包发放策略和预算控制,其中涉及到的动态计算会是个较大的问题(这也是为系统高吞吐服务),实际的系统实现中咱们采用了一些预处理机制。web

最后,这个系统是为了春节的庆祝活动而研发的定制系统,且只上线运行一次,这意味着咱们没法积累经验去对服务作持续的优化。而且相关的配套环境没有通过实际运行检验,缺乏参考指标,系统的薄弱环节发现的难度大。因此必需要追求设计至简,尽可能减小对环境的依赖(数据路径越长,出问题的环节越多),而且实现高可伸缩性,须要尽一切努力保证可靠性,即便有某环节失误,系统依然可以保障核心的用户体验正常。算法

能负责有技术挑战的项目,对于工程师来讲老是压力和兴趣并存的。接手项目后一个月的时间内咱们完成了技术调研,原型设计研发,线上运维等工做。关于这个过程当中的细节,下面为读者一一道来。数据库

系统设计

系统架构图如图 1 所示。整个系统的主干采用主流的 Web 后台设计结构,子系统各自部署为集群模式而且独立。APP 客户端与网关接入层(负责用户鉴权、流量负载均衡、整合数据缓存等)进行交互,再日后是核心的逻辑系统(用户资格校验、红包分发、数据异步持久化、异步财务到帐、降级等),数据持久化采用的 MySQL 集群。除此以外还有静态资源的管理(红包页面图片、视频等资源的访问优化)以及配套的服务总体运行监控。全部的静态资源提早部署在了第三方的 CDN 服务上。为了保障总体系统可靠性,咱们作了包括数据预处理、水平分库、多级缓存、精简 RPC 调用、过载保护等多项设计优化,而且在原生容器、MySQL 等服务基础设施上针对特殊的业务场景作了优化。后端

图 1. 系统架构

红包自己的信息经过预处理资源接口获取。运行中用户和红包的映射关系动态生成。底层使用内部开发的 DB 中间件在 MySQL 数据库集群上作红包发放结果持久化,以供异步支付红包金额到用户帐户使用。整个系统的绝大部分模块都有性能和保活监控。缓存

优化方案

优化方案中最重要的目标是保障关键流程在应对大量请求时能稳定运行,作到这一点,须要很高的系统可用性。所以,业务流程和数据流程要尽可能精简,减小容易出错的环节。此外,cache、DB、网络、容器环境,任何一个部分都有可能会出现短时故障,咱们须要提早作处理预案。针对以上的目标难点,咱们总结了以下的实践经验。服务器

数据预处理

咱们结合活动预案要求,将红包自己的属性信息(金额,状态,祝福语,发放策略),使用必定的算法提早生成好全部的信息,这些数据所占空间不是很大。为了最大化提高性能,咱们事先将这些红包数据,咱们事先存储在数据库中,而后在容器加载服务启动时,直接加载到本地缓存中看成只读数据。另外,咱们将员工信息也作了必定的裁剪,最基本的信息也和红包数据同样,预先生成,服务启动时加载。微信

此外,咱们的活动页面,有不少视频和图片资源,若是这么多的用户从网关实时访问,带宽极可能直接就被这些大流量的请求占满了,用户体验可想而知。最后这些静态资源,咱们都部署在了 CDN 上,经过数据预热的方式加速客户端的访问速度,网关的流量主要是来自于抢红包期间的小数据请求。

精简 RPC 调用

服务请求流程一般是在接入层访问用户中心进行用户鉴权,而后转发请求到后端服务,后端服务根据业务逻辑调用其余上游服务,而且查询数据库资源,再更新服务/数据库的数据。每一次 RPC 调用都会有额外的开销,因此,好比上面一点所说的预加载,使得每一个节点在系统运行期间都有全量的查询数据可在本地访问。抢红包的核心流程就被简化为了生成红包和人的映射关系,以及发放红包的后续操做。再好比,咱们采用了异步拉的方式进行红包发放到帐,用户抢红包的请求再也不通过发放这一步,只记录关系,性能获得进一步提高。 如图 2 所示。

图 2. 服务依赖精简示意图

实际上有些作法的可伸缩性是极强的。例如红包数据的预生成信息,在当时的场景下是可以做为本地内存缓存加速访问的。当红包数据量很大的时候,在每一个服务节点上使用本地数据库、本地数据文件,甚至是本地 Redis/MC 缓存服务,都是能够保证空间足够的,而且还有额外的好处,越少的 RPC,服务抖动越少,咱们只须要关注系统自己的健壮性便可,不须要考虑外部系统 QoS。

抢红包的并发请求处理

春节整点时刻,同一个红包会被成千上万的人同时请求,如何控制并发请求,确保红包会且仅会被一个用户抢到?

  • 作法一:使用加锁操做先占有锁资源,再占有红包。

可使用分布式全局锁的方式(各类分布式锁组件或者数据库锁),先申请 lock 该红包资源且成功后再作后续操做。优势是不会出现脏数据问题,某一个时刻只有一个应用线程持有 lock,红包只会被至多一个用户抢到,数据一致性有保障。缺点是,全部请求同一时刻都在抢红包 A,下一个时刻又都在抢红包 B,而且只有一个抢成功,其余都失败,效率很低。

  • 作法二:单独开发请求排队调度模块。

排队模块接收用户的抢红包请求,以 FIFO 模式保存下来,调度模块负责 FIFO 队列的动态调度,一旦有空闲资源,便从队列头部把用户的访问请求取出后交给真正提供服务的模块处理。优势是,具备中心节点的统一资源管理,对系统的可控性强,可深度定制。缺点是,全部请求流量都会有中心节点参与,效率必然会比分布式无中心系统低,而且,中心节点也很容易成为整个系统的性能瓶颈。

  • 作法三:巧用 Redis 特性,使其成为分布式序号生成器(咱们最终采用的作法)。

前文已经提到,红包系统所使用的红包数据都是预先生成好的,咱们使用数字 ID 来标识,这个 ID 是全局惟一的,全部围绕红包的操做都使用这个 ID 做为数据的关联项。在实际的请求流量过来时,咱们采用了"分组"处理流量的方式,以下图 3 所示。

访问请求被负载均衡器分发到每一个 Service Cluster 的分组 Bucket,一个分组 Bucket 包含若干台应用容器、独立的数据库和 Redis 节点。Redis 节点内存储的是这个分组能够分发的红包 ID 号段,利用 Redis 特性实现红包分发,各服务节点经过 Redis 原语获取当前 拆到的红包。这种作法的思路是,Redis 自己是单进程工做模型,来自分布式系统各个节点的操做请求自然的被 Redis Server 作了一个同步队列,只要每一个请求执行的足够快,这个队列就不会引发阻塞及请求超时。而本例中咱们使用了 DECR 原语,性能上是能够知足需求的。Redis 在这里至关因而充当一个分布式序号发生器的功能,分发红包 ID。

此外,落地数据都持久化在独立的数据库中,至关因而作了水平分库。某个分组内处理的请求,只会访问分组内部的 Redis 和数据库,和其余分组隔离开。

整个处理流程核心的思想是,分组的方式使得整个系统实现了高内聚,低耦合的原则,能将数据流量分而治之,提高了系统的可伸缩性,当面临更大流量的需求时,经过线性扩容的方法,便可应对。而且当单个节点出现故障时,影响面可以控制在单个分组内部,系统也就具备了较好的隔离性。

图 3. 系统部署逻辑视图

系统容量评估,借助数据优化,过载保护

因为是首次开展活动,咱们缺少实际的运营数据,一切都是摸着石头过河。因此从项目伊始,咱们便强调对系统各个层次的预估,既包括了活动参与人数、每一个 APP 界面上的功能点潜在的高峰流量值、后端请求的峰值、缓存系统请求峰值和数据库读写请求峰值等,还包括了整个业务流程和服务基础设施中潜在的薄弱环节。后者的难度更大由于很难量化。此前咱们连超大流量的全链路性能压测工具都较缺少,因此仍是有不少实践的困难的。

在这里心里真诚的感谢开源社区的力量,在咱们制定完系统的性能指标参考值后,借助如 wrk 等优秀的开源工具,咱们在有限的资源里实现了对整个系统的端到端全链路压测。实测中,咱们的核心接口在单个容器上能够达到 20,000 以上的 QPS,整个服务集群在 110,000 以上的 QPS 压力下依然能稳定工做。

正是一次次的全链路压测参考指标,帮助咱们了解了性能的基准,并以此作了代码设计层面、容器层面、JVM 层面、MySQL 数据库层面、缓存集群层面的种种优化,极大的提高了系统的可用性。具体作法限于篇幅不在此赘述,有兴趣的读者欢迎交流。

此外,为了确保线上有超预估流量时系统稳定,咱们作了过载保护。超过性能上限阈值的流量,系统会快速返回特定的页面结果,将此部分流量清理掉,保障已经接受的有效流量能够正常处理。

完善监控

系统在线上运行过程当中,咱们须要对运行状况实时获取信息,以便可以对出现的问题进行排查定位,及时采起措施。因此咱们必须有一套有效的监控系统,可以帮咱们观测到关键的指标。在实际的操做层面,咱们主要关注了以下指标:

  • 服务接口的性能指标

借助系统的请求日志,观测服务接口的 QPS,接口的实时响应总时间。同时经过 HTTP 的状态码观测服务的语义层面的可用性。

  • 系统健康度

结合总的性能指标以及各个模块应用层的性能日志,包括模块接口返回耗时,和应用层日志的逻辑错误日志等,判断系统的健康度。

  • 总体的网络情况

尽可能观测每一个点到点之间的网络状态,包括应用服务器的网卡流量、Redis 节点、数据库节点的流量,以及入口带宽的占用状况。若是某条线路出现太高流量,即可及时采起扩容等措施缓解。

  • 服务基础设施

应用服务器的 CPU、Memory、磁盘 IO 情况,缓存节点和数据库的相应的数据,以及他们的链接数、链接时间、资源消耗检测数据,及时的去发现资源不足的预警信息。

对于关键的数据指标,在超过预估时制定的阈值时,还须要监控系统可以实时的经过手机和邮件实时通知的方式让相关人员知道。另外,咱们在系统中还作了若干逻辑开关,当某些资源出现问题而且自动降级和过载保护模块失去效果时,咱们能够根据情况直接人工介入,在服务不停机的前提下,手动触发逻辑开关改变系统逻辑,达到快速响应故障,让服务尽快恢复稳定的目的。

服务降级

当服务器压力剧增的时候,若是某些依赖的服务设施或者基础组件超出了工做负荷能力,发生了故障,这时候极其须要根据当前的业务运行状况对系统服务进行有策略的降级运行措施,使得核心的业务流程可以顺利进行,而且减轻服务器资源的压力,最好在压力减少后还能自动恢复升级到原工做机制。

咱们在开发红包系统时,考虑到原有 IDC 机房的解决方案对于弹性扩容和流量带宽支持不太完美,选择了使用 AWS 的公有云做为服务基础环境。对于第三方的服务,缺乏实践经验的把握,因而从开发到运维过程当中,咱们都保持了一种防护式的思考方式,包括数据库、缓存节点故障,以及应用服务环境的崩溃、网络抖动,咱们都认为随时可能出问题,都须要对应的自动替换降级策略,严重时甚至可手动触发配置开关修改策略。固然,若是组件自身具备降级功能,能够给上层业务节约不少成本资源,要本身实现所有环节的降级能力的确是一件比较耗费资源的事情,这也是一个公司技术慢慢积累的过程。

结束语

以上就是咱们整个系统研发运维的一些经验分享。对于这类瞬时大流量的秒杀系统而言,高可用是最大的优化目标,分而治之是核心的架构思想;防护式思惟,假定任何环节均可能有弱点,可以提高系统的稳定性。另外,必定要增强监控,及时发现问题,解决问题。作好了如上几点,相信各位读者在应对相似问题时,也能更加全面的思考,少走一些弯路,作出更加优秀的系统。

此次春节红包活动,在资源有限的状况下成功抵抗超乎日常的流量峰值压力,对于技术而言是一次很大的挑战,也是一件快乐的事情,让咱们从中积累了不少实践经验。将来咱们将不断努力,但愿可以将部分转化成较为通用的技术,沉淀为基础架构组件,去更好的推进业务成功。真诚但愿本文的分享可以对你们的技术工做有所帮助。

参考资源

原文连接:https://www.ibm.com/developerworks/cn/web/wa-design-small-and-good-kill-system/index.html

相关文章
相关标签/搜索