目录

[TOC]

官方文档

https://github.com/redisson/redisson

https://github.com/redisson/redisson/wiki

image-20231012164004815

配置及依赖

依赖

下面redisson对应的SpringBoot:2.5.6

redisson和SpringBoot版本对应关系查看链接

1
2
3
4
5
6
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.0</version>
</dependency>

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class RedissonConfig {

/**
* 所有对redis的试用都是通过RedissonClient对象
*
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}

分布式锁

Redisson各锁

官方文档:https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8

  • 可重入锁(Reentrant Lock)
  • 公平锁(Fair Lock)
  • 联锁(MultiLock)
  • 红锁(RedLock)
  • 读写锁(ReadWriteLock)

    • 读写锁保证读一定能读到最新数据。修改期间,写锁式一个排他锁(互斥锁)。读锁是一个共享锁。
      • 读 + 读:相当于无锁,并发读
      • 写 + 读:阻塞方式
      • 写 + 写:有读锁,写也需要等待
      • 读 + 写:有读锁,写也需要等待
  • 信号量(Semaphore)

  • 可过期性信号量(PermitExpirableSemaphore)
  • 闭锁(CountDownLatch)
    • 一个锁内,必须等待所有业务都完成,才能释放锁

Redisson实现分布式锁优点

  1. 阻塞式等待,默认锁的时间都是30s

  2. 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除

  3. 锁的自动续期,如果业务时间超长,如果在期间锁过期了,会自动给锁上新的30s,不用担心业务时间长,锁自动过期被删除

    • 如果设置了锁的过期时间,过期后不会自动续期,也不会自动续期

      1
      2
      3
      4
      5
      6
      // 1、获取同一把锁,只要锁的名字一样,就是同一把锁【这里使用可重入锁作为示例,其他锁同理】
      RLock lock = redissonClient.getLock("user-insert");

      // 2、加锁,阻塞式等待
      // 如果指定了超时时间,锁过期后不会自动续期
      lock.lock(5, TimeUnit.SECONDS);
    • 如果没有设置锁的过期时间,只要占锁成功,就会启动一个定时任务,每隔10s都会自动再次续期到30s【30s时间式看门狗时间】

      1
      2
      3
      4
      5
      // 1、获取同一把锁,只要锁的名字一样,就是同一把锁【这里使用可重入锁作为示例,其他锁同理】
      RLock lock = redissonClient.getLock("user-insert");

      // 2、加锁,阻塞式等待
      lock.lock();
    • 推荐使用指定过期时间的方式加锁

redisson使用

该部分文档是未使用redisson-spring-boot-starter,直接试用的redisson进行操作

可重入锁使用

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
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {

public Boolean add(UserEntity userEntity) throws InterruptedException {
boolean isSuccess = false;

// 1、获取同一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("user-insert");

// 2、加锁,阻塞式等待
lock.lock();
try {
UserEntity result = this.baseMapper.selectOne(new LambdaQueryWrapper<UserEntity>().eq(userEntity.getNickname() != null, UserEntity::getNickname, userEntity.getNickname()));
Thread.sleep(5000);
if (result != null) {
throw new RuntimeException(userEntity.getNickname() + "已经插入");
}
isSuccess = this.save(userEntity);
} finally {
// 3、解锁
lock.unlock();
}

return isSuccess;
}
}

读写锁使用

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
/**
* 写锁
*/
public void writLock() throws InterruptedException {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("anyRWLock");
// 获取写锁
RLock rLock = readWriteLock.writeLock();
try {
// 添加写锁
rLock.lock();

redisUtils.deleteObject("readWriteLock");
redisUtils.setCacheObject("readWriteLock", IdUtil.simpleUUID());
Thread.sleep(15000);
} finally {
rLock.unlock();
}
}

/**
* 读锁
*/
public void readLock() throws InterruptedException {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("anyRWLock");
// 获取读锁
RLock rLock = readWriteLock.readLock();
// 添加读锁
rLock.lock();
System.out.println("读写锁获取值:" + redisUtils.getCacheObject("readWriteLock").toString());
}
1
2
3
4
5
6
7
8
9
@Test
void writLock() throws InterruptedException {
userService.writLock();
}

@Test
void readLock() throws InterruptedException {
userService.readLock();
}

闭锁使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 闭锁测试
*/
public void countDownLatchTest() throws InterruptedException {
RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
// 设置多少个业务完成后,就可以解锁
latch.trySetCount(5);
// 等待
latch.await();
}

/**
* 闭锁处理
*/
public void countDownLatchDown(int i) throws InterruptedException {
RCountDownLatch latch = redissonClient.getCountDownLatch("anyCountDownLatch");
for (int j = 0; j < i; j++) {
System.out.println(j);
// 每执行一次相当于执行一次业务,闭锁次数就会减少一次, 等达到五次时,闭锁就会解锁了,相当于整个业务完成
latch.countDown();

Thread.sleep(1000);
}
}
1
2
3
4
5
6
7
8
9
@Test
void contextLoads() throws InterruptedException {
userService.countDownLatchTest();
}

@Test
void countDownLatchDown() throws InterruptedException {
userService.countDownLatchDown(5);
}

信号量使用

  • 车库停车位
  • 分布式限流
  • ……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 车库停车案例来演示信号量案例
* 停车
*/
public Boolean parking() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
// 把车停车库
park.tryAcquire();
}

/**
* 开车
*/
public String drive() {
RSemaphore park = redissonClient.getSemaphore("park");
// 把车从车库开走
park.release();
}

分布式限流

参考:https://juejin.cn/post/7203364379339931708

依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>

<!--spring-boot-starter-aop切面编程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义异常类

1
2
3
4
5
6
7
8
/**
* 限流自定义异常类
*/
public class RedissonRateLimitException extends RuntimeException {
public RedissonRateLimitException(String message) {
super(message);
}
}

自定义注解

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
import java.lang.annotation.*;

/**
* 自定义注解,简化限流器的使用。项目中都可以使用该注解来标注需要限流的方法
* 注意我们这个限流器不是很精确,但误差不会太大
*/
@Target({ElementType.METHOD}) // 此注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解的作用域为JVM运行时
@Documented // 生成javadoc时包含该注解
@Inherited // 此注解允许被集成
public @interface RedissonRateLimit {

/**
* 限流标识key,每个http接口都应该有一个唯一的key。
*/
String key();

/**
* 限流的时间(单位为:秒),比如1分钟内最多1000个请求。注意我们这个限流器不是很精确,但误差不会太大
*/
long timeOut();

/**
* 限流的次数,比如1分钟内最多1000个请求。注意count的值不能小于1,必须大于等于1
*/
long count();
}

核心限流切面类

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.xiaofei.redisson.aspect;

import com.xiaofei.redisson.annotation.RedissonRateLimit;
import com.xiaofei.redisson.handler.exception.RedissonRateLimitException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
@AutoConfigureAfter 提示应在其他指定的自动配置类之后应用{@link EnableAutoConfiguration自动配置}。
* 也就是说如果容器中有了RedissonClient之后,此类RedissonRateLimitAspect才能被启用
*/
@Slf4j
@Aspect
@Component
@AutoConfigureAfter(RedissonClient.class)
public class RedissonRateLimitAspect {

@Autowired
private RedissonClient redissonClient;

@PostConstruct
public void init() {
log.info("Redisson的限流器被加载.........");
if (redissonClient == null) {
log.warn("Spring容器中没有RedissonClient,Redisson限流器将无法使用............");
}
}

/**
* 在所有方法上带有@RedissonRateLimit注解的方法运行执行之前,先执行我们这个方法进行校验。
* 校验不通过,说明限流了,抛出异常。
*/
@Before("@annotation(redissonRateLimit)")
public void redissonRateLimitCheck(RedissonRateLimit redissonRateLimit) {
if (redissonRateLimit == null) {
// 方法上没有该注解,直接放行
return;
}

// Spring容器中没有RedissonClient,直接放行
if (redissonClient == null) {
log.warn("Spring容器中没有RedissonClient,Redisson限流器将无法使用............");
return;
}

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 不是http请求,不管
if (servletRequestAttributes == null) {
log.warn("请求不是http,Redisson限流器将无法使用............");
return;
}

log.warn("RedissonRateLimit开始工作");

HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
log.info("RedissonRateLimit key:{}, timeOut:{}, count:{}, url:{}", redissonRateLimit.key(), redissonRateLimit.timeOut(), redissonRateLimit.count(), httpServletRequest.getRequestURI());

RRateLimiter rateLimiter = getRedissonRateLimiter(redissonRateLimit);
// 是否触发限流
boolean result = rateLimiter.tryAcquire();

if (!result) {
throw new RedissonRateLimitException("当前访问人数过多,请稍后再试");
}
}

/**
* 获取Redisson的RRateLimiter
*/
private RRateLimiter getRedissonRateLimiter(RedissonRateLimit redissonRateLimit) {
// 限流次数,注意count的值不能小于1,必须大于等于1
long count = redissonRateLimit.count();

// 限流时间
long timeOut = redissonRateLimit.timeOut();

RRateLimiter rateLimiter = redissonClient.getRateLimiter(redissonRateLimit.key());

// 如果限流器不存在,就创建一个RRateLimiter限流器
if (!rateLimiter.isExists()) {
rateLimiter.trySetRate(RateType.OVERALL, count, timeOut, RateIntervalUnit.SECONDS);
return rateLimiter;
}

// 获取限流的配置信息
RateLimiterConfig rateLimiterConfig = rateLimiter.getConfig();

// 上次配置的限流时间毫秒值
Long rateInterval = rateLimiterConfig.getRateInterval();

// 上次配置的限流次数
Long rate = rateLimiterConfig.getRate();

// 将timeOut转换成毫秒之后再跟rateInterval进行比较
if (TimeUnit.MILLISECONDS.convert(timeOut, TimeUnit.SECONDS) != rateInterval || count != rate) {
// 如果rateLimiterConfig的配置跟我们注解上面的值不一致,说明服务器重启过,程序员又修改了限流的配置
// 删除原有配置
rateLimiter.delete();
// 以程序员重启后的限流配置为准,重新设置
rateLimiter.trySetRate(RateType.OVERALL, count, timeOut, RateIntervalUnit.SECONDS);
}
return rateLimiter;
}
}

使用

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
/**
* timeOut = 1, count = 2的意思是1秒钟内,该接口最多可以访问2-4次。注意我们这个限流器不是很精确,但误差不会太大
* 注意count的值不能小于1,必须大于等于1
* 根据id查询
*
* @param id 自增id
* @return 指定条件数据
*/
@ApiOperation(value = "根据id查询", httpMethod = "GET", response = ResponseUtils.class, produces = "application/json")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "自增id"),
})
@RedissonRateLimit(key = "userselectById", timeOut = 1, count = 2)
@GetMapping("{id}")
public ResponseUtils selectById(@PathVariable Long id) {
UserEntity item = userService.selectById(id);
return ResponseUtils.success(item);
}

/**
* 随便哪个接口都可以使用这个限流器注解@RedissonRateLimit,这个接口的限流为:timeOut = 1, count = 200,1秒钟最多访问200-400次。注意我们这个限流器不是很精确,但误差不会太大
* 注意count的值不能小于1,必须大于等于1
* 根据id查询
*
* @param id 主键id
* @return 指定条件数据
*/
@ApiOperation(value = "根据id查询", httpMethod = "GET", response = ResponseUtils.class, produces = "application/json")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "主键id"),
})
@RedissonRateLimit(key = "roleselectById", timeOut = 1, count = 200)
@GetMapping("{id}")
public ResponseUtils selectById(@PathVariable Integer id) {
TbRoleEntity item = tbRoleService.selectById(id);
return ResponseUtils.success(item);
}

image-20231014220624902

image-20231014220703372