Redis
wow-redis 提供 Redis 实现的三种分布式消息总线、EventStore、SnapshotStore 与 PrepareKeyFactory。在团队已运维 Redis,并接受其 Streams、持久化和内存容量边界时使用;它不是 Kafka 或数据库的透明替代品。
架构概述
消息总线使用 Redis Streams consumer group;事件存储使用 sorted set、request set 和 Lua;快照使用 string 与版本保护 Lua;PrepareKey 使用 hash 与 Lua。Wow 定义 key 布局和脚本结果映射,Redis 负责脚本原子性、cluster slot、持久化、复制、淘汰与故障切换。
安装
直接依赖:
implementation("me.ahoo.wow:wow-redis")
implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")Starter capability:
implementation("me.ahoo.wow:wow-spring-boot-starter") {
capabilities { requireCapability("me.ahoo.wow:redis-support") }
}配置
下面显式选择 Redis 作为总线、事件/快照和 Prepare 存储:
spring:
data:
redis:
url: redis://localhost:6379
wow:
command:
bus:
type: redis
event:
bus:
type: redis
eventsourcing:
store:
storage: redis
snapshot:
storage: redis
state:
bus:
type: redis
prepare:
storage: rediswow.redis.enabled=true。pending recovery 默认启用:min-idle-time=5m、interval=30s、batch-size=100;两个 duration 至少 1ms,batch size 必须大于 0。连接、cluster、sentinel、TLS 和 pool 由 spring.data.redis.* 管理。
命令总线
RedisCommandBus 只在 wow.command.bus.type=redis 时装配。发送成功表示 Stream record 已写入 Redis,不表示命令已处理。
Stream 命名规则
默认命令 Stream 为 ${contextAlias}.${aggregateName}:command;领域事件和状态事件分别使用 :event、:state。命名来自 NamedAggregate.toStringWithAlias(),不是可随意改动的数据迁移细节。
消费者组
subscription 的 receiver group 成为 Redis consumer group。group 创建时的 BUSYGROUP 被视为并发创建后的正常状态;权限、键类型或连接错误仍会失败。
Pending 消息恢复
recovery 周期扫描 idle 超过阈值的 pending entry,并在确认原 consumer 不活跃后 claim。它只处理 ack 前因进程终止/取消、transport 或 decode 等路径遗留且仍在 Stream 中的 PEL entry,不恢复被 trim、删除或未持久化的数据。
事件总线
领域事件与状态事件复用相同 Streams 管线和显式 acknowledge 语义。RECOVERABLE 失败先由进程内 RetryableFilter 重试;耗尽后,已启用的 compensation filter 可记录后续补偿,默认 LogResumeErrorHandler 记录并恢复,而 AbstractAggregateEventDispatcher.finallyAck 在成功或错误后都会确认原 exchange。因此普通业务 handler 失败不依赖 Redis PEL recovery 重投;只有确认前终止/取消、transport、decode 等未确认路径才可能由 Redis redelivery/recovery 重新交付。处理器仍应保持幂等以覆盖这些未确认路径和显式补偿。
领域事件 Stream
RedisDomainEventBus 在 wow.event.bus.type=redis 时创建,服务投影、Saga 等领域事件订阅者。
状态事件 Stream
RedisStateEventBus 在 wow.eventsourcing.state.bus.type=redis 时创建,服务快照与状态订阅者。模块存在不等于该 bus 已选中。
事件存储
RedisEventStore 使用 canonical v2 keys。每个聚合实例的事件、request index 和所在 ID bucket 通过 hash tag 保持 Lua 所需键在同一 cluster slot。
数据结构
事件按 version 写入 sorted set;128 个 bucket 的 aggregate-ID index 支持稳定扫描;每个 aggregate 有独立 request-ID set。组件名经编码,避免用户 ID 中的 :、{} 或 Unicode 破坏 key 结构。
请求幂等性
event_stream_append.lua 在一个原子脚本内检查已有事件数、首版本聚合 ID、request ID,再写事件和索引。结果映射为版本冲突、重复聚合 ID、重复请求或成功;不要在应用层复制非原子的预检查。
聚合 ID 扫描
扫描按 canonical index member 的可排序编码跨 128 buckets 合并。它是框架维护/查询能力,不是 Redis SCAN 的包装。
快照存储
snapshot_save.lua 只在候选版本不低于存量版本时 SET;较旧快照 no-op。缺失或非数字的存量 version 直接报错,防止静默覆盖损坏数据。
升级边界
Starter 启动时对实际路由到 Redis EventStore 的聚合检测旧 shared request index 与旧 bucket layout;发现后 fail closed。当前运行时只读写 canonical v2,不在线迁移旧 key。停旧 writer,离线迁移或重建,再启动新版本。
预分配 Key
wow.prepare.storage=redis 才创建 RedisPrepareKeyFactory。Lua 原子执行 prepare/reprepare/rollback,key 使用单一显式 hash tag;调用方负责处理竞争失败与业务补偿。
连接池配置
使用 Spring Data Redis/Lettuce 原生属性。Wow 不拥有 pool size、command timeout 或 topology refresh,也不重复驱动的连接校验。
集群配置
canonical key layout 保证单个 Lua 操作的 keys 同 slot,但不会配置 cluster、resharding 或 replica。上线前用实际 cluster 验证脚本、failover 和 slot 迁移。
哨兵配置
Sentinel master、nodes、认证与 TLS 全由 Spring Boot Redis 配置。模块只消费已创建的 ReactiveStringRedisTemplate。
性能优化
先观察 command latency、Stream lag、pending 数、内存与脚本耗时;不要凭模块存在就启用 trim 或改变淘汰策略。
批量操作
总线使用 Streams 原生批量读取;EventStore/SnapshotStore 以 Lua 保持单聚合原子性。模块没有可配置的 Mongo/Elasticsearch 式写入 batcher。
内存优化
事件历史是权威数据时,任意 eviction 或未审计 trim 都可能造成不可恢复的数据丢失。容量与保留应按事件量、快照和重放目标规划。
建议配置
没有通用 Redis server 模板。根据是否把 Redis 当权威存储,选择持久化、复制、maxmemory-policy、备份和恢复目标,并记录实测证据。
Redis 服务端配置建议
若 Redis 承载 EventStore,至少确保不会因普通缓存淘汰策略删除 v2 event/snapshot keys;若只承载消息总线,也要保证 Stream retention 覆盖最大恢复窗口。
故障排查
当前实现与测试验证的主要失败包括:非法 recovery bounds、损坏 snapshot version、旧 EventStore layout、Lua 返回版本/请求冲突,以及 pending/consumer 元数据解析失败。
常见问题
先区分连接/拓扑、key layout、Lua 结果、Stream group 与容量问题。
1. 连接超时
检查 Spring Redis URL、DNS、认证、TLS、Sentinel/cluster 拓扑和网络;保留 Lettuce 原始异常。
2. 内存不足
停止继续写入并确认是否发生 eviction。不要通过删除 event/request/index key 临时恢复容量;应扩容或按已验证的数据迁移/保留策略处理。
3. Stream 消费延迟
检查 lag、pending、idle consumer、recovery observer 与处理器耗时。先确认 entry 是否在 ack 前因进程/transport/decode 路径遗留;普通 handler 失败通常已由 retry/compensation/error handler 处理并经 finallyAck 确认。recovery 只能 claim 可见 pending entry,不能补回已 trim 的记录。
监控指标
监控 Redis latency、内存、eviction、persistence、replication、Stream length/lag/pending、Lua error 和 Wow handler failure。成功的 module check 不是目标 Redis 健康证明。
完整配置示例
spring:
data:
redis:
url: ${REDIS_URL}
wow:
redis:
enabled: true
message-bus:
recovery:
enabled: true
min-idle-time: 5m
interval: 30s
batch-size: 100
eventsourcing:
store:
storage: redis
snapshot:
storage: redis最佳实践
- 显式选择每个 bus/store,不从 capability 推断运行时;
- 把 canonical v2 升级当数据迁移,不做在线兼容桥;
- 保持事件处理幂等,演练 pending claim、故障切换和恢复;
- 权威数据禁用未审计 eviction/trim,并验证备份可恢复。
聚焦检查:
./gradlew :wow-redis:check