使用 Node.js 构建 BFF 层(二)
日期:2021-08-18 17:55:00
更新:2021-08-18 17:55:00
标签:前端, Javascript, node
分类:Javascript, node
RPC通过传输层协议传输数据,传输层传输的是二进制数据,发送端需要将请求的方法名和数据序列化后发送,接收端接收到二进制数据后则需要反序列化并处理数据。

前言
上一篇文章介绍了 BFF 的概念和应用场景,并且做了一个简单的 BFFdemo。接下来可以讨论一下如何优化 BFF 层

协议设计
上文提到,RPC通过传输层协议传输数据,传输层传输的是二进制数据,发送端需要将请求的方法名和数据序列化后发送,接收端接收到二进制数据后则需要反序列化并处理数据。所以,RPC 调用的流程大致如下:

所谓协议,通俗的就是用固定的格式封装成报文数据,双方按照这一固定格式对数据进行发送和接收。例如以前讲过的利用 TCP 实现一个 HTTP 服务。
根据上图的流程,实现设计一个简单的通信通信协议并且实现它,利用这个协议进行通信。这个协议由报文头和报文主体组成
报文头主要有的信息:
| — type — | ------- requestId ------- | ---- length ---- | — timeout — |
| ------------ | ------------------------- | ---------------- | --------------- |
| ---------------------------------- body -------------------------------- |
| ------------------------------------------------------------------------ |
- 报文头的长度是固定的
type是请求的类型,占1个字节: 规定 0 - REQUEST 1 - RESPONSE
requestId请求的 ID,占4个字节:范围 0 ~ 4 * 2^8
length报文主体长度,占4个字节
timeout响应超时事件,规则报文双文,占1个字节
到这里,一个简单的报文已经设计完成,其中,header 一共占10个字节,接下来就是要在代码中描述它。
用 node.js 描述报文
const body = {
method: "addOrder",
data: {
productId: "1",
num: 1,
},
};
const bodyLength = JSON.stringify(body).length;
const bodyBuf = Buffer.alloc(bodyLength, JSON.stringify(body));
const bufHeader = Buffer.alloc(10);
bufHeader[0] = 0;
bufHeader.writeInt32BE(1, 1);
bufHeader.writeInt32BE(bodyLength, 5);
bufHeader[9] = 60;
console.log(bufHeader);
const buf = Buffer.concat([bufHeader, bodyBuf]);
console.log(buf);
一个发送的报文就描述出来了。当报文被发送出去被接收后,需要将报文数据进行解码,继续来实现一下解码的过程~
function decodeBuf(buf) {
const type = buf[0];
const requestId = buf.readInt32BE(1);
const length = buf.readInt32BE(5);
const timeout = buf[9];
const body = buf.split(10, 10 + length);
return {
type,
requestId,
length,
timeout,
body: json.parse(body),
};
}
console.log(decodeBuf(buf));
将报文数据传输进去之后输出

到这里,我们已经实现了 RPC 通信报文的序列化和反序列化,接下来就是实现一个传输层服务。
用 node 实现 RPC 框架
在 node 中创建 TCP 连接需要使用net的库,不熟悉的可以看一下官方关于 net 的文档
import { createServer, createConnection } from "net";
export function createService() {
const service = createServer((socket) => {
socket.on("connect", () => {
console.log("connect server");
});
socket.on("data", (data) => {
const result = decodeBuf(data);
if (result.body.method === "jump") return;
else {
console.log("server receive data", result);
socket.write(
encodeBuf(1, result.requestId, { code: 200, msg: "success" })
);
}
});
socket.on("end", () => {
console.log("disconnect server");
});
});
service.listen(4444, () => {
console.log(`service running at 4444`);
});
}
export function createClient() {
const client = createConnection({
port: 4444,
});
client.write(
encodeBuf(0, 1000, {
method: "addOrder",
data: { productId: 1000, num: 1 },
})
);
client.on("data", (data) => {
console.log("client receive data", decodeBuf(data));
});
client.on("close", () => {
console.log("client connect close");
});
}
function encodeBuf(type, order, data) {
const bodyLength = JSON.stringify(data).length;
const bodyBuf = Buffer.alloc(bodyLength, JSON.stringify(data));
const bufHeader = Buffer.alloc(10);
bufHeader[0] = type;
bufHeader.writeInt32BE(order, 1);
bufHeader.writeInt32BE(bodyLength, 5);
bufHeader[9] = 60;
const buf = Buffer.concat([bufHeader, bodyBuf]);
return buf;
}
function decodeBuf(buf: any) {
const type = buf[0];
const requestId = buf.readInt32BE(1);
const length = buf.readInt32BE(5);
const timeout = buf[9];
const body = buf.slice(10, 10 + length);
return {
type,
requestId,
length,
timeout,
body: JSON.parse(body),
};
}
createService();
setTimeout(() => {
createClient();
}, 1000);
调用后返回。

成功~ 我们已经成功实现了一个 TCP 通信service和client。最后,把通信接入到 BFF 层。
优化 BFF 层
- 修改一下网络层方法,对外暴露处理数据的接口
export function createService(port, callback) {
const service = createServer((socket) => {
socket.on("connect", () => {
console.log("connect server");
});
socket.on("data", (data) => {
const result = decodeBuf(data);
if (result.body.method === "jump") return;
else {
console.log("server receive data", result);
const returnData = callback(result, socket);
socket.write(encodeBuf(1, result.requestId, returnData));
}
});
});
service.listen(port, () => {
console.log(`service running at ${port}`);
});
}
export function createClient(port: number) {
const client = createConnection({
port,
});
return client;
}
- 第二步,将微服务中的 HTTP 服务改为刚刚创建的网络服务
createService(4444, (data) => {
const result = handleOrderInput(data.body);
return result;
});
createService(4445, (data) => {
const result = handleDataInput(data.body);
return result;
});
- 最后,修改一下公共请求方法
async function publicRequest(port, method, data) {
return new Promise((resolve) => {
const json = { method, ...JSON.parse(data) };
const buf = encodeBuf(0, 1001, json);
const client = createClient(port);
client.write(buf);
client.on("data", (data) => {
const result = decodeBuf(data);
console.log("client receive data", result);
resolve(result);
});
});
}
测试一下

执行后返回

小结
本文主要介绍了 RPC 协议的设计和使用 node 实现协议。
将实现好的 RPC 应用到 BFF 层与微服务之间调用。
参考