DreamMesh抛砖引玉(5)-网络通讯
在微服务时代,由于原有的单体系统被拆分为多个微服务,服务间通讯的数量大为增加。因此,选择什么样的远程通讯方案变得至为重要。
背景
在深入讨论之前,先简单交代一下背景。对于远程通讯来说,有两个重点:
-
序列化协议
序列化和反序列化方式,主要是两大派系:1. 文本格式,主流是REST(或者说JSON),XML。2. 二进制格式,主流如Thrift,Protocol Buffer, Hessian,还有某些框架特有的格式如Dubbo,另外Java社区还有以慢著称的Java序列化。
-
远程调用方式
主要是REST和RPC两个流派的竞争。
传统侵入式开发框架
对于传统侵入式开发框架,远程通讯是其SDK的核心内容。客户端能选择什么样的通讯方案,取决于SDK可以支持哪些。
开发框架 | 远程通讯方案 | 备注 |
---|---|---|
Spring Cloud | REST(或者说HTTP+JSON) | 明确拒绝二进制和RPC |
阿里Dubbo | 主要是RPC方案如Dubbo/Hessian/Rmi/WebService | DubboX增加了对REST的支持 |
微博Motan | Hessian/Json/Motan | Agent方案支持gRPC |
唯品会OSP | 改进版本的Thrift | 只用了thrift的序列化,网络通讯重新改写 |
Dolphin | gRPC | 我在2016年开发的微服务框架 |
TBD:需要更多调研。
总体上说,REST和RPC两个方案都有,早期的部分框架还会保留诸如Hessian/Rmi/WebService
Service Mesh方式
我们进入正题,谈谈Service Mesh下的情况。
首先我们来看看侵入式开发框架和Service Mesh的差异(仅以请求为例,应答是同理):
- 侵入式开发框架下,客户端和服务器端直接通讯,客户端做一次序列化,服务器端做一次反序列化
- Service Mesh就要复杂的多,在客户端和服务器端部署有一个或者两个Mesh,用于转发请求。
首先,我们可以看到,Service Mesh下服务间通讯的方式是基于网络层。在整个过程中,Mesh只需要能支持通讯协议,比如最常见的HTTP1.1/HTTP2,能做到接收请求并转发即可。原则上,Mesh接受到请求之后,只是读取Header获得必要信息,然后透传Payload。
因此,对于选择什么样的序列化方案和远程访问方案并无限制:
- 原则上Payload是透传的,Mesh并不关心其实质内容。
- Mesh收到的是网络请求,不关心这个请求客户端是如何发出来的,也就是说REST和PRC与否和Mesh无关。
理论上说,传统侵入式开发框架可以选择的方案在Service Mesh下都是可以继续沿用的。
但是需要额外特别指出的是,在Service Mesh下,跨语言和跨平台是必须满足的基本需求。因此,任何语言和平台有关的序列化协议和远程调用方式都不适合,比如Java序列化,EJB之类。当然,目前主流的侵入式开发框架也基本都在遵循这个原则,只是Service Mesh下会做的更加的坚决。
轻量级 VS 微服务
按照Martin Fowler老爷子给出的微服务经典定义:
微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP Resource API)。
服务间通信采用轻量级通信机制,这是微服务的美好愿景。但是,事情往往不是想象中那么完美。
侵入式客户端窘境
以REST为例,虽然REST一直标榜轻量级,要发出一个REST请求对于任何语言都不是难事。但是一旦和微服务结合,就立马变得臃肿不堪,以Spring Cloud为例:
- 为了实现服务发现需要引入Eureka
- 为了客户端的负载均衡需要引入Ribbon
- 为了熔断需要引入Hystrix
- 为了分布式追踪引入zipkin
- ……
为了让各个类库协调工作,中间还有大量的集成工作,连自成一家的netflix OSS套件,在Spring Cloud集成中工作量也没少到哪里去。对于普通用户,无论刚开始的REST类库有多简单,只要开始了微服务之路,就不得不一个一个的功能坐上去:服务注册和服务发现是必须的,负载均衡也是必须的,熔断没有未免不够安全,没有APM debug时很不方便,有些还要考虑高级一点的功能如智能路由,还要考虑安全,比如认证/加密……
因此,采用REST实现微服务,要不直接用Spring Cloud,要不就一路做下来做出一套类似Spring Cloud的系统。类似的,采用RPC方案来实现微服务,最终成型的系统也无外如是。
侵入式开发框架,最终都会成为一个庞然大物。
这一路做下来,最开始起步时到底是不是轻量级已经无关轻重。此时作为这个庞然大物的核心和入口,客户端类库已经捆绑了太多的东西,重也是无可奈何。
而太重也就意味着断无可能随意更换。开发框架的SDK集成了什么样的类库,那么用户的客户端就只能选择什么。
源于Service Mesh的自由
在Service Mesh之下,有一个特别的地方:相比侵入式开发框架,客户端大为简化,只需要将生成请求并发送出去即可。上面列出的服务发现,负载均衡等各种功能由Service Mesh接管。客户端功能的极大简化,体现在开发的技术选型上,就是:
可以自由的选择客户端类库。
Service Mesh下的REST,才真的有了轻量级的感觉。采用REST开发,可以选择的类库和方式也就可以天马行空任意发挥了。对于RPC,由于不再需要在RPC中捆绑微服务框架的诸多功能,同样也是大为简化。
Service Mesh协议支持
对于HTTP1.1/HTTP2这样的有明确Header,格式通用的协议,Service Mesh都可以很轻松的提供支持。目前市面上主流的Servic Mesh实现都提供了对HTTP1.1的支持,而且基本也都提供HTTP2的支持。
对于TCP协议的支持要稍微麻烦一些,但是只要格式清晰,有类似的Header/Payload结构,Service Mesh也还是有办法以类似的方式提供支持。麻烦只是在于需要为每个具体的TCP协议提供特定的扩展,无法像HTTP1.1/HTTP2可以通用。
无论如何,Payload只透传不处理是基本原则。
基于这个原则,序列化方式对Service Mesh来说就可以完全无关:无论是文本还是二进制,无论消息体结构如何,对Service Mesh而言都只是一段byte数组。
至此,在Service Mesh加持下,服务间网络通讯的解决方案可以选择的余地就大了:
- 可以自由选择HTTP1.1/HTTP2
- 可以自由选择文本格式或者二进制格式,JSON/xml/thrift/protocol buffer随意
- 可以自由选择REST/RPC
- 可以自由选择相关的类库,不再需要各种集成
自由之下的烦恼
自由的好处是我们可以任意选择各种方案,但是自由带来的一个烦恼就是没有一个足够好的方案可以简单借鉴,没有一个足够好的模式可以严格遵循。
在微服务体系的各种功能之外,我们在开发时,还会遇到很多实际工作:
- REST开发时,到底该先定义好API,然后生成代码?还是先写代码,通过注解等方式来定义API?
- RPC开发时,schema该如何定义才更合理?
- 接口的API文档要如何编写?
- 错误码的定义,错误信息的传递
- 集成测试时如何方便的mock依赖的微服务?
能否以最佳实践的方式给出适合service mesh时代的网络通讯方案?
TBD: 需要继续调研/讨论。
后记
有兴趣的朋友,请联系我的微信,加入DreamMesh内部讨论群。
讨论和反馈
稍后补充。