分布式部署指南
概述
monSQLize 支持单实例和多实例部署。单实例环境通常只需要本地查询缓存。多实例部署中,如果缓存读需要跨进程收敛,应使用 Redis 远端缓存与分布式失效。
为什么需要分布式支持?
在多实例部署中,每个实例都有独立的本地缓存和锁管理器。如果不做特殊处理,会导致:
- 缓存不一致窗口:实例 A 更新数据后,实例 B 的本地缓存可能在失效广播到达前短暂保留旧值。
- 事务与缓存边界:MongoDB 事务保持 session 语义,缓存失效在 commit 后 best-effort flush。
- 关键业务临界区:余额、库存、支付类路径仍需要业务侧幂等、fencing 或显式锁。
架构选择
1. 单实例部署(✅ 推荐:小型应用)
特点:
- ✅ 所有功能完整支持
- ✅ 无需 Redis
- ✅ 配置简单
- ⚠️ 无高可用
适用场景:
- 开发/测试环境
- 流量不大的生产环境
- 单体应用
配置示例:
2. 多实例 + 独立本地缓存(🔴 不推荐)
风险:
- 🔴 高风险:缓存不一致
- 🔴 事务隔离性失效
- ❌ 不推荐生产环境
3. 多实例 + Redis + 分布式缓存失效(🟢 推荐)
特点:
- ✅ 高可用
- ✅ 缓存一致性好(毫秒级延迟)
- ✅ 性能优异
- ⚠️ 依赖 Redis
适用场景:
- 生产环境(推荐)
- 高并发场景
- 可容忍短期(毫秒级)数据不一致
配置示例:
4. 多实例 + 显式业务协调(🟡 金融/交易)
特点:
- ✅ 多实例共享缓存失效
- ✅ 应用/框架层显式选择业务锁与幂等策略
- ✅ 配合持久化业务保护后适合金融/交易场景
- ⚠️ 缓存失效仍是 best-effort,不与数据库提交原子绑定
适用场景:
- 金融系统
- 支付/转账
- 库存扣减
- 业务层已经具备幂等、fencing 或关键读路径绕过缓存的场景
配置示例:
transaction.distributedLock 仅作为兼容配置占位保留,并未接入事务缓存锁。跨实例强一致需要禁用关键读路径缓存,或在业务/框架层显式协调。
5. 多实例 + 禁用缓存(🟡 适用:强一致性要求)
特点:
- ✅ 100% 数据一致性
- ✅ 无外部依赖
- ❌ 性能下降(所有请求查数据库)
适用场景:
- 小流量应用
- 强一致性要求
- 无法使用 Redis
配置示例:
分布式环境下的风险
风险 1: 缓存失效不同步
场景:
影响:
- 用户看到不一致的余额
- 业务逻辑可能出错
解决方案:启用分布式缓存失效广播
风险 2: 事务缓存锁不生效
场景:
影响:
- 读到事务中间状态
- 缓存中可能有脏数据
- 事务隔离性失效
解决方案:对关键路径使用显式业务协调,并在需要严格新鲜度时绕过缓存
解决方案
方案 1: 分布式缓存失效广播(推荐)
原理:使用 Redis Pub/Sub 广播缓存失效消息
工作流程:
配置:
优点:
- ✅ 实时广播,延迟低(毫秒级)
- ✅ 使用现有 Redis 基础设施
- ✅ 实现简单,易于维护
缺点:
- ⚠️ 依赖 Redis
- ⚠️ 极短时间窗口内可能不一致(网络延迟)
方案 2: 显式业务锁与缓存绕过
原理:缓存失效保持 best-effort,对关键业务副作用使用显式业务锁,并配合幂等 / fencing。
工作流程:
配置:
优点:
- ✅ 一致性边界明确落在业务层
- ✅ 可覆盖支付、库存、履约等外部副作用
- ✅ 适合金融/交易场景
缺点:
- ⚠️ 需要应用层幂等与恢复设计
- ⚠️ 不会让缓存失效与数据库事务变成原子提交
- ⚠️ 使用 Redis 锁时依赖 Redis 可用性
配置指南
完整配置示例
💡 配置说明:
- ✅ 必需项:必须配置,否则功能不工作
- ❌ 可选项:可以不配置,使用默认值
- 一个 Redis 实例:用于远端缓存与广播;如需业务锁,请显式接入对应业务锁路径。
配置选项说明
distributed(分布式缓存失效)
⚠️ 重要说明:
redis和redisUrl:分布式失效需要二选一- 推荐:不配置
redis,自动从cache.remote复用 Redis 实例 - 如需单独配置:使用
redis参数(可复用实例) - 不推荐:使用
redisUrl(会创建新连接)
- 推荐:不配置
instanceId:可选,但强烈建议手动设置- 默认值格式:
instance-${timestamp}-${random}(如instance-1732521234567-a2b3c4d5e) - 每个实例的
instanceId必须不同,否则会导致缓存失效广播失败 - 推荐使用环境变量:
process.env.INSTANCE_ID或process.env.HOSTNAME
- 默认值格式:
transaction.distributedLock(兼容配置占位)
transaction.distributedLock 仅作为兼容配置占位保留,不会接入事务缓存锁。事务缓存锁仍是进程内语义。跨实例关键临界区请显式使用 DistributedCacheLockManager 等业务锁,并配合幂等 / fencing;严格新鲜度读路径请禁用或绕过缓存。
面向使用者的运行行为
分布式缓存失效流程
当启用 cache.distributed 且 Redis Pub/Sub 配置可用时,monSQLize 会在 connect() 阶段准备广播通道。对应用侧而言,契约是:
- 写操作是否成功由 MongoDB 结果决定。
- 如果本次写操作启用了缓存失效,monSQLize 会尝试失效本地缓存,并向其他实例发布失效消息。
- 其他实例收到消息后,会按匹配的 namespace 或 pattern 失效本地缓存。
- Redis publish/subscribe 失败会通过日志或统计暴露,但不会回滚已经完成的 MongoDB 写入。
运行注意事项
- 为每个运行实例设置唯一
instanceId,这样实例可以忽略自己发出的广播,并接收其他实例的广播。 - 对严格新鲜度读路径保持显式策略:需要时绕过或禁用缓存。
- 监控缓存失效 warning、Redis publish 错误和缓存统计。数据库写入可能已经提交,即使后续缓存失效步骤失败。
- 把分布式失效视为最终一致辅助能力。它能缩短 stale-cache 窗口,但不是两阶段提交协议,也不提供全局强一致。
最佳实践
1. 环境检测
自动检测是否在分布式环境:
2. 根据业务选择方案
3. 监控和日志
启用日志查看分布式组件状态:
4. 错误处理
分布式组件失败时的降级策略:
性能考虑
1. 分布式缓存失效性能
- 延迟:~1-5ms(Redis Pub/Sub)
- 带宽:每次失效 ~100 bytes
- 影响:对总体性能影响可忽略
2. 显式业务锁性能
- 延迟:~2-10ms(Redis SET/DEL)
- 吞吐量:略有下降(~10-20%)
- 推荐:仅包裹需要跨实例协调的业务临界区
3. 缓存策略对比
故障排查
问题 1: 缓存失效广播不生效
症状:实例 A 更新后,实例 B 仍读到旧数据
排查步骤:
-
检查 Redis 连接是否正常
-
检查 Pub/Sub 订阅
-
查看日志
-
检查实例 ID
问题 2: transaction.distributedLock 不生效
症状:事务期间其他实例仍写入缓存
排查步骤:
-
先确认运行时边界
-
关键临界区使用显式业务锁
-
严格新鲜度读路径禁用或绕过缓存。
问题 3: Redis 连接失败
症状:应用启动报错或缓存不工作
解决方案:
总结
推荐配置
快速开始
最简单的分布式配置(推荐):
⚠️ 重要:
instanceId是可选的,默认自动生成(格式:instance-${timestamp}-${random})- 但强烈建议手动设置,便于调试和日志追踪
- 每个实例的
instanceId必须不同 - 建议使用环境变量:
instanceId: process.env.INSTANCE_ID - 需要显式设置
redis或redisUrl;runtime 不会从cache.remote推导 Pub/Sub 配置。