目录
一、设计思路
二、核心代码
三、压测
首页 数据库 Redis redis怎么实现秒杀系统

redis怎么实现秒杀系统

May 31, 2023 pm 03:11 PM
redis

一、设计思路

秒杀系统的特点就是并发量大,一秒钟就可能几千几万的请求进来了,如果不使点儿手段,系统分分钟就垮了。下面就探讨一下如何设计一个能打的秒杀系统。

1、限流:

首先不考虑业务逻辑,假如有如下一个最简单的接口:

@GetMapping("/test")
public String test() {
 return "success";
}
登录后复制

尽管这个接口非常简单,没有任何逻辑,但如果有成千上万的请求同时访问,服务器也会崩溃。所以,高并发系统该做的第一件事就是限流。springcloud项目可以使用hystrix进行限流,springcloud alibaba可以使用sentinel进行限流,那么非springcloud项目呢?guava为我们提供了一个RateLimiter工具类,可以做限流。它主要有漏桶算法和令牌桶算法。

  • 漏桶算法:一个有洞洞的桶子在水龙头下装水,装一点儿就漏一点儿,但是如果水龙头的水很大,桶里的水迟早会溢出的,溢出就限流。这种适合做限制上传下载速率一类的。

  • 令牌桶算法:以恒定的速率往桶中放入令牌,每次请求进来,要先从桶中拿令牌,如果没有拿到令牌,请求就被挡掉。这种适合做限流,即限制QPS。

这里应该使用令牌桶算法进行限流,如果没拿到令牌,直接返回“人太多了,挤不进去”的提示。

2、检查用户是否登录:

经过第一步的限流,进来的请求应该检查用户是否登录,本项目使用JWT,即先请求登录接口,登录后返回token,请求其他所有接口都在请求头中带上token,然后通过token就可以拿到用户信息。当未获取到用户信息时,提示用户重新登录:无效的token。

3、检查商品是否卖完:

如果前两步校验都通过,就需要进行商品是否售罄的检查,如果售罄了就返回一个提示信息“抱歉,商品已经被秒杀一空”。注意,检查商品是否卖完不能查数据库,否则会很慢。可以使用一个字典来存储商品ID,用商品ID作为键。如果商品售罄,将其值设置为True,否则为False。

4、将参加秒杀的商品加到redis中:

首先搞个ISINREDIS的key,表示商品是否已经加到redis中了,避免每个请求进来都重复此操作。如果ISINREDIS值为false,表示redis中还没有秒杀商品。那么就查询出所有参加秒杀的商品,商品id作为key,商品库存作为value,存到redis中,同时将商品id作为key,false作为value,放到第三步的map中,表示该商品没有售完。最后将ISINREDIS的值设置为true,表示已经将所有参加秒杀的商品加到redis中了。

5、预扣库存:

使用redis的decr函数对商品数量进行减少,并对减少后的值进行判断。如果自减后结果小于0,表示商品已经卖完了,那么就将map中对应的商品id的值设置为true,并且返回“来迟了,商品已秒杀完”的提示。

6、判断是否重复秒杀:

如果用户秒杀成功,在秒杀订单入库后,会将用户id和商品id作为key,true作为value存入redis中,表示该用户已经秒杀过该商品了。所以在这里就根据用户id和商品id去redis中判断是否重复秒杀,如果是,就返回“请勿重复秒杀”的提示。

7、异步处理:

如果以上校验都通过了,那么就可以处理秒杀了。如果对每一个秒杀请求都进行扣库存和创建订单这种操作,那么不仅速度非常慢,而且可能会导致数据库崩溃。所以我们可以异步处理,即通过了以上校验,就将用户id和商品id作为message发送到MQ中,然后立即给用户返回“排队中”的提示。然后在MQ的消费者端对消息进行消费,拿到用户id和商品id,可以根据商品id查询库存,再次确保库存充足;然后也可以再次判断是否重复秒杀。通过了判断后,就操作数据库,扣减库存,创建秒杀订单。注意扣减库存和创建秒杀订单需要在同一个事务中。

8、超卖问题:

超卖问题就是商品库存出现负数的情况。比如库存剩余1了,然后10个用户同时秒杀,在判断库存的时候都是1,所以10个人都能下单成功,最后库存为-9。如何解决?其实本系统中根本就不会出现这样的问题,因为一开始用redis进行了库存预减,而redis命令核心模块是单线程的,所以可以保证不会超卖。如果没有用到redis,也可以给该商品增加一个version字段,每次扣减库存前先查其version,扣减库存的sql加上一个条件,就是version要等于刚才查出来的version。

 

二、核心代码

@RestController
@RequestMapping("/seckill")
public class SeckillController {
 
 @Autowired
 private UserService userService;
 @Autowired
 private SeckillService seckillService;
 @Autowired
 private RabbitMqSender mqSender;
 
 // 用来标记商品是否已经加入到redis中的key
 private static final String ISINREDIS = "isInRedis";
 
 // 用goodsId作为key,标记该商品是否已经卖完
 private Map<Integer, Boolean> seckillOver = new HashMap<Integer, Boolean>();
 
 // 用RateLimiter做限流,create(10),可以理解为QPS阈值为10
 private RateLimiter rateLimiter = RateLimiter.create(10);
 
 @PostMapping("/{sgId}")
 public JsonResult<?> seckillGoods(@PathVariable("sgId") Integer sgId, HttpServletRequest httpServletRequest){
  
  // 1. 如果QPS阈值超过10,即1秒钟内没有拿到令牌,就返回“人太多了,挤不进去”的提示
  if (!rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
   return new JsonResult<>(SeckillGoodsEnum.TRY_AGAIN.getCode(), SeckillGoodsEnum.TRY_AGAIN.getMessage());
  }
  
  // 2. 检查用户是否登录(用户登录后,访问每个接口都应该在请求头带上token,根据token再去拿user)
  String token = httpServletRequest.getHeader("token");
  String userId = JWT.decode(token).getAudience().get(0);
  User user = userService.findUserById(Integer.valueOf(userId));
  if (user == null) {
   return new JsonResult<>(SeckillGoodsEnum.INVALID_TOKEN.getCode(), SeckillGoodsEnum.INVALID_TOKEN.getMessage());
  }
  
  // 3. 如果商品已经秒杀完了,就不执行下面的逻辑,直接返回商品已秒杀完的提示
  if (!seckillOver.isEmpty() && seckillOver.get(sgId)) {
   return new JsonResult<>(SeckillGoodsEnum.SECKILL_OVER.getCode(), SeckillGoodsEnum.SECKILL_OVER.getMessage());
  }
  
  // 4. 将所有参加秒杀的商品信息加入到redis中
  if (!RedisUtil.isExist(ISINREDIS)) {
   List<SeckillGoods> goods = seckillService.getAllSeckillGoods();
   for (SeckillGoods seckillGoods : goods) {
    RedisUtil.set(String.valueOf(seckillGoods.getSgId()), seckillGoods.getSgSeckillNum());
    seckillOver.put(seckillGoods.getSgId(), false);
   }
   RedisUtil.set(ISINREDIS, true);
  }
  
  // 5. 先自减,预扣库存,判断预扣后库存是否小于0,如果是,表示秒杀完了
  Long stock = RedisUtil.decr(String.valueOf(sgId));
  if (stock < 0) {
   // 标记该商品已经秒杀完
   seckillOver.put(sgId, true);
   return new JsonResult<>(SeckillGoodsEnum.SECKILL_OVER.getCode(), SeckillGoodsEnum.SECKILL_OVER.getMessage());
  }
  
  // 6. 判断是否重复秒杀(成功秒杀并创建订单后,会将userId和goodsId作为key放到redis中)
  if (RedisUtil.isExist(userId + sgId)) {
   return new JsonResult<>(SeckillGoodsEnum.REPEAT_SECKILL.getCode(), SeckillGoodsEnum.REPEAT_SECKILL.getMessage());
  }
  
  // 7. 以上校验都通过了,就将当前请求加入到MQ中,然后返回“排队中”的提示
  String msg = userId + "," + sgId;
  mqSender.send(msg);
  return new JsonResult<>(SeckillGoodsEnum.LINE_UP.getCode(), SeckillGoodsEnum.LINE_UP.getMessage());
 }

}
登录后复制
     

三、压测

用jmeter模拟并发请求,测试高并发情况下系统能否扛得住。由于只有一个id为1的商品,所以商品id固定写死1。但是每个用户都要先请求登录接口获取到token才能进行秒杀请求,有点儿麻烦,所以可以先把jwt模块注释掉,把userId当成参数传进去。jmeter配置如下图:

redis怎么实现秒杀系统
jmeter压测配置
redis怎么实现秒杀系统
jmeter压测配置

以上是redis怎么实现秒杀系统的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

redis集群模式怎么搭建 redis集群模式怎么搭建 Apr 10, 2025 pm 10:15 PM

Redis集群模式通过分片将Redis实例部署到多个服务器,提高可扩展性和可用性。搭建步骤如下:创建奇数个Redis实例,端口不同;创建3个sentinel实例,监控Redis实例并进行故障转移;配置sentinel配置文件,添加监控Redis实例信息和故障转移设置;配置Redis实例配置文件,启用集群模式并指定集群信息文件路径;创建nodes.conf文件,包含各Redis实例的信息;启动集群,执行create命令创建集群并指定副本数量;登录集群执行CLUSTER INFO命令验证集群状态;使

redis底层怎么实现 redis底层怎么实现 Apr 10, 2025 pm 07:21 PM

Redis 使用哈希表存储数据,支持字符串、列表、哈希表、集合和有序集合等数据结构。Redis 通过快照 (RDB) 和追加只写 (AOF) 机制持久化数据。Redis 使用主从复制来提高数据可用性。Redis 使用单线程事件循环处理连接和命令,保证数据原子性和一致性。Redis 为键设置过期时间,并使用 lazy 删除机制删除过期键。

redis-server找不到怎么办 redis-server找不到怎么办 Apr 10, 2025 pm 06:54 PM

解决redis-server找不到问题的步骤:检查安装,确保已正确安装Redis;设置环境变量REDIS_HOST和REDIS_PORT;启动Redis服务器redis-server;检查服务器是否运行redis-cli ping。

redis集群是如何实现的 redis集群是如何实现的 Apr 10, 2025 pm 05:27 PM

Redis集群是一种分布式部署模式,允许水平扩展Redis实例,通过节点间通信、哈希槽划分键空间、节点选举、主从复制和命令重定向来实现:节点间通信:通过集群总线实现虚拟网络通信。哈希槽:将键空间划分为哈希槽,确定负责键的节点。节点选举:至少需要三个主节点,通过选举机制确保仅有一个活动主节点。主从复制:主节点负责写请求,从节点负责读请求和数据复制。命令重定向:客户端连接到负责键的节点,节点重定向不正确的请求。故障处理:故障检测、标记下线和重新

redis如何查看版本号 redis如何查看版本号 Apr 10, 2025 pm 05:57 PM

要查看 Redis 版本号,可以使用以下三种方法:(1) 输入 INFO 命令,(2) 使用 --version 选项启动服务器,(3) 查看配置文件。

redis zset怎么使用 redis zset怎么使用 Apr 10, 2025 pm 07:27 PM

Redis 有序集合(ZSet)用于存储有序元素集合,并按关联分数进行排序。ZSet 的用法步骤包括:1. 创建 ZSet;2. 添加成员;3. 获取成员分数;4. 获取排名;5. 获取排名范围的成员;6. 删除成员;7. 获取元素个数;8. 获取分数范围内的成员个数。

redis查询的key怎么唯一 redis查询的key怎么唯一 Apr 10, 2025 pm 07:03 PM

Redis采用五种策略确保键的唯一性:1. 名称空间分隔;2. HASH数据结构;3. SET数据结构;4. 字符串键的特殊字符;5. Lua脚本验证。具体策略的选择取决于数据组织、性能和扩展性需求。

redis怎么查看所有的key redis怎么查看所有的key Apr 10, 2025 pm 07:15 PM

要查看 Redis 中的所有键,共有三种方法:使用 KEYS 命令返回所有匹配指定模式的键;使用 SCAN 命令迭代键并返回一组键;使用 INFO 命令获取键的总数。

See all articles