所有的平台或系统建设和维护中,高并发场景都存在,解决方案也是各种样式,本次将从初中、高二个场景给出设计方案。
本文内容:高并发场景定义,高并发初中级场景与解决方案,高并发高级场景与解决方案
第一部分:高并发场景定义
高并发场景是生产端和消费端共同作用,在短时间内产生大量的并发和大量的消费能力下,保障系统平稳,安全,数据准确。
常见场景:商品秒杀,数据采集与同步,抢红包等
第二部分:高并发初中级场景与解决方案
商品秒杀,数据采集与同步属于较低层次的高并发场景,采用的设计,一般用无锁化(异步),乐观锁(版本号)解决,也无需分布式应用分片。
例如4年前设计和实现的案例(方案设计2):10万+QPS秒杀限流组件设计与实现_luozhonghua2000的博客-CSDN博客_10w qps
方案设计1:使用内存操作替代实时的 DB 事务操作。
这个方案的优点是用内存操作替代磁盘操作,提高了并发性能。
但是缺点也很明显,在内存操作成功但 DB 持久化失败,或者内存 Cache 故障的情况下,DB 持久化会丢数据。
方案设计2:使用乐观锁替代悲观锁。
所谓悲观锁,是关系数据库管理系统里的一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。对应于上文分析中的“并发请求抢锁”行为。
所谓乐观锁,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
商品“秒杀”系统中,乐观锁的具体应用方法,是在 DB 的“库存”记录中维护一个版本号。在更新“库存”的操作进行前,先去 DB 获取当前版本号。在更新库存的事务提交时,检查该版本号是否已被其他事务修改。如果版本没被修改,则提交事务,且版本号加 1;如果版本号已经被其他事务修改,则回滚事务,并给上层报错。
这个方案解决了“并发请求抢锁”的问题,可以提高 DB 的并发处理能力
第三部分:高并发高级场景与解决方案
初中级场景足够应付一般的高并发问题,但是发生在金融领域或应急管理领域,要求数据保证acid,这些方案不可接受的。
比如抢红包场景,会出现:
- 如果拆红包采用乐观锁,那么在并发抢到相同版本号的拆红包请求中,只有一个能拆红包成功,其他的请求将事务回滚并返回失败,给用户报错,用户体验完全不可接受。
- 如果采用乐观锁,将会导致第一时间同时拆红包的用户有一部分直接返回失败,反而那些“手慢”的用户,有可能因为并发减小后拆红包成功,这会带来用户体验上的负面影响。
- 如果采用乐观锁的方式,会带来大数量的无效更新请求、事务回滚,给 DB 造成不必要的额外压力。
方案设计-分而治之,弹性架构
1. 系统垂直 SET 化,分而治之
红包用户发一个红包时,红包系统生成一个 ID 作为这个红包的唯一标识。接下来这个红包的所有发红包、抢红包、拆红包、查询红包详情等操作,都根据这个 ID 关联。
红包系统根据这个红包 ID,按一定的规则(如按 ID 尾号取模等),垂直上下切分。切分后,一个垂直链条上的逻辑 Server 服务器、DB 统称为一个 SET。
各个 SET 之间相互独立,互相解耦。并且同一个红包 ID 的所有请求,包括发红包、抢红包、拆红包、查详情详情等,垂直 stick 到同一个 SET 内处理,高度内聚。通过这样的方式,系统将所有红包请求这个巨大的洪流分散为多股小流,互不影响,分而治之,如下图所示。
这个方案解决了同时存在海量事务级操作的问题,将海量化为小量。
2. 逻辑 Server 层将请求排队,解决 DB 并发问题。
红包系统是资金交易系统,DB 操作的事务性无法避免,所以会存在“并发抢锁”问题。但是如果到达 DB 的事务操作(也即拆红包行为)不是并发的,而是串行的,就不会存在“并发抢锁”的问题了。
按这个思路,为了使拆红包的事务操作串行地进入 DB,只需要将请求在 Server 层以 FIFO(先进先出)的方式排队,就可以达到这个效果。从而问题就集中到 Server 的 FIFO 队列设计上。
红包系统设计了分布式的、轻巧的、灵活的 FIFO 队列方案。其具体实现如下:
首先,将同一个红包 ID 的所有请求 stick 到同一台 Server。
上面 SET 化方案已经介绍,同个红包 ID 的所有请求,按红包 ID stick 到同个 SET 中。不过在同个 SET 中,会存在多台 Server 服务器同时连接同一台 DB(基于容灾、性能考虑,需要多台 Server 互备、均衡压力)。
为了使同一个红包 ID 的所有请求,stick 到同一台 Server 服务器上,在 SET 化的设计之外,红包系统添加了一层基于红包 ID hash 值的分流,如下图所示。
其次,设计单机请求排队方案。
将 stick 到同一台 Server 上的所有请求在被接收进程接收后,按红包 ID 进行排队。然后串行地进入 worker 进程(执行业务逻辑)进行处理,从而达到排队的效果,如下图所示。
最后,增加 memcached 控制并发。
为了防止 Server 中的请求队列过载导致队列被降级,从而所有请求拥进 DB,系统增加了与 Server 服务器同机部署的 memcached,用于控制拆同一个红包的请求并发数。
具体来说,利用 memcached 的 CAS 原子累增操作,控制同时进入 DB 执行拆红包事务的请求数,超过预先设定数值则直接拒绝服务。用于 DB 负载升高时的降级体验。
通过以上三个措施,系统有效地控制了 DB 的“并发抢锁”情况。
3. 双维度库表设计,保障系统性能稳定
红包系统的分库表规则,初期是根据红包 ID 的 hash 值分为多库多表。随着红包数据量逐渐增大,单表数据量也逐渐增加。而 DB 的性能与单表数据量有一定相关性。当单表数据量达到一定程度时,DB 性能会有大幅度下降,影响系统性能稳定性。采用冷热分离,将历史冷数据与当前热数据分开存储,可以解决这个问题。
处理微信红包数据的冷热分离时,系统在以红包 ID 维度分库表的基础上,增加了以循环天分表的维度,形成了双维度分库表的特色。
具体来说,就是分库表规则像 db_xx.t_y_dd 设计,其中,xx/y 是红包 ID 的 hash 值后三位,dd 的取值范围在 01~31,代表一个月天数最多 31 天。
通过这种双维度分库表方式,解决了 DB 单表数据量膨胀导致性能下降的问题,保障了系统性能的稳定性。同时,在热冷分离的问题上,又使得数据搬迁变得简单而优雅。
综上所述,红包系统在解决高并发问题上的设计,主要采用了 SET 化分治、请求排队、双维度分库表等方案,使得单组 DB 的并发性能提升了 8 倍左右,取得了很好的效果。
版权归原作者 luozhonghua2000 所有, 如有侵权,请联系我们删除。