技术支撑着业务高歌猛进,业务增长反过来又驱动着技术不断向前演化,这是每个互联网公司发展过程中不变的旋律。作为全国最大社交媒体网站的微博更是如此。
从 2009 年上线至今,微博架构经历了从最初的单体应用到后面的 RPC 服务化、容器化、混合云架构以及现在的跨语言服务化和 Service Mesh 等诸多阶段,架构演变支撑着微博业务的一次次华丽转身,也见证了微博的飞速成长。
那么,微博架构是如何从一开始的单体应用一步步成长为今天的庞大规模?作为国内最早落地 Service Mesh 的公司,微博为什么要选择做 Service Mesh,具体又是如何做的?这是本系列文章将试图回答的问题。
在第一篇文章中,我会结合微博架构演进的历程向你展示当前微博架构的整体概貌。从第二篇文章开始,我将聚焦于微博在 Service Mesh 方面的具体落地实践,为你详细讲解微博自研的服务网格 WeiboMesh 从 0 到 1 的成长历程。当然,其中也会有我自己对架构演进的一些思考。
下面,我们进入正题。
在业务发展的每个阶段,面临的问题都不尽相同,而问题又有各种优先级,这就难免为了解决某些较为迫切的问题而引入一些当时不 Care 的问题,这也是日常架构演化过程中难以避免的魔咒。
鱼和熊掌不可兼得,所以架构演化的真谛就在于各种方案评估中的利弊权衡,在以业务为重的前提下进行正向演化。
那么,微博各个发展时期的架构又是如何演化的呢?
业务初期:单体架构
微博发展初期,用户规模高速增长,伴随而来的还有不断涌现的新业务,因为你不知道哪个今天还名不见经传的业务明天就会摇身一变成为备受瞩目的核心业务,大家就像在白纸上疯狂试错。这个时候,快速开发上线才是当务之急。
为了达成这个目标,我们对整个系统做了优良的模块化设计,每个业务作为一个独立的模块,保障业务能独立开发、发布并快速上线。
同时我们自研了容器框架 Cedrus,并在接入层实现了一些通用的逻辑,比如提供统一的认证、频次控制、黑白名单、降级开关、配置服务等功能。平台服务内部则通过 Jar 包的方式依赖调用,对外暴露 API 接口。
在部署方面,我们采用大服务池整体部署的方案,这样一来我们可以更合理地利用资源,避免为每个项目每个模块单独配置资源。
这种单体架构的好处在于资源利用更合理,通过 Jar 包应用来完成的本地服务调用不仅更直接,性能也更高,模块之间开发也相互独立,很多通用的前置逻辑被统一剥离出来后,业务的同学只需要关注自己的逻辑实现就可以了。当然,单体架构的缺点也很突出,最主要的就是耦合。项目之间强耦合带来了一系列问题,比如升级困难,回归测试非常难做,以及随着业务模块的增多,模块之间的依赖解决困难等问题,此外还有各种 Jar 包冲突,越往后功能越加臃肿,业务变更十分吃力。这时候,就必须考虑做拆分了。
业务稳定期:服务化改造
要对当时规模已经十分庞大的微博平台做拆分是一件极具挑战的事情,好在当时微博业务的发展已进入稳定期。这就是我之前所说的,在每个发展阶段我们所面临的问题都不尽相同,如果说前期大规模的单体架构是为了解决当时业务的温饱问题,那么以系统拆分为出发点的服务化改造就是要做到不但要温饱,还要吃得好。
我们希望通过架构改造来达到保证服务高可用的同时实现业务解耦的目的,以便更好地支撑业务发展。如何做到这一点?
我们主要从业务模块拆分方面来考虑,基于我们之前模块化的单体应用架构,按业务模块拆分是最自然也最容易想到的方案。我们只需要把以往基于 Jar 包依赖的大一统平台按照业务模块做拆分然后独立部署,即可达到业务解耦的目的。
RPC 服务化
但是拆分之后服务之间依赖的问题如何解决?我们当时面临两种选择,一种是提供 HTTP 的 RESTful 接口,一种是使用 RPC 提供远端过程调用。
RESTful 接口的好处在于 HTTP 是明文协议,开发调试比较方便,RESTful 接口描述也足够简单。但缺点也很突出,HTTP 协议本身比较臃肿,我们内部服务的依赖主要解决数据可靠性传输的问题,并不需要那么多无用的请求头。
相比之下, RPC 具有可编程特性,可以根据微博的业务特性定制化开发。同时,因为使用私有协议,所以能大大减小每个请求的体量,这对内部服务动辄过亿的依赖调用来说,能节省不少专线带宽,同时能收获更高的访问性能。所以我们决定采用 RPC 的方式来解耦服务间依赖。
另外,在技术选型方面,因为 RPC 框架会是我们今后服务依赖的核心组件,所以我们特别慎重。选择使用现成的开源方案还是走自研之路?这是当时摆在我们面前的一大现实问题。
我们最终决定自研 RPC 框架,原因在于,如果使用开源方案,很难找到一款完全适合微博场景的 RPC 框架,就算找到一个差不多能满足的,但要在线上生产使用,不摸个一清二楚我们也不敢上,这个熟悉的过程成本同样不低。
而且,开源软件的发展一般遵从于社区意志,不以微博的需求为转移。如果到时候出现不得不基于微博场景的分叉,离社区越来越远,还不如一开始就走自研的道路。所以 2013 年起我们开始了 RPC 服务化改造之路。
我们自研了微博自己的 RPC 框架 Motan,结合注册中心,实现了业务的解耦和服务的高效治理。之后, Motan 经历了多次热点事件和三节高峰的严峻考验,稳定性和可靠性都得到了实际场景的验证。
容器化、混合云架构
然而好景不长,大量按业务拆分的服务独立部署使得服务的扩缩容操作缓慢,直接拉低了峰值流量的应对能力。正好这个时候, Docker 提出的一整套围绕容器部署和管理相关的生态系统逐渐完善,于是微博率先在重点业务上尝试了容器化。与此同时,虚拟化、云计算领域也在飞速发展。为了低成本高效率地应对各种极端峰值,我们在容器化的基础上探索了公有云和私有云混合部署的模式,研发了微博 DCP 混合云平台,实现了资源的动态扩缩容,结合 Motan RPC 的服务治理实现了对流量的弹性调度。
至此,微博平台在 Java 技术栈形成了配套完善的一整套服务体系,有完善的服务治理相关组件、明确的 SLA 指标、完备的 Trace、监控等体系保障微博平台的高性能高可用运转。
跨语言服务化之路
但微博整体技术栈比较多样化,异构系统一般通过 RESTful 接口进行交互。由于每个团队的服务部署都不尽相同,依赖的服务访问往往要经过层层转发,此过程中繁重的网络 I/O 拖长了请求耗时,影响了系统性能同时也使得问题排查变得更复杂。另外,每种语言都有一套自己的系统或者指标,这也带来了许多不必要的重复资源浪费。
如何解决跨语言交互,平衡各种语言间服务治理能力与标准各异的问题?如何对日常问题快速排查,使上下游业务更容易观测和联动?我们认为必须要有一套跨语言的服务治理方案来解决异构语言交互以及统一服务治理标准等问题。所以从 2016 年开始,我们开始探索跨语言服务化的道路。
Java 和 PHP 是微博内部使用最多的两种语言,所以我们起初的跨语言是立足于微博平台的 Java 体系,探索 Java 与 PHP 之间的跨语言调用。我们最初在 Motan RPC 实现了 PHP RPC 框架 Yar 的协议,实现了服务调通,但是这只完成了 Java 和 PHP 之间的跨语言调用,而其他语言并没有 Yar 协议。于是我们又调研了其他支持跨语言的 RPC 框架,发现 gRPC 可能与我们的需求更接近,于是我们希望通过在 Motan 中添加对 gRPC 协议的支持来达到跨语言的目的。
还是以 Java PHP 跨语言为起点,除了跨语言服务调通外,更为重要的是实现服务化的核心——服务治理功能。这时我们发现用 PHP 实现服务发现不太方便,因为通常 PHP 是以 PHP-FPM 的形式运行在前端服务器,每个 FPM 进程相互独立,并没有一个统一常驻内存的地方来存取服务发现回来的结果以及每次服务请求的状态等基本信息。
我们还尝试了本地守护进程和 OpenResty 的 Timer 来实现服务发现,但也只能实现最基础的节点发现功能。而对于复杂的服务治理功能,比如需要基于每次请求完成情况而实现的请求双发或者快速失败等常用服务治理策略就比较吃力。
另外实现了基础服务发现功能的 PHP 通过 gRPC 调用的性能也并没有 gRPC 宣称的那么强悍。有时改造后的效果跟之前 RESTful 接口的访问性能差不多。因为在微博场景下,比如取一个 Feed 列表,里面每条微博的 proto 文件就有百十个字段,每次会请求回来大量数据, 而 PHP 在 PB 反序列化方面耗时非常大,这就直接抵消了 RPC 直连带来的性能优化。
从跨语言服务化到 Service Mesh
抛开大 PB 反序列化带来的性能损失,类似 PHP 这种原生没有常驻内存控制能力的语言,实现服务治理都会面临同样的问题,就算能很自然地实现服务治理功能,难道需要每种语言都实现一套重复的服务治理功能吗?显然不是这样的。所以我们就希望引入一个 Agent,来统一解决服务治理的问题,Client 只需要实现 Motan 协议解析,直接通过本机 Agent 调用远端服务即可。这便是 Weibo Mesh 的雏形,也就是目前被大家所熟知的 SideCar 模式代理的 Service Mesh 实现。
那么,我们是如何从跨语言服务化走到 Service Mesh 这条路的呢?要解答这个问题,只要弄清楚 Service Mesh 是什么,搞清楚 Service Mesh 解决问题的边界,答案就一目了然了。
Service Mesh 是什么?这个词最早是由开发 Linkerd 的 Buoyant 公司提出,Linkerd 的 CEO William 最早给出定义:服务网格(Service Mesh)是一个基础设施层,功能在于处理服务间通信,职责是负责实现请求的可靠传递。在实践中,服务网格通常实现为轻量级网络代理,与应用程序部署在一起,但是对应用程序透明。
我们在解决跨语言服务化中服务间调用和统一服务治理所引入的 Agent 就是这个 Mesh 层。虽然 Service Mesh 是个新词,但它描述的问题却是一个固有问题,微服务发展到一定阶段,当服务间的调用、依赖、服务治理复杂到一定程度后,都会面临这个问题。所以 Service Mesh 是服务化必经之路,这就是为什么我们的跨语言服务化最终会落脚到 WeiboMesh。
我们基于 Motan-go 实现了客户端服务端的双向代理,基于微博注册中心 Vintage 实现了对 Agent 的动态指令控制,完成了传输与控制的重新定义,并结合 OpenDCP 平台实现了动态流量调度和弹性扩缩容,保障了服务的高可用。目前已经有很多核心业务完成了基于 WeiboMesh 的升级改造,比如大家经常使用的微博热搜、热门微博等。我们从 2016 年开始起步,一路摸索,走到现在与 Service Mesh 理念完美契合,完成了 WeiboMesh 的主体建设。
接下来的几篇文章我会就 WeiboMesh 的具体实现过程与探索中的经验做一些总结和探讨。
进一步学习 Service Mesh
针对 Service Mesh 的特点,落地过程中如何根据现有架构做出合理的取舍?
这个过程中有哪些容易掉入的陷阱?如何避免?
经过最近一年的发展,Service Mesh 形成了哪些事实规范?
经历过整个过程之后,对于架构我有了哪些更深入的思考?