定义Go语言云应用开发的“通信协议”

GoCN

共 6400字,需浏览 13分钟

 · 2020-12-07

本人在 Gopher 大会进行了分享,在分享后将核心内容写了下来,供参考。

华为在 2016 年成立 Cloud BU,引入了 kubernetesprometheus  CNCF 项目。这些软件大部分有 Go 语言编写,研发团队自然开始引入 Go 语言构建云服务,然而那时 Go 的生态并不完善,所以要自己从头到尾编写基础能力模块,以供不同云应用引入。

首先从一个简单云应用展开看他的实现过程

一个简单的注册发现服务 Service Center,和 eureka 一样,这里不多赘述他的职责。

动静分离


为了减少数据信息量,抽出公共部分统一进行管理,通过静态信息来划分实例归属。所以微服务与微服务实例为 1 的映射,将微服务名,版本,描述等信息都抽到了公共部分,通过降低冗余度,来减少网络的开销,这样比如“描述“这样的信息就不在网络中传递,同时又能兼顾微服务管理能力,规范化了微服务模型。比如一个微服务必须具备版本信息。而真正下发到微服务缓存中的实例信息就少多了,许多字段是给人类看到,并非机器执行所需要的。

从图中我们看到微服务静态信息里面包含了一个 Schemas,里面关联了微服务所关联的契约文档,同样是 1  n 的映射关系。可以通过手动上传或者代码自动生成文档上传,可以在注册中心中查看微服务文档,且文档与微服务的版本号绑定,一旦上传就不允许更改。那么为何要保证文档先行?


 

错误的方式:客户端开发团队等待后端的服务编写完成后,才能开始进行集成开发。

正确的方式:以文档为基准,客户端与服务端同时开发,客户端通过 mock 以去除对服务端的依赖。

另外,如果文档不及时审视,那么将会出现非常糟糕的情况。比如不一致的命名规范,定义相似的 API,扩展能力差,任何一点都会大大增加研发成本。及早审视并规避是十分重要的。这就是为何注册中心加入文档上传与查询能力。

服务间依赖管理



调用层级过高将引起定位困难,以及性能下降。合理的层级是 3 个服务 a->b->c 的调用就可以完成一次调用。

彼此互相依赖的两个服务在功能升级或者变更时要花费更多是时间来分析影响,比如 ab 互相依赖,一个新功能涉及 2 个都要更改,那怎么一起上线?

此外简单的依赖有助于系统测试和分析。这给架构师一个很好的审视方式,可以及时看到微服务间的依赖关系,对架构进行调整。

缓存机制



 

 Service Center 内部本身是不存持久化数据的,一旦 etcd 出现网络故障的时候,就会导致Service Center 不可用。然而etcd 需要 3 个不同机房才能做到高可用,比如节点 2,2,1 分布在 3 个数据中心,这成本显然是很大的。

我们就要用软手段来规避,获取一定的可靠性。所以Service Center 引入了异步缓存机制,在启动之初Service Center 会与 etcd 建立一个长连接,也就是 watch。每次 watch 的时候为了防止建立 watch 时间窗发生变化,做了一层保护,在 watch 之前做了全量的查询。在运行过程中查询所得到的资源变化会缓存到 Service Center 本地,然后进行异步的循环,这样不仅低成本的提升了可用性,也提升了性能。

Consul的实现其实是计算存储一体,而非分离,我认为业务体量没上去前,这样的架构对运维的成本很低,是个很好的选择,然而他是否能支撑较大的微服务体量,是要画个问号的,所以我执着于计算与存储分离并践行,同时service center支持内置etcd,也就是说也支持计算存储一体在初期帮助你减少管理成本。

总结:我们通过了多种手段来提升微服务研发效率,减少网络开销,并通过异步缓存提升性能和可靠性,这是我们在这个服务上积累下来的能力。使得注册发现这个组件对技术管理者变得更加友好,可以帮助架构师持续改进并管理架构。然而云应用远远不止交付业务功能这么简单。


我们刚才看到的只是水面之上的冰山,而冰山之下还隐藏着大量的基础能力需要编写。先让我们来展开 Service center 的架构看看

云服务 service center

该组件主要负责微服务的注册发现,提供 restful API 

他有四个主要的模块:

  •  服务注册发现:通过注册发现完成服务拓扑的感知

  • 契约发现:每个服务具备一个契约记录,支持多种格式如 Open APIgRPC proto

  • RBAC:基于角色的访问控制,管理员可以管理账号,将账号分发给微服务或者不同人员

  • 服务治理:针对微服务下发治理规则,比如重试,限流,熔断,路由策略等。


交付一个云服务远远不止交付业务功能,而是要去全方面的考虑安全,韧性,隐私,可运维等能力,当然我们将部分的能力可以交给一些中间件来完成,比如网关。然而仍有大量功能需要自己编写,且可以复用在每个微服务中,这就是基础能力库编写的初衷。

  • 配额管理:云资源按照租户进行配额管理,租户所能使用的资源受到严格限制

  • 告警:当微服务发生关键问题时要直接上报告警系统,而非通过云服务设置阈值等告警策略

  • 安全:加解密证书,密码

  • ID 生成:ID 的生成算法,用于生成微服务 ID,实例 ID 

  • 多种中间件:调用过程需要被审计,调用链追踪,生成指标监控等


项目地址https://github.com/apache/servicecomb-service-center


对于这些能力,抽取普通的库函数也是完全不够用的,混合云,公有云,开源项目,所以要做到如下能力:

  • 可插拔:也就是按需在编译期引入(受限于 go 语言能力),例如配额系统的具体实现在社区是不需要的

  •  异构系统:也就是一个功能要有多种具体实现,比如审计,公有云存在一套审计系统需要对接,而社区则是本地日志打印。

  • 不同的算法:解密工具,ID 生成器,面对不同的交付场景或者是安全要求,都要通过不同实现来替换算法。比如 ID 生成可以是 snowflakeUUID。加解密算法使用- AES 或者其他公开算法。你也可以考虑闭源核心算法,然后写个简单算法作为新插件并连同整个项目开源出去,以让社区帮助完善周边能力,而保护自己的商业版本竞争力

Go chassis 开发框架


 我们该如何面对

  • 可插拔:按需在编译期引入

  • 异构服务:一个后台服务可能有多种具体实现。

  • 不同的算法:解密工具,ID 生成器,面对不同的交付场景或者是安全要求,都要通过不同实现来替换算法。

  • 分布系统难以治理:框架可以帮助满足云原生应用,如何实现这样的框架


为了能够面对需求的多样性,并且所有新规划的组件也能受益,快速进行开发,我们需要统一的框架和标准来加速开发,这就是开发框架 Go chassis 诞生的过程。


从图中我们可以看到业务逻辑就是用户自己编写的业务代码,那么框架本身就是协议层,中间层和插件套件 3 部分,管理部分就是云服务,框架开发出来的应用可以无感知的对接上去使用这些云能力。比如:

  • 注册发现插件可以对接 service center  kubenetes

  • 配额管理插件可以对接云服务的配额管理服务

  • 中间件比如指标监控对接到 prometheus


那么我们如何通过这个框架来加速我们的开发呢。

手段 1:将后端服务作为插件使用

常见的后端

  • 配额管理

  • 认证鉴权服务

  • 对象存储服务

云原生的其中一个要素是,把后端服务当作附加资源。如何界定什么被作为后端服务对待,就是那些不由自己组织开发并运维,从应用运行时到基础设施不可见、serverless的API server,对你来说为黑盒子的服务。

当我们调用这些后端服务时,其实他们并不在微服务的治理体系内,考虑到可测试性(比如 mock 测试)以及可替换性(业务能够连续,且随时更换更好的服务,应对变换的需求等),我们需要将他们插件化,以灵活的进行选择替换或者去除。

开发你的插件并安装到框架中就可以通过配置的方式拉起并调用。

手段 2:沉淀需求基线

在我们提供任何一种服务前,我们都需要满足基本的要求,比如:

  • 请求体必须做大小限制

  •  API 必须限流

  • 密码不能明文存储

  • 访问进行认证鉴权

  • 无单点故障

  • 访问审计

  • 运维能力

那么我们首先要将运行时的调用标准化,因为不同部门可能有私有协议诉求,那么服务治理就交给核心框架完成。协议由业务部门决定自主研发或是集成现有协议。当你发现公司内部不同部门都在开发自己的协议做自己的服务治理时,再向将业务统一一个架构,一个工具链上,将非常困难。


 


我们通过 Invocation 模型将不通过协议归一上去,这样就可以在统一的处理链中进行处理。

处理链的设计比较类似 AOP 的思想,也就是在业务处理的前后加入代码逻辑进行特殊处理,比如:审计用户操作,收集请求指标数据。

ResponseCallBack 用于接受后置 handler 返回的结果,所以每一个 handler 处理时都可以按需定义自己的 ResponseCallBack 来获取后面 handler 甚至是业务逻辑代码的执行结果。帮助通用逻辑(即中间件)和业务逻辑彻底解耦。

可以看下现在已经支持的中间件,无论限流,熔断,负载均衡,认证鉴权,审计,我们都用此机制实现:将公司全部的工具链,服务治理手段,安全合规等都落入到处理链中,可快速加快研发速度,并统一规范,减少管理负担。


框架内部提供了命令式调用能力,比如指标收集就是这样的典型能力也提供了声明式使用方式,比如流量管理,基于流量特征的限流能力声明式使用也支持金丝雀发布

插件能力全景图:


 

可以看到目前已经支持了不少的生态,并对多种后端系统提供了抽象接口帮助应用快速开发。

那么通过这样的框架,我们可以让业务团队专注于业务代码开发,而无需理解后端的复杂性,和其他非功能需求。收益如下

  • 对于庞大的系统可以进行 mock 测试,提升交付质量

  • 应对不同的交付场景

  • 保证后端可替换性

  • 研发职责界面分离

从架构或者业务演进的角度来思考,所采用的后端所使用的技术是在快速演进的,我们需要通过后端服务的快速替换来确保系统和产品可以及时演进,所以重点要考虑接口的设计。可替换性的重要性大于可重用性。这也满足程序设计原则的依赖倒置当我们再开发一个新的微服务时,仅仅需要实现他的业务逻辑即可


手段 3:配置治理


 


可直接参考https://github.com/go-chassis/go-archaius,后面我会专门出文章来讲这块,Shopee在这里已经实践的非常好了。

手段 4:易处理

意思是说它们可以瞬间开启或停止。这里我们不会谈到快速的开始,因为 go 语言+docker 运行时与容器平台就能处理这样的一个场景,我们谈谈面向意外的处理。


 

这个 protocol server 通常代表一个协议,也可以是某种编程模型,比如 http,编程模型如beegogin这有个框架的配置样例,意思是在一个微服务进程中拉起了 2 http 端口和 grpc 端口服务

在收到系统信号后,就会遍历的停止每个 server

另外由社区开发者贡献的自定义优雅停机功能,可以允许用户劫持信号和停机处理过程,也可以在前后自定义处理过程。

手段 5:轻量级内核


 基本上依赖就只有必要的 prometheusopentracingjwtk8s clientgo-restful 相关的库。注册发现也是可插拔的。

多种能力都在另一个仓库中提供,是一系列的扩展插件。比如 grpc 协议,kubernetes 注册中心等,都可以按需引入。名字参照的chrome-extension。

我们能够打造如此轻量的内核得益于三板斧,这也是go chassis几个核心概念:

  • 中间件:handler chain处理请求

  • bootstrap:任意替换默认实现和执行逻辑

  • 插件化:后端和实现任意替换。


拥有自己重新制造的轮子


这就是 go chassis 开发框架的名字和 logo 想要传达的理念,chassis 是底盘。logo 是一个轮胎的形状

我们经常听到有人在重复造轮子,还有人在抨击造轮子,我相信这是由于现有的方案开放性设计依然存在不足,每个团队都在项目发展期植入了太多有自己项目或者业务影子的东西,导致大家不得不重复制造适合自己的轮子。

我们应该描述并实现一个更加抽象并定义范式,而在这个底盘之上进行能力构建。你将这个最原始的轮子引入并进行增强,打造成更加适合自己的轮子,你是要越野还是雪地轮,都可以自己制作和调校。我们将自己研发团队积累的能力抽象成多种接口及插件,为的就是不要再重复制造轮子,而是基于现有轮子重新打造,让项目产品跑的更快,并且所有延展出的功能,原生就是兼容的。这就是对一个 go 研发团队来说最大的效能提升。目前这个轮子我更愿意称他为云服务的开发轮子,非常适合我们做云服务的团队使用。从后面的例子你就可以看出来。

案例

华为荣耀手机和智慧屏等终端的视频通话后台

已经上线华为公有云,支撑终端公司畅联通话上亿注册用户,基于 go-chassis  service center 进行服务治理。

边缘计算 kubeedge

§ 基于 go chassis 开发服务治理底座

§  管理着全国 29 个省、自治区的将近 10 万边缘节点,超过 50 万边缘应用的部署。支撑了 1 万多个收费站中门架信息采集业务的不断调整、更新,满足了每日 3 亿条以上的信息采集。

§ 为日后车路协同、自动驾驶等创新业务的发展提供了良好的平台支撑 

https://github.com/kubeedge/kubeedge


Shoppe

本次Gopher大会还邀请了Shopee来讲述go chassis在他们供应链平台上的使用。可以参考他们的分享


总结

1.   定义你的应用开发通信协议一家公司,非常重要的 2 个东西企业文化与行为规范,这是每个公司的领导者必须优先定义的 2 个事情,这就是一种 “通信协议,保证公司良好的运作下去。这样领导者就无需事必躬亲。显然定义一套通 “信协议” 是非常重要的。Go chassis 就是我们 Go 研发团队的通信协议。每个微服务都是个小团队开发的,有可能是同一个团队,也可能是不同团队,我们所做的框架是为了定义这套通信协议,以此来减轻研发的成本,同时兼顾扩展性,不要对开发有过渡的限制。我们规范化了 api first 来审视 api 设计,依赖管理来审视合理的服务关系,并规定所有的能力要沉淀为插件与中间件等等都是为了定义研发团队开发与治理云服务的 “通信协议

2.    Go 在新基建中的作用我们都知道,5g 的时代允许更多的设备接入,我们看互联网演进第一代是pc 机互联时代,第二代是手机,第三代便是万物互联,而较小的设备势必会催生新的半导体,新的操作系统(比如说华为的鸿蒙),这样一层层下去,势必会需要一种新的语言及对应的框架,go 语言本身很契合这样的一个位置,这里我不会展开去讲,因为大家也知道 go 语言本身适合的场景。而分布式的设备也需要一种框架来进行治理,go chassis 将在这里扮演一个比较重要的角色。他很可能成为基础设施领域的一个开发底座,从 kubeedge,视频云等项目使用 go chassis,可以看出端倪。


欢迎大家参与社区,开源项目地址: https://github.com/go-chassis/go-chassis

 看到这里欢迎大家留言讲述还需要我展开哪部分深入分析下。如果觉得我的内容有帮助,请为我们的项目service center与go chassis点上一个star。


浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报