How to use Redis distributed lock to handle high concurrency?

I. Adding project dependencies

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

Configuration files

spring:
  #Redis configuration
  redis:
    host: localhost
    password: 123456

3. Service Layer of Simulated Buying Commodities

  • Interface class
/**
 * @author Lu Sheng Liu
 */
public interface SellService {

    /**
     * Buy goods according to commodity ID and return the details of the snap-up.
     * @param productId
     * @return
     */
    String orderGoods(String productId);

    /**
     * Search for details of rush buying according to commodity ID
     * @param productId
     * @return
     */
    String queryGoods(String productId);
}
  • Implementation class
@Service
@Slf4j
/**
 * @author Lu Sheng Liu
 */
public class SellServiceImpl implements SellService {

    @Autowired
    private RedisLock redisLock;

    /**
     Set timeout time to 10 seconds
     */
    private static final int TIMEOUT = 10*1000;

    /**
     * For example, on National Day, 1000 books were sold in stock.
     */

    /**
     * Stock
     */
    static Map<String, Integer> products;
    /**
     * Inventory Balance
     */
    static Map<String, Integer> stock;
    /**
     * Buying Successful Information
     */
    static Map<String, String> orders;

    static {
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        products.put("book", 1000);
        stock.put("book", 1000);
    }

    public String queryMap(String productId){
        return "National Day book sale, inventory " + products.get(productId) + " Part, cash surplus " + stock.get(productId) + " Parts have been snapped up " + orders.size() + " piece";
    }
    
    @Override
    public String orderGoods(String productId) {
        //Get the commodity margin first
        int number = stock.get(productId);
        if(number == 0){
            throw new RuntimeException("The goods have been snapped up. Please come again next time. Thank you for your understanding....");
        }else {
            //Simulated order (different users have different ID s)
            orders.put(String.valueOf(UUID.randomUUID()), productId);
            //Reduce stock
            number = number - 1;
            //Analog delay
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stock.put(productId, number);
        }
        log.info("Total panic buying {} Details of snap-up:{}", orders.size(), orders);
        //Return to the details of the snap-up of merchandise
        return this.queryMap(productId);
    }

    @Override
    public String queryGoods(String productId) {
        return this.queryMap(productId);
    }
}

IV. Controller Layer

/**
 * @author Lu Sheng Liu
 */
@RestController
public class SellController {

    @Autowired
    private SellService sellService;

    /**
     * Purchase according to commodity ID
     * @param productId
     * @return Details of commodity rush
     */
    @GetMapping("/order/{productId}")
    public String sellGoods(@PathVariable String productId){
        return sellService.orderGoods(productId);
    }

    /**
     * Query margin based on commodity ID
     * @param productId
     * @return Details of commodity rush
     */
    @GetMapping("/query/{productId}")
    public String queryGoods(@PathVariable String productId){
        return sellService.queryGoods(productId);
    }
}

5. Simulated high concurrency

  • Using Apache ab to simulate high concurrency
ab -n 500 -c 80 http://localhost:8080/order/book

Six, result

7. Using Redis Distributed Lock to Solve High Concurrency Problem

1. Implementing Redis Distributed Lock

/**
 * @author Lu Sheng Liu
 */
@Component
@Slf4j
public class RedisLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * Lock up
     * @param key
     * @param value Current time + timeout time
     * @return
     */
    public boolean lock(String key, String value){
        if(redisTemplate.opsForValue().setIfAbsent(key, value)){
            return true;
        }
        String currentValue = redisTemplate.opsForValue().get(key);
        //If the lock expires
        if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){
            //Get the time of the last lock
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if(!StringUtils.isEmpty(oldValue) && currentValue.equals(oldValue)){
                return true;
            }
        }
        return false;
    }

    /**
     * Unlock
     * @param key
     * @param value Current time + timeout time
     */
    public void unlock(String key, String value){
        try{
            String currentValue = redisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            log.error("[Redis Distributed Lock] Unlock exception {}", e.getMessage());
        }
    }
}

2. Using Distributed Lock to Handle Service Layer Method

/**
 * The first method, synchronized lock mechanism, solves the problem of oversold caused by high concurrency, but greatly reduces the efficiency. It is not recommended to use.
 * The second method uses Redis distributed lock to solve the problem of oversold caused by high concurrency and is relatively efficient.
 */
@Override
public String orderGoods(String productId) {
    //Lock up
    Long time = System.currentTimeMillis() + TIMEOUT;
    //Failure to lock indicates that someone is using it.
    if(!redisLock.lock(productId, String.valueOf(time))){
        log.info("The rush to buy failed. Please try again....");
        //return null;
        throw new RuntimeException("The server seems to have fallen asleep just now. Please try again....");
    }
    //Get the commodity margin first
    int number = stock.get(productId);
    if(number == 0){
        throw new RuntimeException("The goods have been snapped up. Please come again next time. Thank you for your understanding....");
    }else {
        //Simulated order (different users have different ID s)
        orders.put(String.valueOf(UUID.randomUUID()), productId);
        //Reduce stock
        number = number - 1;
        //Analog delay
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stock.put(productId, number);
    }
    log.info("Total panic buying {} Details of snap-up:{}", orders.size(), orders);
    //Unlock
    redisLock.unlock(productId, String.valueOf(time));
    //Return to the details of the snap-up of merchandise
    return this.queryMap(productId);
}

8. Simulated high concurrency

  • Using Apache ab to simulate high concurrency
ab -n 500 -c 80 http://localhost:8080/order/book

Nine, result

  • Browser Display

  • Console Printing