大家好,我今天的分享主要围绕以下几点,首先跟大家简要介绍一下微博服务化的演进过程,其次是微博自研跨语言RPC 框架 Motan 实现的一些关键技术要点,主要是跨语言方面,再次,结合目前市面上的一些Service Mesh 实现对比,给出基于 Motan-Go 的更符合微博场景的Weibo Mesh 实现。
最后,是我个人对于面向未来泛服务化架构的一些思考。一些同学对Service Mesh比较感兴趣,也想在生产上做一些实践,如果没有历史包袱,新开发一个项目用什么架构,怎么实现都是可以的。由架构去取舍,看我们更迫切需要的是什么,所追求的是性能还是其它高扩展性等等,目前也有一些现成的解决方案。但是如果没有做任何服务化,或者服务化过程中历史包袱比较重,可能很难找到一种可以直接实施的现成方案,因为需要考虑的往往会更多。本次分享更多是关于操作性内容,希望对大家有一定的借鉴意义。
微博平台服务化演进
我们先看微博平台服务化的演进过程。
微博2009年上线,在业务高速发展的初期是传统的模块化单体服务,每个小的业务都有可能随时爆发成长为一个核心业务。我们的目标很简单,就是满足业务快速发展的需求,并且在突发流量增长时保证服务的高可用。随着业务的扩张,单体架构各个模块严重耦合,致使相互的影响越来越大,请求成功率得不到保障,长尾问题严重。为了解决这一系列问题,微博从 2013 年开发了Java 语言的 Motan RPC 框架,并基于此完成了服务化改造。Motan 从2013年上线至今经历过每次热点事件、三节高峰的严峻考验,稳定性和可靠性都得到了实际场景的验证。这些经历之下微博 Motan 也积累了一套服务治理型 RPC 的服务化体系。
除了Motan,2015年开始,为了应对越来越猛的流量洪峰,更合理的对资源进行整合利用,开发了Open DCP 弹性云计算平台。实现了动态的弹性扩缩容,告别了以往花费动辄几千万的资源成本,以及节前几个月就要开始的劳民伤财为三节做准备的日子,也不需要在为了一个个的热点事件提心吊胆。
上图是当时微博平台技术体系的概貌,是一个基于 Open DCP 弹性云计算平台和 Motan RPC 的服务化架构,经过几年的运营和考验,我们已经在混合云的服务化方向有了丰富的经验和积累。但在解决了微博平台内部服务高可用、高性能的问题后,业务方平台服务调用的问题开始显现出来。比如调用链路过长,导致坑多性能差,又比如,每个业务方都需要做高可用和服务治理,而这些事情平台已经有丰富的积累,大家都再做一遍特别重复且浪费。当想把在平台上的技术积累服务到微博更多的业务方时,平台内部基于 Java 的服务化体系已经不能完成任务,所以我们需要一个跨语言的解决方案。从2016年起,我们开始了跨语言服务化改造之路。
跨语言RPC要点
异构系统如何做整体服务化的解决方案,也是今天探讨的一个主题。
服务化经历了前面几个阶段后,需要构建一个跨语言的服务化体系,将原来在 Java 体系积累的经验加以总结,给其它的技术栈业务赋能,更好的维护微博整体的稳定和高可用。
上图我简单列举跨语言 RPC 的几个技术要点,最下面是可靠性和易用性,比如我们的 Motan 有 Cluster 的概念,每次调用都是从这个 Cluster 里面通过负载均衡(LB)策略来找出可用的节点(Endpoint),再通过一种高可用的策略(HA)完成调用。整个过程的各种理念可复用,各种语言照章实现就好。各种策略也是可以按需根据实际情况进行扩展的。而传输、I/O、进程线程模型等各个语言都不一样,需要根据语言自身的特性来选择。
协议和序列化,这是解决跨语言比较重要的一点,因为要让各种语言互相理解对方,互相沟通。
上图的故事大家可能都有所耳闻,人类很狂妄地说,我要建一个塔直通到天堂。但是上帝听到以后觉得很不爽,所以让人类说不同的语言。因为语言不通,导致塔建歪了,整个工程就失败了,这是说交流的重要性。要做跨语言服务化,负责数据通信的RPC 框架跨语言是重中之重的基础设施。而协议和序列化如何取舍,很关键。
Motan的Java版,所要解决的是微博平台内部 Java 服务之间的调用,因此 Motan1 协议时,其实并没有考虑到跨语言的问题,用的是对Java比较友好的 Hessian。后期在跨语言方面,motan1的协议显得对跨语言不是很友好,motan2 的协议就给了一个足够容易理解的协议,是一个简单的TCP描述。比如,要请求一些方法、服务的分组,或Body,请求发过去,对方语言收到后,怎么样让这个语言理解呢?关键一点就在于序列化。
现在 Motan 使用简单序列化的方式(Simple),只支持几种简单的类型。不过我们一直都在迭代中,也正在以前支持更复杂的类型上做扩充。但本质上,它仍是简单的协议,我们会尽量保证它的简单。只有足够的简单,才能在跨语言上降低成本,开发成本还好,主要是语言沟通起来解析的成本,比如,编译型语言往往有各种明确的强类型,而解释性语言往往并不需要强制类型约束,如何让他们很好的沟通,这就需要做取舍。
这里给出一个 MotanRPC 框架简图,微博有一个自研的注册中心叫 Vintage,也支持其它像ZK等一些注册中心。图中给出了 Motan RPC 关键的一些组件和他们之间的调用关系。如果对Motan 感兴趣的,欢迎大家到GitHub 搜索关注,我们有更详细的文档,大家也可以网上搜索。为什么要说这些?因为Weibo Mesh 是基于Motan来做的。希望大家对Motan有个整体的认识。
现有实现跨语言,支持不同语言的Motan,最下面是Java版,就是最开始的一版,现在支持Golang(Motan-go)、Openresty-Lua(Motan-OpenResty)、PHP(Motan-PHP)。
基于 Motan-Go的 Weibo-Mesh
下一个话题:基于motango的Weibo-Mesh。数人云敖小剑老师扛着ServiceMesh大旗已经冲了一段时间了。刚开始我们也看到这一点,其实大家的想法都一样,因为需求和痛点基本是一样的。所以我们严重认同ServiceMesh是下一代微服务,当然不同人都有自己的理解,通过初步的实践,如果一个很复杂的架构,大家在做服务化,服务化到一定程度,会由于微服务的力度和规模慢慢增大,出现依赖复杂度的增加带来流量交错,难以管控以及服务治理更加困难等一序列的问题。为了解决这些就需要一个东西很好的管控这些服务和流量,这就是为什么会有Mesh。
看一下现在业界公认最牛的 Mesh Istio是怎么玩的。这里我希望大家关注两点,一个是 Istio 有一个基于Envoy 的数据传输层,另外是控制面板,Istio 通过这个控制面板来完成流量调度,鉴权,服务治理等工作。这是 Istio 现在的做法,那微博怎么做的呢?
首先,我们从2016年开始做跨语言的服务化改造,那时还没有 Service Mesh ,刚开始很简单,只想做跨语言,想解决跨语言调用链路过长的问题。比如,原来所有的跨语言服务都是通过 Restful 接口提供的,每个请求都需要层层转发,尤其现在是混合云的架构。举个极端又及其常见的例子 , A 机房的服务依赖一个Restful 接口,请求发出后,内部 DNS 解析说这个域名在 B 机房提供服务,你到B 机房后,HA 告诉你,这个请求应该去C 机房的一个Member 上去完成,绕了一大圈,很有可能发现那个 C 机房在公有云上,链路特别长。所以,希望通过实现跨语言的 Motan RPC,Client 对Server 发起直连,通过点对点的通信来解决链路过长的问题。而后来的演进,后面再细说。
今天讲的这个可能比较简单,目的是希望大家能更好的入戏,更好的理解。可以用直连的方式,或用任何其它方式,像我们用了 Motan。
微博从2016年开始做这个事情踩了很多坑 。微博做跨语言,刚才在后面跟老师们交流大家还说 GRPC 把跨语言带跑偏了,当时做跨语言的时候就是想GRPC已经做的很好,就想通过Motan支持GRPC 协议的方式来实现Motan的跨语言调用。大家可以去翻 Motan 的开源,里面是有GRPC支持的,现在也还有(不过最早支持的是PHP,对接的是鸟哥写的那个Yar的RPC框架, 最早 Motan还支持Yar协议。)。
所以当时想法很简单,想依托GRPC来解决跨语言的问题,所以是上面这样的一个结构,用GRPC来搞。
这个结构出来以后,我们开始拿线上的业务来摸索改造。发觉需要把原来的Restful 接口完全重写成GRPC的服务,这样的改造避免不了大范围的代码重写。硬着头皮改了一些后,发觉性能也没有像宣称的那么好。
大家平时可能都会上微博,我们一条微博数据里面可能有百十个字段,所以基于GRPC 改造就需要把这些字段和服务都用PB 进行定义,完了以后发现最终的Proto文件写下来一大坨。虽然说请求过程并不需要传递这些proto 数据,但是请求数据回来以后需要解析组装。跨语言的时候,比如说PHP来解析那个对象,性能就比较差了,所以这种方案应对微博这种场景最终被淘汰了,而且就算勉强接受性能的损失,这里也只是基于GRPC 解决了跨语言的部分。那另外一个很重要的部分 --- 服务治理呢?
因为我们多年服务化演进,Motan整个体系在服务治理方面做了很多工作,有很多积累。但是基于 Motan 支持GRPC 协议来完成跨语言的服务化的服务治理怎么做呢?比如说这个时候PHP 怎么去做服务发现?PHP 每一个过来,都是一次处理整个过程就结束了,没有一个常驻的可用存储每次请求状态的地方来实现服务发现、服务治理这类似的事情。
但是我们也做了很多尝试,比如通过一个本机的后台进程来做服务发现,或者在Nginx 上基于OpenResty 的timer 来实现服务发现,并发结果写到PHP 的$_SERVER 变量中,让PHP 能直接使用(上面右图那样)等,但是实践的效果并不理想。
所以整体通过 GRPC 来做跨语言这个方案做下来,效果不是很理想,这并不适合我们的场景。所以我们就走到WeiboMesh的原形(如下图),我们想既然解释性语言的跨语言服务化由于语言本身的特性和我们的特殊场景(主要是改造成本、性能等方面的考量)下,并不适合直接通过语言本身来实现,那这样的话,还不如去做一个代理。这个代理可能就是WeiboMesh的一个雏形,类似现在ServiceMesh 中的SideCar。
比如说Java启动一个服务,通常情况下,比如 Java Client 访问这个服务是这样的一个流程:Java Client 启动的时候会根据配置去注册中心订阅自己需要访问的服务,请求过程中会有一个线程不断的从注册中心去发现当前可用的节点列表回来,组成Client 端的一个Cluster而完成的调用(如前面Motan 请求处理简图所示的那样),而如果是PHP 需要通过Motan RPC 访问这个服务呢?PHP没法做服务发现,它就把它的请求扔给Go Agent,服务订阅、发现、治理这些事情让Motan Go Agent 来做,其他的都一样。我们做这个的时候,还没有ServiceMesh这个东西呢,所以一路踩了很多坑。
继续往下想,如果反过来 PHP要做server的时候呢?之前说 RPC 跨语言所要解决的一堆技术问题,比较重要的是传输模型,怎么选一个比较靠谱的模型来作为Server 处理请求呢?现在也有很多很好的现成的解决方案,但是我们没有从中选哪一套来做。主要因为微博业务实在是很庞杂,里面有很多 PHP 的业务,如果把原来基于 LNMP 的服务架构改成 PHP 直接做Server的话,涉及到排山倒海的业务重写,PHP 同学可能会要了我们的命,他们是不会接受的(我要是他们,我也不会干)。所以我们就想,怎么能更简单的把这个事情做了,因为已经有了前面 PHP 通过 Motan Go Agent 去请求RPC 服务,我们就继续在上面扩展了一下。
PHP 要做SERVER,我们使用Motan-Go-Agent来帮助PHP做服务的导出,这个就不是简单的请求调通了,因为 Motan-Go-Agent 除了负责请求的连通,还负责Server 端 PHP 服务的注册。Motan-Go-Agent的角色更像是是一个Motan Server。启动的时候他会把需要导出的PHP的服务,比如注册中心完整导出,并保持像注册中心服务状态的上报更新。导出后,假如说一个Java的Client 需要调动这个服务,Java-Client 会订阅这些服务,请求过程中会通过服务发现回来的节点对它发起调用。Motan-Go-Agent 接到请求后,它把这个请求转给后端的PHP 处理,它之间可能是基于CGI或者是HTTP协议完成的,回来这个请求就完成了。
服务化以后,最直接的感受,原来服务调用时依赖一些 lib 库、Jar 包,或者Restful接口,现在变成依赖一些服务。在服务调用时,不再是倒入某个包或者Curl 某个接口,而是直接去注册中心订阅和发现某个服务,这是一个本质的改变。
这个本质的改变提高了服务依赖和复用的效率,但是原来的很多服务都通过这种方式暴露了,也直接提升了服务治理的复杂度,服务治理成本变得相对较高。
但是这一切都是值得的,因为这一来我们统一了服务治理的方式,高可用等各种保障,通用逻辑的封装等实现起来都更轻松自然。
我们基于 Motan-Go-Agent提出对像 PHP 这样不方便实现服务化的语言,通过正向、反向代理的方式来实现跨语言服务化,Java、Golang这样很方便实现服务化的语言,有简单的Motan实现,或者也可以直接通过Motan-Go 导出服务,这样就只需要实现一个简单的Motan-Client 来完成服务调用即可。
我们服务化做到这个阶段,发现性能已经符合预期,但是当想扩大规模的时候,发现节点和服务数量少。服务化规模很小的时候,比如平台提供几个服务,供几个业务方来调用,可能人工确认,事先配好就没问题。但服务化规模大了之后,就没法人工确认来解决,我们的做法是通过扩展注册中心的功能来解决。
在我们一筹莫展的时候,发现Service Mesh这个概念出现了,而且解决的问题和面临的挑战都一样,都是解决跨语言服务化的问题,也都面临流量、服务管控和治理复杂化的挑战。而且,那么多大牛公司在后面做背书,所以我们就马上转到这个方向上来,希望实现一套更符合微博现状的Weibo Mesh。
要更符合微博现状,首先我们在 Motan RPC、服务治理上有很多积累,这是已有的,不想抛弃的,这是宝贵的技术资源。另一个目标,要做跨语言。所以,一体化的解决方案更符合微博的结构。
另外,微博是混合云架构,弹性云计算平台已经有了。如果新起一个项目,没有任何历史包袱,任何方案都可以。但假如有历史包袱,就不一样了。
这就是基于微博现状做的 Weibomesh。这里给大家详细的说一下,一个是Mesh,跟Istio就很像。但是不一样的地方是说,微博体系里除了一些编译型语言,还有一些解释型语言。只有解释型语言需要走用Mesh来导出服务的方式。比如Java 就不需要,因为有比较好的原生积累。Istio宣称不需要业务方改任何一行代码。但是我们这里需要一个简单的薄薄的client层,算是我们对自己业务的一点牺牲,对后面的收益极大。
其它都一样,包括服务注册、发现、调用。另外,决策系统好比 Istio 里的控制面板一样,负责发指令。跟Istio不一样的地方是,Istio 中是通过一些请求的 Header 的数据,通过一些规则来做基于 iptables 的流量转发,而Weibo Mesh不需要转发,因为服务都是通过发现回来的,要调什么服务,就发现什么服务。调用是明确的,出门之前就知道自己要去哪儿,不需要转发。只是有一点,可能发现回来是一堆节点,需要把控的是怎么让流量更均匀,更好的控制流量,所以有一个自动流量调度和弹性扩容。
弹性扩容本身就有,为了应对峰值流量,应对节日。比如原来为了过年,要提前三个月准备好几千万的服务器。
服务治理是Motan体系的积累,服务传输就是通过它来收发请求,流量过来,因为请求都经过Mesh层,所以通过时时调用的信息,来时时调配流量,对整体系统进行保障。
这就解决了刚才说的,如果微服务力度大,服务划分很细,Services 规模大到一定程度,多个服务之间,怎么互联互通。
这是微博自动台调动体系,分为几个关键点。容量评估模型、是自动化压测容量评估系统。弹性调动要解决的问题,一个是调配集群的流量,一个是控制集群的规模。怎么调配?首先要度量,度量系统包括:它什么时候是正常的,健康的,什么时候是冗余的。一个系统的冗余度是怎么度量出来的。
通过上面这个公式来度量集群的冗余度,还评估了集群是否需要扩容,流量是否需要调动。
下面这里有几条水位线的概念,一个是安全线,一个是警戒线,一个是危险线,红色的就是危险线,这个时候就需要扩容了。大家可以理解为水库,一个水库能蓄多少水,什么时候水位上来,扛不住了就会发生洪灾。
通过对整个集群的流量评估,可以知道这个集群是否空闲,是否安全。当集群流量扛不住,是否把这些流量切到其它集群,支撑整个弹性调动和动态容量调动。
Weibo Mesh改造收益
WeiboMesh改造,通用于任何异构系统。有些操作比如解释型语言,因为某些语言特性,不能实现那个功能,但是用Mesh的方式,可以在 Mesh 层实现。比如, PHP 请求一个接口,如果接口响应慢,通常的做法是重试,这样会带来危险,因为原来走的都是集群的转发。但是如果直连,如果一个节点慢了,一直重试可能就试挂了。如果重试三次不可以,就不再往这里调了,把它从可用节点里摘掉,解决宕机的问题。
监控里会经常看到一些长尾的调动,发现系统性很好。但是真正跑起来,因为网络或者机器的因素,可能会导致后面的长尾特别长,特别占资源。如果在 Mesh上,时间长于某一个阈值的时候,就再给发一个,如果后发的请求响应先回来的话,可以把前面的请求抛掉。
体系化是说,不需要在每一套语言里都实现一套。这跟通用的ServiceMesh方案一样,大家都是为了通用和统一。
这是微博搜索,接入这个系统之后生产当中的一些数据,也经过了一些实验的验证。比如,这是薛之谦和前妻复合事件,这个图同事在很多场合都讲过,原来运营发一个 PUSH ,大家都提心吊胆。在新浪如果遇到爆炸性的新闻,运营在发 PUSH 之前,都要通知到所有技术。但是现在不需要这样,比如说那次运营发了一个PUSH,大家可以想象一下山洪爆发,调动系统发现已经过了黄色警戒线了,已经需要扩容了,它就自动扩了,冗余就上来了,这是动态扩容。弹性调动也是如此,用的同一个技术体系。
面向未来架构,重新定义服务化
在开发过程中会遇到很多问题,原来做服务化,所提供的是一些Lib、Jar包或是像接口之类的,但是现在已经不再依赖那些,现在依赖的是服务,Service Mesh 里甚至都没有Client和Server的概念了,都是Service。但是在Motan里面,是有Client和Server的。它有两端,因为要去发调动,两端的处理不一样。调动不是通盘都按Service来讲的,是通过服务发现,然后对Server 发起直连。这个过程导致需要每一个实现跨语言的地方,都需要一些特殊的处理。
首先通用性方面,使用Motan来刻画一个服务,现在不再依赖包,而依赖于服务。提供的时候,告诉这个服务,注册到那个分组下,调动那个分组下面的服务就可以了,一个很简单的RPC服务调用的方式,上面会有协议,节点和版本号类似的数据,用URL来唯一刻画一个服务。
微博有历史包袱,希望已有的系统不要改动太多,以最小的成本得到最大的收益。所以,我们对原来基于 Jersey 开发的 Cedrus Restful 框架进行了适配,在Motan框架层面把原来所有的Restful 接口自动导出为Motan RPC 服务,通过这种方式减少改造的成本。原来改造的时候,需要把所有 Restful 服务都重写成RPC服务,成本特别高。
支持协议,比如支持GRPC,前面有GPRC可以去调,现在支持HTTP,HTTP对等的是Cedrus(其实就是Servlet)。它其实中间传输的是Motan协议,但是可以按照HTTP的方式调动一个Motan服务,这样的话,Client也不需要改任何一行代码。
我们对WeiboMesh 的一些思考,Weibo Mesh 跟通常的Service Mesh 有本质的不同,我们对服务的调用是通过服务发现后直连的,不是通过拦截和转发。通常Service Mesh 不能直接满足我们的需求,可能大家也有很多类似的场景。所以这些思考希望能给大家一些借鉴的意义。
Weibo Mesh的服务调用需要更轻薄的Motan Client,一个原因是降低已有架构的改造成本,一个是泛服务化的概念。比如访问微博,需要调动用户的服务、微博的服务,评论的服务,但是后端的底层资源,比如说MC,Kafka等的资源都是服务,如果把这些资源用 Motan 协议来实现资源服务化,只需要一个Client就能完成所有的服务调用。而且服务治理体系,流量管控体系都可以复用,解决了大家在开发的时候面对各种资源的不同Client实现而各种调研,选型。微博基于OpenResty 实现了 Motan-OpenResty 版本,就是想以此来对OpenResty 社区在资源服务化方面实现复用,提供更多更方便的服务。
基于泛服务化理念,通过轻薄的Client,就可以调所有服务。虽然改造的时候需要改一点代码。泛服务化跟 Istio的思路不一样之处还在于,泛服务化不止针对自己开发的应用级服务,还有一些底层服务,资源的服务。他们都可以通过封装为简单的 Motan2 协议来进行简单的访问,所有功能都是可编程可扩展的。用过Motan的人都知道,Motan在做扩展的时候特别简单,比如说要加一个通用的逻辑,无论是扩展一个Filter 还是新增一种HA策略,这个架构是一个可编程,泛服务化的架构。
Weibo Mesh后期规划
微博是混合云架构,如果是新的公司可能就直接上云了,所以我们后期也考虑在云原生计算方面做一些支持。出于微博本身的技术需要,我们还会在服务治理的整体方案方面做更多的尝试。我们也希望Weibo Mesh 能做成更通用化的服务方案,让更多团队受益。
另外,Motan还有Motan—Openresty 版本。出于我个人对OpenResty的理解,我认为OpenResty 可能是目前唯一称得上服务器应用开发平台,把Nginx 作为一个开发平台,在上面开发各种服务,而且是基于极其简单、表现力极强的Lua开发。最令人欣喜若狂的是,重启维护的Stream-lua 模块让OpenResty不止能开发 HTTP 服务,同样能开发 TCP、UDP 服务。基于此的 Motan-OpenResty 可以复用 OpenResty 积累的一切,我希望能在Openresty方面做一些事情。
因为就Mesh体系来讲,大家可能都是有包袱的。当有既有技术体系的时候,Java应用或者其他应用,前面都挂着Nginx,从Nginx 到OpenResty 的迁移成本几乎为零。在整个流量分发当中,它起到比较关键的作用,如果能在这一层上做一些事情,那是不是把很多改造的工作又减轻掉了,平台自己来解决很多问题,应用级别就不需要搞,因为现在比Istio,感觉需要强化一点在于说,有一个Client,需要大家改一点代码,但是又希望能不能更少的成本,所以我需要做这样的一些事情,这是我自己后期的一个摸索的方向。希望同样对这种思路感兴趣的同学能一起参与,本身整个体系都是开源的,欢迎大家有什么想法或者是有什么问题提出来,一起来做。
因为觉得在这个方面走的也不算特别早,也不晚,现在在业务场景上真正落地的Mesh的,微博应该是比较早的,而那个Motan-Openresty版本可能是基于最新的Openresty Stream模块开发的。那个现在也作为Openresty社区比较标准版的Stream模块开发样板项目,也希望大家多多的关注,我今天的分享就到这,谢谢大家。