ARTICLE DETAIL

资讯详情

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

GraphQL Yoga 分布式订阅实战:用 Redis Pub/Sub 让多实例共享订阅消息

GraphQL Yoga 分布式订阅实战:用 Redis Pub/Sub 让多实例共享订阅消息 后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载本篇以 GraphQL Yoga 仓库中的 Redis Pub/Sub 示例 为核心讲解如何让多个 Yoga 服务实例通过一个共享的 Redis 实例跨进程分发 GraphQL Subscription 事件。读完后你将掌握如何在createPubSub中接入graphql-yoga/redis-event-target、如何启动双实例验证订阅链路以及该 event target 在源码层面的惰性订阅、序列化等关键实现细节。为什么需要 Redis Pub/Sub 做分布式订阅GraphQL Yoga 的createPubSub默认使用进程内EventTarget见 create-pub-sub.ts 中config?.eventTarget ?? new EventTarget()的回退逻辑。这意味着publish的事件只会被同一个 Node.js 进程内的subscribe监听器收到。当你横向扩容出多个服务实例或部署在多机环境时客户端 A 连在实例 1 上订阅而写入消息的 mutation 却落在了实例 2 上——A 将永远收不到更新。仓库中的 Redis Pub/Sub 示例 正是为验证这一场景而写启动两个监听不同端口的 Yoga 实例让其中一个执行 mutation另一个实例上的订阅者收到推送从而证明Redis 的魔法——消息通过 Redis 的发布/订阅通道在实例间流动。完整操作步骤继承自官方示例文档1. 用 Docker 启动 Redisdocker run -p 6379:6379 redis:7.0.22. 启动两个不同端口的服务实例PORT4000 pnpm --filter example-redis-pub-sub start PORT4001 pnpm --filter example-redis-pub-sub start对应的启动脚本定义在 examples/redis-pub-sub/package.json 中start: ts-node src/main.ts即直接用 ts-node 运行 src/main.ts。开发热重载则可用pnpm devts-node-dev --respawn。3. 在 4000 端口建立订阅访问 GraphiQL 并点击 Play 按钮设置 subscriptionhttp://127.0.0.1:4000/graphql?querysubscription%7B%0Amessage%0A%7DURL 解码后即为subscription { message }。4. 在 4001 端口执行 mutationhttp://127.0.0.1:4001/graphql?querymutation%7B%0AsendMessage%28message%3A%22Yowesharearedisinstance.%22%0A%7D解码后为mutation { sendMessage(message: Yo we share a redis instance.) }。此时你会看到订阅更新出现在127.0.0.1:4000尽管 mutation 是在另一个运行于127.0.0.1:4001的 Node.js 服务实例上执行的。这条跨实例的消息链路正是本示例要验证的核心能力。示例源码剖析Yoga PubSub Redis EventTarget 三件套examples/redis-pub-sub/src/main.ts 全文仅 51 行结构非常清晰import { createPubSub, createSchema, createYoga } from graphql-yoga; import { Redis } from ioredis; import { createRedisEventTarget } from graphql-yoga/redis-event-target; // 关键发布、订阅各用一个独立的 Redis 客户端 const publishClient new Redis(); const subscribeClient new Redis(); const pubSub createPubSub{ message: [string]; }({ eventTarget: createRedisEventTarget({ publishClient, subscribeClient, }), }); const yoga createYoga{ pubSub: typeof pubSub }({ context: () ({ pubSub }), schema: createSchema({ typeDefs: /* GraphQL */ type Query { _: Boolean } type Subscription { message: String! } type Mutation { sendMessage(message: String!): Boolean } , resolvers: { Subscription: { message: { subscribe: (_, __, context) context.pubSub.subscribe(message), resolve: message message, }, }, Mutation: { sendMessage(_, { message }, context) { context.pubSub.publish(message, message); }, }, }, }), }); const server createServer(yoga); server.listen(parseInt(process.env.PORT || 4000, 10));几个值得注意的设计点createPubSub的类型参数{ message: [string] }声明了主题message的 payload 是一个包含单个string参数的元组。这会让pubSub.subscribe(message)和pubSub.publish(message, message)获得精确的类型推导payload 参数数量不匹配时 TypeScript 会直接报错。发布/订阅分离两个 ioredis 客户端publishClient只发PUBLISHsubscribeClient挂在订阅模式SUBSCRIBE上。ioredis 中连接进入订阅模式后不能再执行普通命令二者分离是 Redis 客户端的标准实践。createYoga的泛型{ pubSub: typeof pubSub }让 resolver 里context.pubSub携带完整的类型信息无需任何any。监听端口从环境变量读取process.env.PORT || 4000这正是文档中能用PORT4000/4001启动双实例的原因。底层实现graphql-yoga/redis-event-target如何桥接 PubSub 与 Redis示例之所以能跨实例核心在于把createPubSub的eventTarget从默认的进程内EventTarget换成了 Redis 实现。该包位于 packages/event-target/redis-event-target其包名与依赖关系可在 package.json 中确认它依赖graphql-yoga/typed-event-target与whatwg-node/events并以ioredis ^5.0.6作为 peerDependency示例中安装的是 ioredis 5.8.2。参数签名createRedisEventTarget 的入参类型为export type CreateRedisEventTargetArgs { publishClient: Redis | Cluster; subscribeClient: Redis | Cluster; serializer?: { stringify: (message: unknown) string; parse: (message: string) unknown; }; };publishClient/subscribeClient接受ioredis的Redis单实例客户端或Cluster集群客户端因此该实现天然兼容 Redis Cluster 部署。serializer可选的自定义序列化器默认回退为JSON源码中const serializer args.serializer ?? JSON。这意味着默认走 JSON 编码跨实例传输对象是安全的如果你有压缩或私有协议需求例如前缀标记、protobuf 等可传入自定义的stringify/parse成对函数。惰性 SUBSCRIBE 与自动 UNSUBSCRIBE从 src/index.ts 的实现可以看出它用一张callbacksForTopic new Mapstring, SetEventListener()管理主题 → 本机监听器集合addEventListener(topic, callback)首次注册某主题的监听器时才会真正调用subscribeClient.subscribe(topic)见addCallback中callbacks undefined分支后续同主题监听器只进集合不重复 SUBSCRIBE。removeEventListener(topic, callback)当某主题的最后一个监听器被移除时主动执行subscribeClient.unsubscribe(topic)。dispatchEvent(event)等价于publishClient.publish(event.type, payload)其中 payload 为event.detail undefined ? : serializer.stringify(event.detail)。这种首个监听者触发订阅、末个监听者取消订阅的策略保证 Redis 侧的订阅关系与本机实际活跃的 GraphQL Subscription 数量精确同步不会为无人消费的主题维持无谓的连接状态。消息分发与空载荷处理subscribeClient.on(message, onMessage)收到 Redis 消息后const event new CustomEvent(channel, { detail: message ? null : serializer.parse(message), }); for (const callback of callbacks) { callback(event); }只有当本机确实注册过该 channel 的监听器callbacks ! undefined时才分发其余消息静默丢弃空字符串载荷对应detail undefined的发布会被还原为null其余情况走serializer.parse同一主题的所有本机监听器都会收到同一个CustomEvent与 EventTarget 语义一致。这一行为有测试佐证redis-event-target.spec.ts 验证了简单发布可被监听、无监听器主题的消息不触发回调、同一事件分发给全部监听器counter 最终为 2三个场景serializer.spec.ts 则验证了默认 JSON 序列化往返以及自定义serializer给数字字段 1 的示例实现确实生效。测试使用ioredis-mock无需真实 Redis 即可在 CI 中运行。类型系统TypedEventTargetcreateRedisEventTarget的返回值类型是TypedEventTargetTEvent定义于 packages/event-target/typed-event-target/src/index.ts。它继承 WHATWG 的EventTarget接口但将type和detail收窄为具体泛型使addEventListener(topic, ...)的主题名与 payload 都受类型约束——这就是为什么createPubSub能把eventTarget字段声明为该接口进程内EventTarget与 Redis 实现得以互换而调用方无感。事件如何从 Redis 流入 createPubSub把链路串起来看对应 packages/subscription/src/create-pub-sub.ts 中的关键行createPubSub({ eventTarget })把你传入的 Redis event target 存起来无传参时回退为进程内EventTarget客户端发起 subscription 时Yoga 调用pubSub.subscribe(message)底层通过target.addEventListener(topic, pubsubEventListener)挂上监听器create-pub-sub.ts 第 118 行附近。此刻 Redis event target 检测到该主题首个监听器执行subscribeClient.subscribe(message)另一实例执行pubSub.publish(message, message)底层构造new CustomEvent(topic, ...)后调用target.dispatchEvent(event)create-pub-sub.ts 第 97–100 行附近Redis event target 将其序列化为 JSON 字符串并publish订阅侧的 ioredis 连接收到message事件反序列化后封装为CustomEvent分发给所有监听器订阅管道Repeater将 payload 推给已建立 WebSocket/HTTP 流的客户端。因此publish与subscribe之间唯一需要跨进程的部分就是事件目标这一层——这正是 Yoga 把 eventTarget 设计为可插拔接口的意义。依赖与运行前提从 examples/redis-pub-sub/package.json 可确认本示例的运行时依赖依赖版本作用graphql-yogaworkspace提供createYoga/createPubSub/createSchemagraphql-yoga/redis-event-targetworkspaceRedis Pub/Sub 事件目标ioredis5.8.2Redis 客户端graphql17.0.2GraphQL 执行引擎适用前提本机可运行 Docker 以启动 Redis或已有 6379 端口的 Redis 服务new Redis()默认连接localhost:6379graphql-yoga/redis-event-target要求node 18。由于 Redis 客户端是 Node 生态的 ioredis该示例面向 Node.js 运行环境与 Yoga 核心的 WHATWG Fetch 无绑定不冲突但 event target 本身运行在 Node 进程中。小结该示例用最小的代码量展示了 Yoga 分布式订阅的完整配方createRedisEventTarget({ publishClient, subscribeClient })→ 注入createPubSub({ eventTarget })→ 注入context→ 多实例各自listen。跨实例消息分发、惰性订阅管理、JSON 序列化等机制都封装在graphql-yoga/redis-event-target内部测试用例redis-event-target.spec.ts、serializer.spec.ts覆盖了其核心行为。若需要更强的消息可靠性如不丢失断线期间事件可在此基础上考虑替代 Redis Pub/Sub 的持久化方案或按serializer扩展点接入自己的编码协议。赞分享后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载相关推荐Go CDK 消息发布与订阅Pub/Sub实战指南Topic 发布、Subscription 订阅与多服务接入Go CDK 消息发布与订阅Pub/Sub实战指南Topic 发布、Subscription 订阅与多服务接入 本指南以 Go CDKGo Cloud云原生后端微服务node-redis Pub/Sub 实战指南订阅、发布、退订与 Buffer 模式详解node redis Pub/Sub 实战指南订阅、发布、退订与 Buffer 模式详解 node redis 的 Pub/Sub发布/订阅API 是构建后端数据库客户端缓存Go CDK 消息订阅实战用 pubsub.OpenSubscription 与 Subscription.Receive 消费 Go Cloud Pub/Sub 消息Go CDK 消息订阅实战用 pubsub.OpenSubscription 与 Subscription.Receive 消费 Go Cloud Pub/S云原生后端微服务上一篇前缀缓存架构优化方案DeepSeek-Reasonix 99.8%命中率性能调优实施指南下一篇终极移动端解决方案3步搞定gridstack.js触摸冲突与滚动优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表