Use Redis to realize the seckill system

1. Introduction

1 seckill system

The seckill system refers to the sale of a certain commodity or service at a very low price within a very short period of time (usually tens of seconds). This sales method needs to ensure high concurrency and high availability, while preventing problems such as overselling and malicious attacks. The characteristic of seckill system is that a large number of users flood into the server instantly at the same time, and this type of highly concurrent read and write operations puts forward higher requirements on system performance.

2 Frequently Asked Questions

In the seckill scenario, you will encounter the following common problems:

  • High concurrency (the number of new TCP connections per second is very high)
  • Oversold (because the page refresh frequency is too fast, the quantity that the user can purchase exceeds the actual remaining quantity)
  • Malicious attack (the attacker uses robots, scripts and other means to snap up the purchase, thereby paralyzing the system)

2. Introduction to Redis

Redis (Remote Dictionary Server) is an open source, network-enabled, memory-based, key-value store database. It supports a variety of data structures, such as strings (String), hashes (Hash), lists (List), collections (Set) and ordered collections (Sorted Set). The access speed of Redis is very fast, and it will not affect system performance at all when storing massive data, so Redis is widely used in high-concurrency Internet projects.

1 Basic concepts of Redis

  • Single-threaded: Redis uses a single-threaded model to work, avoiding the context switching overhead caused by thread switching, so it is very fast.
  • Persistent storage: Redis supports RDB persistence and AOF persistence, which can save data in memory to disk to prevent data loss when the server crashes.
  • Rich data types: Redis supports multiple data types, such as strings, lists, sets, hash tables, etc., to facilitate users to choose the appropriate data type according to different business needs.
  • High performance: Redis is a memory-based database with very fast read and write speeds. Because it is memory-based, Redis has limited storage capacity and is not suitable for storing large amounts of data.
  • Distributed: Redis supports distributed clusters, which can store data in pieces, which improves the concurrent processing capability of the system, increases the scalability of the system, and ensures high availability.

2 Advantages of Redis as a seckill system

  • Efficient reading and writing: The reading and writing performance of Redis is very fast, which can meet the high concurrent reading and writing requirements of the seckill system and ensure the efficient operation of the system.
  • Data persistence: Redis supports persistent storage of data, which can save data in memory to disk, prevent data loss when the server crashes, and reduce the impact on the system.
  • Distributed features: Redis supports distributed clusters, which can store the cache in fragments to avoid excessive pressure on a single node and ensure the scalability and high availability of the system.
  • Atomic operation: Redis supports the atomicity of multiple operations, such as transaction processing, CAS, etc., which ensures data consistency and security, and effectively prevents problems such as overselling.

3. Application of Redis in seckill system

1 Applications in data storage

Redis's fast read and write operations make it the first choice for the second-level cache, and it is often used to cache data that does not change frequently or is not frequently used. In the seckill system, Redis can be used to cache information such as product names, inventory quantities, and whether they are sold out, reducing database access, and improving data read and write efficiency and system response speed.

// jedis 是 Redis 的 Java 客户端

// 设置 key-value 对
jedis.set("product:001:name", "iPhone 12");
jedis.set("product:001:stock", "1000");

// 获取 key-value 对
String name = jedis.get("product:001:name");
String stock = jedis.get("product:001:stock");

2 Application in distributed lock

In order to prevent overselling of goods in the flash sale scenario, it is usually necessary to introduce a distributed lock mechanism. Redis provides a simple and effective way to implement distributed locks. Locks are realized by preempting keys, which avoids the situation where multiple systems modify data at the same time.


// 尝试获取锁
boolean lockResult = jedis.setnx("lock:product:001", "value");
if(lockResult) {
    
    
    // 获取锁成功,执行业务逻辑...
    // 释放锁
    jedis.del("lock:product:001");
} else {
    
    
    // 获取锁失败,等待重试...
}

3 Application in message queue

In the seckill scenario, the system needs to process a large number of concurrent requests. In order to avoid system crashes caused by requests flooding into the server in an instant, message queues can be used to queue user requests, which can effectively relieve system pressure.


// 将秒杀请求加入消息队列
jedis.lpush("seckill:requests", "request001");

// 从消息队列中获取请求
String request = jedis.brpop("seckill:requests", 10).get(1);

4. Redis spike system design

1 Database table design

The seckill system generally requires two tables: product table and order table. The product table is used to store product information, and the order table is used to store order information.

Product table design

The following fields need to be included in the product table:

field name type describe
id int commodity id
name varchar product name
description text product description
price decimal Commodity price
stock int Commodity stocks

Order form design

The following fields need to be included in the order form:

field name type describe
id int order id
user_id int user id
goods_id int commodity id
create_time datetime creation time
status int Order status, 0 means unpaid, 1 means paid

2 interface design

The seckill system requires the following interfaces:

  • Commodity list interface: used to obtain the commodity list.
  • Commodity details interface: used to obtain the detailed information of the specified commodity.
  • Order interface: used to place an order.
  • Order list interface: used to obtain the order list of the corresponding user.

3 queue design

The seckill system needs a queue to process the order request, and Redis can be used as the queue. Use the list data structure as a queue in Redis to run multiple identical consumer programs under multiple servers to achieve distributed processing of order requests.

4 Redis optimization strategy

In order to ensure the high concurrency and performance of the seckill system, Redis needs to be optimized. Optimization strategies include:

  • Increase the memory size of Redis to cache more product and order information.
  • Reasonably set the expiration time of Redis to prevent the data in Redis from occupying memory all the time.
  • Use Redis cluster mode or master-slave replication mode to improve the availability and performance of Redis.

5. Implementation process of seckill system

1 commodity initialization

In the seckill system, product initialization is first required. The specific implementation process is as follows:

// 定义商品实体类
public class Goods {
    
    
  private int id;
  private String name;
  private int stock;
  private double price;
  // 省略 getter 和 setter 方法
}

// 在系统启动时,从数据库中读取所有秒杀商品信息
List<Goods> goodsList = goodsDAO.queryAllSeckillGoods();
for (Goods goods : goodsList) {
    
    
  // 将商品信息存入到 Redis 中,以便后续操作使用
  redisService.set("seckill:good:" + goods.getId(), JSON.toJSONString(goods));
  // 将商品库存数量存入到 Redis 中,以便进行库存的修改操作
  redisService.set("seckill:stock:" + goods.getId(), goods.getStock());
}

5.2 Front-end page current limit

In the seckill system, in order to avoid a system crash caused by a large number of users accessing in an instant, it is necessary to limit the flow of the front-end page. The specific implementation process is as follows:

// 在前端页面中加入验证码或者滑动验证等机制
public class SeckillController {
    
    
  @PostMapping("/seckill")
  public String seckill(@RequestParam("goodsId") int goodsId,
                        @RequestParam("userId") int userId,
                        @RequestParam("verifyCode") String verifyCode) {
    
    
    // 验证码通过之后再执行秒杀操作
    if (verifyCodeIsValid(userId, verifyCode)) {
    
    
      // 秒杀操作
      seckillService.seckill(goodsId, userId);
    }
  }
}

5.3 Backend request interface current limiting

In the seckill system, it is also necessary to limit the flow of the back-end request interface to avoid malicious attacks. The specific implementation process is as follows:

// 使用限流工具对后端接口进行限流
public class SeckillController {
    
    
  @PostMapping("/seckill")
  public String seckill(@RequestParam("goodsId") int goodsId,
                        @RequestParam("userId") int userId) {
    
    
    if (rateLimiter.tryAcquire()) {
    
     // 使用 Guava RateLimiter 进行限流
      // 秒杀操作
      seckillService.seckill(goodsId, userId);
    } else {
    
    
      return "请求过于频繁,请稍后再试!";
    }
  }
}

5.4 Distributed locks control global uniqueness

In the seckill system, because multiple users access the same product at the same time, it is necessary to lock the product to ensure global uniqueness. The specific implementation process is as follows:

// 使用 Redis 的分布式锁实现秒杀商品的唯一性
public class SeckillServiceImpl implements SeckillService {
    
    

  @Override
  public void seckill(int goodsId, int userId) {
    
    
    // 加锁操作
    String lockKey = "seckill:lock:" + goodsId;
    String requestId = UUID.randomUUID().toString();
    long expireTime = 3000; // 锁过期时间设置为 3 秒钟
    boolean isSuccess = redisService.tryLock(lockKey, requestId, expireTime);
    if (isSuccess) {
    
    
      try {
    
    
        // 秒杀操作
        int stock = redisService.get("seckill:stock:" + goodsId, Integer.class);
        if (stock > 0) {
    
    
          redisService.decr("seckill:stock:" + goodsId); // 减库存
          seckillDAO.insertOrder(goodsId, userId); // 写入订单记录
          notificationService.sendSeckillSuccessMsg(userId, goodsId); // 发送通知消息
        }
      } finally {
    
    
        // 释放锁操作
        redisService.releaseLock(lockKey, requestId);
      }
    }
  }

}

5.5 Redis reduces inventory

The operations on commodities in the seckill system are all obtained and modified based on Redis, including the inventory quantity of commodities. The specific implementation process is as follows:

// Redis 减库存操作
public class SeckillServiceImpl implements SeckillService {
    
    

  @Override
  public void seckill(int goodsId, int userId) {
    
    
    // 加锁和减库存操作
    int stock = redisService.get("seckill:stock:" + goodsId, Integer.class);
    if (stock > 0) {
    
    
      redisService.decr("seckill:stock:" + goodsId);
      // 省略其他业务逻辑操作
    }
  }
  
}

5.6 MySQL write order records

In the seckill system, the order information of the successful seckill needs to be recorded in the MySQL database. The specific implementation process is as follows:

// MySQL 写入订单记录操作
public class SeckillDAOImpl implements SeckillDAO {
    
    

  @Override
  public void insertOrder(int goodsId, int userId) {
    
    
    String sql = "INSERT INTO seckill_order (goods_id, user_id, create_time) VALUES (?, ?, ?)";
    jdbcTemplate.update(sql, goodsId, userId, new Date());
  }
  
}

5.7 The message notifies the user that the seckill is successful

In the seckill system, the user can be notified of the success of the seckill through a message queue or other means. The specific implementation process is as follows:

// 消息通知用户秒杀成功
public class NotificationServiceImpl implements NotificationService {
    
    

  private static final Logger logger = LoggerFactory.getLogger(NotificationServiceImpl.class);

  @Override
  public void sendSeckillSuccessMsg(int userId, int goodsId) {
    
    
    // 使用消息队列对用户进行通知
    Message message = new Message();
    message.setUserId(userId);
    message.setGoodsId(goodsId);
    rocketMQTemplate.convertAndSend("seckill-success-topic", message);
    logger.info("通知消息已发送:{}", message);
  }
  
}

6. Security Policy

The seckill system is a high-concurrency business. In order to ensure the security and stability of the system, while using Redis as a cache, it is necessary to design security policies for the following two aspects:

1 Prevent overselling

In the seckill activity, there is only a limited quantity of a product, and when this quantity is exceeded, it can no longer be sold. At this time, measures to prevent overselling must be taken.

Method to realize

  • Based on the single-threaded mechanism of Redis, the inventory reduction operation is performed atomically, and the corresponding commodity id needs to be locked.
  • For the case of locking commodities, use the distributed lock mechanism of Redis. In this way, only one request can successfully request the inventory lock at a time, and the time for holding the lock should be as short as possible.

The following is the Java code implementation:

public boolean decrementStock(String key) {
    
    
    String lockKey = "LOCK_" + key;
    try (Jedis jedis = jedisPool.getResource()) {
    
    
        //加锁
        String lockValue = UUID.randomUUID().toString();
        String result;
        while (true) {
    
    
            result = jedis.set(lockKey, lockValue, "NX", "PX", 3000);
            if ("OK".equals(result)) {
    
    
                break;
            }
            Thread.sleep(100);
        }
        //判断是否加锁成功
        if (!lockValue.equals(jedis.get(lockKey))) {
    
    
            return false;
        }

        try {
    
    
            //操作库存
            int stock = Integer.parseInt(jedis.get(key));
            if (stock > 0) {
    
    
                jedis.decr(key);
                return true;
            }
            return false;
        } finally {
    
    
            //释放锁
            jedis.del(lockKey);
        }
    } catch (Exception e) {
    
    
        log.error("decrementStock failed, key:{}", key, e);
        return false;
    }
}

2 Prevent malicious brushing

Malicious users simulate a large number of requests through programs, causing the server to fail to respond to requests from normal users. In order to solve this problem, it is necessary to add a strategy to prevent malicious swiping.

Method to realize

  • Limit the flow of each user IP and set the number of requests per minute.
  • Set up man-machine verification, such as graphic verification code or SMS verification, so that malicious users will give up the attack because the cost is too high.

The following is the Java code implementation:

public boolean checkUserRequest(String ip) {
    
    
    // 检查ip对应的请求数是否超过最大允许请求次数
    String requestCountKey = "REQUEST_COUNT_" + ip;
    try (Jedis jedis = jedisPool.getResource()) {
    
    
        long currentCount = jedis.incr(requestCountKey);
        if (currentCount == 1) {
    
    
            // 第一次计数,设置过期时间为60s
            jedis.expire(requestCountKey, 60);
        }

        if (currentCount > maxRequestPerMinute) {
    
    
            // 超过最大允许请求次数,返回false
            return false;
        }
        return true;
    }
}

7. Deployment plan

1 Security Optimization

  • Deploy to a dedicated CDN cache server to reduce server bandwidth pressure and protect servers and databases.
  • Set up a server firewall to prohibit external access to sensitive resources such as Redis and databases.
  • Enable Redis persistence to prevent memory data loss or unexpected downtime.

2 Performance optimization

  • Improve Redis performance, adopt cluster mode, add machines and configure Redis related parameters, etc.
  • Use an efficient cache query method to avoid frequent database queries, such as using the hash table that comes with Redis to store flash sale product information.

Guess you like

Origin blog.csdn.net/u010349629/article/details/130904369