在项目开发中,并发安全始终是一个不可忽视的挑战。单机环境下,通过 synchronized 或 Lock 可以轻松实现线程同步。然而,一旦系统迁移到分布式架构,这些本地锁便无法发挥作用——不同机器上的线程彼此孤立,无法感知对方的存在。此时,我们需要借助中间件(例如 Redis)来实现一把全局的分布式锁,从而有效应对分布式场景下的并发问题。
一款基于 Redis 的分布式锁工具——tools-redis
本文介绍一款轻量级、开源的 Redis 分布式锁实现工具——tools-redis。它支持开箱即用,提供注解与手动两种锁控制方式,内置超时处理机制,并允许自定义扩展,能够满足大多数分布式锁的应用需求。
一、引入 Maven 依赖
cn.gjing tools-redis 1.2.0
二、在启动类上启用分布式锁注解
@SpringBootApplication
@EnableToolsLock
public class TestRedisApplication {
public static void main(String[] args) {
SpringApplication.run(TestRedisApplication.class, args);
}
}
三、具体使用方式
1、注解方式
在需要加锁的方法上添加 @Lock 注解即可,简单快捷,无需额外代码。
@RestController
public class TestController {
private static int num = 20;
@GetMapping("/test1")
@Lock(key = "test1")
public void test1() {
System.out.println("当前线程:" + Thread.currentThread().getName());
if (num == 0) {
System.out.println("卖完了");
return;
}
num--;
System.out.println("还剩余:" + num);
}
}
参数说明
| 参数 | 描述 |
|---|---|
| key | 锁的key,建议每个方法保持唯一性 |
| expire | 锁的过期时间,单位为秒,默认值为5 |
| timeout | 尝试获取锁的超时时间,单位为毫秒,默认值为500 |
| retry | 重新获取锁的间隔时间,单位为毫秒,默认值为10 |
使用 ab 压测工具测试的结果如下
2、手动控制方式
在需要使用锁的类中注入 AbstractLock,然后通过代码手动控制加锁与解锁,灵活性更高。
a、lock():加锁
成功获取锁后返回一个用于解锁的值,失败则返回 null。
abstractLock.lock(key, expire, timeout, retry)
参数说明
| 参数 | 描述 |
|---|---|
| key | 锁的key,每个方法应保证唯一性 |
| expire | 锁的过期时间,单位为秒,默认值为5 |
| timeout | 尝试获取锁的超时时间,单位为毫秒,默认值为500 |
| retry | 重新获取锁的间隔时间,单位为毫秒,默认值为10 |
b、release():解锁
成功释放锁后返回当前被解锁的 key,失败则返回 null。
abstractLock.release(key, value)
参数说明
| 参数 | 描述 |
|---|---|
| key | 加锁时对应的key |
| value | 获取锁成功后得到的值 |
使用示例
@RestController
public class LockController {
@Resource
private AbstractLock abstractLock;
private static int num = 10;
@GetMapping("/test2")
public void test2() {
String lock = null;
try {
lock = this.abstractLock.lock("testLock", 20, 10000, 50);
System.out.println("当前线程:" + Thread.currentThread().getName());
if (num == 0) {
System.out.println("卖完了");
return;
}
num--;
System.out.println("还剩余:" + num);
} finally {
this.abstractLock.release("testLock", lock);
}
}
}
ab 压测结果
3、自定义超时处理
当获取锁超时时,默认会抛出超时异常。若需要自定义逻辑,可以继承 AbstractLockTimeoutHandler 并实现自己的处理方法。
@Component
public class TimeoutHandler extends AbstractLockTimeoutHandler {
@Override
public void getLockTimeoutFallback(String s, int i, int i1, int i2) {
// TODO: 自定义处理逻辑
}
}
4、自定义锁实现
@Component
public class DemoLock extends AbstractLock {
@Override
public String lock(String s, String s1, int i, int i1, int i2) {
return null;
}
@Override
public String release(String s, String s1) {
return null;
}
}
四、使用建议
建议将此分布式锁与单机 Redis 配合使用。如果部署在 Redis Sentinel 集群中,需要充分考虑潜在风险:Sentinel 架构采用一主多从模式,数据先写入主库再同步到从库。假设在主库上执行 setnx mykey myvalue 成功,但数据尚未同步到从库时主库宕机,Sentinel 会从从库中选举新的主库。此时新主库中不存在 mykey,另一个客户端再执行 setnx mykey hisvalue 也会成功,导致两个客户端同时持有锁,破坏数据一致性。因此,在 Sentinel 环境下使用需谨慎评估。
