作者黄挺,蚂蚁金服高级技术专家,蚂蚁金服分布式架构 SOFA 的开源负责人。目前在蚂蚁金服中间件团队负责应用框架与服务化相关的工作。
本文根据黄挺在 2018/09/01 微服务实践沙龙(上海站)分享整理,这篇文章中,一共列举了 SOFA 在发展过程中四种多语言的支持方式。
多语言的现状
世界上的编程语言千千万,每个人都有自己偏好的语言,
有人认为 PHP 是世界上最好的语言。也有人非常喜欢 Java,强类型,泛型,多态,性能也非常不错。也有人很喜欢 Ruby,再比如 Paul Graham 在他著名的「黑客与画家」的书中表达了对 Lisp 的无限喜爱。个人对于语言的喜好是无可厚非的。
相信大家都听说过巴别塔,人类想要造巴别塔通天,上帝知道了,就让不同族群的人类说不同的语言,最后人类之间由于语言沟通不流畅,巴别塔没有造成。从这个故事中我们可以学到要做成一件事情,沟通是多么地重要。
假设一家公司的业务的目标就是通天,我们都知道只使用一门语言是不能解决所有的问题,前端几乎都是 JavaScript 系的语言,后端的语言则更加五花八门,从现实地角度来看,必须选择不同的语言来解决不同的问题,那么这些语言之间怎么做相互通信,就是我们需要去解决的问题。
回到微服务这个领域,如果要解决基本的通信的问题,基本上只要解决三个问题就可以了,首先是基本的网络通信的能力,然后是双方需要协商好数据怎么序列化和反序列化,最后要解决服务发现的问题,要调用对方的服务,总得知道从哪里去寻找对方的服务吧。
但是解决完这些基本的问题之后,因为微服务把整个系统给分布式化了,接下来我们又得面对分布式的问题,这个领域的问题可就多了,包括负载均衡,链路追踪,限流,熔断,链路加密,服务鉴权等等一大堆的问题。那么在微服务这个领域下去解决多语言的问题,我们就必然要在这些问题上做考量,做抉择。
简单的解决方式
首先,我们尝试一个比较简单的方法来解决多语言的问题,假设现在有一个 Java 写的 SOFA 系统,它通过 SOFARPC 里面的默认的长连接基于 TCP 的协议 Bolt 暴露了一个服务,这个时候有一个 NodeJS 的系统需要去调用它,最简单的方式可以怎么做呢?
我们可以尝试在 SOFA 这一端把原来的服务通过一种新的形式给暴露出来,提供一个JSONover HTTP 的形式让 NodeJS 的系统去调用,在 SOFA 里面去提供一个 JSON over HTTP 的服务非常方便,只要对原来的配置稍作修改即可。
为什么选择 JSON over HTTP 的方式呢?因为 JSON 以及 HTTP 在每个语言里面几乎都有原生的支持,即使没有原生的支持,也有三方库来支持,而且相对来说,每个语言支持地都比较好。通过 JSON over HTTP,我们解决了网络通信和序列化的问题。
服务发现的问题怎么来解决呢?也很简单,可以通过一些 F5 的这样的设备,然后 NodeJS 的应用通过 DNS 的方式去发现 F5 的设备,然后通过 F5 这样的设备再将请求转到后面的 SOFA 应用的集群上面,就可以解决了,这边可能需要去做的就是花点钱,买个负载均衡设备而已,当然,如果你用 K8s 的话,那么就可以通过 K8s 的 Service,Service 对于使用方来说,跟 DNS 是一模一样的。
但是这种方式只能解决服务发现以及基本的通信的问题,另外的一些高阶的能力,比如熔断,限流等能力,都无法通过这种方式来解决。所以这种方式比较适合于在一些相对来说边缘的场景下去解决多语言通信问题,如果调用过程中的流量可用,不会有突发的流量,错误也是可以忍受的,那么采用这种方式可能是比较经济实惠的。这个也是蚂蚁金服早期很多多语言的场景的普遍的方式。
“重复造轮子”
随着 NodeJS 在业界关注度越来越高,蚂蚁金服也希望将 NodeJS 应用在更加核心的场景,在当前的蚂蚁的整个体系中,BFF 层更多都是由 NodeJS 的应用来承担。
把 NodeJS 应用在核心场景下,上面提到的第一种方式当然是完全不够的,我们需要的不仅仅是一个简单的通信的方式,而是一个完整的微服务的解决方案。恰好蚂蚁的 NodeJS 团队的技术实力也非常地深厚,所以就想到了可以通过将 Java 体系中的各个微服务的组件做一个 NodeJS 的版本,比如对于通信协议 Bolt,在 NodeJS 这边,也搞了一个 sofa-bolt-node,对于 Hessian 序列化的协议,也搞了一个 NodeJS 的 Hessian 的支持 sofa-hessian-node,对于 SOFARPC,也搞了一个 sofa-rpc-node,其他的能力,比如服务发现,负载均衡,限流/熔断,链路追踪等等,都有对应的 NodeJS 的实现。
这种对应的一个语言重复造一遍轮子的方式比较好的支撑了 NodeJS 在蚂蚁金服作为 BFF 层的发展,并且通过这种方式,各项功能基本上没有太大的损失,基本上 Java 能够实现的能力,NodeJS 也都能够实现。
但是随着时间的推移,我们发现这种造轮子的方式也有一些无法绕过的问题,前面说到 Java 的微服务框架的能力 Node 基本上都能够实现,但是对于一些能力已经深入地使用了 Java 语言特性的一些能力,比如注解,在 NodeJS 中是无法直接实现的,导致 NodeJS 这一端只能用一些非常 Hack 的方式来解决这种类型的问题。另外,就是在多语言的维护成本上,同样的功能,需要 Java 和 NodeJS 维护两套,这种方式不但对于每个语言的团队的要求比较高,而最终也导致同一个功能,往往需要相比于原来两倍的能力来实现,同样的 Bug,可能也需要 Fix 两次。
总结来说,这种方式可以让大部分的功能在不同的语言中都得到比较好的实现,但是一旦一些功能和特定的语言特性绑定,别的语言实现起来就会非常 Hack,并且不容易维护。另外,这种方式需要整个团队付出比较多的成本,成本往往和语言的个数成正比,两个语言可能还好,但是一旦出现更多的语言需要支持,成本可能就难以承受。
多语言网关
相信每个公司多多少少都有一个 API 网关,这个网关往往负责多端的接入,并且也会有多协议的支持,浏览器端可能会采用 HTTPS 来接入,iOS 和 Android 可能会采用私有的协议来对接,API 网关会将接入端的协议最终再转换成内部的协议,并且作为一个 API 网关,往往也会有鉴权,限流等等能力。
API 网关作为微服务体系里面的一部分,其需要解决的问题和整个微服务体系需要去解决的问题非常类似,作为 API 网关,本身就需要去对接多语言的客户端(iOS,Android),可以说非常适合用 API 网关类似的方式来解决多语言问题。
我们可以基于 API 网关改造出一个作用于内部的多语言的网关出来,在多语言网关这一层来实现微服务上的一些限流,熔断,服务鉴权,负载均衡等等能力,这样,这些能力就不用每个语言单独去实现,只需要实现一次就够了。但是对于服务发现这一块,业务系统还是需要用某种服务发现的机制来发现多语言网关,这部分还是需要选择一个通用的方案,比如 DNS + F5,或者每个语言单独实现。
除此之外,多语言网关因为是集中式地部署,还会引入一个新的问题,就是资源隔离的问题,假设多语言网关后端的一个服务突然变慢了,那么可能会将多语言网关自己给拖垮掉,进而影响到多语言网关代理的其他服务。
要解决这个问题,有两种方式可以去解决,一种是做线程池的隔离,可以给一些重要的业务一些单独的线程池,不重要的业务再放到一个大的单独的线程池里面。
线程池隔离的方案里面仅仅做到了线程池的隔离,其他的资源的隔离其实并没有做,比如 CPU 和 Memory 之类的隔离等等,如果想要更加彻底的隔离方式,可以采用和线程池隔离类似的方式,给重要的服务用独立的多语言网关来为其服务,不重要的服务,再给一个大的独立的集群去服务。
Service Mesh
如果说要进一步地去解决多语言网关资源隔离的问题,能否进一步地将集中式的网关分布式化呢,假设每一个应用的实例都能够给它单独地配置一个多语言网关的话,那么就可以彻底解决多语言网关的资源隔离的问题。
实际上,最近在业界非常火的 Service Mesh 就是这样的方法,在 Service Mesh 里面,这个「分布式的多语言网关」叫做 Sidecar。
在 Service Mesh 的体系下,我们可以让微服务里面的一些能力下沉到 Sidecar,不管是什么语言开发的系统接入到整个微服务体系中,都只需要接入这个Sidecar 就可以了,这个 Sidecar 里面可以做服务发现,限流熔断,服务鉴权等等的能力,对于特定语言的业务系统来说,只需要和另一个语言的协议能够对接起来就可以了,其他的一些能力都交给 Sidecar 即可。
但是,对于 Service Mesh 这样的架构,虽然只需要实现一次就可以达到支持多语言的目的,并且没有多语言网关那样的资源的瓶颈,但是相对来说,运维成本却高上不少,毕竟,Sidecar 是需要和业务系统部署在一起。在 VM 的场景下,Sidecar 和业务系统部署在同一个 VM 下,那么对于 Sidecar 的保活,升级,回滚都是需要单独去解决的。如果在 K8s 的体系下,那么 Sidecar 的运维的问题就比较好解决了,K8s 的 Pod 的概念非常适合于 Sidecar 这种方式,Sidecar 和业务可以跑在不同的 Container 里面,可以做到 Sidecar 和业务之间的资源的进一步的隔离。
在 Service Mesh 这个方向上,SOFA 也开源了自己的 ServiceMesh 的方案 SOFAMesh,欢迎大家详细了解。
总结
在这篇文章中,一共列举了 SOFA 在发展过程中四种多语言的支持方式:
1. 简单的方式:直接使用 JSON over HTTP + DNS,可以解决基本的通信的问题,但是缺失了微服务下的其他的高级能力。
2. 造轮子的方式:每个语言单独实现微服务的各种能力,适合需要适配的语言比较少的情况,需要投入比较大的人力,并且个别特性可能无法在多个语言中完全实现。
3. 多语言网关的方式:可以实现一次,把微服务里面的大部分的能力集中到多语言网关里实现,需要用额外的手段去解决资源隔离的问题,多语言系统依旧需要面临如何发现多语言网关的问题。
4. Service Mesh 方式:相当于「分布式多语言网关」,给每一个服务的实例都加上一个 Sidecar,由 Sidecar 来提供微服务需要的能力,业务系统只需要专注于选择通信以及序列化协议即可,这种模式在没有 K8s 的支持下有比较大的运维难度。
这些都是我们实践过程中的一些做法和体会,希望大家可以结合自己的业务阶段来参考。
补充
SOFA 中间件是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,包括微服务研发框架,RPC 框架,服务注册中心,分布式定时任务,限流/熔断框架,动态配置推送,分布式链路追踪,Metrics 监控度量,分布式高可用消息队列,分布式事务框架,分布式数据库代理层等组件,也是在金融场景里锤炼出来的最佳实践。
SOFAMesh 相关内容:
项目地址:http://github.com/alipay/sofa-mesh
蚂蚁金服大规模微服务架构下的Service Mesh探索之路
开源 | Service Mesh 数据平面 SOFAMosn 深层揭秘
公众号:金融级分布式架构(Antfin_SOFA)
网友评论