秒杀场景下MySQL的低效原因和改进

在「中国数据库技术大会」上,淘宝分享了「秒杀场景下MySQL的低效」,详细分析了秒杀的技术难点及改进措施,简而言之,主要就是在高并发事务请求的情况下,数据库性能由于死锁检测等因素直线下降,在这种场景下,单纯的关闭死锁检测虽然可以提升一定的性能,但这顶多是治标而已,如何治本?


淘宝给出来两个改进方法

请求排队:如果请求一股脑的涌入数据库,势必会由于争抢资源造成性能下降,通过排队,让请求从混沌到有序,从而避免数据库在协调大量请求时过载。
请求合并:甲买了一个商品,乙也买了同一个商品,与其把甲乙当做当做单独的请求分别执行一次商品库存减一的操作,不如把他们合并后统一执行一次商品库存减二的操作,请求合并的越多,效率提升的就越大。


可惜的是淘宝的这些改进方法都是通过修改MySQL源代码在数据层实现的,对芸芸众生的我们而言,简直是一个无法逾越的技术门槛!那么是否可以在应用层实现呢?


请求排队
通过Redis实现队列是一件很简单的事情,使用LIST或者ZSET就可以搞定,如果没有优先级之类需求的话,通常LIST是一个更好的选择,因为它的时间复杂度更低,当然,如果处理队列的速度足够快,那么ZSET也不错。

把请求保存到队列里之后,可以通过Gearman实现Worker来消费队列,请求的生产和消费是异步的,所以不会出现并发拥堵,但是可能发生延迟,如果出现这种情况,可以通过增加Worker的数量可以加快消费队列的速度。

让我们从头捋捋:程序收到请求,然后把请求保存到Redis队列里,Gearman通过Worker处理队列里的请求,可是处理完之后如何通知程序呢?因为整个过程是异步的,所以除非程序支持某种形式的回调,否则很难通知。

最容易想到的解决办法是在程序里通过轮询来查询请求是否已经处理完成,但这无疑会增加数据库的负载,同时程序的实时性也会大打折扣。好在我们有其它的方法,比如说Redis提供了名为BLPOP和BRPOP的方法,它尝试从一个LIST里取元素,如果LIST为空则会堵塞连接,利用这个特性我们可以实现一个简易的通知功能:程序把请求保存到Redis队列里,然后调用BLPOP或BRPOP方法等通知,因为此时LIST为空,所以会堵塞连接,与此同时Gearman的Work处理完队列里的请求后,往LIST里保存一个状态码,程序感知到这个状态码,并通过状态码判断出请求是成功还是失败。

整个过程中有一些需要注意的地方,比如说因为BLPOP和BRPOP都属于堵塞性质的操作,所以一旦队列处理速度跟不上,程序就会堆积大量连接,这可能会引起很多连锁问题:一方面可能导致内存不足,以PHP为例,一个连接通常占用10M左右,堆积一千个连接的话,10G内存就没有了;另一方面大量的连接可能耗尽端口资源,具体取决于内核参数「net.ipv4.ip_local_port_range」。此时提高处理队列的速度是唯一的出路。


其实Gearman的Jobserver本身就实现了一个队列,而且还可以将这个队列用MYSQL来代替来持久化,保证队列请求不会丢失,客户端的请求先到Jobserver队列,然后Worker是真正的连接数据的程序,Jobserver根据Worker的闲忙将队列里的任务指派给Worker去处理,Worker连接数据是有限的,这样请求就不会一下拥入数据库。


请求合并

把类似的请求合并起来是一件既简单又复杂的事情,介于本文的标题是笨法玩秒杀,我们就挑简单的说,当我们通过Gearman的Work去处理队列里的请求时,通常是弹出一个请求处理一个请求,下面我们做出一些调整,每次不再只从队列里弹出一个请求,取而代之,我们一次性从队列里取出多个请求,然后在程序里完成合并后再执行。当然这里有很多细节问题,由于篇幅关系就不多说了。


  • 1
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页
评论

打赏作者

笑天居士

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值