Redis进阶

主从架构-主从同步 #

在Redis中,主从同步是一种常用的数据复制方式,它允许一个或多个从服务器(slave)获得与主服务器(master)相同的数据副本。这种架构提供了数据的冗余和读取扩展性。

  1. 全量复制:当一个从服务器第一次连接到主服务器时,或是由于某些原因需要重新同步时,会进行全量复制。在这个过程中,主服务器会生成一个当前所有数据的快照,并将这个快照发送至请求同步的从服务器。从服务器接收到数据后,加载到自己的数据空间内。

  2. 部分复制:一旦完成了全量复制,如果从服务器断开连接又重新连接,且中断时间不长,主服务器可以只发送这段时间内发生变化的数据给从服务器,而不是再次进行全量复制。这依赖于主服务器的复制积压缓冲区来存储最近的写命令。

  3. 同步策略:为了保证数据的一致性,从服务器在初始同步完成之前不会对外提供服务。在日常运行时,主服务器会将写命令同时发送给所有的从服务器,以此来保证数据的实时一致性。

主从同步使得从服务器可以承担读操作,减轻主服务器的负载,同时也可以在主服务器遇到故障时,进行故障转移。

主从哨兵架构 #

Redis哨兵(Sentinel)系统是用于管理多个Redis服务器的系统。该体系结构具有以下特点:

  1. 监控:哨兵会监控主从服务器是否正常运作。
  2. 自动故障转移:如果主服务器出现故障,哨兵可以自动选举新的主服务器,并让原来的从服务器指向新的主服务器。
  3. 配置提供者:哨兵还会将当前的主服务器地址提供给客户端,确保客户端总是连接到正确的主服务器。

哨兵机制通过这些功能增加了Redis环境的高可用性和稳定性。

切片集群-Redis Cluster #

Redis Cluster是Redis的分布式解决方案。它支持数据的水平分片,以下是其关键特性:

  1. 自动分片:��动将数据分布在不同的节点上,每个节点只保存整个数据集的一部分。
  2. 高可用性:采用主从复制模型,即使在多个节点失败的情况下也能保证服务的可用性。
  3. 无中心设计:没有中心节点,每个节点都保存着整个集群状态的一部分,节点之间通过Gossip协议交换信息。

Redis Cluster通过对键进行CRC16计算并对16384取余数来决定将键分配到哪个槽位,每个节点负责一部分槽位,从而实现负载均衡。

Redis Cluster 通信开销 #

由于Redis Cluster节点间需要频繁地交换消息以维护集群状态,因此会产生额外的通信开销:

  1. Gossip通信:节点间通过Gossip协议定期交换信息,包括数据迁移、故障检测等。
  2. 重定向操作:客户端可能会尝试向不包含数据所在槽的节点发起请求,这时节点会返回一个重定向信息

切片集群-Codis

Redis 分布式锁是一种用于多个计算节点之间同步访问共享资源的机制。在分布式系统中,当多个进程需要同时访问某些数据或执行某些任务时,为了避免竞态条件(race conditions)和数据不一致,通常需要使用分布式锁来保证在同一时间只有一个进程能够执行特定的操作。

Redis 分布式锁实现 #

一个常见的Redis分布式锁实现是使用 Redis 的 SETNX 命令(Set if not exists)。这个命令只在键不存在的情况下设置键的值,并且返回是否成功设置。由于 SETNX 是原子操作,因此可以用来实现锁的功能。

另一个更加推荐的方式是使用 Redis 2.6.12 版本引入的 SET 命令结合选项 NX(表示只有键不存在时才进行设置)和 PX(给键设置过期时间,单位为毫秒),这可以保证即使在客户端崩溃的情况下,锁也会在一定时间后自动释放,防止死锁。

使用 Redis 分布式锁的步骤: #

  1. 尝试获取锁

    • 使用 SET key value NX PX milliseconds 进行设置。
    • 如果返回 OK,则获取锁成功。
    • 如果返回 nil,则获取锁失败。
  2. 执行业务逻辑

    • 在持有锁的时间内执行必要的操作。
  3. 释放锁

    • 完成操作后,使用 DEL 命令删除键来释放锁。

注意事项 #

  • 安全性:为了确保只有获得锁的客户端能够释放锁,应该将锁和一个随机生成的值关联起来,并在解锁时验证这个值。
  • 死锁:锁应该有一个合理的过期时间,以避免客户端在持有锁时崩溃导致其他客户端永远无法获取锁。
  • 重入性:在某些场景下,同一个客户端可能需要再次获取已经持有的锁,需要考虑锁的重入性设计。

Python 实现示例 #

以下是使用 Python 和 redis-py 库实现 Redis 分布式锁的简单示例:

import redis
import uuid
import time

class RedisLock:
    def __init__(self, redis_client, lock_key):
        self.redis_client = redis_client
        self.lock_key = lock_key
        self.lock_value = None

    def acquire_lock(self, timeout_ms=5000):
        """尝试获取锁"""
        self.lock_value = str(uuid.uuid4())  # 生成唯一标识符作为锁的值
        return self.redis_client.set(self.lock_key, self.lock_value, nx=True, px=timeout_ms)

    def release_lock(self):
        """释放锁"""
        # Lua 脚本,用于检查和删除键,保证操作的原子性
        script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        result = self.redis_client.eval(script, 1, self.lock_key, self.lock_value)
        return result == 1  # 如果删除成功,则返回 True

# 创建 Redis 客户端
client = redis.Redis(host='localhost', port=6379, db=0)

# 使用 RedisLock
lock = RedisLock(client, "my_distributed_lock")

if lock.acquire_lock(timeout_ms=10000):  # 尝试获取锁,设置10秒过期
    try:
        # 执行你的业务逻辑
        print("Lock acquired. Doing some work...")
        time.sleep(2)