java秒杀项⽬:mysql乐观锁+redis限流+redis缓存+kafka队列
最近学习了秒杀系统设计,已经将代码上传到,不断优化,吞吐量达到3000,⼤家喜欢的话还希望多多star
本系统不同优化⽅法下吞吐量⽐较:
如何设计⼀个秒杀系统
本系统设计了⼀个秒杀场景,⼀共有⼀千个⼿机的库存,现在⼤量的⽤户涌⼊抢购⼿机,服务器内部维护了⼀个⼿机库存数量和成功购买⼿机的mysql table.要解决的最基本的问题是并发安全,保证两个表单的⼀致性.其次是增加系统的吞吐量.
系统的特点
⾼性能:秒杀涉及⼤量的并发读和并发写,因此⽀持⾼并发访问这点⾮常关键
⼀致性:秒杀商品减库存的实现⽅式同样关键,有限数量的商品在同⼀时刻被很多倍的请求同时来减库存,在⼤并发更新的过程中都要保证数据的准确性。
⾼可⽤:秒杀时会在⼀瞬间涌⼊⼤量的流量,为了避免系统宕机,保证⾼可⽤,需要做好流量限制
优化思路
后端优化:将请求尽量拦截在系统上游
限流:屏蔽掉⽆⽤的流量,允许少部分流量⾛后端。假设现在库存为 10,有 1000 个购买请求,最终只有 10 个可以成
功,99% 的请求都是⽆效请求
削峰:秒杀请求在时间上⾼度集中于某⼀个时间点,瞬时流量容易压垮系统,因此需要对流量进⾏削峰处理,缓冲瞬时流量,尽量让服务器对资源进⾏平缓处理
异步:将同步请求转换为异步请求,来提⾼并发量,本质也是削峰处理
利⽤缓存:创建订单时,每次都需要先查询判断库存,只有少部分成功的请求才会创建订单,因此可以将商品信息放在缓存中,减少数据库查询
负载均衡:利⽤ Nginx 等使⽤多个服务器并发处理请求,减少单个服务器压⼒
前端优化:
限流:前端答题或验证码,来分散⽤户的请求
禁⽌重复提交:限定每个⽤户发起⼀次秒杀后,需等待才可以发起另⼀次请求,从⽽减少⽤户的重复请求
本地标记:⽤户成功秒杀到商品后,将提交按钮置灰,禁⽌⽤户再次提交请求
动静分离:将前端静态数据直接缓存到离⽤户最近的地⽅,⽐如⽤户浏览器、CDN 或者服务端的缓存中防作弊优化:
隐藏秒杀接⼝:如果秒杀地址直接暴露,在秒杀开始前可能会被恶意⽤户来刷接⼝,因此需要在没到秒杀开始时间不能获取秒杀接⼝,只有秒杀开始了,才返回秒杀地址 url 和验证 MD5,⽤户拿到这两个数据才可以进⾏秒杀
同⼀个账号多次发出请求:在前端优化的禁⽌重复提交可以进⾏优化;也可以使⽤ Redis 标志位,每个⽤户的所有请求都尝试在Redis 中插⼊⼀个 userId_secondsKill 标志位,成功插⼊的才可以执⾏后续的秒杀逻辑,其他被过滤掉,执⾏完秒杀逻辑后,删除标志位
多个账号⼀次性发出多个请求:⼀般这种请求都来⾃同⼀个 IP 地址,可以检测 IP 的请求频率,如果过于频繁则弹出⼀个验证码多个账号不同 IP 发起不同请求:这种⼀般都是僵⼫账号,检测账号的活跃度或者等级等信息,来进⾏限制。⽐如微博抽奖,⽤iphone 的年轻⼥性⽤户中奖⼏率更⼤。通过⽤户画像限制僵⼫号⽆法参与秒杀或秒杀不能成功
双老太婆原唱完整版代码优化
代码整体思路参考的 ,做了以下⼏点变动
1. 将 SSM 换成 SpringBoot,开箱即⽤,替换 Mapper XML 为注解,去掉 Dubbo 和 Zookeeper
2. 原项⽬中依赖了开发者⾃⼰的开源包 ,本项⽬将⽤到的限流部分直接集成到代码中
3. 加⼊缓存预热,在秒杀开始前,将库存信息读到缓存中,并暴露数据库和缓存重置⽅法便于服务器部署压测
4. 缓存更新逻辑中加⼊ Redis 事务,避免脏数据
5. 将 Kafka-client 替换为 spring-kafka,⾃动配置,通过 KafkaTemplate 和 Listen 进⾏消息的⽣产和消费,采⽤ Gson 进⾏
Kafka 消息序列化和反序列化,精简⼤量代码
Jmeter 压测
测试流程如下:
windows 环境下载 zip 安装包,然后将下载的⽂件进⾏解压,进⼊ bin ⽬录运⾏ jmeter.bat 即可。
接下来是 Jmeter 测试计划设置:
(1)在测试计划上右键新建⼀个线程组
线程组属性内可以修改线程数、Ramp-Up 时间和循环次数。
(2)在线程组上右键添加 HTTP 请求
其属性包括 WEB 服务器的协议、服务器名称或 IP 和端⼝号,HTTP 请求的⽅法和路径。
(3)在HTTP请求上右键添加⼀个,可以根据⾃⼰的需求添加汇总报告、查看结果树等等。
这样⼀个简单的 Jmeter 测试计划就算添加完了。⼀个 HTTP 请求对应⼀个接⼝,可以添加多个 HTTP 请求 以达到多个接⼝同时检测的需求。
0. 基本秒杀逻辑
@Override
public int createWrongOrder(int sid)throws Exception {
边框素材图// 数据库校验库存
Stock stock =checkStock(sid);
// 扣库存(⽆锁)
saleStock(stock);
// ⽣成订单
int res =createOrder(stock);
return res;
}
private Stock checkStock(int sid)throws Exception {c语言strlen什么意思
Stock stock = StockById(sid);
Count()<1){
throw new RuntimeException("库存不⾜");
}
return stock;
}
private int saleStock(Stock stock){
if函数加find多条件判断stock.Sale()+1);
stock.Count()-1);
return stockService.updateStockById(stock);
}
private int createOrder(Stock stock)throws Exception {
StockOrder order =new StockOrder();
order.Id());
order.Name());
order.setCreateTime(new Date());
int res = orderMapper.insertSelective(order);
if(res ==0){
throw new RuntimeException("创建订单失败");
}
css中colspan是什么意思return res;
}
// 扣库存 Mapper ⽂件
@Update("UPDATE stock SET count = #{count, jdbcType = INTEGER}, name = #{name, jdbcType = VARCHAR}, "+"sale = #{sale,jdbcType = INTEG ER},version = #{version,jdbcType = INTEGER} "+"WHERE id = #{id, jdbcType = INTEGER}")
1. 乐观锁更新库存,解决超卖问题
超卖问题出现的场景
悲观锁虽然可以解决超卖问题,但是加锁的时间可能会很长,会长时间的限制其他⽤户的访问,导致很多请求等待锁,卡死在这⾥,如果这种请求很多就会耗尽连接,系统出现异常。乐观锁默认不加锁,更失败就直接返回抢购失败,可以承受较⾼并发
@Override
public int createOptimisticOrder(int sid)throws Exception {
// 校验库存
Stock stock =checkStock(sid);
// 乐观锁更新
saleStockOptimstic(stock);
// 创建订单
int id =createOrder(stock);
return id;
}
// 乐观锁 Mapper ⽂件
@Update("UPDATE stock SET count = count - 1, sale = sale + 1, version = version + 1 WHERE "+
"id = #{id, jdbcType = INTEGER} AND version = #{version, jdbcType = INTEGER}")
2. Redis 计数限流
根据前⾯的优化分析,假设现在有 10 个商品,有 1000 个并发秒杀请求,最终只有 10 个订单会成功创建,也就是说有 990 的请求是⽆效的,这些⽆效的请求也会给数据库带来压⼒,因此可以在在请求落到数据库之前就将⽆效的请求过滤掉,将并发控制在⼀个可控的范围,这样落到数据库的压⼒就⼩很多
关于限流的⽅法,可以看这篇博客,由于计数限流实现起来⽐较简单,因此采⽤计数限流,限流的实现可以直接使⽤ Guava 的 RateLimit ⽅法,但是由于后续需要将实例通过 Nginx 实现负载均衡,这⾥选⽤ Redis 实现分布式限流
手机mysql安装配置教程在 RedisPool 中对 Jedis 线程池进⾏了简单的封装,封装了初始化和关闭⽅法,同时在 RedisPoolUtil 中对 Jedis 常⽤ API 进⾏简单封装,每个⽅法调⽤完毕则关闭 Jedis 连接。
限流要保证写⼊ Redis 操作的原⼦性,因此利⽤ Redis 的单线程机制,通过 LUA 脚本来完成。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论