java秒杀项目:mysql乐观锁+redis限流+redis缓存+kafka队列

最近学习了秒杀系统设计,已经将代码上传到github,不断优化,吞吐量达到3000,大家喜欢的话还希望多多star
本系统不同优化方法下吞吐量比较:
在这里插入图片描述

如何设计一个秒杀系统

本系统设计了一个秒杀场景,一共有一千个手机的库存,现在大量的用户涌入抢购手机,服务器内部维护了一个手机库存数量和成功购买手机的mysql table.要解决的最基本的问题是并发安全,保证两个表单的一致性.其次是增加系统的吞吐量.

系统的特点

  • 高性能:秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键
  • 一致性:秒杀商品减库存的实现方式同样关键,有限数量的商品在同一时刻被很多倍的请求同时来减库存,在大并发更新的过程中都要保证数据的准确性。
  • 高可用:秒杀时会在一瞬间涌入大量的流量,为了避免系统宕机,保证高可用,需要做好流量限制

优化思路

  • 后端优化:将请求尽量拦截在系统上游
    • 限流:屏蔽掉无用的流量,允许少部分流量走后端。假设现在库存为 10,有 1000 个购买请求,最终只有 10 个可以成功,99% 的请求都是无效请求
    • 削峰:秒杀请求在时间上高度集中于某一个时间点,瞬时流量容易压垮系统,因此需要对流量进行削峰处理,缓冲瞬时流量,尽量让服务器对资源进行平缓处理
    • 异步:将同步请求转换为异步请求,来提高并发量,本质也是削峰处理
    • 利用缓存:创建订单时,每次都需要先查询判断库存,只有少部分成功的请求才会创建订单,因此可以将商品信息放在缓存中,减少数据库查询
    • 负载均衡:利用 Nginx 等使用多个服务器并发处理请求,减少单个服务器压力
  • 前端优化:
    • 限流:前端答题或验证码,来分散用户的请求
    • 禁止重复提交:限定每个用户发起一次秒杀后,需等待才可以发起另一次请求,从而减少用户的重复请求
    • 本地标记:用户成功秒杀到商品后,将提交按钮置灰,禁止用户再次提交请求
    • 动静分离:将前端静态数据直接缓存到离用户最近的地方,比如用户浏览器、CDN 或者服务端的缓存中
  • 防作弊优化:
    • 隐藏秒杀接口:如果秒杀地址直接暴露,在秒杀开始前可能会被恶意用户来刷接口,因此需要在没到秒杀开始时间不能获取秒杀接口,只有秒杀开始了,才返回秒杀地址 url 和验证 MD5,用户拿到这两个数据才可以进行秒杀
    • 同一个账号多次发出请求:在前端优化的禁止重复提交可以进行优化;也可以使用 Redis 标志位,每个用户的所有请求都尝试在 Redis 中插入一个 userId_secondsKill 标志位,成功插入的才可以执行后续的秒杀逻辑,其他被过滤掉,执行完秒杀逻辑后,删除标志位
    • 多个账号一次性发出多个请求:一般这种请求都来自同一个 IP 地址,可以检测 IP 的请求频率,如果过于频繁则弹出一个验证码
    • 多个账号不同 IP 发起不同请求:这种一般都是僵尸账号,检测账号的活跃度或者等级等信息,来进行限制。比如微博抽奖,用 iphone 的年轻女性用户中奖几率更大。通过用户画像限制僵尸号无法参与秒杀或秒杀不能成功

代码优化

代码整体思路参考的 @crossoverJie,做了以下几点变动

  1. 将 SSM 换成 SpringBoot,开箱即用,替换 Mapper XML 为注解,去掉 Dubbo 和 Zookeeper
  2. 原项目中依赖了开发者自己的开源包 distributed-redis-tool,本项目将用到的限流部分直接集成到代码中
  3. 加入缓存预热,在秒杀开始前,将库存信息读到缓存中,并暴露数据库和缓存重置方法便于服务器部署压测
  4. 缓存更新逻辑中加入 Redis 事务,避免脏数据
  5. 将 Kafka-client 替换为 spring-kafka,自动配置,通过 KafkaTemplate 和 Listen 进行消息的生产和消费,采用 Gson 进行 Kafka 消息序列化和反序列化,精简大量代码

Jmeter 压测

测试流程如下:

首先下载 JMeter 安装包 可以去官网下载:http://jmeter.apache.org

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 {
   
    Stock stock = stockService.getStockById(sid);
    if (stock.getCount() < 1) {
   
        throw new RuntimeException("库存不足");
    }
    return stock;
}
private int saleStock(Stock stock) {
   
    stock.setSale(stock.getSale() + 1);
    stock.setCount(stock.getCount() - 1);
    return stockService.updateStockById(stock);
}
private int createOrder(Stock stock) throws Exception {
   
    StockOrder order = new StockOrder();
    order.setSid(stock.getId());
    order.setName(stock.getName());
    order.setCreateTime(new Date());
    int res = orderMapper.insertSelective(order);
    if (res == 0) {
   
        throw new RuntimeException("创建订单失败");
    }
    return res;
}
// 扣库存 Mapper 文件
@Update("UPDATE stock SET count = #{count, jdbcType = INTEGER}, name = #{name, jdbcType = 			     VARCHAR}, " + "sale = #{sale,jdbcType = INTEGER},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
  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值