关闭

分布式锁的redis缓存使用方式

时间: 2019-03-21阅读: 1004标签: redis

目前有很多成熟的缓存产品,包括Redis,memcached等。这里以Redis为例来分析下使用缓存实现分布式锁的方案。主要的实现方式是使用Jedis.setNX方法来实现。

public boolean trylock(String key) {    
  ResultCode code = jedis.setNX(key, "This is a Lock.");    
  if (ResultCode.SUCCESS.equals(code))        
    return true;    
  else        
    return false; 
  } 
public boolean unlock(String key){
  ldbTairManager.invalid(NAMESPACE, key); 
}

以上实现方式同样存在几个问题:
1、单点问题。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在redis中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,无论成功还是失败都直接返回。
4、这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在redis中已经存在。无法再执行setNX操作。
5、这把锁是非公平的,所有等待的线程同时去发起setNX操作,运气好的线程能获取锁。

当然,同样有方式可以解决。
现在主流的缓存服务都支持集群部署,通过集群来解决单点问题。
没有失效时间?redis的setExpire方法支持传入失效时间,到达时间之后数据会自动删除。
非阻塞?while重复执行。
非可重入?在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。
非公平?在线程获取锁之前先把所有等待的线程放入一个队列中,然后按先进先出原则获取锁。

redis集群的同步策略是需要时间的,有可能A线程setNX成功后拿到锁,但是这个值还没有更新到B线程执行setNX的这台服务器,那就会产生并发问题。
redis的作者Salvatore Sanfilippo,提出了Redlock算法,该算法实现了比单一节点更安全、可靠的分布式锁管理(DLM)。
Redlock算法假设有N个redis节点,这些节点互相独立,一般设置为N=5,这N个节点运行在不同的机器上以保持物理层面的独立。


算法的步骤如下:

1、客户端获取当前时间,以毫秒为单位。
2、客户端尝试获取N个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N个节点以相同的key和value获取锁。客户端需要设置接口访问超时,接口超时时间需要远远小于锁超时时间,比如锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。
3、客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过3个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。
4、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。
5、如果客户端获取锁失败了,客户端会依次删除所有的锁。 
使用Redlock算法,可以保证在挂掉最多2个节点的时候,分布式锁服务仍然能工作,这相比之前的数据库锁和缓存锁大大提

问题一:1、GC等场景可能随时发生,并导致在客户端获取了锁,在处理中超时,导致另外的客户端获取了锁?

在一个客户端获取了分布式锁后,在客户端的处理过程中,可能出现锁超时释放的情况,这里说的处理中除了GC等非抗力外,程序流程未处理完也是可能发生的。之前在说到数据库锁设置的超时时间2分钟,如果出现某个任务占用某个订单锁超过2分钟,那么另一个交易中心就可以获得这把订单锁,从而两个交易中心同时处理同一个订单。正常情况,任务当然秒级处理完成,可是有时候,加入某个rpc请求设置的超时时间过长,一个任务中有多个这样的超时请求,那么,很可能就出现超过自动解锁时间了。当初我们的交易模块是用C++写的,不存在GC,如果用java写,中间还可能出现Full GC,那么锁超时解锁后,自己客户端无法感知,是件非常严重的事情。我觉得这不是锁本身的问题,上面说到的任何一个分布式锁,只要自带了超时释放的特性,都会出现这样的问题。如果使用锁的超时功能,那么客户端一定得设置获取锁超时后,采取相应的处理,而不是继续处理共享资源。Redlock的算法,在客户端获取锁后,会返回客户端能占用的锁时间,客户端必须处理该时间,让任务在超过该时间后停止下来。

问题二:算法依赖本地时间,会出现时钟不准,导致2个客户端同时获得锁的情况?
Redlock有个关键的特性是,获取锁的时间是锁默认超时的总时间减去获取锁所花费的时间,这样客户端处理的时间就是一个相对时间,就跟本地时间无关了。


总结:

由此看来,Redlock的正确性是能得到很好的保证的。仔细分析Redlock,相比于一个节点的redis,Redlock提供的最主要的特性是可靠性更高,这在有些场景下是很重要的特性。但是我觉得Redlock为了实现可靠性,却花费了过大的代价。

首先必须部署5个节点才能让Redlock的可靠性更强。然后需要请求5个节点才能获取到锁,通过java Future的方式,先并发向5个节点请求,再一起获得响应结果,能缩短响应时间,不过还是比单节点redis锁要耗费更多时间。然后由于必须获取到5个节点中的3个以上,所以可能出现获取锁冲突,即大家都获得了1-2把锁,结果谁也不能获取到锁,这个问题,redis作者借鉴了raft算法的精髓,通过冲突后在随机时间开始,可以大大降低冲突时间,但是这问题并不能很好的避免,特别是在第一次获取锁的时候,所以获取锁的时间成本增加了。如果5个节点有2个宕机,此时锁的可用性会极大降低,首先必须等待这两个宕机节点的结果超时才能返回,另外只有3个节点,客户端必须获取到这全部3个节点的锁才能拥有锁,难度也加大了。分析了这么多原因,我觉得Redlock的问题,最关键的一点在于Redlock需要客户端去保证写入的一致性,后端5个节点完全独立,所有的客户端都得操作这5个节点。
如果5个节点有一个leader,客户端只要从leader获取锁,其他节点能同步leader的数据,这样,分区、超时、冲突等问题都不会存在。所以为了保证分布式锁的正确性,我觉得使用强一致性的分布式协调服务能更好的解决问题。

问题又来了,失效时间我设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。

对于这个问题目前主流的做法是每获得一个锁时,只设置一个很短的超时时间,同时起一个线程在每次快要到超时时间时去刷新锁的超时时间。在释放锁的同时结束这个线程。如redis官方的分布式锁组件redisson,就是用的这种方案

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

链接: http://www.fly63.com/article/detial/2450

redis用在哪里?redis 应用场景

Redis是一个由Salvatore Sanfilippo写的key-value存储系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis的主从复制

Redis配置成主从模式,主库(Master)只负责写数据,从库(Slave)只负责读数据。一个主库可以拥有多个从库,但一个从库只能隶属于一个主库。

Redis中设置了过期时间的Key,那么你还要知道些什么?

熟悉Redis的同学应该知道,Redis的每个Key都可以设置一个过期时间,当达到过期时间的时候,这个key就会被自动删除。这就是Redis的过期策略。在为key设置过期时间需要注意的事项

PHP操作Redis常用技巧总结

Redis连接与认证连接参数:ip、端口、连接超时时间,连接成功返回true,否则返回false,设置键值:成功返回true,否则返回false,获取键值:成功返回String类型键值

Redis在Php项目中的实际应用场景

Redis在Php项目中的实际应用场景:商品维度计数、用户维度计数、存储社交关系、用作缓存代替memcached、反spam系统、用户Timeline/Feeds、最新列表&排行榜、消息通知、将Redis用作消息队列

Redis 中 Lua 脚本的应用和实践

前段时间组内有个投票的产品,上线前考虑欠缺,导致被刷票严重。后来,通过研究,发现可以通过 redis lua 脚本实现限流,这里将 redis lua 脚本相关的知识分享出来,讲的不到位的地方还望斧正。

Redis面试常问的知识点总汇

Redis 是一个基于内存的高性能key-value数据库。Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。

PHP使用redis防止大并发下二次写入

php调用redis进去读写操作,大并发下会出现:读取key1,没有内容则写入内容,但是大并发下会出现同时多个php进程写入的情况,这个时候需要加一个锁,即获取锁的php进程有权限写。

docker搭建redis集群

从docker库获取ruby镜像;创建虚拟网卡;查看网卡信息;查看redis-net虚拟网卡网关ip;创建需要挂载的目录以及文件;在/docker/redis目录下创建一个sh脚本文件;执行脚本创建挂载目录以及配置文件

Redis不仅仅是缓存,还是……

一段时间以来,巨大数量的数据处理迫使所有的应用程序在数据库层前添加缓存策略。即使经典数据库进行了大量的下划线优化,仍然不能提供足够的速度和可用性。主要原因在于数据存储越远,获取数据就越困难

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!