Redisson 实现分布式锁

Redisson 是一个 Java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis(继承了和 Java 相同的集合类)

关键词:Java Redis 客户端,分布式数据结构,实现了很多 Java 里支持的集合

两种引用方式

  1. spring boot starter 引入(不推荐,因为版本迭代太快,容易冲突):
    https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
  2. 直接引入:https://github.com/redisson/redisson#quick-start

(1)引入依赖

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.5</version>
</dependency>

(2)新建 RedissonConfig 配置类,配置地址、端口、创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @Description: Redisson 配置
* @Author: mofeng
* @DateTime: 2022/11/10 23:58
**/
@Configuration
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class RedissonConfig {

private String host;
private String port;
@Bean
public RedissonClient redissonClient(){
// 1. 创建配置
Config config = new Config();
String redisAddress = String.format("redis://%s:%s", host, port);
config.useSingleServer().setAddress(redisAddress).setDatabase(3);
// 2. 创建实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}

使用Redisson

创建分布式列表、Map,新建测试类示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Resource
private RedissonClient redissonClient;
@Test
void test(){
// list 数据存在本地 JVM 内存中
List<String> list = new ArrayList<>();
list.add("yupi");
System.out.println("list:" + list.get(0));

// 数据存在 Redis 内存中
RList<Object> rList = redissonClient.getList("test-list");
rList.add("yupi");
System.out.println("RList:" + rList.get(0));
//rList.remove(0);

// map
RMap<Object, Object> map = redissonClient.getMap("test-map");
map.put("yupi", 10);
map.get("yupi");

System.out.println(map.get("yupi"));
}

分布式锁保证定时任务不重复执行

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void testWatchDog(){
RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock");
try {
//只有一个线程获取到锁
if(lock.tryLock(0, -1, TimeUnit.MILLISECONDS)){
// TODO 实际要执行的方法
dosomething();
System.out.println("getLock:" + Thread.currentThread().getId());
}
} catch (InterruptedException e) {
System.out.println(e.getMessage());
} finally {
// 只能释放自己的锁
if(lock.isHeldByCurrentThread()){
System.out.println("unLock:" + Thread.currentThread().getId());
lock.unlock();
}
}

}

注意:

  1. waitTime 设置为 0,只抢一次,抢不到就放弃
  2. 主要释放锁要写在 finally 语句块中,保证最后一定释放锁

Redisson 看门狗机制

Redisson 中提供的续期机制

开一个监听线程,如果方法还没执行完,就帮你重置 Redis 锁的过期时间

原理:

  1. 监听当前线程,默认过期时间是 30 秒,每 10 秒续期一次(续期到 30 秒)
  2. 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期

详情参考文档:https://blog.csdn.net/qq_26222859/article/details/79645203

定时任务全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yupi.yupao.model.domain.User;
import com.yupi.yupao.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* @Description: 预热缓存任务
* @Author: mofeng
* @DateTime: 2022/11/10 6:03
**/
@Component
@Slf4j
public class PreCacheJob {

@Resource
private UserService userService;
@Resource
private RedisTemplate<String, Object> redisTemplate;

@Resource
private RedissonClient redissonClient;

//重点用户
private List<Long> mainUserList = Arrays.asList(1L);

// 每天执行预热推荐用户 cronTable 在线表达式生成 https://cron.qqe2.com/
// 前面三个参数: 秒 分 时
@Scheduled(cron = "0 37 1 * * *")
public void doCacheRecommendUser(){
//获取到 锁对象
RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock");
try {
//只有一个线程获取到锁
if(lock.tryLock(0, -1, TimeUnit.MILLISECONDS)){
System.out.println("getLock:" + Thread.currentThread().getId());
for (Long userId : mainUserList) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);
String redisKey = String.format("yupao:user:recommend:%s", userId);
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
//写缓存
try {
//指定缓存 30 秒过期
valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("redis set key error", e);
}
}
}
} catch (InterruptedException e) {
log.error("doCacheRecommendUser error", e);
} finally {
// 只能释放自己的锁
if(lock.isHeldByCurrentThread()){
System.out.println("unLock:" + Thread.currentThread().getId());
lock.unlock();
}
}


}
}