1. 当缓存遇到一致性难题

作为老牌内存数据库的扛把子,Redis就像个超级快递员,每天处理着数以亿计的数据存取请求。但这位快递员有个职业困惑:当数据需要同时出现在多个仓库(节点)时,如何保证所有包裹(数据)都能准确无误地送达?特别是在网络延迟、机器故障等突发情况下,这个挑战就更加棘手了。

2. 主从复制的数据接力赛

主从架构就像流水线作业,主库(Master)负责接单,从库(Slave)负责打包发货。通过以下命令建立主从关系:

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379

复制过程分为三阶段:

  1. 主库生成RDB快照文件
  2. 传输快照到从库
  3. 持续同步增量数据

但需要注意复制延迟问题,当主库写入后立即查询从库可能会读到旧数据。可以通过INFO replication命令查看复制偏移量:

connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=15456,lag=0

master_repl_offset:15456

3. 持久化双保险策略

Redis提供两种数据落地方案,就像给数据上了双重保险:

RDB快照模式(定时存档):

save 900 1      # 15分钟有1次修改就保存
save 300 10     # 5分钟有10次修改

AOF日志模式(实时记账):

appendonly yes
appendfsync everysec  # 每秒同步

混合模式配置示例:

aof-use-rdb-preamble yes  # 同时启用两种格式

当服务器意外宕机时,重启后会优先加载AOF文件,因为它的数据完整性更好。可以通过BGREWRITEAOF命令手动触发日志重写压缩。

4. 事务与原子操作控制

Redis的事务不像传统数据库那么严谨,更像是批量操作的打包服务。使用MULTI开启事务后,所有命令会进入队列,直到EXEC执行:

using (var redis = ConnectionMultiplexer.Connect("localhost"))
{
    var db = redis.GetDatabase();
    var tran = db.CreateTransaction();
    
    tran.AddCondition(Condition.StringEqual("balance", "100")); // 乐观锁检查
    tran.StringSetAsync("balance", "80");
    tran.StringSetAsync("log", "支付成功");
    
    bool committed = tran.Execute(); // 返回是否提交成功
}

这段C#代码使用StackExchange.Redis库实现了带乐观锁的事务操作,适用于账户余额变更等需要原子性保证的场景。

5. Lua脚本的原子魔法

对于需要复杂判断的原子操作,Redis直接内置了Lua解释器。比如库存扣减场景:

local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
    redis.call('DECR', KEYS[1])
    return "SUCCESS"
else
    return "OUT_OF_STOCK"
end

执行脚本命令:

EVAL "上述Lua代码" 1 item_001

Lua脚本执行期间会阻塞其他命令,相当于给操作加了原子性保护罩。

6. 分布式锁攻防战

跨节点操作时,RedLock算法是常用解决方案。C#实现示例:

var redlockFactory = RedLockFactory.Create(
    new List<RedLockMultiplexer> 
    { 
        ConnectionMultiplexer.Connect("server1:6379"),
        ConnectionMultiplexer.Connect("server2:6380")
    });

using (var redLock = redlockFactory.CreateLock(
    "order_lock", 
    TimeSpan.FromSeconds(30),
    TimeSpan.FromSeconds(10),
    TimeSpan.FromSeconds(1)))
{
    if (redLock.IsAcquired)
    {
        // 执行业务逻辑
    }
}

这里使用RedLockNet库实现分布式锁,设置获取锁的超时时间为10秒,锁持有时间30秒,重试间隔1秒。注意要处理时钟漂移问题,建议配合看门狗线程续期锁。

7. 典型应用场景分析

  • 电商秒杀:Lua脚本保证库存扣减原子性
  • 金融交易:事务+持久化确保操作可追溯
  • 分布式会话:主从复制实现跨机房数据同步
  • 实时排行榜:持久化策略保证数据安全
  • 配置中心:AOF日志实现配置变更审计

8. 技术方案优劣对比

方案 一致性强度 性能影响 适用场景
主从复制 最终一致 读写分离、数据备份
AOF持久化 强一致 金融交易、配置管理
事务操作 弱原子性 简单批量操作
Lua脚本 强原子性 复杂业务逻辑
分布式锁 最终一致 跨服务资源协调

9. 避坑指南

  1. 主从切换时注意脑裂问题,建议设置合理的超时参数
  2. AOF重写期间可能占用大量IO,建议在低峰期执行
  3. 事务中的命令不要包含耗时操作,避免阻塞其他请求
  4. Lua脚本复杂度控制在毫秒级,避免长时间阻塞
  5. 分布式锁要设置合理的TTL,防止死锁

10. 总结与选择建议

Redis的数据一致性保障就像多层级防护网:

  • 单节点场景优先使用事务/Lua脚本
  • 跨节点数据同步依赖主从复制+持久化组合拳
  • 分布式环境配合RedLock实现跨服务协调

没有银弹方案,需要根据业务的实际需求在一致性和性能之间找到平衡点。对于强一致性要求的场景,建议采用Redis模块的增强功能(如Redis Labs的Active-Active架构)或结合其他数据库实现混合存储方案。