Recently, I have been doing old-age technology without any upgrades, and I am almost numb. What should I say? I must take action!~ Come on, let’s increase our internal strength together.
The strongest implementation of distributed locks:Redisson
1. Concept
Before introducing, we need to know what this
Redisson
is? Is itRedis
’sson
? (I I just thought so once, haha!) This is indeed the case –>See explanation belowFirst somethingProfessional pointExplanation:
Redisson
isIn-momery data Grid
(based on a memory-resident grid) (A buffering technology for distributed distributionIt’s great!)Redis
java
Communication is based on
Netty
, oriented to enterprise-level developmentProvide a series of distributed
java
common objects and distributed servicesVernacular:
a. It is a distributed tool that encapsulates many common distributed functions.
b. Simplify developer development and focus more on business development instead of spending most of the time
Redis
on the abovec. Avoid distributed wheel-making
2. Distributed lock
Let me talk about this stuff too, shall I? It is a necessity for concurrent business; as the saying goes: Whether concurrency is good or not depends on how well this lock is managed!
What is thereexample
? (Yes! Wait~) We will use Redis
to write a simple distributed lock:~
Write the simplest
Redis
distributed lock: usingSpringDataRedis
RedisTemplate
setIfAbsent
:setNx
+expire
2.1 Lock operation/unlock operation
// 加锁
public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
// 解锁,防止删错别人的锁,以uuid为value校验是否自己的锁
public void unlock(String lockName, String uuid) {
if(uuid.equals(redisTemplate.opsForValue().get(lockName)){
redisTemplate.opsForValue().del(lockName);
}
}
// 结构
if(tryLock){
// todo
}finally{
unlock;
}
The above code completes the lock operation, but we will find that it cannot guaranteeatomicity. Once the amount of concurrency is high, it will be a big deal! ~
How to solve it? How to ensure atomicity? How do I know! (Haha! Let’s read on and we will understand) We introduce the built-in language of Redis
:lua
,
2.2 For the purposelua
Screenplay
We use
lua
to encapsulate the operation into alua
script, throughRedis
< a i=4>/eval
evalsha
lua
Script code:
Script name:
lockDel.lua
if redis.call('get', KEYS[1]) == ARGV[1]
then
-- 执行删除操作
return redis.call('del', KEYS[1])
else
-- 不成功,返回0
return 0
end
Removaljava
Yogo:
// 解锁脚本
DefaultRedisScript<Object> unlockScript = new DefaultRedisScript();
unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lockDel.lua")));
// 执行lua脚本解锁
redisTemplate.execute(unlockScript, Collections.singletonList(keyName), value);
is written here, we have also addedlua
, which can also ensure atomicity! But look at it, is there anything missing? (Haha, are you thinking in your heart? , I see a hammer!There’s nothing wrong with it)
Hmm, let me remind you - follow the above idea: What if a thread holds the lock multiple times? Good guy Is there a problem? How to solve it? ( Have you thought about it? Just look down!Haha)
2.3 Reentrant lock
How to ensure re-entry?
First of all: We should understand the reentrant core: it is allowed for the same thread to acquire the same lock multiple times, and no deadlock will occur
Hmm~ I don’t understand the truth very well. We use
Synchronized
’s biased lock to understand its implementation idea< /span>
synchronized
Reentrancy is implemented at theJVM
level.java
The object headerMARK word
contains the thread ID and counter to perform operations on the thread. Reentrant judgment to avoid each timeCAS
- When a thread accesses the synchronized block and acquires the lock, it will be in the object header and stack frames 'slock record stores biased threads
ID
- In this way, when this thread ID enters and exits the synchronization block later, there is no need
CAS
to lock and unlock- You only need to check whether the MARK WORD object header stores a bias lock pointing to the current thread.
- Test successful: thread acquires lock
- Test failed: test again
MARK WORD
bias lockflagwhetherSet to 1
- 0: Lost
CAS
Conflict- 1:
CAS
Point the object head bias lock to the current thread- There is also a counter: when the same thread enters, it will increase by 1, and when it leaves, it will decrease by 1 until it reaches 0Release
According to the above idea: (We modify the script to be implementedlua
)
1. Prepare formal parameters (conditions for execution)
需要存储锁名称lockName
该锁线程ID
对应线程进入的次数: count
2. Lock (each time a thread acquires a lock, it determines whether the lock already exists)
不存在:
设置hash的Key为线程ID,value=1
设置expireTime(过期时间)
返回获取锁,成功为true
存在:
继续判断是否存在当前线程ID的hash key
存在:
线程key的value+1,重入次数加1(count++),设置expireTime
不存在:
返回加锁失败
3. Unlock —> Every time a thread comes to unlock, determine whether the lock already exists
存在:
是否有该线程的ID的hash key,有则减1,没有返回解锁失败
减1后:
判断剩余count为不为0
=0: 不再需要这把锁,执行del命令删除
1. Storage structure:
Redis uses HASH structure
Lock name: lock_name1`
key: thread_id (unique key, thread ID)
value
:
count` (计数器)
hset lock_name1 thread_id 1
hget lock_name1
2. Counter addition and subtraction
When the same thread acquires the same lock, we need to add or subtract the counter count of the corresponding thread.
How to determine whether a Redis key exists?
We can use exists/hexists to determine whether a hash key exists.
hset lock_name1 thread_id 1
exists/exists lock_name1
Hash auto-increment command:
hincrby lock_name1 thread_id 1
3. Determination of unlocking
When a lock is no longer needed, every time it is unlocked, the count decreases by 1 until it reaches 0, and the deletion is performed.
finalLUA
daigo
Lock
lock.lua
local key = KEYS[1];
local threadId = ARGV[1];
local releaseTime = ARGV[2];
-- lockname不存在
if(redis.call('exists', key) == 0) then
redis.call('hset', key, threadId, '1');
redis.call('expire', key, releaseTime);
return 1;
end;
-- 当前线程已id存在
if(redis.call('hexists', key, threadId) == 1) then
redis.call('hincrby', key, threadId, '1');
redis.call('expire', key, releaseTime);
return 1;
end;
return 0;
Unlock
unlock.lua
local key = KEYS[1];
local threadId = ARGV[1];
-- lockname、threadId不存在
if (redis.call('hexists', key, threadId) == 0) then
return nil;
end;
-- 计数器-1
local count = redis.call('hincrby', key, threadId, -1);
-- 删除lock
if (count == 0) then
redis.call('del', key);
return nil;
end;
RedisLock.java
: Implement distributed locksFunction implementation: Mutual exclusion, reentrancy, anti-deadlock
/**
* @description 原生redis实现分布式锁
**/
@Getter
@Setter
public class RedisLock {
private RedisTemplate redisTemplate;
private DefaultRedisScript<Long> lockScript;
private DefaultRedisScript<Object> unlockScript;
public RedisLock(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
// 加载加锁的脚本
lockScript = new DefaultRedisScript<>();
this.lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua")));
this.lockScript.setResultType(Long.class);
// 加载释放锁的脚本
unlockScript = new DefaultRedisScript<>();
this.unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));
}
/**
* 获取锁
*/
public String tryLock(String lockName, long releaseTime) {
// 存入的线程信息的前缀
String key = UUID.randomUUID().toString();
// 执行脚本
Long result = (Long) redisTemplate.execute(
lockScript,
Collections.singletonList(lockName),
key + Thread.currentThread().getId(),
releaseTime);
if (result != null && result.intValue() == 1) {
return key;
} else {
return null;
}
}
/**
* 解锁
* @param lockName
* @param key
*/
public void unlock(String lockName, String key) {
redisTemplate.execute(unlockScript,
Collections.singletonList(lockName),
key + Thread.currentThread().getId()
);
}
}
The above code has implemented most of the functions and can handle general scenarios calmly, but (hahaha, are you most afraid of failure?)
针对Special scene:
1. IfA process acquires the lock because the business operation takes too long , the lock is released, but the business is still being executed. At this moment, process B can obtain the lock normally and perform business operations. At this time, processes A and B will appearProblems with shared resources
2. If the
Redis
responsible for storing this distributed lock goes down, the lock will be in Locked state, which will result in locked state
In this case, we need to useLock renewal
Lock renewal: Extend the lock
ReleaseTime
until the desired business results are completed. The essence is tocontinuously extend the lock Expiration date
But, regarding the processing of renewals, performance such as the maximum waiting time for locks, invalid lock applications, and the failed retry mechanism, we have been writing about it for a long time! ~Push now=—>Give up hahaha!!
No, no, no, no, no, no, no, no, no, no, no, no, no, no, here we are going to introduce our protagonist today:Reidsson
!! (solve all the above problems)
4.Redisson
Distributed lock
The soldiers and horses have not moved, but the food and grass go first
Let’s see how to use it~ shall we?
4.1Introducing dependencies
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
<!-- 另一种Spring集成starter,本章未使用 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
</dependency>
4.2 Configuration
@Configuration
public class RedissionConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.password}")
private String password;
private int port = 6379;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().
setAddress("redis://" + redisHost + ":" + port).
setPassword(password);
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}
4.3 Enable distributed locks
Concise and clear, only one is needed
RLock
, as shown in the following code
@Resource
private RedissonClient redissonClient;
RLock rLock = redissonClient.getLock(lockName);
try {
boolean isLocked = rLock.tryLock(expireTime, TimeUnit.MILLISECONDS);
if (isLocked) {
// TODO
}
} catch (Exception e) {
rLock.unlock();
}
4.4RLock
Still updating…