几种后端通信方式的杂谈

计算机的几种通信方式

对于计算机而言,通信 是一件永远不可能被忽视的事情。通信 基本可以分为 进程内通信 和 进程间通信。 进程内通信的方式大致有: ① 共享内存 ② 变量传递。进程间通信又需要分为 在同一操作系统上 和 在不同操作系统上,同一操作系统上的进程间通信主要有 ① 共享内存 ② 共享存储 ③ 管道(命名) ④ 信号量 ⑤ 消息队列 ⑥ socket 通信。不同操作系统上,则只能使用 socket 通信。

实际上,从上面可以看出,除了 信号量 的通信方式比较独特外,其他的通信方式都是基于 共享传递 的进行。只是 共享的方式 和 传递的方式 有所差别。

进程内通信

在 进程内通信时,共享内存 是可以被特定编程语言直接使用的内存格式,因此效率非常高;在进程间的共享内存上,内存是最底层的共享方式,因此内存格式需要自己约定,也就意味着所有传递的值 需要经过一定的格式转换,也就是 序列化的过程。

在 进程内通信时,变量传递可以分为 值传递 和 引用传递 ,所有的操作都是编程语言做的,我们只管使用即可。但在 进程间的消息传递时,我们则需要自己处理 消息格式 的问题,也即是 序列化和反序列化 的过程。

上述的这些通信方式,实际上是很通用的模型,不仅在操作系统层被使用,在应用层,我们基于这些模型造了很多工具或软件系统。例如,共享内存 我们有 redismemcache 等,共享存储 我们有 mysqletcdmongodb 等等,管道 我们有 redis ,消息队列 我们有 kafkazeroMQrabbitMQ 等等,甚至有一些 消息队列的通用协议,例如 MQTTAMQP 等等。

进程间通信

最繁荣的进程间通信方式,实际上还是 socket 通信,socket 本身只是一个接口定义,不过这个接口定义太通用了,任何消息传递几乎都可以套用这个接口,无论是 文件网络 还是 内存

服务端的常见通信方式

对于服务端开发而言,最常用的还是基于 网络 的 socket 编程,最底层自然都依赖 TCP/UDP,在应用层上,我们有非常多的协议,例如 httphttp2http3websocketMQTT 以及各种小领域的协议,其实,所有的协议,都是对特定领域的通信内容的通用抽象,例如,http 是一个很常用的互联网请求协议,包含 请求行 、请求头 、请求体 ,一般认为 http 是一个 文本协议,实际上这指的是其 序列化方式,文本内容直接通过 ascll [待验证] 进行传输,背景是 http 协议出生在 文本内容共享浏览 的互联网初始定位 的时代。

后来发现 http 的传输效率不高,于是 http2 做了 压缩消息头 、二进制分帧传输、连接复用 的优化。在后来,认为基于 tcp 的传输效率还是有点低,而现代基础设施整体是比较好的,因此基于 udp 在应用层做了一些消息质量保证的操作,用于提升传输效率。

其实 http 系列协议,都可以看做是在 http1.1 的协议约定基础上,在传输层做优化。 那么,是否有其他通过优化协议本身内容的方式呢?其实其他协议都是在做这件事。例如,websocket,建立在 tcp 之上,使用非常简洁的控制帧,通信消息全在消息体当中。还有一些追求极致性能的协议,甚至直接建立在 TCP 上做应用层消息传输,例如 kafka 的通信协议。

从 自定义消息结构 这个看待协议的角度出发,我们甚至可以认为 rpc 才是真正的一切协议的源头,可以想象曾经有各类自定义的 rpc 格式,例如 sambanfsssh 等等,当他们具有一定的名气后,大家就把他们从 自定义 rpc 协议 的认识中拎出来,直接用他们的名字代替。也就是说,我们现在所自定义的各种 rpc 协议,当他们具有一定名气后,就可以拥有自己的 名字 。

阐述完各类协议的基本情况后,我们来聚焦一下 httphttp2grpcws ,看一下他们的关系。

几种协议的区别认识

Http 的全名叫 超文本传输协议,是互联网最常用的协议,分为 请求行 (method + path)、请求头 (header) 、请求体 (body),请求体可以是二进制数据(经过编码传输)。

http2 是 http 的升级版,所有请求格式延续了 http 的格式,对上层应用来说整体没啥差别(如果仅当做http来用),但是也提供了 服务端推送、stream 等功能。一定程度上,我们可以认为,http2 是 http 的 传输层封装 (充当 transport 层)。

grpc 是把 http2 当做传输层 (transport 层)。其他特性是自行实现的,例如 interceptorresolverbalancerauthlogstatus(状态码)、stats(监控)。实际上,我们不能把 grpc 当做一种协议,而是一种 rpc 框架 (类似于 http 框架),协议 有特定格式 或 接口约定,而 grpc 是用于生成特定 server 和 client 的一整套工具。

ws 是直接在 tcp 之上的一层通信协议,特点是 轻量、连接保持,ws的想象空间很大,实际上,如果你愿意,甚至可以使用 ws 作为 http 的 transport 层,也可以把 ws 作为 mqtt 的 transport 层,也可以把 ws 作为 grpc 的传输层。

一些情况下,我们可以认为,一个协议 或者 一个框架,为什么选择了某项技术 而并不是 其他技术,是由于 生态 决定的,例如为什么 grpc 选用了 http2 而不是直接的 tcp 连接?为什么后端服务调用大家使用 grpc 而不是 手写 http?为什么后端不用 ws 通信?

ws的特点

ws 现在的主要场景在前后端的即时消息上,这得益于 ws 的 状态保持 的特性,那么,我们是否可以基于这个特性,做更多的事情?

比如,① 基于 ws 的 http 协议转换、② 基于 ws 的 grpc 协议转换、③ 基于 ws 的自定义 rpc 框架、④ 基于 ws 的自定义框架。

对于 ①,应用场景较少,如果是为了传输性能,那么使用 http2 就能解决,而且 http2 的生态更好。
对于 ②,可以,但目前已经有了基于 http 的 grpc 协议转换,使用 http2 的情况下,性能也没啥问题。
对于 ③,可以,但要考虑生态问题,这基本意味着重新实现整套 grpc 的各模块。
对于 ④,可以,但目前已经有一些 ws 框架,例如 socket.io,要考虑清楚为什么需要一套新的框架。

我对于当前 http 、http2、grpc、ws 方式的基本判断

  1. http 有完善的接口定义方式 (openapi),http2 甚至 http3 在性能上和将来的生态上也非常不错。目前没有看到好的基于 http 接口定义 方式自动生产 server 和 client 的工具。
  2. grpc 有完善的接口定义方式,性能上和生态上很不错,有自动生成代码的工具链。前端调用不支持直接通信,需要经过 http 协议再转一次(浏览器端 或 proxy 端)。
  3. ws 有一定的生态支持,ws 的状态保持在一些场景下是非常不错的特性。ws 没有自动生成代码的工具。

ws有什么特殊的价值?

ws 有两个特性: ① 连接保持 ② 协议轻量 。另外,ws 的生态不错 (主要指浏览器的特殊支持)。
ws 在一些需要长链接的场景下,非常有价值,比如: ① 协商缓存内容,② 服务端缓存内容(eg: 权限)

如果解决 ws 的 ① 重连状态保持 ② http 降级 ③ 代码自动生成 ④ 开发模式 ⑤ 测试工具包 问题,那么 ws 不失为一个很好的通信工具。

代码生成的思考

代码生成是一个非常好的思路,可以保证代码的统一性,减少不规范的地方,可维护性更高。现在可以看到,在 client 和 server 代码生成上,grpc 做的是最好的,生态也比较开放,在这个基础上可以开发一些自己需要的功能。

Go-zero 框架是自己实现的一套语法解析并形成特定代码,提供了模板化的方法生成代码,也和 grpc 一样提供了自定义插件的方式,看上去野心不小。

Go-kratos 则是接入 grpc 的生态,通过扩展生成代码的方式,接入了自己的 http 和 rpc。

Go-frame 在接口自动化代码生产上没有动作,只是在 脚手架工具 中简化了 对 grpc 代码生成的命令。

除了上面说到的 基于接口文档 自动化生成 server 及 client 代码外,还有一些其他常用的可生成的代码:

  1. 基于 数据库表 生成 结构体、orm、基本 crud 代码。
  2. 基于 接口定义,生成前端 client 代码。
  3. 生成部署侧的脚本或包 (docker、k8s、devspace等)

第 1 点中的 model 生成,go-frame 和 go-zero 都有做。 第 2 点目前只有 go-zero 做了一些,grpc 也有一些。 第 3 点都有动作。

另外 ,补充一嘴,数据库的结构体生成 是可以 正反使用的,例如,通过 orm,生成数据库表,同样也可以通过 数据库表,生成 orm,这点可以参考 关于基于接口定义的开发流 最后的链接。

一个疑惑

按理,http 才是互联网下的王者,那么,为什么很少见到基于 http 的 接口定义文档 自动生成代码的工具呢?

实际上,相比于 protobuff 的 proto3 这种新的 DSL ,我们使用已有语言的成本可能更低,例如 基于 yaml 或者 基于 json 的,比如使用 openapi 的接口定义。甚至,使用一门我们熟悉的语言做接口描述,例如 js,然后代码生成则直接使用 js 进行拼接 ( 或模板渲染 )。

这个可以找找是否有相关的工具,如果没有,可以自己实现一个。

后记

杂七杂八写了一些自己的想法,很多表述不一定很精准,但思路确实还是有可参考性的,可以经常回味一下。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!