实现
-
加锁方法:SET key value EX 10086 NX
value 是拥有者标识,只有 key-value 匹配才可以解锁
设置过期时间是为了防止锁的持有者后续发生崩溃而没有解锁从而发生死锁的情况
set 和设置时间的操作必须保证原子性,否则 set 后程序突然崩溃就无法设置过期时间,将发生死锁 -
解锁方法:
用 Lua 脚本执行if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
判断锁拥有者和解锁操作同样要保持原子性,否则可能会解除其他人的锁
比如客户端 A 加锁,一段时间之后客户端 A 解锁,在执行 del 之前,锁突然过期了,此时客户端 B 尝试加锁成功,然后客户端 A 再执行 del 方法,则将客户端 B 的锁给解除了
eval 命令执行 Lua 代码的时候,Lua 代码将被当成一个命令去执行,并且直到 eval 命令执行完成,Redis 才会执行其他命令
CAP 理论
- Consistency 一致性
一致性指 “all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,所以,一致性,说的就是数据一致性
一致性级别:
① 强一致性(strong consistency):任何时刻,任何用户都能读取到最近一次成功更新的数据
② 单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也就是说,可获取的数据顺序必是单调递增的
③ 会话一致性(session consistency):任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这个值更旧的值。会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不同用户或同一用户不同会话间则没有保障
④ 最终一致性(eventual consistency):用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障
⑤ 弱一致性(weak consistency):用户无法在确定时间内读到最新更新的值,能容忍后续的部分或者全部访问不到
- Availability 可用性
可用性指 “Reads and writes always succeed”,即服务一直可用,而且是正常响应时间
对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。所以,一般我们在衡量一个系统的可用性的时候,都是通过停机时间来计算的
- 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 策略:
noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错
allkeys-lru:在主键空间中,优先移除最近未使用的 key
volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的 key
allkeys-random:在主键空间中,随机移除某个 key
volatile-random:在设置了过期时间的键空间中,随机移除某个 key
volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除
这里补充一下主键空间和设置了过期时间的键空间,举个例子,假设我们有一批键存储在 Redis 中,则有那么一个哈希表用于存储这批键及其值,如果这批键中有一部分设置了过期时间,那么这批键还会被存储到另外一个哈希表中,这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的键空间为主键空间的子集
为什么不建议使用 redis 分布锁
主从切换可能丢失锁信息
考虑一下这样的场景:在分布式环境中,很多并发需要锁来同步,当使用 redis 分布式锁,通用的做法是使用 redis 的 SET key value EX 10086 NX 这样的命令,设置一个字段,当设置成功说明获取锁,设置不成功说明锁被占用,当获取所之后需要删除锁,也就是删除设置的锁字段,这是锁可以被其他占用
这里在主从切换回出现问题,当第一个线程在主服务器上设置了锁,但是这时候从服务器并没有及时同步主服务器的状态,也就是没有同步主服务器中的锁字段,而此时,主服务器挂了,redis 的哨兵模式升级从服务器为主服务器,如果在并发量大的情况下,虽然第一个线程获取了锁,其他线程会在当前的主服务器(之前的从服务器,但是并没有同步已经设置的锁字段)上设置锁字段,这样并不能保证锁的互斥性
这个缺陷可被 zookeeper 的单调一致性弥补
缓存易失性
假如第一个线程设置了锁,但是之后触发内存淘汰机制很不幸淘汰了设置的锁字段,接下来的线程在第一个线程没有释放锁的情况下,也是重新设置锁字段的,这样并不能保证锁的安全性
网友评论