异构组网:多通信协议融合,组建通信网格
Created By RV, and licensed with Creative Commons "CC BY-NC-ND 4.0"
目的与场景
在这一章,讲解如何将采用不同通信协议的多个网络互相连接,融合成一张通信网格(Net Grid),使网格中任意两个节点,无论身处哪个协议网络,都能建立通信链路、互相调用服务。
一. 概念
1. 传输网络:Network
将采用同种通信协议的各个节点互相连接,得到一个传输网络(Network)。
以TCP网络为例:一个节点Listen某个地址:端口,其它节点以Attach方式连接到该地址:端口。
这组成了一张TCP网络。
2. 通信网格:Net Grid
多张同构、异构网络互相连接,组成的网格,称之为通信网格。
在网格中:
- 节点采用多种通信协议
- 一个节点可同时属于多个网络
- 节点间可动态协商建立新链路,也可动态退出
- 任意两个节点都可通信,它们之间没有直连协议时,其他节点转发亦可到达
详情,参阅: 通信网格:Net Grid
3. 多协议融合,是网格的基本形态
生产系统中,针对不同的场景,我们经常采用不同的通信协议:
- 内部微服务之间,TCP网络可靠、低延迟
- 与浏览器打交道,不得不采用HTTP / WebSocket
- 消息推送、实时同步,采用UDP ,低开销
这些异构网络,经桥接节点(开启了proxyMode)彼此连通,即可融合成一张统一的通信网格。
4. 网格对于使用者是透明的
对于使用KreeX的对象,网格是透明的:
- 发起到某个节点的调用,无需关心对方身处哪个协议网络
- 跨网络的调用,自动经桥接节点转发,无需任何特殊处理
- 网格对有共同协议的节点,可经协商、自动建立直接连接,动态路由
二. 示例代码
在下边的示例中,我们将组建一张由 TCP网络 与 UDP网络 融合的通信网格:
- node-a:TCP网络节点,注册
calc服务 - node-b:桥接节点,同时连接TCP与UDP,开启proxyMode,将两个网络融合成一张网格,注册
str服务 - node-c:UDP网络节点,注册
greet服务
node-a 与 node-c 无共同通信协议,但因同属一张网格,可以跨协议互相调用。
1. 搭建拓扑
// node-a:TCP网络节点,注册calc服务
const nodeA = create('node-a', 'TCP service node')
nodeA.register('calc', {
add (a, b) { return a + b },
multiply (a, b) { return a * b }
})
// node-a:TCP网络, frameLimit不是必须,保持多节点一致可避免重新分帧
nodeA.listen('tcp://127.0.0.1:8091', { frameLimit: 1152 })
// node-b:桥接节点,同时连接TCP与UDP,开启proxyMode跨网络转发
const nodeB = create('node-b', 'UDP bridge node', { transport: { proxyMode: true } })
nodeB.register('str', {
echo (msg) { return `Echo: ${msg}` },
greet (name) { return `Hello, ${name}! (from node-b)` }
})
// node-b:TCP连接至node-a
nodeB.attach('tcp://127.0.0.1:8091', { frameLimit: 1152 })
// node-b: UDP监听,注意“ack: true”开启了KreeX应用层可靠UDP通信
nodeB.listen('udp://127.0.0.1:8092', { frameLimit: 1152, ack: true })
// node-c:UDP网络节点,注册greet服务
const nodeC = create('node-c', 'UDP caller node')
nodeC.register('greet', {
hello (name) { return `Hello, ${name}! (from node-c)` }
})
// node-c: UDP连接node-b,注意“ack: true”开启了KreeX应用层可靠UDP通信
nodeC.attach('udp://127.0.0.1:8092', { frameLimit: 1152, ack: true })
2. 等待网格互相发现
节点启动后,需要时间完成网格发现。whenReady() 等待目标节点在当前节点生成为止:
await nodeC.whenReady(nodeB.id)
await nodeA.whenReady(nodeB.id)
await nodeA.whenReady(nodeC.id)
3. 跨协议调用:node-c(UDP) → node-a(TCP)
UDP网络中的node-c,调用TCP网络中的node-a的 calc 服务,经桥接节点node-b转发:
const calc = nodeC.service('calc')
await calc.add(10, 20) // 30
await calc.multiply(6, 7) // 42
4. 同协议调用:node-c(UDP) → node-b(UDP)
node-c 直接调用同处UDP网络的node-b的 str 服务:
const str = nodeC.service('str')
await str.greet('World') // Hello, World! (from node-b)
5. 反向跨协议调用:node-a(TCP) → node-c(UDP)
TCP网络中的node-a,调用UDP网络中的node-c的 greet 服务:
const greet = nodeA.service('greet')
await greet.hello('World') // Hello, World! (from node-c)
三. 须强调的细节
1. 桥接节点必须同时具备两侧协议连接
桥接节点必须同时连接两侧的协议网络(如 attach(tcp://...) + listen(udp://...))。
同时,开启 transport: { proxyMode: true },才能跨网络转发。
proxyMode: true,详见《帧中继:开启节点数据转发,支持节点间接通信》。
2. UDP网络建议可靠传输
UDP网络建议设置 frameLimit: 1152 并开启 ack: true,保证跨网络的调用可靠传输。
Kree4X,在Transport层,內建了DataFrame的Ack机制,支持DataFrame数据帧的收发确认机制。
此Ack机制,与通信协议无关,在任何通信协议上,都可开启。
3. 等待网格发现完成
节点启动后,网格的互相发现需要时间。
示例中,使用 whenReady() 等待目标节点可见,避免过早发起调用而失败。
注意:
whenReady机制,仅在示例中使用,控制示例按照我们期望的时序执行。
生产环境,网络、服务都是动态的,不需要此种静态机制。
- 生产环境,我们也无从得知远端节点的id,whenReady也无从调用。
4. 网格中继≠直连
跨协议调用经桥接节点转发,属于间接通信。
若两个节点协议兼容且频繁通信,Kree4X支持动态协商,建立直连。
系统可动态建立直接连接,从而规避间接转发的低效,提高通信效率。
下一章《动态链路:开启通信协商,支持动态链路直连与断线重连》,会讨论此问题。
四. 涉及到的API
1. 监听指定URL
/**
* 监听指定URL,可设置连接选项。
* URL的协议部分,决定本节点加入哪种协议网络。
*
* @param {string} url - 监听地址。
* @param {ConnectionOptions} [options] - 连接选项。
* @returns {this} 当前节点,支持链式。
*/
listen(url, options?: ConnectionOptions): this
2. 连接到远端节点
/**
* 连接到远端节点,可设置连接选项。
* URL的协议部分,决定本节点加入哪种协议网络。
*
* @param {string} url - 远端地址。
* @param {ConnectionOptions} [options] - 连接选项。
* @returns {this} 当前节点,支持链式。
*/
attach(url, options?: ConnectionOptions): this
3. 连接选项:ConnectionOptions
type ConnectionOptions = {
frameLimit?: number // 连接(NetPoint)可承载的单个数据帧最大尺寸,默认100KB
ack?: boolean // 是否开启DataFrame报文确认,保证可靠传输
maxSender?: number // 最大并发发送通道数
maxInChannel?: number // 最大接收通道数
maxOutChannel?: number // 最大发送通道数
openTimeout?: number // 连接打开超时(毫秒)
}
UDP网络建议设置 frameLimit: 1152(受MTU限制),如需可靠通信可开启 ack: true 报文确认。
4. 节点选项:TransportOptions
// 创建节点时开启帧中继,跨网络转发
type TransportOptions = {
proxyMode: boolean // default false
}
// 桥接节点必须开启,才能跨网络转发
const nodeB = create('node-b', 'UDP bridge node', { transport: { proxyMode: true } })
详见《帧中继:开启节点数据转发,支持节点间接通信》。
五. 可运行代码
完整示例代码,参见:09-heterogeneous-grid.mjs