redis学习笔记(十六)分布式锁

redis学习笔记(十六)分布式锁,第1张

一、简介:

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。

Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

二、基本使用语法:

1、获取锁
RLock lock = redisson.getLock(“anyLock”);
2、加锁
最常见的使用方法:

lock.lock();
使用tryLock方法获取锁,返回是个布尔值。

boolean res = lock.tryLock();
可以设置leaseTime参数自动解锁

lock.lock(10, TimeUnit.SECONDS); //表示10秒后自动解锁
还可以指定最多等待多少时间

lock.tryLock(100, 10, TimeUnit.SECONDS); //表示尝试加锁,最多等待100秒,上锁以后10秒自动解锁
3、常用代码模板

boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
…(业务逻辑)
} finally {
lock.unlock();
}
}

三、应用场景

1:标签组同步问题
当调用新增标签组带有标签的接口时,同步到企微进行两次回调,一次是标签回调,另一次是标签组回调。两次回调都会有新增标签组的方法,并且是异步的,导致会有冲突。

期望:两次新增标签组的方法能够按顺序执行,第一个保存完后,第二个才会执行,并且第二个发现已经有标签组了直接返回。

解决方案:在方法前面加一个锁,使其两次回调都能顺序执行该方法,能保证只保存一个标签组。

@Override
public QYWXCommonResultVO groupAdd(ToolTagGroupAddDTO dto) {
    RLock lock = redissonClient.getLock("lock:" + "saveQywxTagGroupId:" + dto.getTagGroupId());
    if (lock.tryLock()) {
        int count = sjbTagGroupService.count(new LambdaQueryWrapper()
                .eq(SjbTagGroup::getQywxId, dto.getTagGroupId()).or().eq(SjbTagGroup::getName,dto.getName()));
        if (count > 0) {
            return QYWXCommonResultVO.success();
        }
        try {
            SjbTagGroup tagGroup = new SjbTagGroup();
            tagGroup.setName(dto.getName());
            tagGroup.setOrdering(sjbTagGroupService.count() + 1);
            tagGroup.setQywxId(dto.getTagGroupId());
            sjbTagGroupService.save(tagGroup);
        } catch (Exception e) {
            log.error("保存标签组失败:" + e.getMessage());
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }
    return QYWXCommonResultVO.success();
}

2:防止并发批量注册问题
原本的注册商户方法是可以被并发请求攻击,导致能批量注册商户。

期望:在短时间内,只能有一个注册商户请求能成功,其余的都返回错误。

解决方案:注册逻辑添加读写锁,获取写锁(互斥锁),并且因为注册逻辑代码运行时间很快,所以需要添加线程休眠3秒。

@Override
public LoginVO register(String phone, String storeName, StoreTypeEnum storeType, String password, Integer terminalFlag) {
    RReadWriteLock lock = redissonClient.getReadWriteLock("sjb:register:" + phone);
    if (lock.writeLock().tryLock()) {
        try {
            Thread.sleep(3000);
			……(具体注册逻辑)
		} catch (InterruptedException e) {
            log.error(e.getMessage());
            throw new BusinessException("当前无法注册,请稍后再试");
        } finally {
            if (lock.writeLock().isLocked()) {
                lock.writeLock().unlock();
            }
        }
    } else {
        throw new BusinessException("当前无法注册,请稍后再试");
    }

}
四、源码分析

第一步:set nx
一直以为是set nx,然后客户端自旋实现。
发现并不是set nx,而源码居然是一段lua代码?

KEYS[1] 就是分布式锁的key,即REDLOCK_KEY;

ARGV[1] 就是internalLockLeaseTime,即锁的租约时间,默认30s;

ARGV[2] 就是getLockName(threadId),是获取锁时set的唯一值,即UUID+threadId:释放锁

命令:
1.hincrby:指定增量
2.pexpire:设置成功返回1,失败返回0
3.hexists:hash值在hash中是否存在
4.pttl:返回过期时间

Lua代码解释(不太熟lua,有错欢迎纠正)

第一个if end,表示key没有被锁住,直接拿到锁return null
第二个if end,表示被锁住的key是当前线程,增加重入次数(居然是可重入锁),然后return null
第三个return,表示不是上面两种情况时返回过期时间

第二步确实是自旋

五、与zookper分布式锁优劣势对比

如果有写错的地方,欢迎大家指正,感谢!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/801603.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存