Redis series of directories

Redis series – distributed lock

Redis series – Cache penetration, cache breakdown, cache avalanche

Why Is Redis so fast?

Redis series — Data Persistence (RDB and AOF)

Redis series – consistent hash algorithm

Redis series – High Availability (Master slave, Sentinel, Cluster)

Redis series – Things and Optimism lock

Redis series — Geospatial: Do you have Lao Wang next door?

Bitmaps: Did you check in today?

What is a Bloom filter?!

It’s the last day of the Dragon Boat Festival, the three-day holiday goes too fast, and what’s worse, there will be six days of work next week!

At the end of the holiday, let’s talk about how Redis implemented distributed locking. This issue is also intended to fill in the holes left by the previous Redis series: Cache Penetration, Cache Breakdown, cache Avalanche.

This installment is not dedicated to distributed locking, so it will not cover the various distributed lock implementations and related comparisons, but just how to use Redis to implement distributed locking. I feel that a pit has not been filled, and here dug a pit, a variety of distributed lock implementation and related comparison after time I will kneel to fill, please give me a little more time, a little more gentle.

This is the core output, but also the interview frequency, but also the actual combat will.

What is distributed locking

First, let’s talk about a scenario where a consumer places an order on a shopping website or a cashier places an order on a POS machine. Due to network problems, how will the back-end website handle and respond after two clicks in a row? This is an issue that needs to be dealt with on the front end as well as the back end. This is mainly about the back end, which deals not only with duplicate orders, but also with idempotent problems. The idempotent problem is simply the same request, the same response, so I won’t expand here. How to handle duplicate orders?

For a small, low-traffic site with tomcat deployed, this problem can be easily implemented with the JVM’s synchronized lock. But synchronized doesn’t work when the site gets more and more traffic, and you need to scale the machine. Two consecutive requests for the same order parameter to the back-end server may be distributed to two Tomcat servers, and synchronized failure will occur.

Distributed lock is to solve the problem of resource competition when the same request is accessed concurrently when multiple machines are deployed. When the request arrives at each Tomcat, the lock must be registered in Redis first. If true is returned for successful registration, the lock is obtained and related services can continue to be processed. After the processing is complete, the lock will be released. Only one Tomcat can obtain the lock at a time. The other Tomcat that does not obtain the lock tries to obtain the lock for several times and cannot process services. Only after the tomcat that acquired the lock releases the lock can any other Tomcat acquire the lock.

Redis is used to store locks on external storage media, and ZooKeeper is used similarly. The principle is the same, but the technology selection is different.

Redis implementation

I’ll cut the crap and go straight to the code.

1.pom.xml

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> < version > 2.3.1. RELEASE < / version > < / parent > < the dependency > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>Copy the code

Note here that I’m using spring-boot. The related jar of Redis uses spring-boot-starter-data-redis.

Note that version 2 and up, there is a key difference in redis lock implementation between version 1 and version 2, which will be covered later.

2.application.yml

server:
  port: 8080

spring:
  application:
    name: java-summary
  redis:
    database: 10
    password: 123456
    timeout: 20000
    host: 127.00.1 the port:6379
    pool:
      max-idle: 20
      min-idle: 20
      max-wait: 10000
      max-active: 5000
Copy the code

3.RedisConfiguration.java

package com.wuxiaolong.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Description:
 *
 * @authorZhuge little Ape *@date* * Remove redis serialization, key-value garbled */

@Configuration
public class RedisConfiguration {

    @Bean
    @Primary
	public RedisTemplate setRedisTemplate(RedisTemplate redisTemplate) {
		RedisSerializer stringSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(stringSerializer);
		redisTemplate.setValueSerializer(stringSerializer);
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setHashValueSerializer(stringSerializer);
		returnredisTemplate; }}Copy the code

This configuration class is designed to solve the problem of garbled redis key and value serialization by setting the serialization mode to string format

4.RedisLock.java

package com.wuxiaolong.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Description:
 *
 * @authorZhuge little Ape *@dateThe 2020-06-27 * /
@Component
@Slf4j
public class RedisLock {

    @Autowired
    private RedisTemplate redisTemplate;

    /** * add element **@param key
     * @param value
     */
    public void set(String key, String value) {

        if (key == null || value == null) {
            return;
        }
        redisTemplate.opsForValue().set(key, value);
    }

    /** * Returns false if it already exists, true otherwise@param key
     * @param value
     * @return* /
    public Boolean setNx(String key, String value, Long expireTime, TimeUnit mimeUnit) {

        if (key == null || value == null) {
            return false;
        }

        // In spiring Boot 1.5 setIfAbsent, only key-value can be set. You need to set the expiration time for the key separately
        //Boolean tf = redisTemplate.opsForValue().setIfAbsent(key, value);
        //redisTemplate.expire(key, expireTime, mimeUnit);

        // In spiring Boot 2, you can directly set key-value and expiration time using the setIfAbsent of the redisTemplate
        Boolean tf =redisTemplate.opsForValue().setIfAbsent(key,value,expireTime, mimeUnit);

        return tf;


    }

    /** * get data **@param key
     * @return* /
    public Object get(String key) {

        if (key == null) {
            return null;
        }
        return redisTemplate.opsForValue().get(key);
    }

    /** * delete **@param key
     * @return* /
    public void remove(Object key) {

        if (key == null) {
            return ;
        }

        redisTemplate.delete(key);
    }

    /** * lock **@param key
     * @paramWaitTime specifies the waitTime during which multiple attempts are made to acquire the lock, after which false * is returned@paramInterval Specifies the interval at which locks are attempted@paramExpireTime Key expiration time */
    public Boolean lock(String key, Long waitTime,Long interval, Long expireTime) {

        String value = UUID.randomUUID().toString().replaceAll("-"."").toLowerCase();

        Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);

        // A successful attempt to obtain the lock was returned
        if (flag) {
            return flag;
        } else {
            // Failed to obtain

            // Present time
            long newTime = System.currentTimeMillis();

            // Wait for expiration time
            long loseTime = newTime + waitTime;

            // Repeated attempts to obtain the lock returned with success
            while (System.currentTimeMillis() < loseTime) {

                Boolean testFlag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);
                if (testFlag) {
                    return testFlag;
                }

                try {
                    Thread.sleep(interval);
                } catch (InterruptedException e) {
                    log.error("Lock acquisition exception",e); }}}return false;
    }

    /** * release lock **@param key
     * @return* /
    public void unLock(String key) {
         remove(key);
    }

    public Boolean setIfAbsent(String key, String value){
        Boolean tf =  redisTemplate.opsForValue().setIfAbsent(key, value);
        redisTemplate.expire(key, 60, TimeUnit.DAYS);
        returntf; }}Copy the code

This is the core implementation of the Redis lock. The spring-boot version problem mentioned above is the difference in setIfAbsent for redisTemplate in the setNx method.

SetIfAbsent Returns true if the key does not exist when the key or value is stored. If the key exists, return false, indicating that the lock is in use and cannot be obtained.

When acquiring a lock, there are two steps: first, store the key-value, and then set an expiration time on it to prevent deadlocks. When the spring-Boot version is 1, only the setIfAbsent(key, value) API is provided. After setting this API successfully, you need to call redistemplate. expire(key, expireTime, mimeUnit) to set the expiration time for this key. This is a two-step nonatomic operation, and if the first step succeeds and the second fails, a deadlock may occur, although the probability is very low. When the version of spring-Boot is 2, an atomic API setIfAbsent(key,value,expireTime, mimeUnit) is provided. SetIfAbsent (Key,value,expireTime, mimeUnit) saves the key-value and sets the expiration time. You are advised to use this API.

4.RedisLockTest.java

package com.wuxiaolong.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * Description:
 *
 * @authorZhuge little Ape *@dateThe 2020-06-27 * /
@RestController
@Slf4j
public class RedisLockTest {
    @Autowired
    private RedisLock redisLock;

    public static final String ORDER_LOCK_PREFIX = "order:lock:";

    @RequestMapping(value = "save/order",method = RequestMethod.POST)
    public String saveOrder(@RequestBody Map<String,String> order){

        // Get the merchant number, store number, cash register number and cashier order number to form a globally unique order number
        String orderSn = order.get("merchantSn")+order.get("storeSn")+order.get("workstationSn")+order.get("checkSn");

        // Use globally unique order numbers as distributed lock keys
        String lockKey = ORDER_LOCK_PREFIX + orderSn;

        try{
            // Lock every 100 milliseconds; Return false if the lock is not available within 1 minute; Lock expiration time 5 minutes (to prevent deadlocks)
            Boolean tf = redisLock.lock(lockKey,1L * 60 * 1000.100L.5L * 60 * 1000);

            if(tf){
                // Todo handles the business of getting the lock
                return "success";
            }else {
                // Todo handles the business when the lock is not available
                return "fail"; }}catch (Exception e){
            log.error("Abnormal Service",e);
        }finally {
            // Be sure to release the lock in finally
            redisLock.unLock(lockKey);
        }

        return "success"; }}Copy the code

This is an instance of the test class. You first need to get the globally unique order number, and then use that order number as key. At the beginning of the try block, go to Redis to obtain the lock, and do different things depending on whether the lock is obtained. You need to define waitTime,interval, and expireTime parameters based on your service requirements. Be sure to release the lock in the last finally.

Done, done!!

[Spreading knowledge and sharing value], thank you for your attention and support, I am [Zhuge Little Ape], a struggling Internet migrant worker.