2019独角兽企业重金招聘Python工程师标准>>>

绝大多数秒杀系统都需要实现高并发,这样就必须在原来的项目基础上进行优化。简单的优化很有可能就会很大地提高系统的并发性能,但是这些优化往往是系统开发人员很少注意的,或者直接被人们忽略。因此要成为一个出色的开发人员,学会优化技巧与时刻具备系统优化的意识是必须的。

项目源码地址:

http://git.oschina.net/COOLFLYCOOL/seckill

本项目秒杀业务核心SQL操作:

先是UPDATE货存(货存减1),再是INSERT购买明细。中间可能会出现重复秒杀,秒杀结束,系统内部错误等异常,只要出现异常,事务就会回滚。

事务行为分析:

当一个事务开启的时候拿到了数据库表中某一行的行级锁,另一个事务进来数据库时发现锁住了同一行,若之前的事务不提交或回滚,这个行级锁不会被释放,后面进来的那个事务就要等待行级锁。当第一个事务提交或回滚后,行级锁被释放,第二个事务就能获得这个行级锁进行数据操作,多个事务以此类推,这些过程是一个串行化的操作,也是一个含有大量阻塞的操作。这是MySQL数据库或是绝大多数关系型数据库事务实现的方案。

秒杀系统瓶颈分析:

  1. 现在的事务实现方案是通过Spring的事务对秒杀业务核心进行管理。
  2. 系统目前的秒杀逻辑:java客户端发送UPDATE语句至MySQL服务端(虽然有网络延迟,但是各个事务并行),各事务开始竞争行级锁(阻塞开始),UPDATE执行后将UPDATE结果返回至java客户端(存在网络延迟与可能的GC操作),客户端判断如果执行成功,则发送INSERT购买明细的SQL语句至MySQL服务端再执行(存在网络延迟与可能的GC操作),将执行结果返回至java客户端(存在网络延迟与可能的GC操作),客户端再判断是否执行成功,如果成功,就告知MySQL提交事务(存在网络延迟)。
  3. 因此,阻塞的时间即从各事务在MySQL服务端竞争行级锁开始,一直到最后的事务提交,中间有4次的网络延迟以及java客户端的各种逻辑判断。这样事务的执行周期就会比较长。当排队的事务比较多的时候,系统性能就会呈指数级下降。

注:Java的GC操作:项目中DAO层各数据库操作类通过MyBatis实现的生成相应对象注入spring容器中,当使用后不再被使用时,就会进行垃圾回收。

项目优化分析:

通过分析事务的行为与秒杀系统瓶颈可以知道,要减少事务等待的时间,削弱阻塞的过程,就要想办法减少行级锁持有的时间。

  1. 优化思路一:持有行级锁是在UPDATE上(INSERT不涉及行级锁),释放锁是在Commit(客户端Spring控制),也就是锁持有时间是UPDATE和Commit之间。这个过程网络请求越少,锁持有时间就越短。
  2. 优化思路二:把客户端逻辑放在MySQL服务端(使用存储过程,整个事务在MySQL端完成),避免网络延迟与GC的影响,也没有java客户端的逻辑判断。

简单的并发优化(优化思路一):

分析:

参照优化思路一,持有行级锁在UPDATE上,INSERT不涉及行级锁(没INSERT之前根本不存在相应的行,更不可能会有行级锁)。因此可以先插入购买明细,这个过程虽然存在网络延迟,但是各个事务之间是可以并行的所以不需要等待,这样就可以减少各个事务一部分的等待与阻塞。实现减少MySQL row lock的持有时间。(但还是要把UPDATE库存的结果返回给客户端,客户端再决定是否提交事务,即还有2次网络延迟)

修改秒杀业务核心代码顺序后:

int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone,nowTime);//唯一:seckillId,userPhone(联合主键)if(insertCount<=0){//重复秒杀throw new RepeatKillException("seckill repeated");}else {int updateCount = seckillDao.reduceNumber(seckillId, nowTime);if (updateCount <= 0) {//并发量太高,有可能在等行级锁的时候库存没有了,并且秒杀时间问题在前面已经验证。throw new SeckillCloseException("seckill is closed");}else {//秒杀成功SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);return new SeckillExecution(seckillId, SeckillStateEnums.SUCCESS, successKilled);  //枚举}}

深度优化(利用存储过程实现事务SQL在MySQL端执行):

  1. 参照优化思路二,利用存储过程将秒杀业务核心事务SQL放在MySQL端执行,这样就可以避免事务执行过程中的网络延迟与GC影响,事务行级锁持有时间几乎就是数据库数据操作的时间。大大削弱了事务等待的阻塞效应。

秒杀核心SQL事务存储过程:

DELIMITER //
CREATE PROCEDURE excuteSeckill(IN fadeSeckillId INT,IN fadeUserPhone VARCHAR (15),IN fadeKillTime TIMESTAMP ,OUT fadeResult INT)BEGINDECLARE insertCount INT DEFAULT 0;START TRANSACTION ;INSERT IGNORE success_killed(seckill_id,user_phone,state,create_time) VALUES(fadeSeckillId,fadeUserPhone,0,fadeKillTime);  --先插入购买明细SELECT ROW_COUNT() INTO insertCount;IF(insertCount = 0) THENROLLBACK ;SET fadeResult = -1;   --重复秒杀ELSEIF(insertCount < 0) THENROLLBACK ;SET fadeResult = -2;   --内部错误ELSE   --已经插入购买明细,接下来要减少库存UPDATE seckill SET number = number -1 WHERE seckill_id = fadeSeckillId AND start_time < fadeKillTime AND end_time > fadeKillTime AND number > 0;SELECT ROW_COUNT() INTO insertCount;IF (insertCount = 0)  THENROLLBACK ;SET fadeResult = 0;   --库存没有了,代表秒杀已经关闭ELSEIF (insertCount < 0) THENROLLBACK ;SET fadeResult = -2;   --内部错误ELSECOMMIT ;    --秒杀成功,事务提交SET  fadeResult = 1;   --秒杀成功返回值为1END IF;END IF;END
//DELIMITER ;SET @fadeResult = -3;
CALL excuteSeckill(8,13813813822,NOW(),@fadeResult);
SELECT @fadeResult;

Java客户端(MyBatis)调用数据库存储过程:

首先,在Dao层新建一个接口:void killByProcedure(Map [泛型:String,Object] paramMap); 然后在相应的XML中配置实现(注意:jdbcType没有INT类型的枚举,要使用BIGINT;同样没有VARCHAR的枚举,要使用BIGINT代替。):

    <!--MyBatis调用存储过程 --><select id="killByProcedure" statementType="CALLABLE">CALL executeSeckill(#{ seckillId , jdbcType = BIGINT , mode= IN },#{ phone ,jdbcType = BIGINT , mode= IN },#{ killTime , jdbcType = TIMESTAMP , mode= IN },#{ result , jdbcType = BIGINT , mode= OUT })</select>

然后,Service层重新写入一个方法SeckillExecution executeSeckillProcedure(int seckillId, String userPhone, String md5);(注意:在使用MapUtils时要注入commons-collections 3.2依赖)

public SeckillExecution executeSeckillProcedure(int seckillId, String userPhone, String md5) {if( md5==null || !md5.equals(getMD5(seckillId)) ){return new SeckillExecution(seckillId,SeckillStateEnums.DATA_REWRITE);}Timestamp nowTime = new Timestamp(System.currentTimeMillis());Map<String,Object> map = new HashMap<String,Object>();map.put("seckillId",seckillId);map.put("phone",userPhone);map.put("killTime",nowTime);map.put("result", null);try{seckillDao.killByProcedure(map);int result = MapUtils.getInteger(map,"result",-2);if(result == 1){SuccessKilled sk = successKilledDao.queryByIdWithSeckill(seckillId,userPhone);return new SeckillExecution(seckillId,SeckillStateEnums.SUCCESS,sk);}else{return new SeckillExecution(seckillId,SeckillStateEnums.stateOf(result));}}catch (Exception e){logger.error(e.getMessage(),e);return new SeckillExecution(seckillId,SeckillStateEnums.INNER_ERROR);}}

再者,在web-control层将调用方法改成executeSeckillProcedure,同时因为executeSeckillProcedure已经将重复秒杀,秒杀结束(无库存)合并到返回的SeckillExecution中,所以不用再捕获这两个异常(原本在service层要抛出这两个异常,是为了告诉Spring声明式事务该程序出错要进行事务回滚)

try{SeckillExecution seckillExecution = seckillService.executeSeckillProcedure(seckillId,phone,md5);return new SeckillResult<SeckillExecution>(true,seckillExecution);
}
catch (Exception e){logger.error(e.getMessage(),e);SeckillExecution seckillExecution = new SeckillExecution(seckillId, SeckillStateEnums.INNER_ERROR);return  new SeckillResult<SeckillExecution>(true,seckillExecution);
}

最后,集成测试web层:

 
 

可见秒杀成功,重复秒杀,秒杀结束都正常进行!

转载于:https://my.oschina.net/u/3339803/blog/883179

Seckill秒杀系统高并发优化相关推荐

  1. Seckill系统高并发优化

    绝大多数秒杀系统都需要实现高并发,这样就必须在原来的项目基础上进行优化.简单的优化很有可能就会很大地提高系统的并发性能,但是这些优化往往是系统开发人员很少注意的,或者直接被人们忽略.因此要成为一个出色 ...

  2. java实现请求排队处理_【高并发】秒杀系统高并发请求排队处理

    今天无意中看见了这位兄弟的文章 通过请求队列的方式来缓解高并发抢购(初探)  但文章最后说并发超过500 就会出现超发,看了下代码,的确有这个问题 抽空简单完善了下,经压力测试后发现暂无超发现象, 下 ...

  3. java商城并发_一次线上商城系统高并发优化,涨姿势了~

    对于线上系统调优,它本身是个技术活,不仅需要很强的技术实战能力,很强的问题定位,问题识别,问题排查能力,还需要很丰富的调优能力. 本篇文章从实战角度,从问题识别,问题定位,问题分析,提出解决方案,实施 ...

  4. Java高并发秒杀API(四)之高并发优化

    Java高并发秒杀API(四)之高并发优化 1. 高并发优化分析 关于并发 并发性上不去是因为当多个线程同时访问一行数据时,产生了事务,因此产生写锁,每当一个获取了事务的线程把锁释放,另一个排队线程才 ...

  5. 每秒上千订单场景下的分布式锁高并发优化实践!

    本文授权转自石杉的架构笔记 背景引入 首先,我们一起来看看这个问题的背景? 前段时间有个朋友在外面面试,然后有一天找我聊说:有一个国内不错的电商公司,面试官给他出了一个场景题: 假如下单时,用分布式锁 ...

  6. Java架构-每秒上千订单场景下的分布式锁高并发优化实践!

    "上一篇文章我们聊了聊Redisson这个开源框架对Redis分布式锁的实现原理,如果有不了解的兄弟可以看一下:<拜托,面试请不要再问我Redis分布式锁实现原理>. 今天就给大 ...

  7. php秒杀防重复中奖_PHP如何应对秒杀抢购高并发思路

    原标题:PHP如何应对秒杀抢购高并发思路 我们常用QPS(Query Per Second,每秒处理请求数)来衡量一个web应用的吞吐率,解决每秒数万次的高并发场景,这个指标非常关键. 举个栗子:假设 ...

  8. 用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景?

    用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景? 转载 codeing_doc 最后发布于2018-11-23 09:44:41 阅读数 1073 ...

  9. api商品分享源码_SSM框架高并发和商品秒杀项目高并发秒杀API源码免费分享

    前言: 一个整合SSM框架的高并发和商品秒杀项目,学习目前较流行的Java框架组合实现高并发秒杀API 源码获取:关注头条号转发文章之后私信[秒杀]查看源码获取方式! 项目的来源 项目的来源于国内IT ...

  10. 网易高并发优化 | 公开课-02

    网易严选中的高并发优化 (一)单机系统缓存优化 1.背景导入 在单机情况下,CSD模型如果出现慢查询一般会把问题归结到数据库 CSD模型: 实际操作发现:当有2KW级别数据层查询是,统计总行数约1s, ...

最新文章

  1. 383. Ransom Note/691. Stickers to Spell Word-- String, Map, back tracking-- 未完待续
  2. Knative 入门系列1:knative 概述
  3. @NotNull-JSR-303验证
  4. 【长篇连载】桌面管理演义 第六回 违规言论别乱发 访问控制把你抓
  5. Spring Boot(04)——创建自己的自动配置
  6. windows下php7安装redis扩展
  7. Ubuntu文本检索神器——SearchMonkey
  8. 学习日报 1028 分支结构 if分支语句
  9. SpringCloud与Hystrix断路器
  10. Filecoin网络目前总质押量约为3314万枚FIL
  11. mysql开启binlog日志影响性能吗_mysql binlog日志优化及思路
  12. easy-hiphop一键安装hiphop脚本
  13. iSCSI服务部署网络存储
  14. 动图在线压缩怎么操作?教你快速压缩动图
  15. 信用卡到底有什么好处?教你四个技巧拥有大额信用卡
  16. python下载bt文件_给定一个.torrent文件,如何在python中生成一个磁力链接?
  17. 月薪五万,996真的就像呼吸一样自然吗?
  18. 精确查找top k和非精确查找top k
  19. 深圳软件测试培训:软件测试质量
  20. python中oo是什么意思_python中的OO

热门文章

  1. 单总体分布卡方拟合优度检验
  2. 蔚蓝(Celeste)Mod下载、安装指南
  3. 深入Elasticsearch:索引的创建
  4. 计算机远程协助是什么,Windows7系统下远程协助和远程桌面有什么区别?
  5. Java Excel 列号数字与字母互相转换
  6. MyBatis第一天课堂笔记
  7. cad断点快捷键_cad打断快捷键(cad十字路口路口怎么画)
  8. IAR各版本下载链接
  9. VMware虚拟机双屏显示
  10. 图解通信原理与案例分析-14:“大哥大”与1G模拟蜂窝移动通信案例--频率调制与频分多址FDMA