高并发下的事务
秒杀业务场景具有典型的“事务”特性
秒杀红包类需求常见
Demo:https://github.com/sunzeying/seckill
视频来源:https://www.imooc.com/video/11830
秒杀事务相关:(减库存,记录购买明细) -> 事务处理 -> 数据落地
MySQL实现秒杀难点分析:
难点问题:竞争 (多个用户同时秒杀这个商品。就产生了竞争)
秒杀“竞争” : 事务+行级锁
事务工作机制:
Start Transaction (开启事务)
Update 库存数据 (竞争出现在Update)
insert购买明细
Commit
在Update过程中使用行级锁(一个用户的业务未提交,其余均是等待):
秒杀功能:1.秒杀接口暴露;2.执行秒杀;3.相关查询。
Spring 声明式事务
事务的概念:事务是一个不可分割的工作逻辑单元,在数据库系统上执行并发操作时做为最小的控制单元来使用。所包含的所有数据库操作命令必须作为一个整体一起向系提交或撤消,即这一组数据库操作命令要么都执行,要么都不执行,其目的就是为了保证数据的完整性。所以,大概可以猜出事务的应用场合了,就是在干一件事儿的时候执行了多条操作的时候最好加上事务,比如说表的级联操作等,这些操作要么一起提交,要么一起回滚回来回复到起始状态。
使用注解控制事务方法的优点:
1、开发团队达成一致约定,明确标注事务方法的编程风格。
ps:使用aop管理事务会造成可能遗忘需要使用什么方法命名等问题
2、保证事务方法的执行时间尽可能短,不要穿插其他网络操作rpc/http等或者剥离到事务外部。
ps:因为这些操作一次要几毫秒到几十毫秒,影响事务速度。
3、不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制。
ps:如果在配置文件里配置永久
秒杀系统高并发优化分析
高并发容易发生的地方:
1.前端详情页面优化 ----> cdn: 内容分发网络。加速用户获取资源
2.系统时间优化 ---->java访问一次内存10ns,1秒等于1亿纳秒。=> 获取系统时间不用优化
3.地址暴露接口优化 ---->1.CDN只能缓存url固定的资源,秒杀地址是变化的,无法用CDN进行缓存;不适合放入CDN,2.可以放入Redis服务端缓存等。3.一致性维护成本低
4.执行秒杀操作优化 ---->1.无法使用cdn缓存 2.后端缓存困难: 库存问题 3.一行数据竞争:热点商品
常用高并发方案分析:
优势:能抗很大的高并发
缺点:1.运维成本和稳定性:NoSql,MQ等;2.开发成本:数据一致性,回滚方案等
3.幂等性难保证;重复秒杀问题 4.不适合新手的架构
为什么不用Mysql来解决?Mysql真的低效?
(1)一条update语句可以抗4W (qps),这其实是一个很高的并发
(2)单条update很快。加上事务后。一个线程在执行一个事务时,其他的需要等待。
(3)瓶颈分析:不是mysql慢,也不是java 慢,而是java客户端 执行这些sql,等待这些sql的结果,再去执行sql。这一系列的事务可能会遇到网络延迟(0.5-2ms),GC(一般的GC在50ms左右),这些时间也要加在这些事务的执行周期里面。而同一行的事务,是执行串行化的。加上这些时间,加入一个事务执行需要2ms。那么qps为500,1s可以执行500次数据库的操作,对于一般系统来说是满足的,但对于秒杀系统或热点系统是不能满足要求的,特别排队等待时间长的时候,优势会逐渐下降。
所以,这就是高并发情况下不是mysql的原因。
如何判断Update更新库存成功?
两个条件:1.update自身没报错 2.客户端确认update影响记录条数
优化思路:把客户端逻辑放在Mysql服务端,避免网络延迟和GC影响
如何放到Mysql服务端?
两种解决方案:
1.定制Sql方案:update /+[auto_commit]/,需要修改mysql源码
2.使用存储过程:整个事务在Mysql端完成。
秒杀的实际优化:
1.redis 后端缓存优化:超时的基础上维护一致性
google的protostuff技术 对对象进行序列化比JVM对对象进行序列化效率要高很多。
在高并发情况下使用protostuff技术:
get -> byte[] -> 反序列化 -> object[Seckill]
redis不关心 是 JAVA Obj 还是 PHP Obj。 都用 二进制数组存储对象。具体序列化交给语言
2.并发优化是通过事务执行的
优化第一步:简单优化:mysql执行顺序换下
为什么要把mysql的执行顺序换一下呢?
目的就是降低mysql的rowLock的持有时间.时间降低一倍,减少了一个网络延迟
优化第二步:深度优化:事务sql在mysql端执行(存储过程)
存储过程优化:
1.事务行级锁持有的时间
2.不要过度依赖存储过程
3.简单的逻辑可以应用存储过程
4.QPS:一个秒杀单6000QPS
因为最后一步还是需要在客户端去判断修改成功与否,这一步还是有网络延迟。为了避免网络延迟,把插入购买明细和更新库存的操作放入到存储过程里面。
项目中调用存储过程:
通过Mybatis调用存储过程有几个注意点:
1、在调用sql时传入的参数,就是DAO接口中的参数是map类型,其中包含存储过程要使用的in/out类型的参数,!!!在执行完之后返回的结果就在那个代表out的键值对中。在本示例中就是result键值对。(用map做存储过程方法参数便于封装,根据存储过程定义result会在执行完后赋值。)
2、在xml文件中,需要设置statementType=CALLABLE
3、传入参数时需要指定mode=IN/OUT,注意是大写的
系统架构部署
CDN:放置一些静态化资源,或者可以将动态数据分离,比如秒杀详情页,做成HTML放在cdn上,动态数据可以通过ajax请求后台获取。
Nginx:作为http服务器,响应客户请求,为后端的servlet容器做反向代理,以达到负载均衡的效果。
Redis:加速后台获取数据,减少数据库的请求量
MySQL:保证秒杀过程的数据一致性与完整性
智能DNS解析+智能CDN加速+Nginx并发+Redis缓存+MySQL分库分表
分库分表工具例如阿里巴巴的TDDL工具
优化总结
1.前端控制,暴露接口,放按钮重复
2.动静态数据分离:CDN缓存,后端缓存
3.事务竞争优化,减少事务锁时间
4.集群化部署