本人属于个人总结:
原文转载: https://blog.youkuaiyun.com/jeffleo/article/details/56015710
名词解释:
QPS :每秒查询率QPS
本文是学习了immoc网视频之后的个人理解和知识汇总
项目源码:https://github.com/jeff-leo/SpikeSystem,希望大家能star和fork
1. 秒杀优化四个方面
(1) 详情页面
使用CDN加速,将css,js,html 不变的数据存放到cdn服务器;
用户请求较近的cdn缓存服务器,获取请求数据,如果cdn没有,则cdn会自动向上直至找到网站根源;
使用CDN服务的网站,只需将其域名解析权交给CDN的GSLB设备,将需要分发的内容注入CDN,就可以实现内容加速了。
(2) 获取系统时间操作的优化
Java每秒访问内存10亿次,服务器获取系统时间不用优化;
3. 秒杀地址接口获取的优化
先操作redis,每个用户请求在redis中进行记录,如果当前请求量增加,修改redis记录,暂时关闭秒杀接口;
(3) Redis是否可以管理库存?
库存使用数据库维护比较好,秒杀数量则可以利用redis的原子性进行控制;
凡是需要进行写操作的数据都不适合做缓存。 库存不能在redis中维护;
(4) 秒杀操作高并发的问题(重点)
不能在redis中作库存管理,因为会导致数据一致性问题,(凡是需要进行写操作的数据都不适合做缓存。)
① 秒杀流程:
用户请求-> 已提交请耐心等待
② 引发问题:
热点商品同时秒杀,会引起数据库行级锁,这是其他用户等待;
③ 解决办法:
1) 方案一介绍:
利用redis原子计数器,秒杀商品计数器-1,为0则隐藏接口,不再秒杀;
然后记录用户,将用户ID与商品ID,购买数量发送MQ中,进行订单处理,此时返回用户“正在秒杀,请等待。。。”
接着消息队列的消费者进程异步获取数据,生成订单信息,此时订单状态为“未付款”
页面显示“正在秒杀,请等待。。。” ,但页面每2秒进行一次异步查询秒杀结果,根据结果进行页面跳转处理;
秒杀成功后,30分钟内进行付款;过期则将商品数量返还给秒杀原子计数器;
用户付款成功,真正修改数据库库存状态;
2) 方案一问题:
需要强大的运维团队,NoSql不如MySql稳定
重发秒杀的问题。一个用户重发秒杀请求,避免这种情况需要创建另一个NoSql存储请求者信息,如果发现存在该用户秒杀请求,下一次不进行处理;
问题:mysql性能上不去,是因为存在网络延迟
把服务器的执行逻辑放在mysql服务端,避免网络延迟和gc延迟;
3) 方案二介绍:
使用存储过程完成MySql秒杀的事物操作;
<!-- mybatis 调用存储过程-->
<select id="seckillByProduce" statementType="CALLABLE">
CALL excuteSeckill(
#{ seckillId , jdbcType = BIGINT , mode= IN },
#{ phone ,jdbcType = BIGINT , mode= IN },
#{ killTime , jdbcType = TIMESTAMP , mode= IN },
#{ result , jdbcType = BIGINT , mode= OUT }
)
</select>
public SeckillExecution executeSeckillByProducure(long seckillId, long userPhone, String md5) {
if(md5 == null || !md5.equals(getMd5(seckillId))){//数据篡改
return new SeckillExecution(seckillId, SeckillStatEnum.DATE_REWRITE);
}
Date now = new Date();
Map params = new HashMap();
params.put("seckillId", seckillId);
params.put("phone", userPhone);
params.put("killTime", now);
params.put("result", null);
try {
seckillMapper.seckillByProduce(params);
Integer result = MapUtils.getInteger(params, "result", -3);
if(result == 1){//成功
SuccessKilled successKilled = successKilledMapper.queryByIdWithSeckill(seckillId, userPhone);
return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);
}else{//如果失败,把错误状态-1或者-2等返回
return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));
}
} catch (Exception e){//出现异常也要返回
logger.error(e.getMessage(), e);
return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
}
}
参考:
CDN的基本工作过程
imooc
更多关于系统架构和分布式的了解:
分布式架构的演进