gRPC 数据编码采用 Protobuf、底层传输协议使用了 HTTP/2,支持双向流( Bidirectional streaming
)接口,用来开发消息推送服务的再完美不过。
由于 gRPC 的实现需要访问 HTTP/2 的底层细节(如: Trailers),而这些实现细节浏览器上是无法访问到的,因此也就注定了无法在浏览器上直接进行调用 gRPC。
HTTP/2 在 2015 年底开始被广泛支持(参考 HTTP/2 - 维基百科,自由的百科全书),另一个流行的消息推送技术 WebSocket 在 2013 年底开始被广泛支持(参考 WebSockets - Web APIs | MDN)。从 HTTP/2支持情况 页面可以看到 IE11 以下都不支持,从 WebSocket支持情况 页面可以看到 IE11 以下及 Android 4.4 以下都不支持。相对来讲 WebSocket 更成熟,而且由于 socket.io 的存在,即使在旧浏览器上也能提供服务。
现在的情况下,最好是开发一个 socket.io 代理供浏览器访问 gRPC 服务,一个 WebSocket 连接可以很方便地映射到一个 gRPC 双向流( Bidirectional streaming)接口。
下面是方案调研时接触的一些相关开源项目,供参考。
grpc-gateway
grpc-gateway is a plugin of protoc. It reads gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition.
grpc-gateway 为 Grpc 服务生成 HTTP/1.1 JSON RESTful 反向代理服务,使得 Grpc 服务可以被不方便采用 Grpc 的客户端访问。
存在的问题
不支持双向流(Bidirectional streaming)
grpc-websocket-proxy
A proxy to transparently upgrade grpc-gateway streaming endpoints to use websockets
grpc-gateway 的 WebSocket 代理,进行了多次协议转换:
WebSocket <-> HTTP/1.1 <-> gRPC
在收到 WebSocket 连接后,会创建一个 HTTP/1.1 客户端,与 grpc-gateway 进行对接。
根据示例 Protobuf 协议文件中的描述,貌似支持 Bidirectional streaming
,但是 grpc-gateway 的 README 中明确声明了不打算支持 True bi-directional streaming
,理论上只要是基于 HTTP/1.1,就不可能实现 Bidirectional streaming
。
这个方案的一个优点就是 proxy 代码是根据 proto 定义自动生成的,开发效率超高。
然而运行效率低下,特别是维护大量长连接的消息推送应用,连接数要翻倍。
socket.io
Socket.IO enables real-time bidirectional event-based communication. It works on every platform, browser or device, focusing equally on reliability and speed.
node.js
下的实时双向通讯方案,优先使用 websocket
做为传输层,使用其它传输方式做为备选:ajax、jsonp 轮询,flash。
socket.io 本身已经进化成了一种规范,有各种语言下的实现。
go 语言实现 go-socket.io ,这个项目 README 中建议使用 v1.4 版本,实际使用时发现浏览器直接关闭时服务器未能检测客户端离线,未转化成 "Disconnect" 事件,使用 master 分支则没有这个问题:
go get github.com/googollee/go-socket.io
go 的 socket.io 实现没有官方支持,很不完善,go-socket.io 只有服务器端实现,已经很久没有更新了,而 go 的 socket.io 客户端实现就更不靠谱了,实际使用时很多问题,直接使用 websocket 会更靠谱一些。
envoy
envoy 有一些插件完成了部分功能:
-
实现了通过 HTTP/1.1 协议调用 gRPC(HTTP/2) 的功能。
采用和 gRPC 一致的数据编码方式(Protobuf 二进制数据),只支持
unary gRPC API
。
-
支持 gRPC 的
Server streaming
和Client streaming
,Envoy 将流式请求及响应缓存起来了,所以实时性较差;不支持Bidirectional streaming
。由于客户端是通过 HTTP/1.1 进行访问,所以不支持真正的 streaming。
详见 Envoy 的 json_transcoder_filter 介绍
要在 Envoy 中实现 Websocket 访问 gRPC 可以参考 gRPC-JSON transcoder filter 。
-
基于 gRPC Web 协议的实现,跟原始的 gRPC 协议基本一致,通过去除对 HTTP/2 底层(如 frame)的依赖,使得在浏览器上调用 gRPC 成为可能。
当前不支持
Bidirectional Streaming
,以后在最新的浏览器中可能支持。
-
支持接入 WebSocket,会建立一条到 Upstream 的 TCP 连接,Upstream 服务需要是一个完整的 WebSocket 服务器,相当于是一个 WebSocket 的透明代理。
由于客户端发起的 WebSocket 连接与到 Upstream 的连接(TCP)是
1:1
的关系,可扩展性较差。
Envoy 中编写插件支持 WebSocket 到 gRPC 的协议转换(支持 Bidirectional streaming
)理论上是可以实现的,不过难度会比较高,需要对 Envoy 的实现非常了解。初步看了一下 envoy 的相关代码,表面上代码层次分明,但是实际上却是错综复杂,大量使用继承,上下层会互相调用,由于大量采用回调函数,逻辑不好理解。
nginx
与 envoy 的 WebSocket support 相似,支持接入 WebSocket,会建立一条到 Upstream 的 TCP 连接,Upstream 服务需要是一个完整的 WebSocket 服务器,相当于是一个 WebSocket 的透明代理,由于客户端发起的 WebSocket 连接与到 Upstream 的连接(通过 proxy_pass 指令)是 1:1
的关系,连接数倍增,参考 https://www.nginx.com/blog/http2-module-nginx/#QandAnginx 。
-
Fast, horizontally scalable, multiprocess pub/sub queuing server and proxy for HTTP, long-polling, Websockets and EventSource (SSE), powered by Nginx.
是 Nginx 的一个插件,支持各种接入方式,是搭建消息推送系统的一种很好的选择。通过回调(HTTP/1.1 Restful)与业务系统集成在一起,使得业务系统不需要关心客户端接入细节。