其实本文没有结论,对优化方案持开发态度。类似秒杀的优化核心是削峰填谷,排队处理。本文只是考虑如何优化DB层面的并发更新的速度。
背景
因为有赞接入快手以及部分商家的热卖商品的交易流程走的是正常的商品交易途径,但是他们具有秒杀的属性。发生热点商品售卖对应的数据库监控如下图:
数据库的qps 瞬间增长4倍,tps 则暴涨30-40倍,活跃会话暴涨导致正常的请求被堵住(都在处理扣减库存以及库存回补的update动作),导致业务请求也排队,超时或者被kill等一系列的雪崩效应。
为了解决该热点问题,我们从数据库的纬度测试提高单行数据更新的方法。
相关知识
MySQL为了提高数据库的数据安全性,设置2个参数来控制数据落盘的策略,我们目前的设置为每次提交事务都会出发写数据到磁盘,带来的好处是数据安全,最极端的情况下会丢失一个事务,其负面影响是会导致大量的IO操作。
另外一点数据库并发执行update同一行的动作会被其他已经持有锁的会话堵住,并且需要要进行判断会不会由于自己的加入导致死锁,这个时间复杂度O(n),如果有1000个请求,每个线程都要检测自己和其他999个线程是否死锁。如果其他线程都没有持有其他锁,约比较50w次(计算方式 999+998+…+1)。这个种锁等待和检查死锁冲突带来巨大的时间成本。
优化思路
基于上面的背景以及MySQL更新数据的相关知识,我们将数据库的刷盘策略从同步改为异步也即批量执行刷盘,提高IO利用效率。关闭死锁检测,减少单行整体服务时间。
相关参数:
sync_binlog=0
innodb_flush_log_at_trx_commit=0
innodb_deadlock_detect=OFF
压测数据
压测场景:
CREATE TABLE seckill
(
id
int(11) DEFAULT NULL,
num
bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into seckill(id,num) values(1,200000000000000);
mysqlslap -uroot -h127.0.0.1 -P 3316 --concurrency=72 --create-schema=‘test’ --query=‘begin;update seckill set num=num-1 where id =1 and num>1;commit;’ --number-of-queries=500000
主要模拟数据库在高并发场景,并发数分别设置为:72,96,144,192,256个活跃会话并发.
从压测结果来看,在高并发场景下比如256并发,tps 为6300左右是未调优的12倍。看到这个结果是不是很开心,但是生产过程中的结果并不令人满意。
风险点
“每一朵漂亮的玫瑰都会有刺”,对于我们这种优化方式,性能提高之后,
OS系统掉电crash的情况下 非双一模式存在数据丢失的风险,之前是每次事务就刷盘,调整之后是刷新binlog交给系统来维护,刷新redo则变为每1000次commit刷一次。
关闭死锁检测,对于无无死锁时无任何负面作用,有死锁时,会有事务等待超时,系统支持最小的超时时间为1s。对于死锁频发的业务场景不适合。
适合我们的解决方式
对于热卖商品提前报备,我们手动处理,活动结束之后我们重新设置为安全模式。
通过我们的zandb系统的快照机制检查数据库实例的thread_running的值,到达一定的阈值比如大于48,我们就设置该参数。待thread_running恢复正常,zandb的会自动设置为安全参数。
后记
生产环境我们自己用全链路压测,其并发update的吞吐量并没有上面测试的那么出色,提高了1.2-2倍的样子,整体库存扣减、热点更新还需要进一步优化。
https://mp.weixin.qq.com/s?__biz=MzIwMzY1OTU1NQ%3D%3D&mid=2247485343&idx=1&sn=aec6027c6c75062a8f621ae4c2648bc6&chksm=96cd47d3a1bacec50490e0039364a88c4632b3336e89be45e585fc36b118dab82bc45faed04f&mpshare=1&scene=23&srcid=0116RVJDHhQOgeq7K9rMdM23%23rd