二.通过redission这一开源框架的分布式锁,实现两次重复点击不允许
(以查询用户为例)
- Redission是一个在redis的基础上实现的Java驻内存数据网格,它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务
- Redission提供了使用Redis最简单和便捷的方法。它的宗旨就是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
- Redission的Github地址:
https://github.com/redisson/redisson
- Redission 其实跟Jedis差不多,都是用来操作Redis的框架。提供了很多封装,其中分布式锁就是其中之一。
Redis 分布式锁使用
Rlock =redisson.getLock("anyLock");
//最常见的使用方法 lock.lock();
//支持过期解锁功能
//10秒钟以后自动解锁
//无须调用unlock方法手动解锁
lock.lock(10,TimeUnit.SECONDS);
//第二种
//尝试加锁,最多等待100秒,上锁以后10秒自动解锁
//boolean res =lock.tryLock(100,10,TimeUnit.SECONDS);
...
lock.unlock();一般放在finally 中,实现解锁。因为程序执行速率可快可慢,最好是加一个手动解锁
重点是下面这一脚本,基于redis的底层原理
-- 若锁不存在:则新增锁,并设置锁重入计数为1、设置锁过期时间
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
-- 若锁存在,且唯一标识也匹配:则表明当前加锁请求为锁重入请求,故锁重入计数+1,并再次设置锁过期时间
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
-- 若锁存在,但唯一标识不匹配:表明锁是被其他线程占用,当前线程无权解他人的锁,直接返回锁剩余过期时间
return redis.call('pttl', KEYS[1]);
附加我使用redission加锁解锁的代码
在这里插入代码片段
老生常谈,先加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
配置Redisson
public class RedissonManager {
private static Config config = new Config();
//声明redisso对象
private static Redisson redisson = null;
//实例化redisson
static{
config.useClusterServers()
// 集群状态扫描间隔时间,单位是毫秒
.setScanInterval(2000)
//cluster方式至少6个节点(3主3从,3主做sharding,3从用来保证主宕机后可以高可用)
.addNodeAddress("redis://127.0.0.1:6379" )
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
//得到redisson对象
redisson = (Redisson) Redisson.create(config);
}
//获取redisson对象的方法
public static Redisson getRedisson(){
return redisson;
}
}
锁的获取和释放(以查询用户为例)
RedisTemplate<String, Object> redisTemplate;
private RedissonClient redissonClient;
。。。
String key = "account:" + cardId;
RLock lock = redissonClient.getLock("account_lock:" + cardId);
try {
log.info("----尝试加锁----" + "获取锁前的时间:" + LocalDateTime.now());
//boolean locked = lock.tryLock(3, 8, TimeUnit.SECONDS);
lock.lock(10, TimeUnit.SECONDS);
if (lock.isLocked()) {
Account account = (Account) redisTemplate.opsForValue().get(key);
if (account == null) {
account = accountsMapper.findByAccountId(cardId);
if (account != null) {
redisTemplate.opsForValue().set(key, account, 30, TimeUnit.MINUTES);
}
}
TimeUnit.SECONDS.sleep(100);**//为了更直观看到加锁的变化**
if (account != null) {
log.info("account: {}", account);
} else {
log.warn("未能从数据库中找到对应的账户信息");
}
return account;
} else {
log.error("---尝试获取锁失败---");
throw new InvalidCredentialsException("请求锁失败,点击过快,请重试");
}
} catch (InterruptedException e) {
log.error("---请求锁中断异常---", e);
throw new InvalidCredentialsException("请求锁失败,系统错误,请稍后重试");
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("----已释放锁----" + "释放锁的时间:" + LocalDateTime.now());
}
}
}
private
补充一下:只有在未指定锁超时时间时才会使用看门狗!
然后这个看门都加锁机制就通了,很多大佬有一堆原理解析,就不献丑了,想深入了解的同志可以去自行研究
-----未完待续------