0%

【Redis】分布式锁

Redis实现分布式锁


1 Redis实现

1.1 环境

1
2
3
4
5
6
<!--依赖-->
<!-- redis(lettuce)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><
1
2
3
4
5
# application.yaml
spring:
redis:
host: 127.0.0.1 # redis地址
port: 6379 # redis端口

1.2 代码

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
@Autowired
private RedisTemplate redisTemplate;

@RequestMapping("/test")
public void test() {
// 循环通过setnx获取锁,获取失败,沉睡两秒
// 锁设置过期时间,避免程序停掉,未删除锁,造成死锁
String uuid = UUID.randomUUID().toString();
while (!redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS)) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取锁成功,执行业务,业务执行完后删除锁
try {
System.out.println(Thread.currentThread() + " -- 获取锁成功,执行业务...");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 利用lua脚本,保证原子性删除(uuid相同才删除lock,避免业务超时lock过期,删除了新进程产生的lock)
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList("lock"), uuid);
System.out.println(Thread.currentThread() + " -- 释放锁");
}
}

2 Redisson实现

1
2
3
4
5
6
7
<!-- 依赖 -->
<!-- springboot redisson整合 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.5</version>
</dependency>
1
2
3
4
5
6
7
8
# application.yaml
spring:
redis:
redisson:
config: | # 具体参数查询org.redisson.config.SingleServerConfig类
singleServerConfig:
address: "redis://127.0.0.1:6379"
database: 0

2.1 可重入锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void redissonLock() {
// 创建锁
RLock lock = redissonClient.getLock("lock");
// 获取锁(阻塞,只有获取成功才会向下执行)
lock.lock();
try {
// 执行业务
System.out.println(Thread.currentThread() + " -- 获取锁");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
System.out.println(Thread.currentThread() + " -- 释放锁");
lock.unlock();
}
}
  • 补充
    • 不手动设置key过期时间,会默认设置30s,且内部有看门狗(watch dog)机制,会对业务进行自动续期
    • 手动设置过期时间,看门狗功能不会生效,不会自动续期
    • 支持非阻塞获取锁,方法为lock.tryLock()

2.2 读写锁

  • 读写锁:一旦写锁获取时,禁止获取读锁。写锁释放时,读锁才能获取;
  • 读写锁主要保证读取的数据永远是最新的;
  • 写锁是互斥锁(获取锁后,读锁无法获取);读锁是共享锁(读锁获取时相当于无锁,所有请求都可以同时获取锁);
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
@Autowired
private RedissonClient redissonClient;

@GetMapping("/write")
public void writeLock() {
RLock wlock = redissonClient.getReadWriteLock("rwlock").writeLock();
// 获取写锁
wlock.lock();
try {
System.out.println(Thread.currentThread() + " -- 获取写锁");
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread() + " -- 释放写锁");
wlock.unlock();
}
}

@GetMapping("/read")
public void readLock() {
RLock rlock = redissonClient.getReadWriteLock("rwlock").readLock();
// 获取读锁
rlock.lock();
try {
System.out.println(Thread.currentThread() + " -- 获取读锁");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread() + " -- 释放读锁");
rlock.unlock();
}
}

2.3 信号量

  • 信号量:预先设置空间大小,每获取一次信号,可使用空间减少,直到信号量为0时。禁止获取空间,阻塞处理。直到有空间释放时,才能进行下一步处理。
  • 信号量可以应用在停车场车位限流等场景
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
@Autowired
private RedisTemplate redisTemplate;

@Autowired
private RedissonClient redissonClient;

@GetMapping("/acquire")
public void acquire() {
// 设置可使用信号量
redisTemplate.opsForValue().setIfAbsent("semaphore", 3);
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
try {
// 获取信号量,阻塞获取(需要非阻塞,使用tryAcquire)
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

@GetMapping("/release")
public void release() {
RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
// 释放信号量
semaphore.release();
}