阅读 MQTTX 项目:CLI 【2】
信息的格式转换与 Avro/Protobuf 编码之间的冲突
以发送消息的流程为例,当前发送消息前的预处理中,格式转换的顺序是要先于 schema-based 编码的。
pub.ts
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const processPublishMessage = (
message: string | Buffer,
schemaOptions: SchemaOptions,
format?: FormatType,
): Buffer | string => {
const convertMessageFormat = (msg: string | Buffer): Buffer | string => {
if (!format) {
return msg
}
const bufferMsg = Buffer.isBuffer(msg) ? msg : Buffer.from(msg.toString())
return convertPayload(bufferMsg, format, 'encode')
}
const serializeProtobufMessage = (msg: string | Buffer): Buffer | string => {
if (schemaOptions.protobufPath && schemaOptions.protobufMessageName) {
return serializeProtobufToBuffer(msg.toString(), schemaOptions.protobufPath, schemaOptions.protobufMessageName)
}
return msg
}
// ---HERE---
const pipeline = [convertMessageFormat, serializeProtobufMessage]
// ---HERE---
return pipeline.reduce((msg, transformer) => transformer(msg), message) as Buffer
}可以看到
pipeline
的处理顺序中convertMessageFormat
先于serializeProtobufMessage
。然而,基于 Schema 的编码方式所接受的
message
只能是javascript object
,不然程序就无法读取并判断其内容是否符合 Schema Type,更不能将其中内容按照 Schema 编码成 Buffer 了。因此,此处
protobuf
的编码能够运作的前提是format
为空(undefined)或者能使用toString()
方法进行还原的 Buffer。假设用户将消息的发送格式设置为
hex
或是cbor
等编码格式,经过convertPayload
处理后产生的Buffer
将无法用于 protobuf/avro 的编码。可以看到,
serializeProtobufToBuffer
函数中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20export const serializeProtobufToBuffer = (
raw: string | Buffer,
protobufPath: string,
protobufMessageName: string,
): Buffer => {
let rawData = raw.toString('utf-8') // ???
let bufferMessage = Buffer.from(rawData) // ???
try {
const root = protobuf.loadSync(protobufPath)
const Message = root.lookupType(protobufMessageName)
const err = Message.verify(JSON.parse(rawData)) // ???
if (err) {
logWrapper.fail(`Message serialization error: ${err}`)
process.exit(1)
}
const data = Message.create(JSON.parse(rawData)) // ???
...
}
...
}- 该函数的参数
raw
被设为了string | Buffer
类型。很奇怪的是,此后的几行代码似乎都在试图将其从Buffer
还原为string
,再使用JSON.parse(rawData)
将其还原为一个 JavaScript Object。 - 为什么要这样?直接设定传入的
raw
为string
或者更干脆的设为object
好像更加简单直接。 - 当然,这里面可能有一些以我的水平没有考量到的情况,请予以指正。
- 该函数的参数
- 在我看来,格式转换和基于 Schema 的编码恐怕不应当被同时使用。
string 和 Buffer 类型的混用
注意到,在发送的消息中,对于
message
的类型是这样定义的:1
2
3
4
5
6
7
8
9interface PublishOptions extends ConnectOptions {
topic: string
// ---HERE---
message: string | Buffer
// ---HERE---
qos: QoS
retain?: boolean
...
}对于这个
message
变量的类型,为什么他不能只是string
?到底在什么情况下他会以一个Buffer
类型从命令行输入?该 Union Type 在后续的消息处理中带来了一些麻烦,我想了解他在何种情况下会作为
Buffer
类型输入,以及如果可能的话,是否能直接将其类型直接修改为string
。
更新
了解到在使用了
--file-read
option 后,消息主体会从文件中读取。读取文件的方法是fs.readFileSync
,该方法在未设置第二项参数时返回值的类型为Buffer
。请问能否将其更改为传回
string
的形式?当前processPublishMessage
方法中string
和Buffer
的混用实在是令我感到些许反感。虽然使用
string
作为返回值的fs.readFileSync
在性能上大概降低了 75% 左右,但是我认为在serializeProtobufToBuffer
等众多函数中因为不能确定msg
的类型而进行反反复复的JSON.parse()
和toString()
操作更加消耗性能(例如上个段落中serializeProtobufToBuffer
里面那样,以及诸多的格式转换函数中频繁的toString()
)。相关测试请看:
性能测试
下面是一个使用 ChatGPT 生成的简易测试程序,用于测试 fs.readFileSync
返回 Buffer
类型和 string
类型的性能差异:
1 | const fs = require("fs"); |
测试结果:
1 | $ node fsType.js |
可以看到,性能差距的确相当巨大。
但是,只要在以 Buffer
类型读取的数据后添加一行 toString()
方法:
1 | ... |
他们的性能就会变得相差无几:
1 | $ node fsType.js |
所以,我建议直接使用 string
作为返回值。
- 或者我们还可以选择另一个方案:为
Buffer
类型的消息发送建立一个新的消息发送数据流,在分离msg
为string
和Buffer
这两种情况的同时也保持良好的文件读写性能。 - 当然,这会需要更多的开发投入。
- 以上纯属个人的一己之见,倘若有理解错误的地方,还希望老师予以指正。
- Title: 阅读 MQTTX 项目:CLI 【2】
- Author: Last
- Created at : 2024-08-14 17:02:26
- Link: https://blog.imlast.top/2024/08/14/reading-mqttx-2/
- License: This work is licensed under CC BY-NC-SA 4.0.