目录
- SETNX
- Redisson
参考/来源
SETNX
大家熟知的是使用SETNX
命令实现Redis的分布式锁
SETNX
是 set If not exist
的简写。意思就是当 key 不存在时,设置 key 的值,存在时,什么都不做。
set <key> <value> NX
set <key> <value> PX <多少毫秒> NX
set <key> <value> EX <多少秒> NX
各种基于SETNX
的Java代码思路大致如下:
// 1.生成唯一 id
String uuid = UUID.randomUUID().toString();
// 2. 抢占锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
if(lock) {
System.out.println("抢占成功:" + uuid);
// 3.抢占成功,执行业务
List<TypeEntity> typeEntityListFromDb = getDataFromDB();
// 4.获取当前锁的值
String lockValue = redisTemplate.opsForValue().get("lock");
// 5.如果锁的值和设置的值相等,则清理自己的锁
if(uuid.equals(lockValue)) {
System.out.println("清理锁:" + lockValue);
redisTemplate.delete("lock");
}
return typeEntityListFromDb;
} else {
System.out.println("抢占失败,等待锁释放");
// 4.休眠一段时间
sleep(100);
// 5.抢占失败,等待锁释放
return getTypeEntityListByRedisDistributedLock();
}
Redisson
Redisson 提供了使用 Redis 的最简单和最便捷的方法。Redisson 的宗旨是促进使用者对 Redis 的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
- Netty 框架:Redisson 采用了基于 NIO 的Netty框架,不仅能作为 Redis 底层驱动客户端,具备提供对 Redis 各种组态形式的连接功能,对 Redis 命令能以同步发送、异步形式发送、异步流形式发送或管道形式发送的功能,LUA脚本执行处理,以及处理返回结果的功能
- 基础数据结构:将原生的 Redis
Hash
,List
,Set
,String
,Geo
,HyperLogLog
等数据结构封装为 Java 里大家最熟悉的映射(Map)
,列表(List)
,集(Set)
,通用对象桶(Object Bucket)
,地理空间对象桶(Geospatial Bucket)
,基数估计算法(HyperLogLog)
等结构, - 分布式数据结构:这基础上还提供了分布式的
多值映射(Multimap)
,本地缓存映射(LocalCachedMap)
,有序集(SortedSet)
,计分排序集(ScoredSortedSet)
,字典排序集(LexSortedSet)
,列队(Queue)
,阻塞队列(Blocking Queue)
,有界阻塞列队(Bounded Blocking Queue)
,双端队列(Deque)
,阻塞双端列队(Blocking Deque)
,阻塞公平列队(Blocking Fair Queue)
,延迟列队(Delayed Queue)
,布隆过滤器(Bloom Filter)
,原子整长形(AtomicLong)
,原子双精度浮点数(AtomicDouble)
,BitSet
等 Redis 原本没有的分布式数据结构。 - 分布式锁:Redisson 还实现了 Redis文档中提到像分布式锁
Lock
这样的更高阶应用场景。事实上 Redisson 并没有不止步于此,在分布式锁的基础上还提供了联锁(MultiLock)
,读写锁(ReadWriteLock)
,公平锁(Fair Lock)
,红锁(RedLock)
,信号量(Semaphore)
,可过期性信号量(PermitExpirableSemaphore)
和闭锁(CountDownLatch)
这些实际当中对多线程高并发应用至关重要的基本部件。正是通过实现基于 Redis 的高阶应用方案,使 Redisson 成为构建分布式系统的重要工具。 - 节点:Redisson 作为独立节点可以用于独立执行其他节点发布到
分布式执行服务
和分布式调度服务
里的远程任务。
下面介绍Springboot整合Redisson的基本使用
依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.5</version>
</dependency>
注意,这里不在需要引入redis的依赖即可使用redisson!!!
配置文件
主要进行redis配置
spring:
#redis
redis:
host: localhost
password: 123456
port: 6379
pool:
max-idle: 100
min-idle: 1
max-active: 1000
max-wait: -1
配置类
这里前提应该是要启动Redis,这里省略了相关配置
@Configuration
public class MyRedissonConfig {
/**
* 对 Redisson 的使用都是通过 RedissonClient 对象
* @return
* @throws IOException
*/
@Value("${spring.redis.password}")
private String password;
@Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
public RedissonClient redisson() throws IOException {
// 1.创建配置
Config config = new Config();
// 集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
// 2.根据 Config 创建出 RedissonClient 示例。
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword(password);
return Redisson.create(config);
}
}
分布式可重入锁
@RestController
public class TestRedisson {
@Autowired
private RedissonClient redisson;
@GetMapping("test-lock")
public String TestLock() {
// 1.获取锁,只要锁的名字一样,获取到的锁就是同一把锁。
RLock lock = redisson.getLock("mylock");
// 2.加锁
lock.lock();
try {
System.out.println("加锁成功,执行后续代码。线程 ID:" + Thread.currentThread().getId());
Thread.sleep(10000);
} catch (Exception e) {
// TODO
} finally {
lock.unlock();
// 3.解锁
System.out.println("Finally,释放锁成功。线程 ID:" + Thread.currentThread().getId());
}
return "test lock ok";
}
}
为了防止服务宕机导致未释放锁,Redisson 内部提供了一个监控锁的看门狗
,它的作用是在 Redisson 实例被关闭前,不断的延长锁的有效期。
如果我们未制定 lock 的超时时间,就使用 30 秒作为看门狗的默认时间。只要占锁成功,就会启动一个定时任务
:每隔 10 秒重新给锁设置过期的时间,过期时间为 30 秒。
自定义设置超时时间:
lock.lock(8, TimeUnit.SECONDS);
其他锁
Redis还提供了很多其他的锁形式,如分布式读写锁RReadWriteLock、分布式信号量RSemaphore等等。
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
// 获取信号量(停车场)
RSemaphore park = redisson.getSemaphore("park");
// 获取一个信号(停车位)
park.acquire();
// 释放一个信号(停车位)
park.release();