This is the 17th day of my participation in the Gwen Challenge.More article challenges

Preface:

With the development of the Internet, the problems existing in the single architecture have also been exposed one by one, such as high deployment cost, slow iteration speed, not easy to expand and other problems. Micro-service architecture has also emerged. The emergence of micro-service is not to replace the original single architecture, but to solve the problems related to the single architecture. Microservices are not meant to replace one application architecture, but rather they are better suited to a business scenario or better at solving a problem.

However, the emergence of micro-services will also bring some related problems, such as:

  • Distributed problems are more complicated: because distributed problems already exist, such as distributed locks, distributed transactions, data consistency and other problems, with the refinement of services, naturally make distributed problems more complicated;
  • Increased difficulty in troubleshooting: When there are many micro-services, if there is a problem, it needs to be located clearly. It is more difficult than locating a single problem, but you can use tracking tools and log analysis tools to assist.
  • Overall project quality control is more difficult: in terms of performance, multi-service interaction consumes network IO; A single service fails, which can lead to a system avalanche if not handled properly. Generally need to do fusing, isolation, current limit and other related protection;

Today we mainly talk about the distributed problem brought by micro-services distributed lock implementation and use scenarios

There will be situations in our daily work where multiple processes must monopolize shared resources in a mutually exclusive manner, where distributed locking is most straightforward. You can go back to an article that I wrote earlier where the common idempotent component implementation has some associations;

Distributed lock implementation:

  • Redis implements distributed locking
  • ZK implements distributed locking
  • Mysql implements distributed locking

In fact, it is to choose a common storage middleware to monitor the state;

Conditions for distributed locks:

  1. Mutual exclusion: In distributed high concurrency conditions, we most need to ensure that,Only one thread can acquire the lock at a timeThis is the most basic point.
  2. Deadlock prevention: In distributed and high concurrency conditions, for example, when a thread obtains a lock, it cannot execute the command to release the lock before it has time to release the lock because of system failure or other reasons. As a result, other threads cannot obtain the lock, resulting in deadlock. Therefore, it is necessary to set the effective time of the lock to ensure that the system can actively release the lock within a certain period of time after a fault occurs.Avoid causing deadlocksIn the case.
  3. Performance: For shared resources with heavy traffic, reduce the lock waiting time to avoid a large number of threads blocking. So in the lock design, need to consider two points. (1)The granularity of the lock should be as small as possible(2)Lock as small as possible
  4. Reentrant: The same thread can repeatedly acquire the lock on the same resource. Reentrant locking is very efficient for resource utilization.

Redis implements distributed locks:

To import Maven dependencies, add the following code to the POM.xml file:

Clients </groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>Copy the code

We use Redis command setnx:

SETNX key Value // Will be added if and only if it does not existCopy the code

The preceding command is easy to cause deadlocks, so you need to set the validity period:

SETEX key seconds value // Associates the value value with the key and sets the lifetime of the key to seconds.Copy the code

If the key already exists, the setex command overwrites the old value.

Setex is an atomic operation in which the associative value and the set lifetime are performed at the same time. Do not worry about the failure to complete the setting value setting time

Let’s walk through the Java code using Jedis:

Lock:

private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * @param lockKey lock * @param requestId requestId * @param expireTime expiration time * @return whether the request was successfully obtained */ public boolean tryLock(String lockKey, String requestId, int expireTime) { try (Jedis jedis = initJedis()) { lockKey = "lock:" + lockKey; String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } catch (Exception e) { log.error("Redis tryLock is fail", e); return false; }}Copy the code
  • If there is no lock (key does not exist), the lock operation is performed and a validity period is set for the lock. Value indicates the client that locks the lockreturn true ;
  • No operation is performed on an existing lockreturn false;

Unlock:

private static final Long RELEASE_SUCCESS = 1L; Public Boolean unLock(String lockKey,String requestId) {try (Jedis Jedis) = initJedis()) { lockKey = "lock:" + lockKey; String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false; } catch (Exception e) { log.error("Redis tryLock is fail", e); return false; }}Copy the code

Check whether the value of the lock is equal to that of requestId. If so, delete the lock.

  1. Lua code is executed as a command when eval is executed, and Redis does not execute any other commands until eval is executed
  2. Lua ensures that the above operations are atomic

Business implementation:

/** * distributed locking logic ** @param key * @return */ private Boolean saveCacheLock(String key) {String requestId = UUID.randomUUID().toString(); try { if (! Redisclient. tryLock(key, requestId, expireMsecs)) {log.info(" failed to obtain lock, key = {} requestId = {}", key, requestId); return false; //todo business code} finally {redisclient.unlock (key, requestId); } return true; }Copy the code

Of course, we can also choose Redisson, we do not need to repeat the wheel, source analysis even, interested you can search for a look, the bottom is also through Lua to ensure atomic;

Github has a detailed tutorial on redisson

Import POM dependencies:

<! Maven--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.10.4</version> </dependency>Copy the code

Put a utility class and use it directly:

import java.util.concurrent.TimeUnit; import com.caisebei.aspect.lock.springaspect.lock.DistributedLocker; import org.redisson.api.RLock; @author caisebei ** / public class RedissLockUtil {private static DistributedLocker redissLock; public static void setLocker(DistributedLocker locker) { redissLock = locker; } /** * @param lockKey * @return */ public static RLock lock(String lockKey) {return redisslock. lock(lockKey); } @param lockKey */ public static void unlock(String lockKey) {redisslock.unlock (lockKey); } @param lock */ public static void unlock(RLock lock) {redissLock. Unlock (lock); } /** * @param lockKey * @param timeout Timeout unit: Seconds */ public static RLock lock(String lockKey, int timeout) {return redissLock. Lock (lockKey, timeout); } /** * @param lockKey * @param unit Time unit * @param timeout time unit */ public static RLock lock(String lockKey, String lockKey) TimeUnit unit ,int timeout) { return redissLock.lock(lockKey, unit, timeout); } @param lockKey * @param waitTime maximum waitTime * @param leaseTime automatically release lock * @return */ public static Boolean  tryLock(String lockKey, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime); } /** ** attempt to obtain the lock * @param lockKey * @param unit time unit * @param waitTime maximum waitTime * @param leaseTime automatic release time after lock * @return */ public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, unit, waitTime, leaseTime); }}Copy the code

The Redisson environment needs to be configured

Config config = new Config(); The config. UseSingleServer (.) setAddress (" redis: / / 127.0.0.1:5379 "). The setPassword (" 123456 "). SetDatabase (0); RedissonClient redissonClient = Redisson.create(config);Copy the code

Ok! A simple Redis distributed lock is achieved, we can also according to their own needs to do optimization, I hope to help you, there are wrong places I hope we can put forward, grow together;

Neat makes for great code, and there’s only so much detail