redis-分布式锁

redis5种数据类型:1、redis字符串(String);2、字符串列表(list)3、有序字符串集合(sorted set)4、哈希(hash)5、字符串集合(set)

 实现分布式锁要满足3点:多进程可见,互斥,可重入。(redis的hash结构实现:hash散列值(hash的key必须是唯一的)

重入锁:----实现方式:使用hash进行存储,key数为(字符串+线程id),value为重入次数。

                加锁时重入次数加1,解锁时重入次数减1。

也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。可重入锁可以避免因同一线程中多次获取锁而导致死锁发生。像synchronized就是一个重入锁,它是通过moniter函数记录当前线程信息来实现的。实现可重入锁需要考虑两点:
   获取锁:首先尝试获取锁,如果获取失败,判断这个锁是否是自己的,如果是则允许再次获取, 而且必须记录重复获取锁的次数。
   释放锁:释放锁不能直接删除了,因为锁是可重入的,如果锁进入了多次,在内层直接删除锁, 导致外部的业务在没有锁的情况下执行,会有安全问题。因此必须获取锁时累计重入的次数,释放时则减去重入次数,如果减到0,则可以删除锁。

    重入锁可以避免因同一线程中多次获取锁而导致死锁发生。

     3种Redis分布式锁的对比 - 吴磊的 - 博客园

1、redis集群扩容缩容实现

在Redis Sentinel模式中,每个节点需要保存全量数据,冗余比较多,而在Redis Cluster模式中,每个分片只需要保存一部分的数据,对于内存数据库来说,还是要尽量的减少冗余。在数据量太大的情况下,故障恢复需要较长时间。

总结:redis cluster集群新增节点,槽范围移动的时候,槽分区(实际存放数据的地方)也会做相应的移动。

redis集群环境搭建、扩容、缩容原理_芭蕉扇-CSDN博客_redis扩容机制

2、redis怎么做到数据隔离(对于不同的微服务看到不容的数据)

3、分布式锁

在分布式场景中,分布式锁经常选用redis或者ZK来实现,在使用redis时,应注意的问题。

使用分布式锁时需要解决的问题:

1、锁需要具备唯一性

问题讲解:分布式场景下为了保证同一变量在分布式环境中的唯一性,需要对同一资源加锁。

解决方案:使用redis命令setnx(set if not exist),即只能被一个客户端操作成功,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝。在锁操作完了后,需要进行锁释放>del lockkey #释放锁。

2、 死锁(因操作客户端挂掉,锁无法释放。----引入原子性的超时时间)

问题讲解: redis释放锁需要客户端的操作,如果此时客户端突然挂了,就没有释放锁的操作了,也意味着其他客户端想要重新加锁,却加不了的问题。

解决方案:,需要在加锁的同时,给锁加上超时时间。

 >setnx lockkey true #加锁操作  >expire lockkey 5    #给锁加上超时时间

因为setnx和expire需要两个命令来完成操作,也就是需要两次RTT操作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题.

进一步优化: 使用set扩展命令,set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个操作,也就是解决了原子性问题.( 锁的创建和设置锁超时时间需要具备原子性

 >set lockkey true ex 5 nx #加锁,过期时间5s-----

  ... do something critical ...

  >del lockkey

3、锁的超时问题(任务超时--使用lua删除锁再重建锁,保证原子性)

  问题讲解:  虽然给锁加上了超时时间,但是客户端并不能一定在超时时间之内完成定时任务,所以,即使当前客户端没有完成任务,此时又会有其他的客户端设置锁成功,此时同一资源将会面临多个客户端同时操作的问题.

 解决方案:客户端可以在锁设置成功之后,进行定时任务,在锁超时之前使用lua脚本删除锁并重新设置锁和超时时间。当然,这里为什么会使用lua来完成操作呢,其实和上面的原子性问题一样,在删除锁和重新设置锁和锁的超时时间之间,可能面临其他的客户端将锁资源占有,而lua具有原子性的特性,删除锁和重新加锁这两个操作要么都完成,要么都不完成.(具体方案以及代码后续补充)

Redis 允许将 Lua 脚本传到 Redis 服务器中执行, 脚本内可以调用大部分 Redis 命令, 且 Redis 保证脚本的原子性:(

  • Lua 嵌入 Redis 优势: 
    1. 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
    2. 原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
    3. 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.

  • 首先需要准备Lua代码: script.lua
local key = "rate.limit:" .. KEYS[1]

local limit = tonumber(ARGV[1])

local expire_time = ARGV[2]

local is_exists = redis.call("EXISTS", key)

if is_exists == 1 then

    if redis.call("INCR", key) > limit then

        return 0

    else

        return 1

    end

else

    redis.call("SET", key, 1)

    redis.call("EXPIRE", key, expire_time)

    return 1

end

Java调用:

private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {

    List<String> keys = Collections.singletonList(ip);

    List<String> argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));

    return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);

}

// 加载Lua代码

private String loadScriptString(String fileName) throws IOException {

    Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));

    return CharStreams.toString(reader);

}

4、锁的可重入问题

问题讲解: 拥有锁的客户端想要再次获得锁

解决方案:我们可以选择使用lua脚本的方案,将锁重新删除和设置.

5、 集群下分布式锁的问题以及需要考虑的其他问题

 问题讲解:

  这一问题是在redis集群方案时会出现的.事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写操作,从节点负责读操作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行操作,此时又会出现多客户端对同一资源进行访问和操作的问题.

  解决方案:后续补充

6、 默认的是非阻塞锁,怎么实现阻塞锁:

Redis 实现分布式阻塞锁_sui_feng_piao_guo的博客-CSDN博客_redis 阻塞锁

redis 阻塞锁 和非阻塞锁 lua 脚本 - 走看看

Redis监听 集群阻塞锁_一抹春色的博客-CSDN博客

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值