Uber微服务经验谈

2020-05-21 00:00:00 微服 代码 团队 服务 系统

Matt是Uber的主系统架构师,他经历了Uber流量10倍的快速增长,在此过程中,Uber技术团队犯了很多错误,他在GOTO大会2016的分享主题是WIWIK-What I wish I Had Known(我当初要是知道这些就好了),总结了他在此过程中学到的经验和教训。

Uber有2000名工程师,8000个代码仓库,部署了1000多个微服务。这看起来非常混乱,但是为了在全球的市场竞争中确立领先的位置,Uber必须快速成长,拥抱混乱无序的状态。而微服务的架构用于应对Uber快速扩张是完美的解决方案,这并不是完全出于技术的原因,更多是技术团队快速增长、功能快速上线带来的压力。当公司快速增加大量工程师,以很高的频率上线新功能的时候,要让开发人员快速进入角色,微服务架构大概是可行的方式。微服务归根结底是用API通信代替人的交流,人类的交流中会出现信息不对称,会涉及团队政治,与此相比写代码要简单高效得多。

在考虑扩展性的时候,往往并不存在一个正确的方案,因此这篇分享提到的很多设计决策都是权衡的结果。

Uber的统计数据(2016年4月)

  • 70个国家
  • 400多个城市
  • 6000多名员工,其中2000多名是工程师(一年前只有200人)
  • 线上共有1000多个服务
  • 代码分别维护在8000余个不同的仓库中

微服务

把庞大的单个服务分解成多个微服务有很多好处,但是也有弊端。当微服务发生改动的时候,整个系统都很脆弱,Uber在周末的时候非常稳定,因为那时工程师没有更改代码。

微服务的好处在于,允许我们快速组建小团队独立运作。在开发人员快速增加的时候,我们需要团队能够快速成型并开始步入研发正轨,所以有一个清晰的问题边界很重要。微服务提供了一个小团队可以理解的边界。这个团队不但开发服务,而且要负责这个服务的正常运行,在这个服务出现问题的时候,他们需要及时响应。

微服务的代价:分布式系统比单个程序更难处理的问题在于,大部分调用都是RPC,所以需要考虑各种失败模式,以及在发生问题时怎样定位、处理。还有一些隐性的代价,比如,每个小团队都有自己喜好的语言和技术选型,在微服务的架构下,每个小团队都可以自由地纵容自己的喜好,而这对于整个项目来说并不一定是优的选择。Uber的子项目中使用了各种不同语言:

  • 分发服务一开始用NodeJS开发,后来迁移到Go语言
  • 核心服务使用Python后来迁移到Go语言
  • 地图服务使用Python和Java
  • 数据团队使用Python和Java
  • 内部统计系统使用Go

不同语言的服务之间可以正常通信,但是也存在很多问题:

  1. 无法共享代码
  2. 开发人员无法轻易转换团队,有一定的学习成本
  3. 支持多种语言可能造成工程师分裂的文化阵营

RPC

使用HTTP处理服务间通信并不合适,对于状态码、头部、查询字符串、方法类型等等的解读的不同,会给各个团队带来很大的沟通成本;JSON在可读性方面很棒,但是类型定义(schema)的缺乏会使数据在各个服务间传递的过程中带来混乱。使用带有类型定义的RPC可以避免这些问题。

代码库

多少个代码库是优的?

一个统一代码库有其优点。在做出改动的时候,很容易同时同时处理所有需要改变的代码,浏览代码也更容易。缺点在于,构建系统需要非常复杂的配置和特殊的工具,Google用一个虚拟文件系统来实现统一的repo。

多个代码库是业界的趋势-更多更小的模块,意味着易于开源、易于替换。缺点在于不易浏览代码,而且很难正确做出影响全局的改动(cross cutting change)。

Uber当前有8000个代码库,一个月之前还只有7000多个。

性能问题

因为微服务相互依赖的特性,性能问题迟早会出现。由于不同语言有不同的性能工具,使用统一的性能度量格式就非常必要(比如火焰图、统一格式的dashboard)。

Fanout问题与追踪

微服务之间的调用的Fanout会给性能代码来灾难。比如一个普通服务99%情况下的响应时间是1ms,1%情况下是1秒。但是如果有很多fanout,响应时间慢的可能性就大大提高,如果有100个调用,就会有63%的响应时间超过1秒(1.0 - 0.99100 = 63.4%)。应对这个问题的方案是分布式追踪系统。Uber遵循Opentracing协议,开发了Jaeger系统。

当然,传统的log也可以定位性能问题,但是每个log条目都应该有一个跨服务的公用ID,才能把不同服务的log串起来。

追踪本身是有很大代价的。我们只能追踪一部分请求,Uber在统计上追踪1%的请求。追踪系统必须在服务间传递上下文信息,即使服务本身不理解这个信息的含义。

日志

提供一种公用的日志工具,保证它易理解易使用,以致于开发人员不会自找麻烦用其他的方法记录日志,这样就可以保持日志格式的统一性。

记录太多日志会给故障中的系统带来更多压力,所以必须在日志系统中添加反向压力,当log过多时,丢弃一些条目。甚至有必要引入向开发人员log“收费”的机制,以免开发人员打印过多log。

Uber开源了一个log工具zap来实现结构化的log:

测试

压力测试系统有时候无法模拟真实的在线流量,也无法模拟各个不同系统所产生的测试数据。此时只能在非高峰时段在生产环境中完成测试。这样做可能会打乱度量系统,因此度量系统必须能通过上下文判断是否测试流量。

失败测试:必须引入失败测试(chaos monkey),以保证生产环境正常工作。各个服务必须经得起停止、变慢和各种操作的干扰。

自研还是购买

任何基础设施类型的服务,任何看起来属于平台而不是产品的部分,早晚都会变成无差别的商业化产品,Amazon或者其他公司会提供这个服务,他们的成本更低、做得更好。是自研还是采用现成的方案(第三方/开源项目)是一个问题,因为可能伤害开发人员的感情:)

参考

youtube.com/watch?

highscalability.com/blo

eng.uber.com/distribute

github.com/uber-go/zap

相关文章