Redis分布式锁服务(八)

阅读目录:

  1. 概述
  2. 分布式锁
  3. 多实例分布式锁
  4. 总结

概述

在多线程环境下,通常会使用锁来保证有且只有一个线程来操作共享资源。比如:

object obj = new object();
lock (obj)
{
//操作共享资源
}

利用操作系统提供的锁机制,可以确保多线程或多进程下的并发唯一操作。但如果在多机环境下就不能满足了,当A,B两台机器同时操作C机器的共享资源时,就需要第三方的锁机制来保证在分布式环境下的资源协调,也称分布式锁。

Redis有三个最基本属性来保证分布式锁的有效实现:

  • 安全性: 互斥,在任何时候,只有一个客户端能持有锁。
  • 活跃性A:没有死锁,即使客户端在持有锁的时候崩溃,最后也会有其他客户端能获得锁,超时机制。
  • 活跃性B:故障容忍,只有大多数Redis节点时存活的,客户端仍可以获得锁和释放锁。

分布式锁

由于Redis是单线程模型,命令操作原子性,所以利用这个特性可以很容易的实现分布式锁。 获得一个锁

SET key uuid NX PX timeout
SET resource_name uniqueVal NX PX 30000

命令中的NX表示如果key不存在就添加,存在则直接返回。PX表示以毫秒为单位设置key的过期时间,这里是30000ms。 设置过期时间是防止获得锁的客户端突然崩溃掉或其他异常情况,导致redis中的对象锁一直无法释放,造成死锁。
Key的值需要在所有请求锁服务的客户端中,确保是个唯一值。 这是为了保证拿到锁的客户端能安全释放锁,防止这个锁对象被其他客户端删除。
举个例子:

  1. A客户端拿到对象锁,但在因为一些原因被阻塞导致无法及时释放锁。
  2. 因为过期时间已到,Redis中的锁对象被删除。
  3. B客户端请求获取锁成功。
  4. A客户端此时阻塞操作完成,删除key释放锁。
  5. C客户端请求获取锁成功。
  6. 这时B、C都拿到了锁,因此分布式锁失效。

要避免例子中的情况发生,就要保证key的值是唯一的,只有拿到锁的客户端才能进行删除。 基于这个原因,普通的del命令是不能满足要求的,我们需要一个能判断客户端传过来的value和锁对象的value是否一样的命令。遗憾的是Redis并没有这样的命令,但可以通过Lua脚本来完成:

if redis.call("get",KEYS[1]) == ARGV[1] then
   return redis.call("del",KEYS[1])
 else    return 0
 end

逻辑很简单,获取key中的值和参数中的值相比较,相等删除,不相等返回0。

多实例分布式锁

上面是在单个Redis实例实现分布式锁的,这存在一个问题就是,如果这台实例因某些原因崩溃掉,那么所有客户端的锁服务全部失效。
Redis本身支持Master-Slave结构,可以一主多从,采用高可用方法,可以保证在master挂的时候自动切换到slave。 但是由于主从之间是异步同步数据的,所以redis并不能完全的实现锁的安全性。 举个例子来说:

  1. A客户端在master实例上获得一个锁。
  2. 在对象锁key传送到slave之前,master崩溃掉。
  3. 一个slave被选举成master。
  4. B客户端可以获取到同个key的锁,但A也已经拿到锁,导致锁失效。

在多台master情况下实现这个算法,并保证锁的安全性。 步骤如下:

  1. 客户端以毫秒为单位获取当前时间。
  2. 使用同样key和值,循环在多个实例中获得锁。 为了获得锁,客户端应该设置个偏移时间,它小于锁自动释放时间(即key的过期时间)。 举个例子来说,如果一个锁自动释放时间是10秒,那偏移时间应该设置在5~50毫秒的范围。 防止因为某个实例崩溃掉或其他原因,导致client在获取锁时耗时过长。
  3. 计算获取所有锁的耗时,即当前时间减去开始时间,得到a值。 用锁自动释放时间减去a值,在减去偏移时间,得到c值,如果获取锁成功的实例数量大于实际的数量一半,并且c大于0,那么锁就被获取成功。
  4. 锁获取成功,锁对象的有效时间是上面的c值。
  5. 若是客户端因为一些原因获取失败,原因可能是上面的c值为负数或者锁成功的数量小于实例数,以用N/2+1当标准(N为实例数)。 那么会释放所有实例上的锁。

上面描述可能不方便理解,用代码表示如下:

//锁自动释放时间
TimeSpan ttl=new TimeSpan(0,0,0,30000)
//获取锁成功的数量
 int n = 0;
//记录开始时间
 var startTime = DateTime.Now;

  //在每个实例上获取锁
                for_each_redis(
                    redis =>
                    {
                        if (LockInstance(redis, resource, val, ttl)) n += 1;
                    }
                );

//偏移时间是锁自动释放时间的1%,根据上面10s是5-50毫秒推出。
 var drift = Convert.ToInt32(ttl.TotalMilliseconds * 0.01); 

//锁对象的有效时间=锁自动释放时间-(当前时间-开始时间)-偏移时间
 var validity_time = ttl - (DateTime.Now - startTime) - new TimeSpan(0, 0, 0, 0, drift);

//判断成功的数量和有效时间c值是否大于0 if (n >= (N/2+1) && validity_time.TotalMilliseconds > 0) { }

总结

用Redis做分布式锁相比其他分布式锁(zookeeper)实现更简单,速度更快。
在ServiceStack.Redis客户端组件上是直接支持锁实现的。
或者用stackexchange客户端组件,锁实现及示例代码:https://github.com/kidfashion/redlock-cs。
官方介绍文档:http://redis.io/topics/distlock。

http://www.cnblogs.com/mushroom/p/4752499.html

时间: 2025-01-26 13:53:03

Redis分布式锁服务(八)的相关文章

redis分布式锁的运用和理解

问题描述 redis分布式锁的运用和理解 redis是单进程单线程模式 ,为什么还需要分布式锁?跨jvm是指多个服务器上在运行同一个服务吗? 网上看了很多说跨jvm会需要锁 ,但还是不怎么理解 解决方案 redis分布式锁redis分布式锁-SETNX实现Redis实现分布式锁 解决方案二: redis 3.0也有集群模式了,可以多个机器组成一个整体的cache 解决方案三: redis分布式锁和zookeeper分布式锁都在在企业服务中常用的知识.譬如:你有一个服务程序,需要部署在5台独立的机

Redis 分布式锁的正确实现方式( Java 版 )

分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁.可靠性首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:互斥性.在任意时刻,只有一个客户端能持有锁. 不会发生死锁.即使有一个客户端在持有锁的期间

C#和.NET中的分布式锁服务

背景 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那一层来挡.当大量的行锁.表锁.事务充斥着数据库的时候,不如换个角度思考问题.一般web应用很多的瓶颈都在数据库上,这里给大家介绍的是减轻数据库锁负担的一种方案. 简介 如果我们的需求很简单,例如对于用户的账户资金,要保证原子性操作.并且不同的客户端在同一时间内只能提交一个对象操作.lock.单例?!在单台上还可以,但是大型web项目上,负载均衡是常用的技术手段手段,同一意义的对象可能存在不同的副本,这时我们又如何保证排他操

redis分布式锁,无须设置有效期,自动检测hold锁的节点是否存活

1.有一个独立的keeplive守护线程保证节点存活,频率是n. 2.节点存活信息由固定前置+mac+进程id+进程启动时间,保证节点重启问题. 3. 锁的信息由固定前置+mac+进程id+进程启动时间. 4. 具体锁的逻辑参考lock方法. package six.com.crawler.common; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.Netw

基于Redis的分布式锁真的安全吗?(下)

自从我写完这个话题的上半部分之后,就感觉头脑中出现了许多细小的声音,久久挥之不去.它们就像是在为了一些鸡毛蒜皮的小事而相互争吵个不停.的确,有关分布式的话题就是这样,琐碎异常,而且每个人说的话听起来似乎都有道理.   今天,我们就继续探讨这个话题的后半部分.本文中,我们将从Antirez反驳Martin Kleppmann的观点开始讲起,然后会涉及到Hacker News上出现的一些讨论内容,接下来我们还会讨论到基于Zookeeper和Chubby的分布式锁是怎样的,并和Redlock进行一些对

基于Redis的分布式锁真的安全吗?(上)

网上有关Redis分布式锁的文章可谓多如牛毛了,不信的话你可以拿关键词"Redis 分布式锁"随便到哪个搜索引擎上去搜索一下就知道了.这些文章的思路大体相近,给出的实现算法也看似合乎逻辑,但当我们着手去实现它们的时候,却发现如果你越是仔细推敲,疑虑也就越来越多.   实际上,大概在一年以前,关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间就发生过一场争论.由于对这个问题一直以来比较关注,所以我前些日子仔细阅读了与这

基于redis实现的分布式锁

  基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标识,因此基于redis实现的分布式锁主要依赖redis的SETNX命令和DEL命令,SETNX相当于上锁,DEL相当于释放锁,当然,在下面的具体实现中会更复杂些.之所以称为分布式锁,是因为客户端可以在redis集群环境中向集群中任一个可用Master节点请求上锁(即SETNX命令存储key到re

基于Redis实现分布式锁以及任务队列_Redis

一.前言 双十一刚过不久,大家都知道在天猫.京东.苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力,严重时说不定就宕机了,另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下 减1),那次这个

详解Java如何实现基于Redis的分布式锁_java

前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以