伸缩与容量
服务通过增加节点来伸缩:节点在彼此之间划分它的分区,每个节点运行它拥有的分区的命令和订阅,而底下的数据库承载所有节点的追加。本页说明要确定什么的大小、节点为了降级而不是崩溃所施加的限制,以及下一个瓶颈通常在哪里。分区与归属见部署模型。
节点与分区
每个上下文的日志有固定数量的分区 wow.log.partitions,默认 64 个。每个分区同一时刻只有一个属主,它运行该分区内聚合的每条命令,以及该分区上的每个订阅。所以:
- 节点越多,吞吐越高,只要数据库跟得上:N 个节点时,每个节点拥有大约 64 / N 个分区。
- 每个分区至多一个节点在工作。 超出分区数的节点不拥有任何分区,只转发命令。64 个分区适合大多数服务;可能需要比这更多节点的服务,选择一个更大的 2 的幂,最多 4,096。
- 分区数是固定的,日志有了条目之后就不能改,所以要在首次部署之前选定。
节点加入或离开时,大约 N 分之一的分区会移动,它们的订阅在新属主上从检查点继续。宕掉的节点会一直持有它的租约,直到租约过期,即 wow.cluster.lease-ttl(10 秒):它的分区要等这么久才有新属主。分区没有可达属主的聚合,其命令在收到它的节点上运行;聚合的版本保证结果正确。
命令发往属主
命令可以到达任何节点;节点经内部端口把它转发给分区的属主,所以每个聚合的命令在一个节点上、基于它缓存的状态逐条运行。转发的代价是集群内的一跳。负载均衡器无法按聚合路由,也不需要这样做。
节点施加的限制
节点限制它接受的量,并让调用方稍后再来,而不是无休止地排队:
| 限制 | 默认值 | 超出时 |
|---|---|---|
wow.executor.max-in-flight:节点上的在途命令 | 4,096 | 命令以 wow.command.busy 拒绝(429,带 Retry-After) |
wow.executor.lane-capacity:等待同一个聚合的命令 | 1,024 | 同上,针对这个聚合 |
| 存储的并发查询 | PostgreSQL 上为追加连接池的一半,自适应 | 查询以 wow.query.busy 拒绝(429) |
wow.query.dataset.max-concurrent:同时运行的数据集查询 | 固定上限,不自适应 | 查询在一个短队列中等待,然后以 wow.query.busy 拒绝 |
带 Retry-After 的拒绝是背压,不是错误:客户端稍后重试,遇到它的 Saga 会放慢它的分区,而不是停放任何东西(失败与重试)。
查询永远不会让追加饿死。 每个存储的快照查询和 events 查询都在它们自己的上限下运行。除非 wow.query.max-concurrent 把它固定下来,这个上限会随存储的写入压力自适应:追加变慢或等待连接时减半,恢复后再增长回来。为查询使用单独的连接池或只读副本,可以完全消除这种争用(PostgreSQL)。
订阅
分区的订阅在它的属主上运行,每个订阅至多领先它的检查点 wow.subscription.max-in-flight(1,024)个条目。落后的订阅不会让节点停止服务:它表现为降级的健康状况和不断增长的 wow.subscription.backlog。原因通常是它的处理函数的延迟:缓慢的外部调用,或者写入读模型存储时缺少索引。更多节点能分散它的分区;更快的处理函数帮助更大。
要关注什么
| 负载增长时 | 关注 | 然后 |
|---|---|---|
| 命令延迟上升 | wow.command.commit、存储的追加延迟 | 存储跟得上就增加节点;否则扩容存储 |
| 命令被拒绝 | 带 wow.command.busy 的 wow.command.refused | 增加节点,或者找出缓慢的存储或聚合 |
| 某个聚合很热 | 它的车道上的拒绝 | 把它的工作拆分到多个聚合:一个聚合的命令按设计就是串行的 |
| 订阅落后 | wow.subscription.backlog、wow.subscription.lag | 加快处理函数,增加节点 |
| 查询被拒绝 | wow.query.limit、wow.query | 查询连接池或只读副本、更窄的查询、索引 |
指标见健康、指标与追踪。