type
status
date
slug
summary
tags
category
icon
password
This is one excerpt from the main website(CN) blog, check link below to know more.
Distributed Lock,Customized AOP,Redisson,SPEL
Distributed Lock
- Background: The “Synchronized” lock in Java is implemented based on the JVM Monitor. In a clustered environment, multiple JVMs mean multiple monitors, which cannot achieve mutual exclusion. Therefore, there should be one single lock across multiple instances, which is called distributed lock.
- It seems a Distributed Lock can be implemented with Redis?
- Redis can be accessed by multiple JVM instances.
- The SETNX command provides mutual exclusion.
- The DEL command can be used to release the lock.
- Redis executes commands in a single-threaded (serial) manner.
- Potential Issues:
- Direct Implementation:
- 1. Timeout Release:
- 2. Storing thread Identifier:
- 1. Timeout Issue: Use a WatchDog mechanism. When a lock is successfully acquired, a scheduled task is started that automatically extends the lock’s expiration before it expires, thereby preventing a timeout release. This task also stops if the instance crashes, avoiding deadlocks.
- 2. Atomicity Issue: Use Lua scripts to ensure atomic operations.
- 3. Lock Reentrancy:
Similar to the behavior of
synchronized
, a hash can be used to record the holder and the reentrancy count. The lock is deleted when the count drops to zero. - 4. Master-Slave Synchronization Delay: Use the RedLock.
- 5. Lock Failure Retry: Implement mechanisms to retry obtaining the lock upon failure.
SET lock thread1 NX EX 20
– This involves two main steps.The lock may not be properly released (for example, if redis instance crashes), which could lead to a deadlock. Setting an expiration time is necessary to mitigate this (in the example, the lock acquisition and expiration time setting are performed atomically).
The lock stores the identifier of the thread that acquired it, and it should only be deleted if the identifier matches. This helps prevent accidental release of the lock (however, it cannot be completely avoided).
Image source(CN website): https://b11et3un53m.feishu.cn/wiki/wikcnkbaeh4T9AyYlM7rHSaKMsc

(IMG:thread 1 got blocked before its release of lock, the lock got itself released due to timeout which allowed thread 2 to get its share, yet thread 1 then awake and ruin its day)

(IMG:now thread 1 can no longer release the lock after timeout since the identifier is thread 2, not thread 1, and thread 1 cannot release the lock for the identifier does not correspond)
However, since the check and deletion are not atomic, there is still a risk of accidental deletion.

(IMG:thread 1 may check the identifier before its release move, and then directly release after its awakening from block, rendering the identifier mechanism ineffective)
From above, we learn:
Timeout-based release does not entirely prevent accidental deletion, and lock release operations require atomicity, there is also a problem on delays in master-slave synchronization, and the same thread may not be able to acquire the same lock multiple times (potentially leading to deadlocks), therefore such issues need to be addressed:
- Mature Solution:
Redisson provides a well-tested, comprehensive implementation that addresses these issues.
Redisson Quick Start
maven import
Config class (with autoconfigure)
ConditionalOnClass autoconfigure - this config class only takes effect when you import Redisson dependency
Where does this RedisProperties come from?:
from the nacos config center

the config file (you may add these to the local config file in case you do not use config center)

resources/META-INF/spring.factories(for autoconfigure, mainly for conditional config class):
Basic usage of redisson client
Business Scenario
- waitTime:how long you can wait, you can retry repeatedly before the time runs out. -1 by default,return immediately after failure, no retry.
- leaseTime:how long you can use the lock before compulsory release。by default 30,keep renewing with WatchDog.
- TimeUnit:in literal
Distributed Lock AOP (General Scenario)
- avoid boilerplate code coupling with business logic
- mark pointcut with annotation, passing params(lock name[SPEL expression, dynamic name], waitTime, leaseTime, timeunit) of lock meanwhile
Annotation
Aspect
Implement Ordered interface so you make sure the lock wraps @Transactional(any value less than Integer.MAX_VALUE should do), i.e higher precedence(lower value) in AOP.
This value of @Transactional is by default Integer.MAX_VALUE(fyr, org.springframework.transaction.annotation.EnableTransactionManagement)

btw, you may find something similar at:
org.springframework.context.annotation.ConfigurationClassUtils
except it’s for @Configuration

ENUM
Lock Type(Strategy Pattern)
Simple factory pattern(another way)
EnumMap implemented with pure array, likely faster than HashMap, fyr.
Implementation note: All basic operations execute in constant time. They are likely (though not guaranteed) to be faster than their HashMap counterparts.
Example in coupon discount strategy
Lock fail strategy
waitTime defines the time you may retry,no retry if no such param
Rewrite API with AOP
Results devoid of oversell(both for total and single user limit)
- Find some lucky coupon

- Fill its id in jmeter, configure threads and loop count (1k user, each try 3 times on the same coupon with 1 per user limit)


- Done, check Redis 100 rows√ 1 per user√ 0 total left√


- check DB 100 issue√

- check summary report √

- check jmeter report, should be only 100 true√

- check reasons for failed http requests, 优惠卷库存不足(No enough inventory)√ 请求超时(Timeout for distributed lock retrying)√



- Align with the failure strategy √

Full Code:
receive coupon


exchange coupon

- Author:CamelliaV
- URL:https://camelliav.netlify.app/en/article/lock-aop
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!