SoFunction
Updated on 2024-07-16

Distributed locking based on SpringBoot + Redis implementation

As distributed architecture is now more and more prevalent, in many scenarios need to use distributed locks. Many partners for the distributed lock is not particularly understand, so specially summarized in an article, so that you can read a text to understand the past and present life of distributed locks.

Distributed locks are implemented in a variety of ways, such as based on the database, Redis, zookeeper and other implementations, the examples in this article focus on the use of Redis to achieve distributed locks.

I. What is a distributed lock

Distributed locks, i.e., locks in a distributed system, distributed locks are to control the distributed system to operate on shared resources in an orderly manner, in a monolithic application we realize the shared resource access through locks, and distributed locks, is to solve the problem of controlling the access to shared resources in a distributed system.

May be the beginning of the partners will have questions, Java multi-threaded fair locks, non-fair locks, spin locks, re-entrant locks, read-write locks, mutual exclusion locks, these have not yet understood it? How to come out of a distributed lock?

In fact, it can be understood in this way: Java's native locking is to solve the operation of shared resources under multiple threads, while distributed locking is the operation of shared resources under multiple processes. The minimum granularity of competition for shared resources in a distributed system is upgraded from threads to processes.

Distributed locks have been applied to a variety of high concurrency scenarios, canonical scenario examples include: seconds, tickets, orders, refunds, inventory and other scenarios.

II. Why Use Distributed Locks

In distributed systems, it is often necessary to coordinate their actions. If different systems or different hosts of the same system share a resource or a group of resources, then access to these resources, it is often necessary to prevent each other from interfering with each other to ensure consistency, at this time, it is necessary to use the distributed locks.

At present almost many large-scale websites and applications are distributed deployment, how to ensure data consistency in distributed scenarios has been a more important topic. In some scenarios, in order to ensure data integrity and consistency, we need to ensure that a method can only be executed by the same thread at the same time, which requires the use of distributed locks.

图片

As shown in the figure above, assuming that user A and user B purchase a certain item at the same time, after the order is created successfully, the ordering system A and the ordering system B will deduct the inventory of the item in the database at the same time. If there is no control at this time, the data update submitted by system B will overwrite the data of system A, resulting in inventory errors, overselling and other problems.

III. What conditions should a distributed lock have

Before describing how distributed locks are implemented, it is important to understand what conditions a distributed lock should have:

1, in a distributed system environment, a method can only be executed by a thread on a machine at the same time;

2. Highly available lock acquisition and lock release;

3. High-performance lock acquisition and release;

4. With reentrant characteristics;

5, with a lock failure mechanism to prevent deadlock;

6, with non-blocking lock characteristics , that is, not to obtain the lock will return directly to the lock failed to obtain .

IV. Distributed lock implementation

With the needs of business development, the original monolithic application was evolved into a distributed cluster system, due to the system is distributed on different machines, which makes the original concurrency control locking strategy fails, in order to solve this problem requires a cross-process mutual exclusion mechanism to control access to shared resources, which requires the use of distributed locks!

Distributed locks can be implemented in a variety of ways, and these distributed lock implementations are described below:

  • Database based implementation of distributed locking, (for systems with small concurrency);
  • Distributed locking based on cache (Redis, etc.) implementation, (efficient, most popular, suffers from lock timeouts);
  • Implementing distributed locks based on Zookeeper, (reliable, but not messing around with efficiency);

Although there are these three programs, but different businesses also need to choose according to their own situation, there is no best between them only more suitable!

V. Redis-based implementation of distributed locks

The use of Redis to implement distributed locks is currently a popular solution, mainly because the use of Redis to obtain locks and release locks are very efficient, the implementation is also very simple.

Realization Principle:

(1) When acquiring a lock, use setnx to add a lock, and use the expire command to add a timeout for the lock, after which the lock will be automatically released, and the value value of the lock will be a randomly generated UUID, through which it will be judged when the lock is released.

(2) When acquiring a lock, also set a timeout for acquiring the lock, if this time is exceeded, then give up acquiring the lock.

(3) When releasing the lock, determine whether it is the lock by UUID, if it is the lock, then execute delete to release the lock.

Next we will implement Redis Distributed Lock step by step.

The first step is to create a Spring Boot project and introduce relevant dependencies.

<dependency>
       <groupId></groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>commons-lang3</artifactId>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.72</version>
   </dependency>
   <dependency>
       <groupId></groupId>
       <artifactId>lombok</artifactId>
   </dependency>

The second step is to create the Redis Distributed Lock Generic Operation class, the sample code is as follows:

@Slf4j
@Component
public class RedisLockUtils {
    @Resource
    private RedisTemplate redisTemplate;
    private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();
    private static final Long SUCCESS = 1L;
    public static class LockInfo {
        private String key;
        private String value;
        private int expireTime;
        //Update time
        private long renewalTime;
        // Update interval
        private long renewalInterval;
        public static LockInfo getLockInfo(String key, String value, int expireTime) {
            LockInfo lockInfo = new LockInfo();
            (key);
            (value);
            (expireTime);
            (());
            (expireTime * 2000 / 3);
            return lockInfo;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
             = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
             = value;
        }
        public int getExpireTime() {
            return expireTime;
        }
        public void setExpireTime(int expireTime) {
             = expireTime;
        }
        public long getRenewalTime() {
            return renewalTime;
        }
        public void setRenewalTime(long renewalTime) {
             = renewalTime;
        }
        public long getRenewalInterval() {
            return renewalInterval;
        }
        public void setRenewalInterval(long renewalInterval) {
             = renewalInterval;
        }
    }
    /**
     * Update redis lock expiration time using a lua script
     * @param lockKey
     * @param value
     * @return True on success, false on failure.
     */
    public boolean renewal(String lockKey, String value, int expireTime) {
        String luaScript = "if ('get', KEYS[1]) == ARGV[1] then return ('expire', KEYS[1], ARGV[2]) else return 0 end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        ();
        (luaScript);
        List<String> keys = new ArrayList<>();
        (lockKey);
        Object result = (redisScript, keys, value, expireTime);
        ("updateredisLock expiration time:{}", result);
        return (boolean) result;
    }
    /**
     * @param lockKey Lock
     * @param value Identity of the lock (to ensure that the lock will not be released by anyone else).
     * @param expireTime The expiration time of the lock in seconds.
     * @return true for success, false for failure.
     */
    public boolean lock(String lockKey, String value, long expireTime) {
        return ().setIfAbsent(lockKey, value, expireTime, );
    }
    /**
     * redisTemplate unlock
     * @param key
     * @param value
     * @return Returns true if successful, false if not.
     */
    public boolean unlock2(String key, String value) {
        Object currentValue = ().get(key);
        boolean result = false;
        if (((currentValue)) && (value)) {
            result = ().getOperations().delete(key);
        }
        return result;
    }
    /**
     * Timing to check the redis lock expiration time.
     */
    @Scheduled(fixedRate = 5000L)
    @Async("redisExecutor")
    public void renewal() {
        long now = ();
        for (<String, LockInfo> lockInfoEntry : ()) {
            LockInfo lockInfo = ();
            if (() + () < now) {
                renewal((), (), ());
                (now);
                ("lockInfo {}", (lockInfo));
            }
        }
    }
    /**
     * Distributed locks set up separate thread pool
     * @return
     */
    @Bean("redisExecutor")
    public Executor redisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        (1);
        (1);
        (1);
        (60);
        ("redis-renewal-");
        (new ());
        return executor;
    }
}

Step 3, create a RedisTemplate configuration class, configure Redistemplate, sample code is as follows:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws Exception {
        RedisTemplate redisTemplate = new RedisTemplate();
        (redisConnectionFactory);
        // Create serialized classes
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer();
        (new StringRedisSerializer());
        (genericToStringSerializer);
        return redisTemplate;
    }
}

The fourth step, the realization of the business call, here to deduct inventory as an example, the sample code is as follows:

@RestController
public class IndexController {
    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLockUtils redisLock;
    @RequestMapping("/deduct-stock")
    public String deductStock() {
        String productId = "product001";
        ("---------------->>> Start Deducting Inventory");
        String key = productId;
        String requestId = productId + ().getId();
        try {
            boolean locked = (key, requestId, 10);
            if (!locked) {
                return "error";
            }
            // Implementation of business logic
            //("---------------->>>Execute business logic: "+appTitle);;
            int stock = (().get("product001-stock").toString());
            int currentStock = stock-1;
            ().set("product001-stock",currentStock);
            try {
                Random random = new Random();
                ((3) *1000);
            } catch (InterruptedException e) {
                ();
            }
            ("---------------->>>Deductions from stock end:current stock:" + currentStock);
            return "success,current stock:" + currentStock;
        } finally {
            redisLock.unlock2(key, requestId);
        }
    }
}

VI. Verification testing

Once the code is complete, start testing. We start two instances at the same time with port numbers: 8888 and 8889 to simulate a distributed system.

Next, we request each: http://localhost:8888/deduct-stock

and http://localhost:8889/deduct-stock, or use JMater to request both addresses separately to simulate a highly concurrent situation.

图片

As we can see from the above figure, there is no problem with inventory deduction even in case of batch request. It means that the distributed lock is working.

To this article on the implementation of distributed locks based on SpringBoot Redis article is introduced to this , more related SpringBoot Redis distributed locks content please search for my previous articles or continue to browse the following articles hope that you will support me in the future !