身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法

1 前言前端

今天开始来和你们一块儿学习一下Redis实际应用篇,会写几个Redis的常见应用。程序员

在我看来Redis最为典型的应用就是做为分布式缓存系统,其余的一些应用本质上并非杀手锏功能,是基于Redis支持的数据类型和分布式架构来实现的,属于小而美的应用。面试

结合笔者的平常工做,今天和你们一块儿研究下基于Redis的分布式锁和Redlock算法的一些事情。redis

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法
 
 

2.初识锁算法

1. 锁的双面性数据库

如今咱们写的程序基本上都有必定的并发性,要么单台多进线程、要么多台机器集群化,在仅读的场景下是不须要加锁的,由于数据是一致的,在读写混合或者写场景下若是不加以限制和约束就会形成写混乱数据不一致的状况。编程

若是业务安全和正确性没法保证,再多的并发也是无心义的。缓存

这个不禁得让我想起一个趣图:安全

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法
 
 

高并发多半是考验大家公司的基础架构是否强悍,合理正确地使用锁才是我的能力的体现。网络

凡事基本上都是双面的,锁能够在必定程度上保证数据的一致性,可是锁也意味着维护和使用的复杂性,固然也伴随着性能的损耗,我见过的最大的锁可能就是CPython解释器的全局解释器锁GIL了。

没办法 好可怕 那个锁 不像话--《说锁就锁》

锁使用不当不但解决不了数据混乱问题,甚至会带来诸如死锁等更多问题,通俗地说死锁现象:

 

几年前会出现这样的场景:在异地须要买火车票回老家,可是身份证丢了没法购票,补办身份证又须要本人坐火车回老家户籍管理处,就这样生活太难。

2. 无锁化编程

既然锁这么难以把控,那不得不思考有没有无锁的高并发。

无锁编程也是一个很是有意思的话题,后续能够写一篇聊聊这个话题,本次就只提一下,要打开思路,不要被困在凡是并发必须加锁的思惟定势。

在某些特定场景下会选择一种并行转串行的思路,从而尽可能避免锁的使用,举个栗子:

 

Post请求:http://abc.def/setdata?reqid=abc123789def&dbname=bighero

假若有一个上述的post请求的URI部分是个覆盖写操做,reqid=abc123789def,服务部署在多台机器,在大前端将流量转发到Nginx以后根据reqid进行哈希,Nginx的配置大概是这样的:

upstream myservice{ #根据参数进行Hash分配 hash $urlkey; server localhost:5000; server localhost:5001; server localhost:5002;}

通过Nginx负载均衡相同reqid的请求将被转发到一台机器上,固然你可能会说若是集群的机器动态调整呢?我只能说不要考虑那么多那么充分,工程化去设计便可。

然而转发到一台机器仍然没法保证串行处理,由于单机仍然是多线程的,咱们仍然须要将全部的reqid数据放到同一个线程处理,最终保证线程内串行,这个就须要借助于线程池的管理者Disper按照reqid哈希取模来进行多线程的负载均衡。

通过Nginx和线程内负载均衡,最终相同的reqid都将在线程内串行处理,有效避免了锁的使用,固然这种设计可能在reqid不均衡时形成线程饥饿,不太高并发大量请求的状况下仍是能够的。

只描述不画图 就等于没说:

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法
 
 

3. 单机锁和分布式锁

锁依据使用范围可简单分为:单机锁和分布式锁。

Linux提供系统级单机锁,这类锁能够实现线程同步和互斥资源的共享,单机锁实现了机器内部线程之间对共享资源的并发控制。

在分布式部署高并发场景下,常常会遇到资源的互斥访问的问题,最有效最广泛的方法是给共享资源或者对共享资源的操做加一把锁。

分布式锁是控制分布式系统之间同步访问共享资源的一种方式,用于在分布式系统中协调他们之间的动做。

3.分布式锁

1. 分布式锁的实现简介

分布式CAP理论告诉咱们须要作取舍:

 

任何一个分布式系统都没法同时知足一致性Consistency、可用性Availability和分区容错性Partition Tolerance三个方面,最多只能同时知足两项。

在互联网领域的绝大多数的场景中,都须要牺牲强一致性来换取系统的高可用性,系统每每只保证最终一致性。在不少场景中为了保证数据的最终一致性,须要不少的技术方案来支持,好比分布式事务、分布式锁等。

分布式锁通常有三种实现方式:

  • 基于数据库在数据库中建立一张表,表里包含方法名等字段,而且在方法名字段上面建立惟一索引,执行某个方法须要使用此方法名向表中插入数据,成功插入则获取锁,执行结束则删除对应的行数据释放锁
  • 基于缓存数据库RedisRedis性能好而且实现方便,可是单节点的分布式锁在故障迁移时产生安全问题,Redlock是Redis的做者 Antirez 提出的集群模式分布式锁,基于N个彻底独立的Redis节点实现分布式锁的高可用
  • 基于ZooKeeperZooKeeper 是以 Paxos 算法为基础的分布式应用程序协调服务,为分布式应用提供一致性服务的开源组件

2. 分布式锁须要具有的条件

分布式锁在应用于分布式系统环境相比单机锁更为复杂,本文讲述基于Redis的分布式锁实现,该锁须要具有一些特性:

  • 互斥性在任意时刻,只有一个客户端能持有锁 其余尝试获取锁的客户端都将失败而返回或阻塞等待
  • 健壮性一个客户端持有锁的期间崩溃而没有主动释放锁,也须要保证后续其余客户端可以加锁成功,就像C++的智能指针来避免内存泄漏同样
  • 惟一性加锁和解锁必须是同一个客户端,客户端本身不能把别人加的锁给释放了,本身持有的锁也不能被其余客户端释放
  • 高可用没必要依赖于所有Redis节点正常工做,只要大部分的Redis节点正常运行,客户端就能够进行加锁和解锁操做

3. 基于单Redis节点的分布式锁

本文的重点是基于多Redis节点的Redlock算法,不过在展开这个算法以前,有必要提一下单Redis节点分布式锁原理以及演进,由于Redlock算法是基于此改进的。

最初分布式锁借助于setnx和expire命令,可是这两个命令不是原子操做,若是执行setnx以后获取锁可是此时客户端挂掉,这样没法执行expire设置过时时间就致使锁一直没法被释放,所以在2.8版本中Antirez为setnx增长了参数扩展,使得setnx和expire具有原子操做性。

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法
 
 

在单Matster-Slave的Redis系统中,正常状况下Client向Master获取锁以后同步给Slave,若是Client获取锁成功以后Master节点挂掉,而且未将该锁同步到Slave,以后在Sentinel的帮助下Slave升级为Master可是并无以前未同步的锁的信息,此时若是有新的Client要在新Master获取锁,那么将可能出现两个Client持有同一把锁的问题,来看个图来想下这个过程:

身为一枚优秀的程序员必备的基于Redis的分布式锁和Redlock算法
 
 

为了保证本身的锁只能本身释放须要增长惟一性的校验,综上基于单Redis节点的获取锁和释放锁的简单过程以下:

// 获取锁 unique_value做为惟一性的校验

SET resource_name unique_value NX PX 30000

// 释放锁 比较unique_value是否相等 避免误释放

if redis.call("get",KEYS[1]) == ARGV[1] then

return redis.call("del",KEYS[1])

else

return 0

end

这就是基于单Redis的分布式锁的几个要点。

4.Redlock算法过程

Redlock算法是Antirez在单Redis节点基础上引入的高可用模式。

在Redis的分布式环境中,咱们假设有N个彻底互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。

如今假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程当中,客户端会执行如下操做:

  • 1.获取当前Unix时间,以毫秒为单位
  • 2.依次尝试从5个实例,使用相同的key和具备惟一性的value获取锁当向Redis请求获取锁时,客户端应该设置一个网络链接和响应超时时间,这个超时时间应该小于锁的失效时间,这样能够避免客户端死等
  • 3.客户端使用当前时间减去开始获取锁时间就获得获取锁使用的时间。当且仅当从半数以上的Redis节点取到锁,而且使用的时间小于锁失效时间时,锁才算获取成功
  • 4.若是取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间,这个很重要
  • 5.若是由于某些缘由,获取锁失败(没有在半数以上实例取到锁或者取锁时间已经超过了有效时间),客户端应该在全部的Redis实例上进行解锁,不管Redis实例是否加锁成功,由于可能服务端响应消息丢失了可是实际成功了,毕竟多释放一次也不会有问题

上述的5个步骤是Redlock算法的重要过程,也是面试的热点,有心的读者仍是记录一下吧!

5.Redlock算法是否安全的争论

相关文章
相关标签/搜索