在「中国数据库技术大会」上,淘宝 share了「秒杀场景下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链接数据是有限的,这样请求就不会一下拥入数据库。spa
请求合并
orm
把相似的请求合并起来是一件既简单又复杂的事情,介于本文的标题是笨法玩秒杀,咱们就挑简单的说,当咱们经过Gearman的Work去处理队列里的请求时,一般是弹出一个请求处理一个请求,下面咱们作出一些调整,每次再也不只从队列里弹出一个请求,取而代之,咱们一次性从队列里取出多个请求,而后在程序里完成合并后再执行。固然这里有不少细节问题,因为篇幅关系就很少说了。server