微服务系统的架构演进之路
1
在开始之前,先介绍下本文所讨论的事情。在视频输出方面的平台——我们提供实时的视频。我们负责“NTV-Plus”和“Match TV”频道的视频平台。该平台有 30 万的并发用户,每小时输出 300TB 的内容。这是一个很有意思的任务。那么我们是如何做到的呢?
这背后都有哪些故事?这些故事都是关于项目的开发和成长,关于我们对项目的思考。总而言之,是关于如何提升项目的伸缩能力,承受更大的负载,在不宕机和不丢失关键特性的情况下为客户提供更多的功能。我们总是希望能够满足客户的需求。当然,这也涉及到我们是如何实现这一切,以及这一切是如何开始的。
在最开始,我们有两台运行在 Docker 集群里的服务器,数据库运行在相同机器的容器里。没有专用的存储,基础设施非常简单。
我们就是这样开始的,只有两台运行在 Docker 集群里的服务器。那个时候,数据库也运行在同一个集群里。我们的基础设施里没有什么专用的组件,十分简单。
我们的基础设施最主要的组件就是 Docker 和 TeamCity,我们用它们来交付和构建代码。
在接下来的时期——我称其为我们的发展中期——是我们项目发展的关键时期。我们拥有了 80 台服务器,并在一组特殊的机器上为数据库搭建了一个单独的专用集群。我们开始使用基于 Ceph 的分布式存储,并开始思考服务之间的交互问题,同时要更新我们的监控系统。
2
现在,让我们来看看我们在这一时期都做了哪些事情。Docker 集群里已经有数百台服务器,微服务就运行在它们上面。这个时候,我们开始根据数据总线和逻辑分离原则将我们的系统拆分成服务子系统。当微服务越来越多时,我们决定拆分我们的系统,这样维护起来就容易得多(也更容易理解)。
这张图展示的是我们系统其中的一小部分。这部分系统负责视频剪切。半年前,我在“RIT++”也展示过类似的图片。那个时候只有 17 个绿色的微服务,而现在有 28 个绿色的微服务。这些服务只占我们整个系统的二十分之一,所以可以想象我们系统大致的规模有多大。
深入细节
服务间的通信是一件很有趣的事情。一般来说,我们应该尽可能提升服务间通信效率。我们使用了 protobuf,我们认为它就是我们需要的东西。它看起来是这样的:
3
微服务的前面有一个负载均衡器。请求到达前端,或者直接发送给提供了 JSON API 的服务。Protobuf 被用于内部服务之间的交互。
Protobuf 真是一个好东西。它为消息提供了很好的压缩率。现如今有很多框架,只要使用很小的开销就能实现序列化和反序列化。我们可以将其视为有条件的请求类型。
但如果从微服务角度来看,我们会发现,微服务之间也存在某种私有的协议。如果只有一两个或者五个微服务,我们可以为每个微服务打开一个控制台,通过它们来访问微服务,并获得响应结果。如果出现了问题,我们可以对其进行诊断。不过这在一定程度上让微服务的支持工作变得复杂。
在一定时期内,这倒不是什么问题,因为并没有太多的微服务。另外,Google 发布了 gRPC。在那个时候,gRPC 满足了所有我们想做的事情。于是我们逐渐迁移到 gRPC。于是我们的技术栈里出现了另一个组件。
4
实现的细节也是很有趣的。gRPC 默认是基于 HTTP/2 的。如果你的环境相对稳定,应用程序不怎么发生变更,也不需要在机器间迁移,那么 gRCP 对于你来说就是个不错的东西。另外,gRPC 支持很多客户端和服务器端的编程语言。
现在,我们从微服务角度来看待这个问题。从一方面来看,gRPC 是一个好东西,但从另一方面来看,它也有不足之处。当我们开始对日志进行标准化(这样就可以将它们聚合到一个独立的系统里)时,我们发现,从 gRPC 中抽取日志非常麻烦。
于是,我们决定开发自己的日志系统。它解析消息,并将它们转成我们需要的格式。这样我们才可以获得我们想要的日志。还有一个问题,添加新的微服务会让服务间的依赖变得更加复杂。这是微服务一直存在的问题,这也是除版本问题之外的另一个具有一定复杂性的问题。
于是,我们开始考虑使用 JSON。在很长的一段时间里,我们无法相信,在使用了紧凑的二进制协议之后会转回使用 JSON。有一天,我们看到一篇文章,来自 DailyMotion 的一个家伙在文章里提到了同样的事情:“我们知道该如何使用 JSON,每个人都可以使用 JSON。既然如此,为什么还要自寻烦恼呢?”
5
微服务系统的架构演进之路



