一、背景
在分布式服務(wù)中,經(jīng)常有例如定時任務(wù)這樣的場景。
在定時任務(wù)中,如果不使用 quartz
這樣的分布式定時工具,只是簡單使用 @Schedule
注解來實(shí)現(xiàn)定時任務(wù),在服務(wù)分布式部署中,就有可能存在定時任務(wù)并發(fā)重復(fù)執(zhí)行問題。
對于解決以上場景中的問題,我們引入了分布式鎖。
二、具體實(shí)現(xiàn)
1.RedisTemplate 實(shí)現(xiàn)(非阻塞)
RedisUtils 工具類:
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 原子性操作 加鎖
*/
public boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit));
}
/**
* 原子性操作 解鎖
*/
public void delete(String key) {
redisTemplate.delete(key);
}
}
使用示例:
@Resource
private RedisUtils redisUtils;
public void updateUserWithRedisLock(SysUser sysUser) throws InterruptedException {
// 1.獲取分布式鎖
boolean lockSuccess = RedisUtils.setIfAbsent("SysUserLock" + sysUser.getId(), "value", 30, TimeUnit.SECONDS);
if (lockSeccess) {
// 加鎖成功...
// TODO: 業(yè)務(wù)代碼
// 釋放鎖
redisUtils.delete("SysUserLock" + sysUser.getId());
} else {
// 如果需要阻塞的話,就睡一段時間再重試
Thread.sleep(100);
updateUserWithRedisLock(sysUser);
}
}
setIfAbsent()
方法的作用就是在 lock key 不存在的時候,才會設(shè)置值并返回 true;如果這個 key 已經(jīng)存在了就返回 false,即獲取鎖失敗。
2.RedisLockRegistry 實(shí)現(xiàn)(阻塞)
RedisLockRegistry 是 spring-integration-redis
中提供的 Redis 分布式鎖實(shí)現(xiàn)類。
集成 spring-integration-redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-interation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
注冊RedisLockRegistry:
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactor) {
// 第一個參數(shù) redisConnectionFactory
// 第二個參數(shù) registryKey,分布式鎖前綴,建議設(shè)置項(xiàng)目名稱
// 該構(gòu)造方法對應(yīng)的分布式鎖,默認(rèn)有效期是60秒,可以自定義。
return new RedisLockRegistry(redisConnectionFactory, "boot-launch");
// return new RedisLockRegistry(redisConnectionFactory, "boot-launch", 60);
}
}
使用RedisLockRegistry:
代碼實(shí)現(xiàn):
@Resource
private RedisLockRegisty redisLockRegistry;
public void updateUser(String userId) {
String lockKey = "config" + userId;
Lock lock = redisLockRegistry.obtain(lockKey); // 獲取鎖資源
try {
lock.lock(); // 加鎖
// TODO: 業(yè)務(wù)代碼
} finally {
lock.unlock(); // 釋放鎖
}
}
注解實(shí)現(xiàn):
@RedisLock("lock-key")
public void save() {
}
RedisLockRegistry 實(shí)現(xiàn)的分布式鎖是支持阻塞的。RedisLocckRegistry 是 Spring Integration Redis 模塊中的一個類,它是基于 Redis 實(shí)現(xiàn)的分布式鎖機(jī)制。
當(dāng)一個線程嘗試獲取分布式鎖時,如果該鎖已經(jīng)被其他線程占用,則當(dāng)前線程會被阻塞,等待鎖釋放。阻塞的線程會一直等待,直到鎖被成功獲取或者等待超時。
阻塞的實(shí)現(xiàn)是通過 Redis 的特性實(shí)現(xiàn)的:通過 Redis 的 SETNX 命令(SET if Not eXists)嘗試將鎖的鍵值對設(shè)置到 Redis 中。如果設(shè)置成功,則表示獲取鎖成功;如果設(shè)置失敗,則表示鎖已被其他線程占用,當(dāng)前線程會被阻塞。
因此,使用 RedisLockRegistry 可以實(shí)現(xiàn)支持阻塞的分布式鎖機(jī)制,能夠?qū)崿F(xiàn)多線程之間的協(xié)調(diào)和互斥。
3.Redisson 實(shí)現(xiàn)(阻塞)
Redission 是一個獨(dú)立的 Redis 客戶端,是與 Jedis、Lettuce 同級別的存在。
集成 Redisson:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.0</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<!-- 默認(rèn)是 Spring Data Redis v.2.3.x 所以排除掉 -->
<artifactId>redisson-spring-data-23</artifactId>
</exclusion>
</exclusions>
</dependency>
配置:
1)在配置文件中添加如下內(nèi)容:
spring:
redis:
redisson:
file: classpath:redisson.yaml
2)然后新建一個 redisson.yaml
文件,也放在 resources
目錄下:
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: 123456
subscriptionsPerConnection: 5
clientName: null
address: "redis://192.168.161.3:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: "NIO"
代碼實(shí)現(xiàn):
@Resource
private RedissonClient redissonClient;
public void updateUser(String userId) {
String lockKey = "config" + userId;
RLock lock = redissonClient.getLock(lockKey); // 獲取鎖資源
try {
lock.lock(10, TimeUnit.SECONDS); // 加鎖,可以指定鎖定時間
// TODO: 業(yè)務(wù)代碼
} finally {
lock.unlock(); // 釋放鎖
}
}
對比 RedisLockRegistry 和 Redisson 實(shí)現(xiàn):
- Redisson 優(yōu)點(diǎn):可以為每一個鎖指定不同的超時時間,而 RedisLockRegistry 目前只能針對所有的鎖設(shè)置統(tǒng)一的超時時間。
注意:如果業(yè)務(wù)執(zhí)行超時之后,再去 unlock
會拋出 java.lang.IllegalMonitorStateException
。
三、思考
1.為什么可以用 setIfAbsent(),而不能用 setnx() ?
-
setIfAbsent()
是 Redis 專門為分布式鎖實(shí)現(xiàn)的原子操作,其中封裝了獲取 key、設(shè)置 key、設(shè)置過期時間等操作。 -
setnx()
主要用于分布式 ID 的生成,如果要用來實(shí)現(xiàn)分布式鎖的話,雖然可以通過返回結(jié)果為0表示獲取鎖失敗,1表示獲取鎖成功,但是對于設(shè)置過期時間的操作需要手動實(shí)現(xiàn),無法保證原子性。
整理完畢,完結(jié)撒花~ ??
參考地址:文章來源:http://www.zghlxwxcb.cn/news/detail-657752.html
1.Java三種方式實(shí)現(xiàn)redis分布式鎖,https://blog.csdn.net/w_monster/article/details/124472493文章來源地址http://www.zghlxwxcb.cn/news/detail-657752.html
到了這里,關(guān)于Redis學(xué)習(xí)(八)Java三種方式實(shí)現(xiàn)分布式鎖的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!