几种后端通信方式的杂谈
计算机的几种通信方式
对于计算机而言,通信 是一件永远不可能被忽视的事情。通信 基本可以分为 进程内通信 和 进程间通信。 进程内通信的方式大致有: ① 共享内存
② 变量传递
。进程间通信又需要分为 在同一操作系统上 和 在不同操作系统上,同一操作系统上的进程间通信主要有 ① 共享内存
② 共享存储
③ 管道(命名)
④ 信号量
⑤ 消息队列
⑥ socket
通信。不同操作系统上,则只能使用 socket
通信。
实际上,从上面可以看出,除了 信号量
的通信方式比较独特外,其他的通信方式都是基于 共享
或 传递
的进行。只是 共享的方式 和 传递的方式 有所差别。
进程内通信
在 进程内通信时,共享内存 是可以被特定编程语言直接使用的内存格式,因此效率非常高;在进程间的共享内存上,内存是最底层的共享方式,因此内存格式需要自己约定,也就意味着所有传递的值 需要经过一定的格式转换,也就是 序列化的过程。
在 进程内通信时,变量传递可以分为 值传递 和 引用传递 ,所有的操作都是编程语言做的,我们只管使用即可。但在 进程间的消息传递时,我们则需要自己处理 消息格式 的问题,也即是 序列化和反序列化 的过程。
上述的这些通信方式,实际上是很通用的模型,不仅在操作系统层被使用,在应用层,我们基于这些模型造了很多工具或软件系统。例如,共享内存 我们有 redis
、memcache
等,共享存储 我们有 mysql
、etcd
、mongodb
等等,管道 我们有 redis
,消息队列 我们有 kafka
、zeroMQ
、rabbitMQ
等等,甚至有一些 消息队列的通用协议,例如 MQTT
、AMQP
等等。
进程间通信
最繁荣的进程间通信方式,实际上还是 socket
通信,socket 本身只是一个接口定义,不过这个接口定义太通用了,任何消息传递几乎都可以套用这个接口,无论是 文件
、网络
还是 内存
。
服务端的常见通信方式
对于服务端开发而言,最常用的还是基于 网络 的 socket 编程,最底层自然都依赖 TCP/UDP,在应用层上,我们有非常多的协议,例如 http
、http2
、 http3
、websocket
、 MQTT
以及各种小领域的协议,其实,所有的协议,都是对特定领域的通信内容的通用抽象,例如,http 是一个很常用的互联网请求协议,包含 请求行 、请求头 、请求体 ,一般认为 http 是一个 文本协议,实际上这指的是其 序列化方式,文本内容直接通过 ascll [待验证] 进行传输,背景是 http 协议出生在 文本内容共享浏览 的互联网初始定位 的时代。
后来发现 http 的传输效率不高,于是 http2 做了 压缩消息头 、二进制分帧传输、连接复用 的优化。在后来,认为基于 tcp 的传输效率还是有点低,而现代基础设施整体是比较好的,因此基于 udp 在应用层做了一些消息质量保证的操作,用于提升传输效率。
其实 http 系列协议,都可以看做是在 http1.1 的协议约定基础上,在传输层做优化。 那么,是否有其他通过优化协议本身内容的方式呢?其实其他协议都是在做这件事。例如,websocket
,建立在 tcp 之上,使用非常简洁的控制帧,通信消息全在消息体当中。还有一些追求极致性能的协议,甚至直接建立在 TCP 上做应用层消息传输,例如 kafka 的通信协议。
从 自定义消息结构 这个看待协议的角度出发,我们甚至可以认为 rpc 才是真正的一切协议的源头,可以想象曾经有各类自定义的 rpc 格式,例如 samba
、nfs
、ssh
等等,当他们具有一定的名气后,大家就把他们从 自定义 rpc 协议 的认识中拎出来,直接用他们的名字代替。也就是说,我们现在所自定义的各种 rpc 协议,当他们具有一定名气后,就可以拥有自己的 名字 。
阐述完各类协议的基本情况后,我们来聚焦一下 http
、http2
、grpc
、 ws
,看一下他们的关系。
几种协议的区别认识
Http 的全名叫 超文本传输协议,是互联网最常用的协议,分为 请求行 (method + path)、请求头 (header) 、请求体 (body),请求体可以是二进制数据(经过编码传输)。
http2 是 http 的升级版,所有请求格式延续了 http 的格式,对上层应用来说整体没啥差别(如果仅当做http来用),但是也提供了 服务端推送、stream 等功能。一定程度上,我们可以认为,http2 是 http 的 传输层封装 (充当 transport 层)。
grpc 是把 http2 当做传输层 (transport 层)。其他特性是自行实现的,例如 interceptor
、resolver
、balancer
、auth
、log
、status
(状态码)、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 方式的基本判断
- http 有完善的接口定义方式 (
openapi
),http2 甚至 http3 在性能上和将来的生态上也非常不错。目前没有看到好的基于 http 接口定义 方式自动生产 server 和 client 的工具。 - grpc 有完善的接口定义方式,性能上和生态上很不错,有自动生成代码的工具链。前端调用不支持直接通信,需要经过 http 协议再转一次(浏览器端 或 proxy 端)。
- 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 代码外,还有一些其他常用的可生成的代码:
- 基于 数据库表 生成 结构体、orm、基本 crud 代码。
- 基于 接口定义,生成前端 client 代码。
- 生成部署侧的脚本或包 (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 协议 ,转载请注明出处!