Second kill system learning notes, limited time rush purchase, and the rush purchase interface is hidden

Implementation of limited time rush purchase

Use Redis to record the time of second kill goods and reject requests that have expired!

Put the second kill goods into Redis and set the expiration time

Use String type to set a certain expiration time in the form of kill + commodity id as key and commodity id as value (180 seconds here)

Introducing Redis dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Configure Redis

spring.redis.database=0
spring.redis.port=6379
spring.redis.host=localhost

Introduce the StringRedisTemplate object into the service

@Autowired
private StringRedisTemplate stringRedisTemplate;

During second kill, first judge whether the goods of second kill have expired. If not, rush purchase can be carried out.

@Override
public int kill(Integer id) {
    //Verify whether the second kill commodity times out
    if (!stringRedisTemplate.hasKey("kill" + id)) {
        throw new RuntimeException("The rush buying activity of current goods has ended");
    }

    //Verify inventory
    Stock stock = checkStock(id);
    //Update inventory
    updateSale(stock);
    //Create order
    return createOrder(stock);
}

Hiding of snap up interface

If the interface is not hidden, the user can get the connection of our rush purchase interface when entering the console in the client browser, write a crawler code, and directly request the interface to complete the order in the code before the second kill, which will cause harm. Therefore, the interface needs to be hidden.

  • Each time you click the second kill button, first obtain a second kill verification value from the server (determine whether the second kill time is reached in the interface).
  • Redis caches the verification value with the cached user ID and commodity ID as the key and the seckill address as the value.
  • When users request second kill products, they should bring the second kill verification value for verification.

Add database table User

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
  `name` varchar(80) DEFAULT NULL COMMENT 'user name',
  `password` varchar(40) DEFAULT NULL COMMENT 'User password',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

Create User entity class

@Data
public class User {
    private Integer id;
    private String name;
    private String password;
}

Create UserDao and UserDaoMapper

@Mapper
public interface UserDao {
    User findById(Integer id);
}
<mapper namespace="com.lany.miaosha.dao.UserDao">
    <select id="findById" parameterType="Integer" resultType="User">
        select id, name, password
        from miaosha.user
        where id = #{id}
    </select>
</mapper>

Write business code

@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private StockDao stockDao;

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private UserDao userDao;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Override
    public String getMd5(Integer id, Integer userid) {
        //Verify the legitimacy of users
        User user = userDao.findById(userid);
        if (user == null) throw new RuntimeException("User information does not exist!");
        log.info("User information:[{}]", user.toString());
        //Legal line of inspection
        Stock stock = stockDao.checkStock(id);
        if (stock == null) throw new RuntimeException("Illegal commodity information!");
        log.info("Commodity information:[{}]", stock.toString());
        //Generate hashkey
        String hashKey = "KEY_" + userid + "_" + id;
        //Generate md5 / / here! QS # is a randomly generated salt
        String key = DigestUtils.md5DigestAsHex((userid + id + "!Q*jS#").getBytes());
        stringRedisTemplate.opsForValue().set(hashKey, key, 3600, TimeUnit.SECONDS);
        log.info("Redis write in:[{}] [{}]", hashKey, key);
        return key;
    }
}

Write controller code and generate verification code

@RequestMapping("stock")
@RestController
@Slf4j
public class StockController {

    @Autowired
    private OrderService orderService;
//Method of generating md5 value
@RequestMapping("md5")
public String getMd5(Integer id, Integer userid) {
    String md5;
    try {
        md5 = orderService.getMd5(id, userid);
    } catch (Exception e) {
        e.printStackTrace();
        return "obtain md5 fail: " + e.getMessage();
    }
    return "obtain md5 Information is: " + md5;
}

The foreground generates a string of verification codes by calling stock/md5 and stores them in Redis, and then carries the verification for second kill.

Write second kill interface

OrderController class

//Develop a second kill method optimistic lock to prevent oversold + token bucket algorithm to limit current
@GetMapping("killtokenmd5")
public String killtoken(Integer id,Integer userid,String md5) {
    System.out.println("Second kill commodity id = " + id);
    //Current limiting measures for adding token bucket
    if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) {
        log.info("Discard request: Rush purchase failed,The current second kill activity is too hot,Please try again");
        return "Rush purchase failed,The current second kill activity is too hot,Please try again!";
    }
    try {
        //Call the seckill business according to the seckill commodity id
        int orderId = orderService.kill(id,userid,md5);
        return "Second kill success,order id by: " + String.valueOf(orderId);
    } catch (Exception e) {
        e.printStackTrace();
        return e.getMessage();
    }
}

OrderServiceImpl class

@Override
public int kill(Integer id, Integer userid, String md5) {

    //Verify whether the second kill commodity in redis times out
    //        if(!stringRedisTemplate.hasKey("kill"+id))
    //            throw new RuntimeException("the rush buying activity of the current commodity has ended ~ ~);

    //Verify the signature first
    String hashKey = "KEY_" + userid + "_" + id;
    String s = stringRedisTemplate.opsForValue().get(hashKey);
    if (s == null) throw new RuntimeException("No verification signature is carried,Illegal request!");
    if (!s.equals(md5)) throw new RuntimeException("The current request data is illegal,Please try again later!");

    //Verify inventory
    Stock stock = checkStock(id);
    //Update inventory
    updateSale(stock);
    //Create order
    return createOrder(stock);
}

Single user restricted access frequency

Redis can be used to make access statistics for each user, or even bring a commodity id to make access statistics for a single commodity.

When the user applies for an order, check whether the user's access times exceed the specified access times. If they exceed the specified access times, they will not be allowed to place an order.

controller code

//Develop a second kill method optimistic lock to prevent oversold + token bucket algorithm to limit current
@GetMapping("killtokenmd5limit")
public String killtokenlimit(Integer id,Integer userid,String md5) {
  //Current limiting measures for adding token bucket
  if (!rateLimiter.tryAcquire(3, TimeUnit.SECONDS)) {
    log.info("Discard request: Rush purchase failed,The current second kill activity is too hot,Please try again");
    return "Rush purchase failed,The current second kill activity is too hot,Please try again!";
  }
  try {
    //Add single user limit call frequency
    int count = userService.saveUserCount(userid);
    log.info("The number of user visits up to this time is: [{}]", count);
    boolean isBanned = userService.getUserCount(userid);
    if (isBanned) {
      log.info("Purchase failed,Frequency limit exceeded!");
      return "Purchase failed, frequency limit exceeded!";
    }
    //Call the seckill business according to the seckill commodity id
    int orderId = orderService.kill(id,userid,md5);
    return "Second kill success,order id by: " + String.valueOf(orderId);
  } catch (Exception e) {
    e.printStackTrace();
    return e.getMessage();
  }
}

Service interface and Implementation

public interface UserService {
    //Write user access times to redis
    int saveUserCount(Integer userId);
    //Judge the number of calls per unit time
    boolean getUserCount(Integer userId);
}

realization

@Service
@Transactional
@Slf4j
public class UserServiceImpl  implements UserService{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public int saveUserCount(Integer userId) {
        //Generate key s for the number of calls according to different user IDs
        String limitKey = "LIMIT" + "_" + userId;
        //Gets the number of calls to the specified key in redis
        String limitNum = stringRedisTemplate.opsForValue().get(limitKey);
        int limit =-1;
        if (limitNum == null) {
            //The first call is put into redis and set to 0
            stringRedisTemplate.opsForValue().set(limitKey, "0", 3600, TimeUnit.SECONDS);
        } else {
            //Not the first call, every time + 1
            limit = Integer.parseInt(limitNum) + 1;
            stringRedisTemplate.opsForValue().set(limitKey, String.valueOf(limit), 3600, TimeUnit.SECONDS);
        }
        return limit;//Returns the number of calls
    }

    @Override
    public boolean getUserCount(Integer userId) {
        String limitKey = "LIMIT"+ "_" + userId;
        //The number of calls in the redis is obtained from the number of calls to the library user key.
        String limitNum = stringRedisTemplate.opsForValue().get(limitKey);
        if (limitNum == null) {
            //If it is empty, the key will be discarded directly, indicating that an exception occurred
            log.error("The user did not access the application verification value record, which is suspected to be abnormal");
            return true;
        }
        return Integer.parseInt(limitNum) > 10; //false means no more than true means more than
    }
}

Reference article: programming bad people

Keywords: Redis Cache

Added by Iasonic on Fri, 05 Nov 2021 22:38:15 +0200