尧图精选

Effect Cluster 中 `availableShardGroups` 的作用:确保分片咨询锁(Advisory Locks)不发生冲突

🕒 发布时间:2026/9/14 6:55:04 📁 来源:尧图网络
Effect Cluster 中availableShardGroups的作用确保分片咨询锁Advisory Locks不发生冲突【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code在 Effect 的 Cluster集群分片体系中ShardingConfig负责描述一个 runner运行节点如何参与分片runner 地址、分片组shard group归属、每组分片数、锁的刷新与过期时机、实体邮箱与生命周期限制、各类轮询间隔等。近期effect包的一个 patch 变更见 forty-trees-pay.md引入了一项关键配置availableShardGroups。它的目的非常明确——让所有 runner 对“整个集群里到底有哪些分片组”保持一致视图从而保证 advisory lock 在跨节点加锁时不会发生编号冲突。读完本文你将理解availableShardGroups的定义、默认值与加载方式以及它如何从源码层面影响 PostgreSQL / MySQL 的 advisory lock 编号进而在多节点部署中正确划分与锁定分片。变更本身在说什么这条 changeset 是一条典型的 patch 级别发布记录--- effect: patch --- add availableShardGroups to ShardingConfig, to ensure advisory locks do not conflict它说明了两件事一是对effect包做了一次patch升级二是新增了一个字段availableShardGroups到ShardingConfig服务中动机是“确保 advisory locks 不会冲突”。下面结合 vendored 的 effect 源码把这句话背后的机制展开讲清楚。ShardingConfig描述一个 runner 如何参与分片ShardingConfig是一个Context.Service定义在 ShardingConfig.ts。模块顶部的注释概括了它的职责ShardingConfigdescribes the runner address, shard group membership, shard counts and weights, lock timing, entity mailbox and lifecycle limits, polling intervals, health checks, and local serialization simulation.其中与本文主题直接相关的核心字段有三组availableShardGroups: ReadonlyArraystring——整个集群所有 runner 可用的分片组注释说明 “The shard groups available across all runners”默认[default]assignedShardGroups: ReadonlyArraystring——分配给当前 runner 的分片组默认[default]shardsPerGroup: number——每个分片组要分配多少个分片注释特别强调 “this value should be consistent across all runners”默认300。默认值对象 defaults 明确了缺省行为shardsPerGroup: 300, availableShardGroups: [default], assignedShardGroups: [default], shardLockRefreshInterval: Duration.seconds(10), shardLockExpiration: Duration.seconds(35), shardLockDisableAdvisory: false,从这些默认值可以看出单个节点、单组default、每组 300 个分片是“零配置即可跑起来”的形态而availableShardGroups的存在正是为了在多组、多节点场景下让每个节点都对“全集群有哪些组”达成一致。为什么需要availableShardGroupsadvisory lock 的编号机制要理解“冲突从何而来”需要看 SQL 版 runner 存储 SqlRunnerStorage.ts。Effect Cluster 用 SQL 记录 runner 注册与分片所有权在 PostgreSQL 与 MySQL 上默认使用数据库级 advisory lock来保证“一个分片同一时间只被一个 runner 持有”。advisory lock 的精髓在于锁是一个整数编号而不是字符串。PostgreSQL 用pg_try_advisory_lock(classid, objid)MySQL 用GET_LOCK(name)。要让不同 runner 对“同一个分片”用同一个锁编号就必须有一套确定性的编号规则。源码中正是这样做的SqlRunnerStorage.tsconst lockNumbers new Mapstring, number() const lockNumbersReverse new Mapnumber, string() for (let i 0; i availableShardGroups.length; i) { const group availableShardGroups[i] const base (i 1) * 1000000 for (let shard 1; shard config.shardsPerGroup; shard) { const shardId ShardId.make(group, shard).toString() const lockNum base shard lockNumbers.set(shardId, lockNum) lockNumbersReverse.set(lockNum, shardId) } }关键就在这里每个分片组按其在该数组中的下标i得到一个基数(i1) * 1_000_000组内分片号再叠加上去。也就是说锁编号是由“分片组在availableShardGroups中的位置”决定的。这里正是冲突的根源如果不同 runner 对availableShardGroups的取值不一致比如 A 节点认为是[default, workflow]B 节点认为是[default, special]那么同一个物理分片例如workflow:7在两个节点上会被算出不同的 lockNum两个节点用不同的锁编号去“抢”同一个逻辑分片advisory lock 就无法起到互斥作用——锁与锁之间不再对应同一把锁从而出现“两个节点自认为同时持有同一个分片”的冲突。因此把availableShardGroups提升为一个所有 runner 必须保持一致的集群级配置就能保证“同一个分片在所有节点上都被映射到同一个锁编号”从根上消除 advisory lock 冲突。这就是这条 changeset 的核心意图。补充一个底层细节PostgreSQL 的锁还依赖一个稳定的命名空间整数源码用一段FNV-1a 哈希生成SqlRunnerStorage.ts并注释强调 “This exact FNV-1a hash, including its tag and UTF-8 encoding, is a persistent advisory-lock wire format and must never change.” 可见这套编号/哈希是被当作持久化的 wire format来对待的——一旦变化旧锁全部失效。这进一步说明“让所有节点共享同一套availableShardGroupsshardsPerGroup”的必要性只有编号规则全集群一致持久化的锁语义才稳定。shardGroupConfig把“可用”与“分配”归一化拿到配置后真正参与分片计算的是availableShardGroups与assignedShardGroups的交集。归一化逻辑在 shardGroupConfigexport const shardGroupConfig (config: ShardingConfig[Service]): { readonly available: ReadonlySetstring readonly assigned: ReadonlySetstring } { const available new Set(config.availableShardGroups.slice().sort()) const assigned new Setstring() available.forEach((group) { if (config.assignedShardGroups.includes(group)) { assigned.add(group) } }) return { available, assigned } }可以看到available是全集群分片组的有序集合而assigned只保留既在 available 里、又在当前 runner 的 assigned 列表里的组。这个“求交集”的写法天然防御了“runner 声明了一个并不存在的组”的误配置——不存在的组会被过滤掉不会进入锁编号计算。SqlRunnerStorage内部正是调用ShardingConfig.shardGroupConfig(config)得到available集合再据此构建lockNumbers映射。如何加载availableShardGroups编程式与环境变量两种途径ShardingConfig提供了两套使用方式对应不同部署场景。1. 编程式测试 / 本地 / 显式注入layer(options?)把传入的 partial 覆盖到defaults上是一个浅合并layerexport const layer (options?: PartialShardingConfig[Service]): Layer.LayerShardingConfig Layer.succeed(ShardingConfig)({ ...defaults, ...options })其文档注释里专门写了一条 Gotchas非常值得记住This layer only merges and provides configuration; it does not check that cluster-wide settings are consistent across runners. Keep values such asshardsPerGroupandavailableShardGroupsaligned for runners that should share shard assignments.也就是说框架不会替你校验各 runner 的availableShardGroups是否一致——这属于运维/部署层面的契约需要你自己保证。2. 环境变量式生产部署config描述用Config.schema读取一个字符串数组键名为availableShardGroups默认[default]configavailableShardGroups: Config.schema(Config.Array(Schema.String), availableShardGroups).pipe( Config.withDefault([default]) ), assignedShardGroups: Config.schema(Config.Array(Schema.String), shardGroups).pipe( Config.withDefault([default]) ),注意一个易踩的坑字段名availableShardGroups与环境变量键名并不完全对称——availableShardGroups读availableShardGroups而assignedShardGroups读的是shardGroups见上面Config.Array(Schema.String), shardGroups。configFromEnv再套上ConfigProvider.fromEnv().pipe(ConfigProvider.constantCase)以常量风格CONSTANT_CASE从环境变量取值configFromEnv。layerFromEnv则允许先读环境变量、再叠加显式 optionslayerFromEnv。测试里如何配置多组一个可复制的用法示例仓库中的集成测试给出了“一个集群多组分片组”的真实配置写法。例如 ClusterCron.test.ts 与 Entity.test.ts 都这样配置// ClusterCron.test.ts availableShardGroups: [default, special], // Entity.test.ts config: { availableShardGroups: [default, special], shardsPerGroup: 30 },而 ClusterWorkflowEngine.test.ts 则用了[default, workflow]。这些例子共同演示了正确的用法把整个集群要使用的全部组一次性列进availableShardGroups再配合一个足够大的shardsPerGroup例如测试中缩小到 30 以加快执行。在真实部署中所有 runner 应当使用同一份availableShardGroups与shardsPerGroup仅用assignedShardGroups来区分“本节点负责哪些组”。关键约束与注意事项综合源码与文档注释使用availableShardGroups时有几条必须遵守的约束全集群一致性availableShardGroups与shardsPerGroup必须在所有应共享分片分配的 runner 上保持一致否则 advisory lock 编号错位、锁冲突。这一点被layer的文档与shardsPerGroup字段的注释双重强调。assignedShardGroups必须是availableShardGroups的子集才有意义shardGroupConfig只保留两者的交集声明了不存在的组会被静默过滤。与 advisory lock wire format 绑定PostgreSQL 的命名空间哈希与每个 shard 的lockNum都是持久化的 wire format。变更availableShardGroups/shardsPerGroup会改变编号等价于让既有锁“失效”应在停机窗口或受控迁移时进行而不是热切换。锁参数配合shardLockRefreshInterval默认 10s与shardLockExpiration默认 35s共同决定锁的刷新节奏内部 effectiveInterval 会把刷新间隔进一步收紧到“过期时间的 1/3 与配置刷新间隔的较小值”以在锁存储不可用时留出安全停机时间。shardLockDisableAdvisory为true时改用 locks 表行锁此时availableShardGroups仍参与 shard 编号但不再依赖数据库 advisory lock。小结这条一句话的 changeset背后是 Effect Cluster 分片锁体系中一个非常具体且重要的设计决策advisory lock 的编号必须由“分片组在availableShardGroups中的位置”确定性地推导出来而这套推导规则必须在所有 runner 上保持一致否则锁与锁不再对应同一把锁互斥语义就会被破坏。availableShardGroups的引入正是把“全集群有哪些组”从隐式假设变成显式、可配置、可校验尽管需自行保证的契约。理解它有助于你在多节点、多业务分组如default/workflow/special的 Effect Cluster 部署中正确划分分片组、避免 advisory lock 冲突并安全地进行分片配置变更。适用前提与限制以上机制基于 vendored 在.repos/effect-smol下的effect4.0.0 预发布unstable/cluster源码availableShardGroups字段、shardGroupConfig归一化与lockNumbers编号规则均以该源码为准正式版本行为可能随 API 稳定化而调整。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →