DreamMesh架构设计(2)-设计原则
在展开详细的架构设计之前,先罗列一下Dream Mesh将在之后遵循的一些基本原则,以及这些原则背后的原因和期望的目标。
不敢立论说这些原则一定是对的,或者是最好的选择,只是从个人的感悟上出发,我更倾向于在这些原则的基础上架构整个Dream Mesh的体系。
原则:要服务不要类库
以注册中心和配置中心为例,通常的做法,是引入诸如zookeeper/consul/etcd等分布式一致性方案,然后编写代码调用其客户端API来操作数据。然后将编写的代码打包为类库,分发给使用者。典型如Java中就是提供一个依赖,然后可以导入一个或者若干个Jar。
这个方式非常的正统,由于是以类库的方式嵌入到应用中,实际运行时应用时直接和zookeeper/consul/etcd等服务器连接和访问。好处是设计上简单直白容易理解,执行效率较高。
但是,也存在一些问题:
-
升级困难
即任何情况下需要升级类库,就必须重新分发,然后所有使用者都要重新打包上线。
-
引入大量依赖包
由于是类库模式,需要引入依赖和递归依赖,导致应用的依赖数量大增,增加版本冲突等的可能性。
-
严重依赖客户端API
类库是建立在客户端API上的,因此客户端API能做什么,有什么限制,有什么不足,都直接反应在类库上。
举例,在2016年我们开发Dolphin微服务框架时,当时选择consol,它的监听机制是基于HTTP long pull的,客户端引入了一大堆依赖,然后还非常不稳定,客户端版本升级也特别频繁。大受折磨,最后换etcd v3才得以解脱。
在Dream Mesh中,所有的功能模块都将以服务的方式提供,包括注册中心自己:
配置中心:
应用在运行时,就不再依赖各种类库了,转而直接调用服务提供的接口:
原则:万物皆服务,服务皆注册
在Dream Mesh体系内,各个功能模块,都抽象为服务,然后在注册中心注册。
在使用时,也是尽量依赖服务发现机制。
好处是,所有模块都在注册中心有注册,则我们通过注册中心,就可以掌控整个体系。至少,有什么东西,部署在哪里就可以有一个统一而完备的记录。
这应该算是一个目前比较主流的做法,只是Dream Mesh中会做的比较彻底,比较绝。
原则:内部通讯走gRPC
Dream Mesh体系中的内部通讯,尽可能的选择使用gRPC作为通讯机制。
主要理由如下:
- gRPC有完备的跨语言/跨平台的支持,也已经坐稳了下一代RPC的位置,日渐主流
- gRPC基于HTTP/2,可以提供远比HTTP/1.1强大的特性,最关键的,监听实现起来舒服多了
- gRPC提供的双向stream,在后面的设计中会大量使用,成为很多模块的主要工作模式
- gRPC单连接的工作模式,不需要连接池,简化了客户端实现。
和用在服务间通讯时,需要权衡REST和gRPC相比,在内部通讯中,有些常见的制约和不足不再明显:
- 开发和使用方式:内部通讯都是直接提供客户端依赖(注:这个客户端已经简化到只是完成访问服务接口),使用者完全不涉及到gRPC的开发和直接使用,对使用者其实是透明的。
- 不是文本模式:对于内部通讯,基本不存在需要抓包看报文类的操作,我们会通过收发请求前后的日志来提供足够的debug信息。
原则:优先提供直接语义的专用接口
所谓"直接语义",指不需要考虑通用性,直接针对某个意图特别明确的操作,提供和这个操作的意图明确匹配的业务语义。
典型如服务发现,正统的接口设计,会提供若干个查询接口,然后组合监听/通知的接口,一起为客户端提供完备而有弹性的选择。
但是对于大多数情况的客户端,其业务语义往往非常的直白而集中:我要做服务B的服务发现,现在给我服务B的实例列表,然后如果有变动请及时通知我。中间我们要保持网络连接,发心跳包,顺便实现简单的健康检查。
在有gRPC的双向stream支持的情况下,我们可以将类似的场景直接封装为一个"直接语义"的接口,一次性的解决客户端的各种需求。
当然,这个"直接语义"的接口是额外提供的,虽然我们推荐尽量使用这个接口,但是普通的接口也是会一并提供。
在后面注册中心和配置中心的设计中,我们将广泛应用这个原则。
讨论和反馈
TBD:等收集后整理更新
后记
有兴趣的朋友,请联系我的微信,加入Dream Mesh内部讨论群。