• 秒杀应用的MySQL数据库优化


    其实秒杀应用的数据库层优化非常简单,各个层面做好排队即可,MySQL企业版提供了线程池插件,但是需要额外的费用。

    小伙伴们可以使用开源的MySQL版本InnoSQL,其免费提供了线程池,可以保证应用在大并发量下依旧保证应用的稳定性,特别是对于秒杀类的应用。

    关于秒杀

    随着双11活动的不断发展,小米饥饿营销模式的兴起,“秒杀”已经成为一个热点词汇。在一些活动中,热销商品会以惊人的速度售罄,比如最近笔者在抢购美图M4手机,12点开卖,1分钟之内就被售罄。

    秒杀的实现

    对于关注数据库的笔者来说,更关心的是如何高效的实现秒杀应用。之前淘宝在2013年的数据库大会上分享过他们的秒杀方案,修改MySQL数据库源码来实现高效的秒杀应用。但是,那篇分享过于高大上,没有给出具体的实现过程。另外,从其他渠道打听到的是这个方案并没有在生产环境上线,不知道有没有其他知道内幕的小伙伴,具体来说说淘宝的方案是否有上线。

    当然,有多种方法来优化秒杀应用,比如使用memcached的CAS功能,但是这些方法都不能实现事务的特性。对于深受Jim Gray事务处理教育长大的一代,笔者觉得任何事情都应该事务的,不支持事务只不过能取得暂时的胜利,整个世界的哲学应该就是事务,即要么全做,要么全不做,不要处于一个中间状态。笔者的为人哲学就是,要么不去设定一个目标,否则这个目标一定会去实现。比如,笔者决定去读博,那么一定会完成这个学业。

    笔者感觉虽然淘宝没有给出具体的实现方式,但是抛出了秒杀应用对于数据库压力的问题所在,即大并发量下更新同一行数据的压力。例如并发执行如下的SQL语句模拟秒杀场景:

    1. BEGIN; 
    2.  
    3. INSERT INTO stock_log VALUES 
    4.  
    5. SELECT count FROM stock WHERE id=1 AND count>0 FOR UPDATE; 
    6.  
    7. UPDATE stock SET count = count -1 WHERE id=1 AND count > 0; 
    8.  
    9. COMMIT; 

    在做秒杀时,最主要是对库存表进行操作,在操作前可能需要插入一些其他操作,比如日志等,然后就是对库存表进行更新。下图显示增大并发量的情况下,事务处理的性能:

    InsideMySQL

    显而易见的是随着并发量的增大,事务处理的性能越差。这和淘宝之前分享的数据基本一致。导致其中的原因就是秒杀是对同一件商品进行更新,需要对同一行记录加锁,因此秒杀操作虽然是并行的,但是在数据库层面是串行的。

    随着并发的不断增大,不断发生事务的锁等待与唤醒操作,导致性能的急剧下降。如果通过perf工具来观察的话,应该可以观察到类似如下的内容:

    1.  
    2. 59.06% mysqld mysqld [.] lock_deadlock_recursive 
    3.  
    4. 16.63% mysqld libc-2.13.so [.] 0x115171 3.09% mysqld mysqld [.] lock_rec_get_prev 
    5.  
    6. 2.96% mysqld mysqld [.] my_strnncollsp_utf8 
    7.  
    8. ...... 

    可以发现锁的死锁检测占据了大部分的CPU时间,究其原因,就是因为锁等待。

    innodb_thread_concurrency

    有小伙伴或许会知道可以通过innodb_thread_concurrency参数来控制InnoDB存储引擎层的并发量。的确,通过这个参数可以限制进入InnoDB引擎层的事务数量,对比测试的话,性能上的确会有一定的提升:

    可以发现,将innodb_thread_concurrency设置为16,性能的确会有一定的提升。并发线程数在128的时候,TPS从原有的4300提升为了7200,将近有65%的性能提升。但是在256线程之后,性能依旧堪忧。

    导致上述的原因是虽然在InnoDB存储引擎层做了“限流”,但是MySQL数据库上层的线程依然需要等待唤醒。

    线程池技术

    业界提供了很多关于秒杀MySQL的解决方案,然而非常的定制化,并且需要应用修改相信的程序,比如通过在SQL语句中写hint来进行排队,而这种的排队机制在我看来在低并发量下性能反而又会变差。因此,一个通用的解决方案是采用线程池技术。

    线程池可以在MySQL上层限制住同时运行的MySQL的事务数,这样就解决了由秒杀而导致的资源竞争问题。例如,通过前面的测试,已经得知并发16线程时,秒杀可以有最好的性能,那么这时用户将线程池的大小设置为16,这样就能获得用户预期想要的性能:

    可以发现即使在4096个并发线程下,秒杀依然可以有近10000的TPS。通过线程池技术,秒杀就是这么简单,无需任何应用端的修改。

    但是线程池这里有个参数thread_pool_oversubscribe,这个参数其实有点类似云计算中“超售”概念,即MySQL的线程池允许有额外的线程运行。该参数默认是3,之前thread_pool_size设置为16,那么总共允许16*(1+3)=64个线程同时运行。这个参数的默认值本身没有问题,但是对于秒杀应用来说确是不需要的,因为之前已经讨论过,秒杀应用是串行的。所以将参数thread_pool_oversubscribe设置为1,秒杀应用还能有进一步的提升:

    可以发现在大并发的线程下,性能还能有10%~30%的提升。

    总结

    其实秒杀应用的数据库层优化非常简单,各个层面做好排队即可,如:

    • 应用层做好对于单个商品抢购的数量限制
    • MySQL数据库层使用线程池技术来保证大并发量下的性能
    • 调整参数thread_pool_oversubscribe用来进一步提升性能

    MySQL企业版提供了线程池插件,但是需要额外的费用。小伙伴们可以使用开源的MySQL版本InnoSQL,其免费提供了线程池,可以保证应用在大并发量下依旧保证应用的稳定性,特别是对于秒杀类的应用。

    原文

  • 相关阅读:
    回文自动机(PAM) 学习笔记
    UOJ#103. 【APIO2014】Palindromes PAM模板题
    UOJ#348. 【WC2018】州区划分
    STL 抄袭笔记
    ZJOI2019Day1 游记
    UOJ#370. 【UR #17】滑稽树上滑稽果 动态规划
    UOJ#24. 【IOI2014】Rail 交互题
    UOJ#373. 【ZJOI2018】线图 搜索,树哈希,动态规划
    UOJ#75. 【UR #6】智商锁 随机化算法 矩阵树定理
    AtCoder Grand Contest 031 (AGC031) D
  • 原文地址:https://www.cnblogs.com/yccmelody/p/5648227.html
Copyright © 2020-2023  润新知