


Let's talk about how to implement distributed locks in Redis that support almost all locking scenarios.
This article will introduce you to the implementation method of Redis distributed lock. I hope it will be helpful to you!
Hello everyone, today I will share with you the multi-type lock implemented by redisson, the implementation of redis distributed lock that supports almost all locking scenarios, and also supports small MQ and redis various data operations. [Related recommendations: Redis video tutorial]
Theoretical part
In the previous article, two methods of implementing distributed locks through redis were introduced The methods are:
Through the command that comes with redis: setNX
Through the redis client: redisson
The author recommends using the redisson client, because redisson supports more lock types, such as interlock, red lock, read-write lock, fair lock, etc., and the implementation of redisson is more Simple, developers only need to call the corresponding API
, and do not need to worry about the underlying locking process and the atomicity
issue of unlocking.
In the Redis distributed lock, redisson's simple implementation of various lock types is listed, that is, programmatic implementation. Such an implementation can fully meet our daily development needs, but its shortcomings are also obvious.
For example:
- There are too many codes embedded, which is not elegant enough
- Duplicate code
- The use of lock parameters is not intuitive
- It is easy to forget the unlocking steps
Students who have used Spring must have Knowing the @Transactional annotation, Spring supports both programmatic transactions and annotated (declarative) transactions.
Can we also refer to such an implementation?
The answer is: completely OK!
AOP
is specialized in doing this kind of thing.
Practical part
1. Introduction of redisson dependency
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.2</version> </dependency>Copy to clipboardErrorCopied
2. Custom annotations
/** * 分布式锁自定义注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Lock { /** * 锁的模式:如果不设置自动模式,当参数只有一个.使用 REENTRANT 参数多个 MULTIPLE */ LockModel lockModel() default LockModel.AUTO; /** * 如果keys有多个,如果不设置,则使用 联锁 * * @return */ String[] keys() default {}; /** * key的静态常量:当key的spel的值是LIST、数组时使用+号连接将会被spel认为这个变量是个字符串,只能产生一把锁,达不到我们的目的, * 而我们如果又需要一个常量的话。这个参数将会在拼接在每个元素的后面 * * @return */ String keyConstant() default ""; /** * 锁超时时间,默认30000毫秒(可在配置文件全局设置) * * @return */ long watchDogTimeout() default 30000; /** * 等待加锁超时时间,默认10000毫秒 -1 则表示一直等待(可在配置文件全局设置) * * @return */ long attemptTimeout() default 10000; }
3. Constant class
/** * Redisson常量类 */ public class RedissonConst { /** * redisson锁默认前缀 */ public static final String REDISSON_LOCK = "redisson:lock:"; /** * spel表达式占位符 */ public static final String PLACE_HOLDER = "#"; }
4. Enumeration
/** * 锁的模式 */ public enum LockModel { /** * 可重入锁 */ REENTRANT, /** * 公平锁 */ FAIR, /** * 联锁 */ MULTIPLE, /** * 红锁 */ RED_LOCK, /** * 读锁 */ READ, /** * 写锁 */ WRITE, /** * 自动模式,当参数只有一个使用 REENTRANT 参数多个 RED_LOCK */ AUTO }
5. Custom exception
/** * 分布式锁异常 */ public class ReddissonException extends RuntimeException { public ReddissonException() { } public ReddissonException(String message) { super(message); } public ReddissonException(String message, Throwable cause) { super(message, cause); } public ReddissonException(Throwable cause) { super(cause); } public ReddissonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }
6. AOP aspects
/** * 分布式锁aop */ @Slf4j @Aspect public class LockAop { @Autowired private RedissonClient redissonClient; @Autowired private RedissonProperties redissonProperties; @Autowired private LockStrategyFactory lockStrategyFactory; @Around("@annotation(lock)") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable { // 需要加锁的key数组 String[] keys = lock.keys(); if (ArrayUtil.isEmpty(keys)) { throw new ReddissonException("redisson lock keys不能为空"); } // 获取方法的参数名 String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod()); Object[] args = proceedingJoinPoint.getArgs(); // 等待锁的超时时间 long attemptTimeout = lock.attemptTimeout(); if (attemptTimeout == 0) { attemptTimeout = redissonProperties.getAttemptTimeout(); } // 锁超时时间 long lockWatchdogTimeout = lock.watchdogTimeout(); if (lockWatchdogTimeout == 0) { lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout(); } // 加锁模式 LockModel lockModel = getLockModel(lock, keys); if (!lockModel.equals(LockModel.MULTIPLE) && !lockModel.equals(LockModel.RED_LOCK) && keys.length > 1) { throw new ReddissonException("参数有多个,锁模式为->" + lockModel.name() + ",无法匹配加锁"); } log.info("锁模式->{},等待锁定时间->{}毫秒,锁定最长时间->{}毫秒", lockModel.name(), attemptTimeout, lockWatchdogTimeout); boolean res = false; // 策略模式获取redisson锁对象 RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient); //执行aop if (rLock != null) { try { if (attemptTimeout == -1) { res = true; //一直等待加锁 rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS); } else { res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS); } if (res) { return proceedingJoinPoint.proceed(); } else { throw new ReddissonException("获取锁失败"); } } finally { if (res) { rLock.unlock(); } } } throw new ReddissonException("获取锁失败"); } /** * 获取加锁模式 * * @param lock * @param keys * @return */ private LockModel getLockModel(Lock lock, String[] keys) { LockModel lockModel = lock.lockModel(); // 自动模式:优先匹配全局配置,再判断用红锁还是可重入锁 if (lockModel.equals(LockModel.AUTO)) { LockModel globalLockModel = redissonProperties.getLockModel(); if (globalLockModel != null) { lockModel = globalLockModel; } else if (keys.length > 1) { lockModel = LockModel.RED_LOCK; } else { lockModel = LockModel.REENTRANT; } } return lockModel; } }
Strategy pattern
is used here to provide implementation for different lock types.
7. Implementation of lock strategy
First define the abstract base class of the lock strategy(you can also use interface) :
/** * 锁策略抽象基类 */ @Slf4j abstract class LockStrategy { @Autowired private RedissonClient redissonClient; /** * 创建RLock * * @param keys * @param parameterNames * @param args * @param keyConstant * @return */ abstract RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient); /** * 获取RLock * * @param keys * @param parameterNames * @param args * @param keyConstant * @return */ public RLock[] getRLocks(String[] keys, String[] parameterNames, Object[] args, String keyConstant) { List<RLock> rLocks = new ArrayList<>(); for (String key : keys) { List<String> valueBySpel = getValueBySpel(key, parameterNames, args, keyConstant); for (String s : valueBySpel) { rLocks.add(redissonClient.getLock(s)); } } RLock[] locks = new RLock[rLocks.size()]; int index = 0; for (RLock r : rLocks) { locks[index++] = r; } return locks; } /** * 通过spring Spel 获取参数 * * @param key 定义的key值 以#开头 例如:#user * @param parameterNames 形参 * @param args 形参值 * @param keyConstant key的常亮 * @return */ List<String> getValueBySpel(String key, String[] parameterNames, Object[] args, String keyConstant) { List<String> keys = new ArrayList<>(); if (!key.contains(PLACE_HOLDER)) { String s = REDISSON_LOCK + key + keyConstant; log.info("没有使用spel表达式value->{}", s); keys.add(s); return keys; } // spel解析器 ExpressionParser parser = new SpelExpressionParser(); // spel上下文 EvaluationContext context = new StandardEvaluationContext(); for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i]); } Expression expression = parser.parseExpression(key); Object value = expression.getValue(context); if (value != null) { if (value instanceof List) { List valueList = (List) value; for (Object o : valueList) { keys.add(REDISSON_LOCK + o.toString() + keyConstant); } } else if (value.getClass().isArray()) { Object[] objects = (Object[]) value; for (Object o : objects) { keys.add(REDISSON_LOCK + o.toString() + keyConstant); } } else { keys.add(REDISSON_LOCK + value.toString() + keyConstant); } } log.info("spel表达式key={},value={}", key, keys); return keys; } }
Then provide the specific implementation of various lock modes:
- Reentrant lock:
/** * 可重入锁策略 */ public class ReentrantLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { List<String> valueBySpel = getValueBySpel(keys[0], parameterNames, args, keyConstant); //如果spel表达式是数组或者集合 则使用红锁 if (valueBySpel.size() == 1) { return redissonClient.getLock(valueBySpel.get(0)); } else { RLock[] locks = new RLock[valueBySpel.size()]; int index = 0; for (String s : valueBySpel) { locks[index++] = redissonClient.getLock(s); } return new RedissonRedLock(locks); } } }
- Fair Lock:
/** * 公平锁策略 */ public class FairLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { return redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0)); } }
- Interlocking
/** * 联锁策略 */ public class MultipleLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant); return new RedissonMultiLock(locks); } }
- Red Lock
/** * 红锁策略 */ public class RedLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant); return new RedissonRedLock(locks); } }
- Read lock
/** * 读锁策略 */ public class ReadLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0)); return rwLock.readLock(); } }
- Write lock
/** * 写锁策略 */ public class WriteLockStrategy extends LockStrategy { @Override public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0)); return rwLock.writeLock(); } }
Finally provides aStrategy FactoryInitialization lock strategy:
/** * 锁的策略工厂 */ @Service public class LockStrategyFactory { private LockStrategyFactory() { } private static final Map<LockModel, LockStrategy> STRATEGIES = new HashMap<>(6); static { STRATEGIES.put(LockModel.FAIR, new FairLockStrategy()); STRATEGIES.put(LockModel.REENTRANT, new ReentrantLockStrategy()); STRATEGIES.put(LockModel.RED_LOCK, new RedLockStrategy()); STRATEGIES.put(LockModel.READ, new ReadLockStrategy()); STRATEGIES.put(LockModel.WRITE, new WriteLockStrategy()); STRATEGIES.put(LockModel.MULTIPLE, new MultipleLockStrategy()); } public RLock createLock(LockModel lockModel, String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) { return STRATEGIES.get(lockModel).createLock(keys, parameterNames, args, keyConstant, redissonClient); } }
8. How to use
@Lock(keys = "#query.channel") // 支持spel @ApiOperation("分页列表") @GetMapping public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) { return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query)); }
You’re done, eat chicken smoothly
For more programming-related knowledge, please visit: Programming Video! !
The above is the detailed content of Let's talk about how to implement distributed locks in Redis that support almost all locking scenarios.. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











Redis cluster mode deploys Redis instances to multiple servers through sharding, improving scalability and availability. The construction steps are as follows: Create odd Redis instances with different ports; Create 3 sentinel instances, monitor Redis instances and failover; configure sentinel configuration files, add monitoring Redis instance information and failover settings; configure Redis instance configuration files, enable cluster mode and specify the cluster information file path; create nodes.conf file, containing information of each Redis instance; start the cluster, execute the create command to create a cluster and specify the number of replicas; log in to the cluster to execute the CLUSTER INFO command to verify the cluster status; make

How to clear Redis data: Use the FLUSHALL command to clear all key values. Use the FLUSHDB command to clear the key value of the currently selected database. Use SELECT to switch databases, and then use FLUSHDB to clear multiple databases. Use the DEL command to delete a specific key. Use the redis-cli tool to clear the data.

To read a queue from Redis, you need to get the queue name, read the elements using the LPOP command, and process the empty queue. The specific steps are as follows: Get the queue name: name it with the prefix of "queue:" such as "queue:my-queue". Use the LPOP command: Eject the element from the head of the queue and return its value, such as LPOP queue:my-queue. Processing empty queues: If the queue is empty, LPOP returns nil, and you can check whether the queue exists before reading the element.

On CentOS systems, you can limit the execution time of Lua scripts by modifying Redis configuration files or using Redis commands to prevent malicious scripts from consuming too much resources. Method 1: Modify the Redis configuration file and locate the Redis configuration file: The Redis configuration file is usually located in /etc/redis/redis.conf. Edit configuration file: Open the configuration file using a text editor (such as vi or nano): sudovi/etc/redis/redis.conf Set the Lua script execution time limit: Add or modify the following lines in the configuration file to set the maximum execution time of the Lua script (unit: milliseconds)

Use the Redis command line tool (redis-cli) to manage and operate Redis through the following steps: Connect to the server, specify the address and port. Send commands to the server using the command name and parameters. Use the HELP command to view help information for a specific command. Use the QUIT command to exit the command line tool.

Redis counter is a mechanism that uses Redis key-value pair storage to implement counting operations, including the following steps: creating counter keys, increasing counts, decreasing counts, resetting counts, and obtaining counts. The advantages of Redis counters include fast speed, high concurrency, durability and simplicity and ease of use. It can be used in scenarios such as user access counting, real-time metric tracking, game scores and rankings, and order processing counting.

There are two types of Redis data expiration strategies: periodic deletion: periodic scan to delete the expired key, which can be set through expired-time-cap-remove-count and expired-time-cap-remove-delay parameters. Lazy Deletion: Check for deletion expired keys only when keys are read or written. They can be set through lazyfree-lazy-eviction, lazyfree-lazy-expire, lazyfree-lazy-user-del parameters.

In Debian systems, readdir system calls are used to read directory contents. If its performance is not good, try the following optimization strategy: Simplify the number of directory files: Split large directories into multiple small directories as much as possible, reducing the number of items processed per readdir call. Enable directory content caching: build a cache mechanism, update the cache regularly or when directory content changes, and reduce frequent calls to readdir. Memory caches (such as Memcached or Redis) or local caches (such as files or databases) can be considered. Adopt efficient data structure: If you implement directory traversal by yourself, select more efficient data structures (such as hash tables instead of linear search) to store and access directory information
