分布式锁
分布式锁是控制分布式系统之间共同访问共享资源的一种锁的实现。如果一个系统,或者不同系统的不同主机之间共享某个资源时,往往需要互斥,来排除干扰,满足数据一致性。
分布式锁需要解决的问题如下:
-
互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁。
-
安全性:锁只能被持有该锁的客户端删除,不能由其他客户端删除。
-
死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其他客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁。
-
容错:当各个节点,如某个 Redis 节点宕机的时候,客户端仍然能够获取锁或释放锁。
如何使用 Redis 实现分布式锁
使用 SETNX 实现,SETNX key value:如果 Key 不存在,则创建并赋值。
该命令时间复杂度为 O(1),如果设置成功,则返回 1,否则返回 0。
由于 SETNX 指令操作简单,且是原子性的,所以初期的时候经常被人们作为分布式锁,我们在应用的时候,可以在某个共享资源区之前先使用 SETNX 指令,查看是否设置成功。
如果设置成功则说明前方没有客户端正在访问该资源,如果设置失败则说明有客户端正在访问该资源,那么当前客户端就需要等待。
但是如果真的这么做,就会存在一个问题,因为 SETNX 是长久存在的,所以假设一个客户端正在访问资源,并且上锁,那么当这个客户端结束访问时,该锁依旧存在,后来者也无法成功获取锁,这个该如何解决呢?
由于 SETNX 并不支持传入 EXPIRE 参数,所以我们可以直接使用 EXPIRE 指令来对特定的 Key 来设置过期时间。
用法:
EXPIRE key seconds
解决:从 Redis 2.6.12 版本开始,我们就可以使用 Set 操作,将 SETNX 和 EXPIRE 融合在一起执行,具体做法如下:
-
EX second:设置键的过期时间为 Second 秒。
-
PX millisecond:设置键的过期时间为 MilliSecond 毫秒。
-
NX:只在键不存在时,才对键进行设置操作。
-
XX:只在键已经存在时,才对键进行设置操作。
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
注:SET 操作成功完成时才会返回 OK,否则返回 nil。
如何实现异步队列
①使用 Redis 中的 List 作为队列
使用上文所说的 Redis 的数据结构中的 List 作为队列 Rpush 生产消息,LPOP 消费消息。
此时我们可以看到,该队列是使用 Rpush 生产队列,使用 LPOP 消费队列。
在这个生产者-消费者队列里,当 LPOP 没有消息时,证明该队列中没有元素,并且生产者还没有来得及生产新的数据。
缺点:LPOP 不会等待队列中有值之后再消费,而是直接进行消费。
弥补:可以通过在应用层引入 Sleep 机制去调用 LPOP 重试。
②使用 BLPOP key [key…] timeout
BLPOP key [key …] timeout:阻塞直到队列有消息或者超时。
缺点:按照此种方法,我们生产后的数据只能提供给各个单一消费者消费。能否实现生产一次就能让多个消费者消费呢?
③Pub/Sub:主题订阅者模式
发送者(Pub)发送消息,订阅者(Sub)接收消息。订阅者可以订阅任意数量的频道。
Pub/Sub模式的缺点:消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的。
此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列,如 Kafka…此处不再赘述。
Redis 的同步机制
主从同步原理
Redis 一般是使用一个 Master 节点来进行写操作,而若干个 Slave 节点进行读操作,Master 和 Slave 分别代表了一个个不同的 Redis Server 实例。
另外定期的数据备份操作也是单独选择一个 Slave 去完成,这样可以最大程度发挥 Redis 的性能,为的是保证数据的弱一致性和最终一致性。
另外,Master 和 Slave 的数据不是一定要即时同步的,但是在一段时间后 Master 和 Slave 的数据是趋于同步的,这就是最终一致性。
全同步过程如下:
-
Slave 发送 Sync 命令到 Master。
-
Master 启动一个后台进程,将 Redis 中的数据快照保存到文件中。
-
Master 将保存数据快照期间接收到的写命令缓存起来。
-
Master 完成写文件操作后,将该文件发送给 Slave。
-
使用新的 AOF 文件替换掉旧的 AOF 文件。
-
Master 将这期间收集的增量写命令发送给 Slave 端。
增量同步过程如下:
-
Master 接收到用户的操作指令,判断是否需要传播到 Slave。
-
将操作记录追加到 AOF 文件。
-
将操作传播到其他 Slave:对齐主从库;往响应缓存写入指令。
-
将缓存中的数据发送给 Slave。
Redis Sentinel(哨兵)
主从模式弊端:当 Master 宕机后,Redis 集群将不能对外提供写入操作。Redis Sentinel 可解决这一问题。
解决主从同步 Master 宕机后的主从切换问题:
-
监控:检查主从服务器是否运行正常。
-
提醒:通过 API 向管理员或者其它应用程序发送故障通知。
-
自动故障迁移:主从切换(在 Master 宕机后,将其中一个 Slave 转为 Master,其他的 Slave 从该节点同步数据)。
Redis 集群
如何从海量数据里快速找到所需?
①分片
按照某种规则去划分数据,分散存储在多个节点上。通过将数据分到多个 Redis 服务器上,来减轻单个 Redis 服务器的压力。
②一致性 Hash 算法
既然要将数据进行分片,那么通常的做法就是获取节点的 Hash 值,然后根据节点数求模。
但这样的方法有明显的弊端,当 Redis 节点数需要动态增加或减少的时候,会造成大量的 Key 无法被命中。所以 Redis 中引入了一致性 Hash 算法。
该算法对 2^32 取模,将 Hash 值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为 0、1、2…2^32-1。
之后将每个服务器进行 Hash 运算,确定服务器在这个 Hash 环上的地址,确定了服务器地址后,对数据使用同样的 Hash 算法,将数据定位到特定的 Redis 服务器上。
如果定位到的地方没有 Redis 服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。
③Hash 环的数据倾斜问题
Hash 环在服务器节点很少的时候,容易遇到服务器节点不均匀的问题,这会造成数据倾斜,数据倾斜指的是被缓存的对象大部分集中在 Redis 集群的其中一台或几台服务器上。
如上图,一致性 Hash 算法运算后的数据大部分被存放在 A 节点上,而 B 节点只存放了少量的数据,久而久之 A 节点将被撑爆。
针对这一问题,可以引入虚拟节点解决。简单地说,就是为每一个服务器节点计算多个 Hash,每个计算结果位置都放置一个此服务器节点,称为虚拟节点,可以在服务器 IP 或者主机名后放置一个编号实现。
例如上图:将 NodeA 和 NodeB 两个节点分为 Node A#1-A#3,NodeB#1-B#3。
文章来源于:https://mp.weixin.qq.com/s/1EZcXPiE6jwGDPTwfvpE6g