目前比较主流的分布式锁有两种选择:一种是使用redis集群做分布式锁,另外一种是使用zookeeper,这两种分布式锁有着各自的特点,但是在技术选型上,我还是推荐使用zookeeper来做分布式锁,至于为什么不推荐redis集群来做分布式锁,我会在下面阐述。

预备知识

  • CAP理论

1.Consistency 一致性

一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,所以,一致性,说的就是数据一致性。分布式的一致性

对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

三种一致性策略

对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。

如果能容忍后续的部分或者全部访问不到,则是弱一致性

如果经过一段时间后要求能访问到更新后的数据,则是最终一致性

CAP中说,不可能同时满足的这个一致性指的是强一致性

2.Availability 可用性

可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。

对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。所以,一般我们在衡量一个系统的可用性的时候,都是通过停机时间来计算的。

3.Partition Tolerance分区容错性

分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔未独立的几个部分,各个部分还能维持分布式系统的运作,这样就具有好的分区容错性。

简单点说,就是在网络中断,消息丢失的情况下,系统如果还能正常工作,就是有比较好的分区容错性。

  • redis集群并不是强一致性

redis-cluster和大多数集群一样有主从之分,从服务器是主服务器的备份,但是主从服务器之前的同步并不是同步的,而是异步的。这里的异步是指在修改主服务器的值,修改成功之后并不能保证从服务器立马能同步修改之后的状态,redis通过同步和命令传播来同步主从服务器的状态。

  • redis sentinel模式

redis哨兵模式:Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

  • redis内存淘汰机制

Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:

  1. noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。

  2. allkeys-lru:在主键空间中,优先移除最近未使用的key。

  3. volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。

  4. allkeys-random:在主键空间中,随机移除某个key。

  5. volatile-random:在设置了过期时间的键空间中,随机移除某个key。

  6. volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。

这里补充一下主键空间和设置了过期时间的键空间,举个例子,假设我们有一批键存储在Redis中,则有那么一个哈希表用于存储这批键及其值,如果这批键中有一部分设置了过期时间,那么这批键还会被存储到另外一个哈希表中,这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的键空间为主键空间的子集。

为什么不建议使用redis分布锁

  • 主从切换可能丢失锁信息

    考虑一下这样的场景:在分布式环境中,很多并发需要锁来同步,当使用redis分布式锁,通用的做法是使用redis的setnx key value px 这样的命令,设置一个字段,当设置成功说明获取锁,设置不成功说明锁被占用,当获取所之后需要删除锁,也就是删除设置的锁字段,这是锁可以被其他占用。

    这里在主从切换回出现问题,当第一个线程在主服务器上设置了锁,但是这时候从服务器并没有及时同步主服务器的状态,也就是没有同步主服务器中的锁字段,而此时,主服务器挂了,redis的哨兵模式升级从服务器为主服务器,如果在并发量大的情况下,虽然第一个线程获取了锁,其他线程会在当前的主服务器(之前的从服务器,但是并没有同步已经设置的锁字段)上设置锁字段,这样并不能保证锁的互斥性。

  • 缓存易失性

    假如第一个线程设置了锁,但是之后触发内存淘汰机制很不幸淘汰了设置的锁字段,接下来的线程在第一个线程没有释放锁的情况下,也是重新设置锁字段的,这样并不能保证锁的安全性。

参考

http://www.hollischuang.com/archives/666

http://blog.jobbole.com/105335/

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐