阿里云函数计算(fc)不支持直接维持websocket连接,需通过api网关实现全双工通信:网关负责连接管理与事件分发,fc处理消息逻辑,下行推送须调用网关/push接口,并依赖redis等外部存储维护connectionid状态。

阿里云函数计算(FC)本身不支持长期维持 WebSocket 连接,但通过 API 网关 + FC 的组合方式,可以完整承接 WebSocket 的连接管理、消息上行与下行——这不是“模拟”,而是生产可用的全双工通信链路。
API 网关才是 WebSocket 的真正入口
你不能在 FC 函数里直接 new WebSocket.Server() 并监听端口(比如 9000),那套写法只适用于「Web 函数」类型(即函数平台启动一个内嵌服务进程),且有严重限制:单实例单连接、超时即断、无法反向推送。真实业务中应放弃该路径。
正确做法是把 API 网关当作 WebSocket 的 TCP 层代理:
- 客户端用
wss://your-api-gateway-domain.com建连,不是直连 FC 函数地址 - API 网关自动分配并维护
connectionId,全程托管连接生命周期 - 所有上行消息(如聊天文本、心跳)由网关封装成 HTTP 事件,触发注册好的 FC 函数(如
onMessage) - FC 函数处理完后,若需回推,必须调用 API 网关提供的
POST /push接口,并带上目标connectionId
FC 函数接收 WebSocket 消息的实际结构
当客户端发来一条消息,API 网关会以标准 HTTP POST 触发你的 FC 函数,environ 或 event 中包含关键字段:
-
requestContext.connectionId:唯一标识本次连接,必须存到 Redis 或表格存储,否则无法后续推送 -
body:原始消息体(字符串或 base64 编码的二进制),需手动JSON.parse()或Buffer.from(body, 'base64') -
requestContext.eventType:值为CONNECT/MESSAGE/DISCONNECT,区分连接阶段 - HTTP headers 里可能带
x-ca-deviceid等自定义头,用于透传设备信息
示例(Node.js):
exports.handler = async (event, context) => {
const { requestContext, body } = JSON.parse(event.toString());
if (requestContext.eventType === 'MESSAGE') {
const msg = JSON.parse(body);
console.log(`Received from ${requestContext.connectionId}:`, msg);
// 存 connectionId → userId 映射,或转发至 RocketMQ
}
};
下行推送必须走 API 网关的 /push 接口
FC 函数无法主动“找到”某个 WebSocket 连接并发送数据——它没有 socket 句柄,也没有长连接上下文。所有下行动作都依赖 API 网关的反向推送能力:
- 调用地址格式:
https://[region].api.aliyuncs.com/2016-07-14/api-gateway/push?connectionId=xxx - 请求头必须含
Authorization(使用 RAM Role 临时 Token)和Content-Type: application/json - Body 是纯消息内容(字符串),不是 JSON 包裹体;网关会原样透传到客户端
- 失败响应常见状态码:
404(connectionId 已失效)、429(推送频率超限)、401(鉴权失败)
注意:这个 /push 接口不是你部署的函数地址,而是阿里云 API 网关的系统接口,必须用阿里云 SDK(如 @alicloud/pop-core)或自行构造签名请求。
状态管理最容易被跳过的一步
很多人写了 CONNECT 和 MESSAGE 处理,却没做连接状态持久化,结果导致:
- 用户重连时,旧
connectionId仍存在缓存中,新连接覆盖失败 - 群聊广播时,遍历不到在线用户列表,消息石沉大海
-
DISCONNECT事件未清理,Redis 里堆积大量僵尸 ID
建议方案:
- 用 Redis 存
connectionId → {userId, joinTime, lastActive},TTL 设为略大于空闲超时(如 7500 秒) - 每次
MESSAGE到达时,更新lastActive时间戳 - 收到
DISCONNECT后,立即DEL对应 key;若没收到,靠 TTL 自动淘汰 - 避免用 FC 函数本地内存(
Map或全局变量)存连接状态——实例随时可能销毁或复用
真正的难点不在“怎么收消息”,而在“怎么知道该往哪推”以及“推的时候连接还在不在”。这两点不靠外部存储兜底,WebSocket 在 Serverless 下就只是个半残协议。











