最近的爆款文 《Segment 放弃了微服务》 流传很广,让我想到了 2015 年底,Segment 的一篇关于微服务的文章。似乎目前没有中译文,因此翻译出来,便于对照。
原文: Why Microservices Work For Us
作者: Calvin French-Owen
在 Segment,我们全心拥护微服务理念,但个中原因,可能并不是读者所以为的那样。
微服务和单体的争论已经够多了,我不想再次复盘。微服务的拥趸们说,微服务有更好的伸缩能力,是清晰软件工程师团队责任的的最佳方式。然而单体应用的拥护者则认为,微服务的运维太复杂,难于启动。
但是微服务的主流益处和今天我们要讨论的好处不太相关,我们要讨论的是: 可观察 。
当周二凌晨 3 点钟收到传呼的时候,如果看到特定的工作单元出了问题,而无需在单体应用的每个函数调用中加入追踪,那这个过程就轻松百倍了。
并不是说紧耦合代码中无法实现良好的可观察性——只是极少有案例能够从第一天开始就具备完善的可观察性支持。
可观察性从何而来?稍作思考,就能列出 htop
、 sysdig
、 iftop
、 ps
等运维工具。
但是这些 都不是 用来监控单一程序的执行情况的:热门执行路径、堆栈尺寸等。过去 20 年中,我们打磨出来的工具都是围绕主机、进程或设备进行的。
在分布式系统中,我们可以在我们的监控指标中加入请求和网络吞吐,但是多数工具还是会尝试聚合到主机或者服务的级别。
单体应用中,以进程为中心的监控工具很难感知到一个程序的耗时情况。在单体应用中,我们最好的调试方式,要么是用 Profiler 运行程序,或者实现自己的计时指标。
还有更头痛的事情,正因为我们没有能够在函数级别完成监控,才成全了火焰图,令其成为流行工具。
所以在 Segment,我们不再尝试往单体应用中塞进大量功能,而是在微服务方向花大力气。我们打赌,容器调度和编排会变得更简单也更强大,而多数指标和监控会持续以主机和服务为中心。
这里做个提醒,微服务仅在容易创建新服务的情况下才奏效。否则我们只是把可观察性问题替换成了交付问题了。
在另外一篇文章中,我们讲了一下我们 服务的大体情况 ,以及我们 是如何使用 Terraform 的 。如今我们开始把每个服务拆为模块,所以我们可以在预备和生产环境中复用同样的配置了。
这里有个认证服务的例子,使用 Terraform 来进行资源分配:
/** * Task definition. */ module "task" { source = "../task" # sets up an ECS task name = "auth" port = 5027 image = "segment/auth image_version = "latest" } /** * Service implementation. */ module "service" { source = "../service" # sets up our shared service resources # Module variables task = "${module.task.arn}" name = "${module.task.name}" port = "${module.task.port}" # Input variables iam_role = "${var.iam_role}" zone_id = "${var.zone_id}" elb_subnets = "${var.elb_subnets}" elb_security_group = "${var.elb_security_group}" cluster = "${var.cluster}" desired_count = "${var.desired_count}" environment = "${var.environment}" }
如果感到好奇,可以看看 完整的模块定义的例子 。
这里有着显而易见的好处(监控指标)和很低的成本(少量的 Terraform 脚本),因此我们就不再需要将不同的功能挤到一个现存服务之中了。
目前为止,这套方法很有用。
Segment 有点不同寻常:并非微服务们协调工作,我们有大量称为 microworker
的单元。基本上这是同样的概念,不过 Worker 不为客户请求提供服务,而是从队列中读取数据,进行处理之后,然后给消息做一个 ACK。
Worker 没有依赖,因此它比服务简单很多。没有耦合,因此不用担心一个 Worker 中的问题会影响到系统中的其它成员。
实际工作中,有很多因素迫使我们做出了 microworker 的决定。其中最大的因素是我们的团队规模以及我们要尝试构建的产品的规模。
微服务经常的宣传就是,当团队规模变大时,太多人在编写同一套代码。这种情况下,以团队为单位分配代码库的权限是个自然的举措。但是我们认为,对小团队来说,微服务也一样有好处。
很多和我交流的人都吃惊于我们团队的规模之小,列几个数字来证明一下:
我们的产品规模和团队规模形成了鲜明的对比。所以如果我接到告警,告警可能是我编写的代码导致的,这段代码我半年前开发出来之后就再没碰过。
如此情况下,小型的、定义清晰的服务就很有吸引力了。
下面是一个典型场景:因为队列深度导致的告警。
我们可以在队列的监控中检查一下是否是这个原因。
我们可以清楚的知道是哪个 Worker 出了问题(因为每个 Worker 都会订阅单独的队列),也知道去哪里看日志。每个服务日志都有自己的标签,所以我们无需担忧同一应用中不同请求生成不相关的日志造成的干扰。
我们可以在 Datadog 的独立的 Dashboard 上查看这个 Worker 的 CPU、内存,以及 ELB 报告的延迟。一旦我们识别了问题,只要阅读 50-100 行文件就可以获知问题的确切位置(例如内存泄漏)。
在单体应用中,我们也可以为每个端点加入特定的监控。然而如果每个端点都运行在各自的进程中,我们完全可以自由的进行修改,无需担心影响其它部分。
还没有提到的是,微服务让我们有了隔离 CPU、内存和延时(如果是 ELB 代理的服务)的能力。同样是查找一处内存泄漏,在有上百个端点的单体应用的代码中查找,和在一个 Worker 的百多行代码中进行查找,其难度是不可同日而语的。
我理解,这种方式不会适用于所有场合。为了在创建新服务时能够获得所有支持,需要很大的投入。这种投入会受到团队、工作负载以及产品形态等多种因素的影响,可能不那么理想。
但是对任何产品来说,一旦其运行过程存在复杂的运维和负载,我会选择微服务架构。这种架构让基础设施有弹性、可伸缩,并易于监控,无需牺牲开发人员的生产力。