ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Medusa 2.x @medusajs/locking 锁模块:版本演进、Provider 架构与内存实现详解

Medusa 2.x @medusajs/locking 锁模块:版本演进、Provider 架构与内存实现详解 Medusa 2.x medusajs/locking 锁模块版本演进、Provider 架构与内存实现详解【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa本文以medusajs/locking包的 CHANGELOG 为主线梳理该锁模块从 Medusa 2.0 到 2.20.1 的版本演进脉络并结合仓库中的模块声明、Provider 注册加载器、默认内存锁实现与集成测试讲清楚 Medusa 分布式/并发锁的能力边界、配置方式与底层工作原理。读完你可以掌握在medusa-config中如何注册锁 Provider、默认内存 Provider 的排队与超时机制如何实现以及如何验证锁行为。包定位与当前版本根据 package.jsonmedusajs/locking的描述为 Locking Module for Medusa当前版本为2.20.1采用 MIT 协议main入口为dist/index.js。它对medusajs/framework采用固定版本的 peer dependency2.20.1且 devDependencies 中的 framework 与 test-utils 版本与之对齐——这是 Medusa 各模块包锁版本号的典型模式。值得注意的是2.6.1 的 CHANGELOG 记录了一次 chore: Remove ranges on Medusa packages见 CHANGELOG.md说明从 2.6.1 起 Medusa 内部包之间不再使用版本范围依赖改为精确锁定版本这正是当前 package.json 中所见2.20.1固定写法的历史来源。CHANGELOG 版本演进的关键节点整份 CHANGELOG.md 采用 changesets 风格绝大多数条目是 Updated dependencies跟随medusajs/framework同版本联动发布。剔除依赖联动噪声后真正发生在 locking 包内部的事件可归纳为下表版本变更类型变更内容2.0.0Majorchore: Medusa 2.0随 Medusa 2.0 主版本发布2.0.2PatchDisable default locking provider warn禁用默认锁 Provider 警告日志2.0.5PatchFix/mikro orm cli wrapperUpdate module provider retrieval error message and type改进 Provider 检索失败时的错误信息与类型2.4.0Minorchore: upgrade to mikro-orm 6随 ORM 大版本升级2.6.1Patchchore: Remove ranges on Medusa packages内部依赖改为精确版本2.7.0Patchfix: register locking provider with its unique id按唯一 id 注册锁 Provider2.11.0Patchchore(): Move peer deps into a single package and re export from framework2.12.5Patchfeat(): Add modules options autocomplete to medusa configmedusa-config 中模块选项自动补全2.17.2Patchchore: add package bugs metadatapackage.json 增加 bugs 字段2.20.1Patch当前仓库版本跟随 framework 2.20.1 联动发布这些条目与当前仓库源码可以相互印证下面逐一展开。Disable default locking provider warn 与 register locking provider with its unique id这两条历史变更在当前源码中留下了直接痕迹。查看 Provider 加载器当配置中没有任何显式标记is_default的 Provider 时加载器只会logger.info输出 Using in-memory as default.而原本的logger.warn调用被整段注释掉了——这正是 2.0.2 版本禁用默认 Provider 警告的落地形态文件第 90~103 行。每个 Provider 注册时都会构造LockingProviderRegistrationPrefix id即lp_{id}作为容器注册键并通过getProviderRegistrationKey生成规范注册名——对应 2.7.0 register locking provider with its unique id 的修复语义Provider 以自身唯一 id 区分注册而非静态 identifier从而允许同类 Provider 以不同 id 注册多份。2.0.5 提到的改进 Provider 检索错误信息则体现在 LockingProviderService当 Awilix 解析失败时会抛出具名的可读错误 Unable to retrieve the locking provider with id: {providerId}...并区分AwilixResolutionError与其他异常分别记录日志。模块声明与服务结构模块入口 src/index.ts 只有十余行使用 framework 提供的Module工厂完成声明export default Module(Modules.LOCKING, { service: LockingModuleService, loaders: [loadProviders], }) // Module options types export { LockingModuleOptions } from ./types模块名取自Modules.LOCKING常量核心服务为LockingModuleService负责对外暴露execute / acquire / release / releaseAll四个方法loadProviders是模块初始化时执行的加载器负责注册全部锁 Provider。LockingModuleService 的实现是一个纯粹的路由层每个公开方法都先解析目标 Provider idargs?.provider ?? this.defaultProviderId再通过providerService_.retrieveProviderRegistration(providerId)从容器中取出对应 Provider 实例并委托执行。它注入的依赖为EntityManager、LockingProviderService、可选Logger以及default_provider这个注册键值见文件第 11~30 行。换言之模块服务本身不持有任何锁状态全部锁行为都下沉到 Provider 中这是该模块可扩展性的关键设计。LockingModuleOptionsProvider 配置项说明配置类型定义在 src/types/index.ts这也是在medusa-config中配置该模块时的类型来源export type LockingModuleOptions PartialModuleServiceInitializeOptions { providers?: { resolve: string | ModuleProviderExports // Provider 模块路径或类/函数导出 is_default?: boolean // 是否作为默认 Provider id: string // 容器内注册用的唯一 id options?: Recordstring, unknown // 传入 Provider 构造函数的配置 }[] }该文件还通过declare module medusajs/types将medusajs/locking与medusajs/medusa/locking两个模块名都挂到ModuleOptions接口上使medusa-config.ts中的modules配置获得类型提示——CHANGELOG 中 2.12.5 Add modules options autocomplete to medusa config 的变更就是这类配置体验改进的一部分。文件顶部还定义了三个模块内部注册常量理解 Provider 注册机制需要它们export const LockingDefaultProvider default_provider export const LockingIdentifiersRegistrationName locking_providers_identifier export const LockingProviderRegistrationPrefix lp_Provider 注册加载流程loaders/providers.ts 是理解多 Provider 机制的核心流程分四步默认注册内存 ProviderInMemoryLockingProvider以asFunctionLifetime.SINGLETON注册为单例注册键为lp_in-memory并将其 identifier 追加到locking_providers_identifier列表设置默认 Provider 兜底先注册LockingDefaultProvider → in-memory即未配置任何自定义 Provider 时模块默认走内存锁加载用户配置调用moduleProviderLoader对每个配置的 Provider 执行registrationFn——该函数要求 Provider 类必须带有静态identifier否则抛出No id provided for provider类错误并创建lp_{id}别名指向规范注册键覆写默认 Provider遍历配置若某 Provider 标记is_default或仅配置了唯一一个 Provider则把它写入LockingDefaultProvider若最终没有任何显式默认值输出 info 日志warn 已被注释如前所述。仓库中还提供了两个可参考的第三方实现目录locking-postgres Provider 与 locking-redis Provider它们演示了如何将锁状态外置到 PostgreSQL 或 Redis适用于多进程/多实例部署场景内存 Provider 则适合单进程内的并发控制。默认内存 Provider 源码剖析InMemoryLockingProvider 是默认实现其数据结构为Mapstring, LockInfo其中LockInfo包含ownerId、expiration与一个currentPromise可手动 resolve 的 Promise。几个关键行为值得逐点说明execute 的超时竞争机制execute(keys, job, { timeout })先归一化超时Math.max(args?.timeout ?? 5, 1)非法值回退为 1 秒然后构造两个 Promise 参与Promise.race超时 PromisegetTimeout到期后把cancellationToken.cancelled置真并 reject Timed-out acquiring lock.加锁 Promise以awaitQueue: true调用acquire_即允许排队等待其他持有者释放。竞争胜出后执行job()并在finally中release(keys)保证异常路径也释放锁。acquire_ 的三种分支文件第 93~137 行键无锁 → 直接创建锁条目可带ownerId与秒级expire换算为now expire * 1000锁已过期 → resolve 旧的currentPromise唤醒所有排队者并重建锁条目锁的ownerId与请求方相同 → 幂等通过若传了expire则顺延过期时间若awaitQueue为真 →await lock.currentPromise.promise排队等待被唤醒后递归重新acquire否则抛出Failed to acquire lock for key {key}。release 的属主校验若传入了ownerId且与锁记录的属主不一致或键本身不存在则返回false且不删除成功释放时会 resolvecurrentPromise以唤醒下一个排队者。releaseAll则按无属主参数清空全部 / 有属主参数仅清该属主锁两种模式处理。需要注意的是从源码结构看内存锁的状态存放在单例实例的Map中只覆盖同一 Node.js 进程内的并发跨实例场景需要换用 Postgres/Redis 类 Provider。ILockingProvider 与 ILockingModule 契约模块对外契约定义在 packages/core/types/src/locking/index.ts包含两个接口且自带完整的 TSDoc 示例是自行开发 Provider 的权威参照ILockingProviderProvider 需实现execute先竞争超时与加锁再执行 jobfinally 释放、acquire文档明确要求覆盖三种场景无属主可被任何人扩展/释放同属主可顺延过期时间属主不同必须抛错、release属主不匹配返回false、releaseAll按属主过滤释放。每个 Provider 必须声明静态identifier其注册名为lp_{identifier}ILockingModule模块服务接口四个方法均支持provider参数指定使用哪个 Provider文档示例中使用形如provider: lp_my-lock的写法execute的timeout默认 5 秒、小于 1 时回退为 1 秒——与内存 Provider 源码中的归一化逻辑一致。类型文档中给出的使用范式以删除商品为例await lockingModuleService.execute(prod_123, async () { await productModuleService.delete(prod_123) })集成测试对行为的验证integration-tests/tests/index.spec.ts 用moduleIntegrationTestRunner驱动Modules.LOCKING模块验证了四个核心行为可作为锁语义的事实依据并发串行化10 个并行buy()无锁执行会把库存扣到-5超卖换成service.execute(item_1, buy)后同样 10 次并行库存精确停在0——证明execute确实将并发调用串行化到锁粒度属主权限user_id_123持锁后user_id_456尝试release返回false且尝试acquire抛Failed to acquire lock for key key_name持主本人释放返回true无属主锁的排他性不带ownerId的acquire成功后任何后续 acquire无论带不带其他属主都会失败只有不带属主的release才能释放异常与超时路径execute内 job 抛错后锁仍被释放后续 job 可正常执行三个并发executejob1 耗时 1010ms 且 timeout1、job2 正常 timeout1、job3 timeout2的结果断言为[fn_1, Error(Timed-out acquiring lock.), fn_3]——即 job1 先拿到锁执行job2 排队 1 秒超时失败job3 在超时窗口内等到了锁并执行且fn_2调用次数为 0。小结medusajs/locking通过模块服务做路由、Provider 做实现的分层设计把并发锁抽象成可插拔能力CHANGELOG 从 2.0.0 到 2.20.1 的演进中实质性变更集中在 Provider 注册的唯一 id 修复2.7.0、错误信息完善2.0.5、默认 Provider 警告降级2.0.2与依赖治理2.6.1 / 2.11.0几处其余均为跟随medusajs/framework的联动版本。开发时可直接从 类型定义 的ILockingProvider契约出发实现自定义 Provider如基于 Redis 的锁并通过 模块配置类型 在medusa-config中注册单进程场景使用默认内存 Provider 即可其行为语义已由上述集成测试完整覆盖。【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表