Home  >  Article  >  Database  >  How to implement flash sale system in redis

How to implement flash sale system in redis

PHPz
PHPzforward
2023-05-31 15:11:132485browse

1. Design Ideas

The flash kill system is characterized by a large amount of concurrency. Tens of thousands of requests may come in in one second. If some means are not used, the system will It collapsed in minutes. Let’s discuss how to design a flash kill system that can be played.

1. Current limiting:

First of all, do not consider the business logic. If there is the following simplest interface:

@GetMapping("/test")
public String test() {
 return "success";
}

Although this interface is very simple and does not have any logic, The server can also crash if there are thousands of requests accessing it simultaneously. Therefore, the first thing that a high-concurrency system should do is to limit the current flow. Springcloud projects can use hystrix for current limiting, and springcloud alibaba can use sentinel for current limiting. What about non-springcloud projects? Guava provides us with a RateLimiter tool class that can limit current. It mainly includes leaky bucket algorithm and token bucket algorithm.

  • Leaky Bucket Algorithm: A bucket with holes is filled with water under the faucet. It will leak a little when it is filled. However, if the water in the faucet is very large, the bucket will leak. Sooner or later, the water will overflow, and the flow will be limited when it overflows. This is suitable for limiting upload and download rates.

  • Token bucket algorithm: Put tokens into the bucket at a constant rate. Every time a request comes in, the token must be taken from the bucket first. If If the token is not obtained, the request is blocked. This is suitable for current limiting, that is, limiting QPS.

The token bucket algorithm should be used here to limit the flow. If the token is not obtained, a prompt of "too many people can't squeeze in" will be returned directly.

2. Check whether the user is logged in:

After the first step of current limiting, incoming requests should check whether the user is logged in. This project uses JWT, that is, first requests the login interface and returns after logging in. Token, request all other interfaces with token in the request header, and then you can get the user information through the token. When the user information is not obtained, the user is prompted to log in again: invalid token.

3. Check whether the product is sold out:

If the first two steps of verification are passed, you need to check whether the product is sold out. If it is sold out, a prompt message "Sorry" will be returned. , the product has been sold out instantly." Note that you cannot check the database to check whether the product is sold out, otherwise it will be very slow. You can use a dictionary to store product IDs, using the product ID as the key. If the item is sold out, set its value to True, otherwise to False.

4. Add the products participating in the flash sale to redis:

First get a key of ISINREDIS to indicate whether the product has been added to redis to avoid each Repeat this operation for every request that comes in. If the ISINREDIS value is false, it means that there are no flash sale products in redis. Then query all the products participating in the flash sale, use the product id as the key, and the product inventory as the value, and store them in redis. At the same time, use the product id as the key and false as the value, and put them into the map in the third step, indicating that the product is not sold. over. Finally, set the value of ISINREDIS to true, which means that all the products participating in the flash sale have been added to redis.

5. Withholding inventory:

Use the decr function of redis to reduce the quantity of goods and judge the reduced value. If the result after self-decrement is less than 0, it means that the product has been sold out, then the value of the corresponding product ID in the map is set to true, and a prompt of "It's late, the product has been sold out" is returned.

6. Determine whether the flash sale is repeated:

If the user's flash sale is successful, after the flash sale order is stored in the database, the user id and product id will be used as the key, and true will be stored in redis as the value, indicating This user has already sold this product instantly. So here we go to redis based on the user ID and product ID to determine whether the flash sale is repeated. If so, the prompt "Do not repeat the flash sale" is returned.

7. Asynchronous processing:

If the above verifications pass, then the flash sale can be processed. If you deduct inventory and create orders for every flash sale request, not only will the speed be very slow, but it may also cause the database to crash. So we can process it asynchronously, that is, after passing the above verification, the user ID and product ID will be sent to MQ as messages, and then a "queuing" prompt will be returned to the user immediately. Then consume the message on the consumer side of MQ, get the user ID and product ID, and query the inventory based on the product ID to ensure sufficient inventory again; then you can also determine whether to repeat the flash sale. After passing the judgment, operate the database, deduct inventory, and create a flash sale order. Note that deducting inventory and creating flash sale orders need to be in the same transaction.

8. Oversold problem:

The oversold problem is a negative inventory of goods. For example, if the inventory is 1, then 10 users sell instantly at the same time. When judging the inventory, they are all 1, so 10 people can place orders successfully, and the final inventory is -9. How to solve? In fact, such a problem will not occur in this system at all, because redis is used to pre-reduce inventory at the beginning, and the redis command core module is single-threaded, so it can be guaranteed not to be oversold. If redis is not used, you can also add a version field to the product. Check the version before deducting inventory each time. Add a condition to the sql for deducting inventory, that is, the version must be equal to the version just found.

 

二、核心代码

@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> seckillOver = new HashMap<integer>();
 
 // 用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 (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());
 }

}
</seckillgoods></integer></integer>
     

三、压测

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

How to implement flash sale system in redis
jmeter压测配置
How to implement flash sale system in redis
jmeter压测配置

The above is the detailed content of How to implement flash sale system in redis. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete