How to use distributed locks in a distributed environment like the synchronized keyword. For example, develop an annotation called @distributionLock that applies to a method function and automatically releases the lock after each DistributionLock.
You can take advantage of Spring AOP’s surround-notification nature to do just that.
Maven dependency
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4. The RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
Copy the code
Here using redis redisson this third-party libraries to do distributed lock, redis configuration is not here, you can refer to the end of the article posted code address or specific can see this article zhaoxiaobin.net/Redis/Redis…
2. Develop custom annotations
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributionLock {
/** * distributed lock key */
String value(a) default "";
/** * The wait time for the distributed lock */
int waitTime(a) default 5 * 1000;
/** * The position of the distributed lock key in the parameter list */
int index(a) default- 1;
}
Copy the code
There are two modes based on the size of the lock:
- Coarse-grained: from annotations
value
Field specifies that the lock is shared by the same method (business) at compile time. Whoever tuned this method did it sequentially. - Fineness: One parameter in the method’s argument list is used as the lock’s
key
Values, such as a number, a serial number, and so on, are business unique parameters. If the same (same group) transaction allows different people to do at the same time, but the same person must be serial execution of the scene; byindex
Specified as akey
Parameter position to.
3. Development facets
@Order(Integer.MIN_VALUE + 1)
@Aspect
@Component
@Slf4j
public class DistributionLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(distributionLock)")
public Object doAround(ProceedingJoinPoint point, DistributionLock distributionLock) throws Throwable {
String methodName = point.getSignature().getName();
if (StringUtils.isNotBlank(distributionLock.value())) {
// The lock is coarse-grained. The name of the lock is specified in the DistributionLock annotation on the target method and is shared by the same method (business)
return this.tryLock(point, distributionLock.value(), distributionLock.waitTime());
} else if (distributionLock.index() >= 0) {// The lock is fine-grained, with the x th parameter of the target method as the lock name, which can be a business number, name, etc
Object[] args = point.getArgs(); // Parameter list
int index = distributionLock.index(); // The number of parameters in the parameter list is the key of the distributed lock
if (args.length <= index) {
log.error("No :{} argument on target method :{}", methodName, index);
throw new RuntimeException("Target method :" + methodName + "There is no :" + index + "Parameters");
}
String key = args[index].toString();
return this.tryLock(point, key, distributionLock.waitTime());
} else {
log.error("No key configured for a specific distributed lock, target method :{}", methodName);
throw new RuntimeException("No key configured for a specific distributed lock, target method :"+ methodName); }}/** * only get, release lock, no exception, throw exception **@paramPointcut *@paramKey Key * of the distributed lock@paramWaitTime waitTime to get the lock *@return
* @throws Throwable
*/
private Object tryLock(ProceedingJoinPoint point, String key, int waitTime) throws Throwable {
RLock disLock = redissonClient.getLock(key);
try {
// By default, the lock expires after 30 seconds. Every 30/3=10 seconds, the watchdog (daemon thread) will renew the lock, reset to 30 seconds
boolean tryLock = disLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
if(! tryLock) { log.error("Failed to acquire distributed lock :{}", key);
// It is up to the business to throw an exception or return NULL or other business objects
// throw new RuntimeException(" Failed to acquire distributed lock ");
return false;
}
return point.proceed(point.getArgs());
} finally {
// Only the thread that acquired the lock performs the lock release operation
if(disLock.isHeldByCurrentThread()) { disLock.unlock(); }}}}Copy the code
4. Simulated test to deduct inventory
Business operations
@Service
public class DistributionLockDemo {
/** * inventory */
public static int count = 20;
public boolean increment(a) {
if (count > 0) {
count--;
return true;
}
return false;
}
public int get(a) {
returncount; }}Copy the code
Simulated transaction initiation
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DistributionLockTests {
@Autowired
private DistributionLockDemo distributionLockDemo;
/ * * * multi-threaded parallel processing 500 requests, buckle inventory * /
@Test
public void testDistributionLock(a) {
long successCount = IntStream.range(0.500).parallel().filter(j -> distributionLockDemo.increment()).count();
log.info("Success number :{}", successCount);
log.info("Remaining inventory :{}", distributionLockDemo.get()); }}Copy the code
Here, we use Java8’s parallel stream to send 50 transactions simultaneously to simulate a 20-lock inventory without locking:
2021-06-30 18:10:20.942 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:35[] Success number:27
2021-06-30 18:10:20.942 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:36[] Surplus inventory:0
Copy the code
Mysql > alter table t2 add lock increment;
@DistributionLock(value = "incrementLock", waitTime = 1000)
public boolean increment(a) {
if (count > 0) {
count--;
return true;
}
return false;
}
Copy the code
2021-06-30 18:11:30.722 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:35[] Success number:20
2021-06-30 18:11:30.722 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:36[] Surplus inventory:0
Copy the code
The above case is not too rigorous, in order to test the convenience, just tested the effect of a single cluster and not tested. Conditions can be connected to the database and a number of services to withhold the database data, to see how the effect of distributed lock.
5, distributed lock security
I believe many students know that Redis distributed lock is not so foolproof; Such as master-slave lock loss, as a result of switching, and NPC problems influence the safety of the lock, concrete can refer to this article zhaoxiaobin.net/Redis/Redis…
Advice:
- For businesses that require data to be absolutely correct, you must do a “bottom pocket” in the resource layer, such as optimistic locking of databases, etc
CAS
Such operations. - With distributed locks, the purpose of “mutually exclusive” is accomplished in the upper layer. Although the lock will fail in extreme cases, it can prevent concurrent requests to the maximum extent in the top layer, reducing the pressure on the operation resource layer.
The code address
- Github:github.com/senlinmu100…
- Gitee:gitee.com/ppbin/redis…
Personal website
- Github Pages
- Gitee Pages