一、订阅是什么?
订阅()是 规范中用于获取实时数据的一种操作类型。它允许客户端与服务器建立一个持久连接,当服务器端特定事件发生时,服务器主动将数据推送给客户端。这与传统的查询(Query)和变更()不同,后者采用“请求-响应”模式,而订阅采用“发布-订阅”模式。
适用场景:实时通知、聊天消息、股票报价、物联网设备状态、协作编辑等需要即时数据更新的应用。
二、工作原理
订阅基于 协议(通常是 -ws 或 --ws)实现。流程如下:
1. 客户端发起订阅请求:通过 连接发送订阅操作。
2. 服务器接收并注册:服务器将订阅注册到对应的事件分发器(如 系统)。
3. 事件触发:当业务数据变化(如数据库插入、消息队列消息)时,服务器执行订阅的 并生成数据。
4. 推送数据:服务器通过已建立的 连接将数据发送给所有订阅该事件的客户端。
5. 客户端接收:客户端监听消息,更新 UI 或执行相应逻辑。
三、服务端实现步骤(以 为例)
1. 安装依赖
npm @/ @-tools/ -ws ws @-tools/merge
2. 创建 (定义订阅类型)
type {
id: ID!
: !
: !
}
type {
(: ID!): !
}
3. 实现 (包含订阅)
{ } from 'http';
{ } from '@/';
{ } from '@//';
{ } from '@///';
{ } from '@-tools/';
{ } from 'ws';
{ } from '-ws/lib/use/ws';
from '';
from 'body-';
{ } from '-';
const = new ();
const = #
type {
id: ID!
: !
: !
}
type Query {
:
}
type {
(: ID!, : !): !
}
type {
(: ID!): !
}
;
const = {
: {
: (_, { , }) => {
const = {
id: Date.now().(),
,
: new Date().(),
};
// 发布事件
.(${}, { : });
;
},
},
: {
: {
: (_, { }) => .(${}),
},
},
};
const = ({ , });
const app = ();
const = (app);
// 服务器配置
const = new ({
: ,
path: '/',
});
const = ({ }, );
const = new ({
,
: [
({ }),
{
async () {
{
async () {
await .();
},
};
},
},
],
});
await .start();
app.use('/', .json(), ());
.(4000, () => {
.log(' ready at :4000/');
});
四、客户端实现步骤(以 为例)
1. 安装依赖
npm @/ -ws
2. 配置 连接
{ , , split } from '@/';
{ } from '@//link/';
{ } from '-ws';
{ } from '@//';
const = new (({
url: 'ws://:4000/',
}));
const = new ({
uri: ':4000/',
});
// 根据操作类型分流
const = split(
({ query }) => {
const = (query);
(
.kind === '' &&
. === ''
);
},
,
,
);
const = new ({
link: ,
cache: new (),
});
3. 发起订阅
{ gql, } from '@/';
const PTION = gql
($: ID!) {
(: $) {
id
}
}
;
({ }) {
const { data, , error } = (
PTION,
{ : { } }
);
if () <div>等待消息…</div>;
if (error) <div>订阅出错:{error.}</div>;
(
<div>
{data && <p>新消息:{data..}</p>}
</div>
);
}
权威来源:参考 订阅文档。
五、与轮询()的对比
| 维度 | 订阅 | 轮询() |
|---|---|---|
| 实时性 | 毫秒级推送,真正实时 | 取决于轮询间隔,存在延迟 |
| 资源消耗 | 保持长连接,仅在有数据时传输 | 定期请求,即使无数据也消耗网络和计算资源 |
| 网络开销 | 低(头部开销小) | 高(每次请求包含完整 HTTP 头) |
| 适用场景 | 高频更新、实时通知 | 低频更新、允许延迟的场景 |
| 实现复杂度 | 需要 支持 | 简单,标准 HTTP 请求 |
建议:对实时性要求高的场景(如即时通讯、协同编辑)优先使用订阅;对实时性要求不高且更新频率低的场景(如统计数据刷新)可继续使用轮询。
六、关键注意事项与最佳实践
1. 身份认证与授权
订阅连接时需携带认证信息(如 JWT),通常通过 连接的 传递:
const = new (({
url: 'ws://:4000/',
: () => ({
: .('token'),
}),
}));
在服务器端,使用 函数验证 token,并将用户信息注入到订阅上下文中,确保用户只能订阅其有权访问的数据。
2. 负载与扩展
内存限制:默认 - 的 使用内存存储事件,不适用于生产环境的多实例部署。生产环境应使用 Redis 或其他共享存储(如 -redis-)。
连接数管理:每个客户端维持一个 连接,需根据服务器资源合理限制最大连接数。
断开重连:客户端应实现自动重连机制,-ws 客户端默认支持指数退避重连。
3. 错误处理
订阅 中的错误会通过 发送错误消息,客户端需监听 error 回调。
若事件发布失败(如数据库异常),应记录日志并优雅降级。
4. 性能优化
过滤数据:订阅 中仅返回必要的字段,避免推送过大 。
限流:对高频事件(如鼠标位置)进行节流或采样,避免压垮客户端。
合并订阅:多个相似订阅可尝试合并为单个订阅,减少连接数。
七、常见问题解答
Q: 订阅与 原生 API 的关系?
A: 订阅是基于 的上层协议,它定义了消息格式({"type":"","id":"xxx","":{...}}),并处理多路复用、操作管理等功能。开发者无需直接操作 帧。
Q:订阅支持哪些传输协议?
A:主流实现支持 、-Sent (SSE)以及 。目前最成熟的是 。
Q:如何调试订阅?
A:可使用 、 或 等工具测试订阅;在浏览器开发者工具中查看 消息帧;服务端可开启调试日志输出 -ws 消息。
Q:订阅是否影响 的“单一端点”原则?
A:不会。订阅与查询、变更共用同一个端点(如 /),只是底层传输协议不同,对客户端透明。
Q:能否在订阅中同时返回查询的数据?
A:不可以。订阅返回的是事件触发的数据,但你可以通过“订阅 + 初始查询”的组合模式,先执行一次查询获取历史数据,再订阅实时增量。
Q:订阅与实时数据库(如 )的区别?
A: 订阅是 API 层级的实时推送,与后端数据源解耦; 提供完整的后端即服务(BaaS),包含实时数据库。两者可结合使用,比如在 服务中调用 作为数据源,再通过订阅推送数据。
八、总结
订阅是构建实时应用的强大工具,它遵循 规范,提供类型安全的实时数据推送。实现时需重点关注:
传输层:正确配置 服务与客户端。
事件系统:生产环境使用 Redis 等持久化 。
安全与授权:连接认证 + 订阅级权限校验。
监控与容错:连接监控、自动重连、错误上报。
按照本指南的步骤和最佳实践,即可快速集成稳定可靠的 实时数据能力。

