故障排查
每一节从你看到的现象出发,指出常见的原因,并说明该怎么做。节点给出的每个回答都带着一个错误码;全部错误码见错误码。
节点无法启动
宿主宁可拒绝启动,也不带着一个会悄悄做错事的设置运行。日志会指出原因:
| 日志说 | 原因 | 处置 |
|---|---|---|
| 没有持久后端 | 没有 DataSource 或 MongoClient Bean,又不在开发 profile 中,且内部端口不只绑定在回环网卡上 | 设置 spring.datasource.url(类路径上要有 HikariCP)或 spring.mongodb.uri,给应用提供它的存储 Bean,或者有意设置 wow.backend: memory |
| 服务名不是节点能在内部端口上声明的名字 | wow.service(没有时为 spring.application.name)含有字母、数字、.、_ 和 - 之外的字符,不以字母或数字开头,或者超过 253 个字符 | 重命名服务,每个节点以及对端的 wow.callers 里都一致 |
| 未知的 Wow 配置键 | 拼错的 wow.* 键、更早版本的键,或不在类路径上的模块的键 | 修正这个键、按消息链接的迁移指南配置页改用现在的键,或加上那个模块(配置服务) |
| 节点不提供的订阅、聚合或上下文 | wow.subscriptions、wow.aggregates 或 wow.contexts 下有一个什么都不匹配的名字 | 修正名字,或删除这一项 |
| 没有命令传输 | 某个 Saga 向另一个上下文发命令,而节点既没有 Kafka 也没有内存主题 | 设置 wow.kafka.bootstrap-servers,或在开发中运行,主题保存在它的 JVM 里(wow.transport) |
| 缺少主题,或命令主题的分区数不对 | wow.kafka.ddl: validate,而运维人员没有按日志的需要创建主题 | 创建主题:命令主题的分区数恰好是 wow.log.partitions(Kafka) |
| 另一种格式的结构 | 一张以本版本 Wow 不读取的格式记录的表 | 用写下它的那个 Wow 版本打开它,或者从空的存储开始;绝不要编辑 wow_format |
命令超时
wow.wait.timeout(504)表示等待用完了时间,而不是命令失败:命令可能已经提交。problem 会指出到达的最后一个阶段。
- 用同一个请求 ID 再发一次:确实已经提交的命令会被当作重复请求回答,带着它原来的结果。
- 等待较后的阶段(
HANDLED、Saga 的那一跳、SNAPSHOT)也就是在等待订阅。链上有被停放或落后的订阅时,等待就会超时(等待结果)。 wow.command.unknown(503)表示不知道命令是否已提交:转发给属主的命令没有及时得到回答、命令主题没有确认它,或者存储没有给出追加的结果;用同一个请求 ID 重试是安全的。
命令被拒绝
| 错误码 | 原因 | 处置 |
|---|---|---|
wow.command.busy(429) | 节点或聚合的在途命令太多 | 在 Retry-After 之后重试;持续出现就增加节点(伸缩与容量) |
wow.aggregate.version-mismatch(409,带 If-Match 时为 412) | 聚合不处于客户端期望的版本 | 重新读取,再决定是否重试 |
wow.command.contended(409) | 并发的写者一再抢先拿到版本 | 用同一个请求 ID 重试 |
wow.command.request-id-reused(409) | 这个请求 ID 已被另一条命令使用 | 给每条命令自己的请求 ID |
wow.aggregate.forbidden(403) | 创建命令指明了属于与调用方不同的所有者或空间的聚合:已存在的聚合,或 ID 是另一个所有者的聚合。对这样的聚合的其他命令得到 wow.aggregate.not-found(404),如同它不存在。在访问策略下:调用方能读取这个聚合但不能修改它,或者命令会把聚合创建到、或把它的访问属性改到调用方够不着的地方 | 检查调用方的作用域与命令指明的 ID(内核检查什么),或者策略的 write 与调用方的声明(策略在哪里生效) |
wow.auth.forbidden(403) | 授权器拒绝调用方执行这条命令 | 检查调用方的角色或策略 |
wow.call.command-forbidden(403) | 某个服务调用了它在 wow.callers 下的那一项没有列出的命令 | 在目标服务上为这个服务列出这条命令 |
wow.storage.unavailable(503) | 访问不到某个存储 | 检查数据库;用同一个请求 ID 重试 |
wow.node.standby(503) | 节点正为迁移切换处于待机 | 在切换时激活它(部署模型) |
订阅停放聚合
wow.subscription.parked.aggregates 大于零,ops 控制台会列出这些聚合。每个条目都带着错误,以及订阅停下时的版本。
- 先修复原因:部署修正后的处理函数,或者让它调用的外部系统恢复。
- 然后重试这些聚合:订阅从每个聚合停下的地方重新读取它的流(ops 服务与控制台)。
- Saga 发出的命令被拒绝,只有在 Saga 没有对应的
@OnRejected处理函数时才会停放聚合(失败与重试)。
部署之后订阅重新开始了
发布之后,Saga 或反应器跳过了事件,或者投影从头重建,而节点启动时记录了某个订阅组 has checkpoints of subscriptions it no longer has。某个处理器或处理函数改了名,或移到了另一个上下文:它的名字就是它的身份,所以它是一个新订阅,按新订阅的方式开始。用 @Name 钉住旧名字再部署一次;订阅就会从旧的检查点继续(改名与回卷)。新名字在此期间处理过的内容仍算已处理。
订阅对每个聚合都停下了
wow.subscription.paused.partitions 大于零:订阅把一次失败判定为每个聚合都依赖的东西(某个存储或下游服务)发生了故障,于是暂停这个分区,直到它恢复。期间不会停放任何东西。从订阅的日志中找出是什么不可用;分区会自行恢复。
订阅落后
wow.subscription.backlog 持续增长,subscriptions 健康组件处于降级。处理函数比日志增长得慢:缓慢的外部调用、写入一个需要索引的存储,或者下游在施加背压(wow.subscription.throttled.partitions 大于零)。先加快处理函数,再增加节点来分散它的分区(伸缩与容量)。
分区没有属主
ownership 健康组件为 DOWN,或者在某个节点离开期间,某些聚合的命令变慢了。宕掉的节点的租约在 wow.cluster.lease-ttl(10 秒)后过期,然后分区会移动。如果没有移动:
- 节点之间访问不到彼此的内部端口:检查网络策略和
wow.node.address,即每个节点公布的地址。 - 租约存储不可用:ownership 健康组件的
leaseStore详情会说明这一点。
另一个上下文的主题
| 症状 | 原因 | 处置 |
|---|---|---|
| 节点启动后一直未就绪 | 它读取的某个主题仍在加入它的租约组 | 等待;或者设置 wow.health.topics.required: false |
wow.subscription.offset.gaps 大于零 | 保留策略删除了某个订阅尚未读取的偏移量 | 提高主题的保留期;决定 wow.kafka.offset-gap 怎么设(Kafka) |
wow.subscription.undecodable.messages 大于零 | 这个版本无法解码的消息 | 升级读取方服务的 api,或者是写入方发送了未知的格式 |
| 某个主题的订阅停住,日志说有一条记录不是 Wow 信封 | 有别的生产者在写这个主题,例如共用 broker 的 9.x | 用 wow.kafka.topics.prefix 给服务各自的主题(Kafka) |
查询被拒绝或很慢
wow.query.busy(429)表示达到了存储的查询上限;存储的追加处于压力之下时,这个上限会自适应地降低。wow.query.timeout(504)表示查询运行的时间超出了它的预算:收窄它,而不是再运行一次。给查询一个单独的连接池或只读副本(PostgreSQL)。
按版本或时刻读取状态、或读取轨迹时,返回 wow.query.invalid(400)且 violation 为 replay-too-long,表示要读的状态离得比 wow.query.state.max-replay(10,000)允许的提交数更远:较旧的状态要从第一个版本开始重放。改经事件查询读取聚合的事件,它没有这个上限;或者,如果部署需要读到那么早的状态,就调高上限(读取状态)。