@liuhui0803
2016-10-15T09:32:12.000000Z
字数 9008
阅读 2310
Uber
文化
架构
微服务
摘要:
目前优步已落地全球70个国家和地区的400多个城市,员工总数超过6000人,其中2000人是工程师。这些工程师开发了超过1000个微服务,而这些服务存储在超过8000个Git代码库中。如此短的时间里经历十倍以上的疯狂增长,有幸体验过这种经历的公司并不多。面对这种独一无二的扁平式快节奏增长,这种孤注一掷的体验无疑可以带给我们一些全新的,比以往任何时候更为深入的经验。
正文:
通过这则视频的前几秒内容可以很直观地感受到优步业务的飞速增长。这段视频来自优步首席系统架构师,兼Voxer共同创始人Matt Ranney所做的一场名为在将优步扩张至包含1000个服务之前,我希望自己“早知道”的那些事的精彩演讲(幻灯片)。
中国的几个城市道路交通正在不断经历快节奏的波动式增长,其实全球各大城市都在经历这样的增长模式。目前优步已落地全球70个国家和地区的400多个城市,员工总数超过6000人,其中2000人是工程师。仅仅在一年半之前,他们的工程师总数刚满200人。这些工程师开发了超过1000个微服务,而这些服务存储在超过8000个Git代码库中。
如此短的时间里经历十倍以上的疯狂增长,有幸体验过这种经历的公司并不多。面对这种独一无二的扁平式快节奏增长,这种孤注一掷的体验无疑可以带给我们一些全新的,比以往任何时候更为深入的经验。
Matt对这一切已经很熟悉了。作为Voxer的共同创始人,他以前也经历过飞速增长,但这一次与以往有所不同。看过视频就会知道,Matt试图向以往取得的成果让步。
Matt一直以来都是个爱思考的人,在最近的一次采访中他提到:
在QCon和其他类似活动中发言的很多架构师让我感觉自己很不称职,很多人- 例如谷歌的员工 – 可以解决各种对我来说束手无策的问题。
这次讲话似乎将Matt从漩涡中拉远了一点,试图让他的经验更有意义,借此克服各种问题。好在他成功了。
在这段蕴含智慧又包含忏悔的讲话中,Matt说:“很多错误其实早就显现苗头了,”而他学到的教训也源自这里。
这次讲话主要围绕WIWIK(What I Wish I Had Known,希望自己早知道的那些事),而这似乎已经成为近来的网络流行语。他针对一年半之前那个更年轻,也更天真的自己提出了很多宝贵的建议,当然和我们所有人一样,当时的他肯定是听不进去的。
这是我们所有人的通病。近来很多人在批评优步(HackerNews、Reddit)。毕竟从数字来看,他们的发展实在是太疯狂了。两千位工程师?八千个代码库?一千个服务?他们的做法肯定在某处出现了大问题,对吧?
也许吧。出人意料的是,对于所有这些事Matt并未采取批评的态度。他所采取的探究方法更多地偏重于质疑和研究,不仅仅是独断专行。他本人对海量代码库似乎也感觉很困惑,但他会对更多或更少代码库的利弊进行权衡,而不光是直接断言哪种做法更好,毕竟考虑到优步的具体情况,又该怎么定义“更好”呢?
优步涉足的是一场全球范围内的倾斜战争,他们需要构建一套全球化规模的系统,借此赢得“赢家通吃”的市场。他们的业务模式就是如此,他们的目标是成为业内最后一家屹立不倒的服务商。这种情况下“更好”意味着什么?
赢家通吃意味着首先需要非常快速地增长。虽然也可以用更有序的方式慢慢增长,但速度太慢肯定会输。因此只能站在风口浪尖上进行权衡并试水,或者可能需要一头扎进混乱的局面中,只有这样才能继续扩张并让业务占领全球。这本就是一条快车道,需要采取孤注一掷全身心投入的战略。还有谁觉得自己能做的更好?真的吗?
考虑到优步的目标,微服务是一种非常适合的方法。无论是否接受,这就是一种康威定律(Conway's Law,译注——康威定律:人力组织的架构往往决定了设计的层次,详见:http://www.infoq.com/cn/articles/every-architect-should-study-conway-law),开发那么多服务是因为,只有这样才能雇佣那么多人并让大家都有活干。
服务多,不是技术原因导致的;代码库多,也不是技术原因造成的。这都是人的问题。Mranney的总结很犀利:
流量的扩张不是问题所在。团队和产品功能发布速度的扩张才是主要原因。
这类探讨通常有一个亘古不变的主题:这个或者那个很棒,可是需要权衡,但令人惊异的是,这样的权衡通常只能在达到一定规模之后才会体验到。我从他们的讨论中产生的两个最大的想法也正是基于此:
作为比文字更有力的交流方式,只有在看过视频之后才能更好地理解这些观点。不过我对这些视频的总结依然还是值得一看的 :-)
优步已经落地全球400多个城市。
涉足70个国家和地区。
员工总数超过6000。
工程师总数2000人(一年半之前刚刚达到200人)。
1000个服务(大概值)
Git中创建了超过8000个代码库。
将整体式的系统拆分为多个小组件是种不错的做法。甚至“整体式”这名字听着就很糟,但微服务也有不好的一面。
对系统进行改动的时候最容易出错。优步的服务通常在周末最稳定,因为工程师都在休息,尽管周末也是业务最忙的时段,但服务始终很稳定。
每次进行任何更改都需要承担一定的风险。那么微服务一旦上线就再也不要修改是否是一种更好的做法?也许吧。
好处
有必要思考一下我们为什么要在微服务方面做出如此多的投入。这并不是无心之举,而是能获得一些切实的好处。
微服务使得我们可以快速组建相互独立运行的团队。随时有新人加入,我们必须能快速组建团队并投入工作,借此明确自己的边界之所在。
自己的持续运行时间自己保障,自己写的代码自己运行。所有服务团队都需要为自己在生产环境中运行的服务随时待命。
在工作中使用最棒的工具。但具体要哪方面最棒?写的最棒?运行效果最棒?因为自己熟悉而显得最棒?因为是库文件所以最棒?深入探究一下,“最棒”并没有太大意义。
显性成本
运行大规模的微服务部署,成本如何?
运行了分布式系统后,相比整体式系统哪些方面的工作变得更困难了?
一切都是RPC,需要应对所有疯狂的失效模式。
故障了怎么办?如何排错?如何确定故障到底是服务链上的哪一环导致的?如何联系到恰当的负责人?如何确保通过恰当的措施修复问题?
这些显性成本依然不可避免。
不那么明显的成本
一切都需要权衡,哪怕你根本没有意识到自己正在权衡。以微服务作为代价你有所得,但同时也有所失。
通过将一切实现超级“模块化”,我们可能会遇到一些不易察觉,同时也不明显的问题。
故障的服务不想修复,你可能会新建一个服务。从某个时候开始,面对问题总是构建新的服务借此消除老问题,这种做法会产生不容忽视的成本。
你会发现自己宁愿用复杂度取代办公室权术。不需要尴尬的对话,也无须面对他人情绪,写更多软件就行了,足以取代人际交流。
你已经习惯于继续保持偏见。如果你喜欢Python而与你合作的团队喜欢Node,此时你并不需要在工作中使用其他语言,而是可以用自己偏好的语言新建一套东西。哪怕对于组织或整体系统而言并非最好的选择,你依然会坚持自己认定的“真理”。
历史回顾:最开始优步的开发工作是全部外包的。从技术上来看这算不得什么问题,于是他们找人开发了第一版移动应用和后端系统。
后续处理:开发工作转为由公司内部人员接手后,最开始他们使用了Node.js,现在已换为Go。
核心服务:系统的其他部分最初用Python开发,现在也已换为Go。
地图服务最终也转为内部人员接手,相关团队使用了Python和Java。
数据工程团队使用Python和Java写代码。
内部衡量系统使用Go编写。
看到了吧,他们使用了多种语言,这一切都要归功于微服务。
不同团队可以使用不同的语言来开发,而不同语言编写的服务依然可以相互通信。有效,但也需要付出代价:
代码难以共享。
人员难以在不同团队间轮职。一个平台上积累的知识无法沿用到另一个平台,虽然大家都可以学习新技术,但更换技术同样需要付出成本。
希望自己早知道:使用不同语言会导致企业文化的碎片化。全面拥抱微服务将导致各自为营。有Node营,有Go营,还有其他营。人们会本能地出于共同特征聚集在一起,但多语言战略是需要付出代价的。
团队之间可以通过RPC相互通信。
当越来越多的人在大规模环境中非常快速地联合在一起之后,HTTP的薄弱之处开始显现。例如状态代码是什么?标头是什么?查询字符串里要放些什么?是RESTful的吗?用什么方法?
这一切在浏览器编程领域显得很酷,但在服务器编程领域会变得极为复杂。
你真正想要的只是运行相关功能并获得需要的结果。但是在HTTP/REST体系中你会遇到各种不易察觉的解释问题,这一切都需要付出不菲的代价。
JSON是很棒,无需额外的工具就可以直接读取,但如果没有恰当的类型,那也只是一堆杂乱无章的代码。先别急,下文还将详细讨论。当有人改了一些服务,而下游还有其他服务需要通过这些服务对空字符串或Null值进行解释,或者某些类型要求必须使用某种语言而不能用其他语言,这就会造成极大的混乱,可能要用很长时间才能理顺。通过不同接口的类型应该可以解决所有此类问题。
RPC的速度远远比过程调用更慢。
希望自己早知道:服务器并不是浏览器。
代码库数量以多少为宜?也许很多人并不赞同,但他认为只使用一个是最好的。
很多人认为同时使用多个代码库是最好的做法,至少每个项目一个,或者每个项目多个。
使用多个代码库,这种做法复合“使用多个小模块”的行业趋势。小模块更易于开源或更换。
只使用一个代码库也很好,因为这样可以更容易地应用交叉改动。如果想要进行改动,可以很容易找到所有需要修改的代码。此外这样也可以更方便地浏览所有代码。
这样做的劣势在于会对构建系统造成较大压力,并影响到在代码中进行导航的能力。确保交叉改动的恰当应用也是一个痛苦的过程。
另一个劣势在于代码库会逐渐变得极为庞大,以至于除非预先搭建一套非常精巧的系统,否则根本无法构建甚至签出自己的软件。在缺乏特殊工具的情况下,单一代码库的做法也不现实。谷歌就使用了单一代码库的方法,但他们会通过一种虚拟文件系统让用户觉得自己可以将整个代码库签出。
超过8000个Git代码库,一个月前才只有7000个。
有些人有自己的个人代码库。
有些团队使用单独的代码库,独立于服务本身追踪服务的配置。
但其中大部分还是生产用代码库。
代码库的数量实在是太大了。
出错之后怎么办?在大型的微服务部署中会遇到一些令人吃惊的问题。
如果其他团队被你的服务影响而该服务还没有准备好发布,是否可以由其他团队为你的服务打补丁并发布?
自己发布的服务,持续运行时间由自己负责保障,这一要求是否会与其他团队相冲突,哪怕所有测试均已顺利通过?自动化机制是否已经足够好到可以让不同团队相互发布对方的软件了?
优步的做法需要看具体情况。有时候是可以的,但通常都不行,一个团队不能接受另一个团队的补丁。
小规模团队是一种好做法,大家都可以更快速地前进,所有功能都可以非常迅速地发布,但有时候你必须明白,整个系统实际上是一个相互连接的有机整体,在将整个系统拆解为大量微服务的过程中往往很难意识到这一点。
这个问题很难办,希望大家能将更多时间用于确保整个系统正常运转。
同时依然需要能将系统作为整体来看待。
鉴于相互独立的微服务层出不穷,迟早会遇到性能问题。
RPC成本很高,尤其是在使用多种语言的情况下,此时该如何理解性能问题将完全取决于各种不同的语言和工具。
你让大家使用自己惯用的语言编程,现在应该已经意识到跨越不同编程语言的性能问题是个真正的挑战。
可以试着通过Flame Graphs让所有语言使用一种通用的仿形格式(Profiling format)。
在你想要理解系统性能的时候,找出性能问题根源的最大阻力在于工具之间的差异之处。
大家都有仪表盘,但如果仪表盘并不是自动生成的,只是由不同团队将自己认为重要的东西放在仪表盘上,在你想要确定问题根源时可能会发现,每个团队的仪表盘上显示的内容竟然截然不同。
每个服务在创建时都应该有一个标准的仪表盘,显示同一套有用的数据。任何人都应该能轻松创建仪表盘。这样在你查看其他团队的服务时就可以看到相同的内容。
希望自己早知道:一流性能并非必须的,但你需要知道自己的实际情况。
这方面有一个很大的争议:你是否真的需要关注性能。很多反对性能优化的人普遍产生了“性能优化是一切恶果的根源”类的思维。服务并不那么忙,这一点也不重要,但我们始终需要针对开发者的速度进行优化。购买计算机总比雇佣更多工程师便宜。
工程师非常贵,这是真理。
问题在于在得到重视之前,性能对你而言根本算不上一个问题。有朝一日你遇到了性能问题,而如果企业内部已经培养了“性能并不重要”的文化,就很难突然开始重视性能问题。
创建各种服务时,你可能希望根据性能与其建立某种类型的SLA,因此总有一些数字可供参考。
Fanout会造成大量性能问题。
假设有个典型的服务,99%的时间可以在1ms内响应,1%的时间可以在1秒内响应。还不算太糟。用户只会在1%的时间里感觉到慢。
假设这个服务遇到了严重的Fanout,需要调用大量其他服务。响应速度变慢的几率很快将大幅增加。如果使用100个服务,63%的时间里响应速度将至少为1秒钟(1.0 - 0.99^100 = 63.4%)。
你可以通过分布式追踪机制追踪Fanout问题。但如果无法理解请求在整个架构中行进的细节,Fanout问题也会显得非常难以追踪。
优步使用了OpenTracing和Zipkin。
另一种方法是使用日志。每个日志项包含了将所有服务串联在一起的通用ID。
可以通过一个棘手的案例来体会一下。顶层针对同一个服务产生了大量Fanout。单看这个服务本身很正常,每个请求都快速且一致,问题在于这个顶层服务会获得一个ID清单,并针对每个ID调用该服务。就算调用会并发进行,这个过程也需要很长时间。使用批处理命令吧。如果不进行追踪可能很难发现这个问题。
另外还有个例子:某个服务需要发出数千个服务调用。虽然每次调用都很快速,但大量调用导致该服务运行缓慢。最终发现在遍历列表更改属性时,这些操作很神奇地变成了数据库请求。虽然数据库团队称数据库运行正常,因为每个操作速度都很快,但大家奇怪的是为什么会执行这么多操作。
追踪工作的庞大开销也会影响到结果。追踪需要做很多工作,方法之一是不要追踪所有的请求,只要追踪请求中具备统计学意义的部分即可。优步只追踪了1%的请求。
希望自己早知道:追踪需要跨语言的上下文传播(Context propagation)。
由于不同语言使用了多种框架,如果无法通过某种方式对上下文进行传播,了解请求上下文(例如具体是哪个用户,是否通过了身份验证,位于哪个地理范围内)的过程将显得极为复杂。
一个服务依赖的其他任何请求必须传播上下文,哪怕该服务根本无法理解该上下文。如果早点实现这一特性,这样的做法将能帮你节约大量时间。
考虑到有不同语言,大量团队,以及大量新人,有超过半数的工程师团队成立时间都不超过6个月,可能大家都会趋向于通过不同的方式记录日志。
“强制(Mandate)”这个词很诡异,但却是我们真正需要的,我们可以强制使用统一的日志记录方法。但更易于让人接受的做法是提供简单易用的工具,让人们无须考虑使用其他方式就能获得一致的结构化日志。
多种语言的使用会让日志工作更困难。
遇到问题后,因为记录了太多信息,日志本身也可能让问题变得更严重。日志满载后必须能删除日志项,希望系统能早点具备这样的功能。
希望自己早知道:有种说法认为需要统计日志消息的大小,这样才能知道过多的日志数据是谁生成的。
需要通过某种工具对所有日志进行索引,这样大家才能搜索日志并了解情况。
如果日志功能是免费的,可记录的数据量会显得无穷无尽。有些人会记录很多内容并导致系统被数据淹没。
需要通过账务系统对发送到集群建立索引的日志数据出具账单,并由发送数据的服务付费。
这种做法的目的在于将压力重新转嫁给开发者,促使他们用更智能,而非更麻烦的方式记录日志。
优步针对结构化日志创建了uber-go/zap。
希望将服务发布至生产环境之前进行负载测试,但无法构建出与生产环境同等规模的测试环境。
生成能对系统所有组件进行测试所需的现实测试数据,这本身也会遇到一些问题。
解决方案:在非峰值时间通过生产系统进行测试。
这会造成大量问题。
所有衡量指标都会被这种测试放大。
你肯定不希望人们觉得测试负载的规模超过实际情况。这个问题的解决又回到了之前的上下文传播问题。
需要确保所有测试流量的请求能提供相应的上下文,借此说明这是测试请求,并据此对衡量机制进行调整。这种做法需要贯彻到整个系统中。
希望自己早知道:我们真正希望做到的,是在所有时间对所有服务进行负载测试,因为很多问题只会在流量峰值的时候出现。
希望系统能以接近峰值的状态进行测试,并在真实流量增加时暂停。
希望系统能早点实现用不同的方式处理测试流量以及相关的汇总统计工作。
希望自己早知道:并非所有人都喜欢失败测试,例如Chaos Monkey(译注——“混世魔猴”,Netflix自行设计的一个软件,可随机模仿基于云的系统发生的故障,以了解这些系统的健壮性),尤其是如果需要在事后添加这样的功能时。
所有东西都在“老化(Legacy)”,重点在于迁移。大部分负责存储的人,他们唯一的工作就是将数据从一个老化的系统转移到另一个老化程度不那么高的系统中。
有些人始终在将某些东西迁移到另一个地方。无论开会的时候具体怎么说,大家其实都在这样做。
老化的东西需要继续工作,业务需要继续运转。维护窗口这个概念已经不存在,剩下的只有“可接受的停机时间”。
随着业务走向全球,已经不存在非峰值时段。任何时候,总有某个地方处于峰值时段。
希望自己早知道:强制迁移是一种糟糕的做法。
没人愿意被安排必须接受某个新的系统。
因为组织需要改变而让某人改变自己的做法,还是提供一个明显更好的系统让大家自发地接受新事务?
只有胡萝卜没有大棒。任何时候使用大棒都是糟糕的做法,除非是与安全性或合规有关的问题,这种情况下也许可以考虑对大家的做法进行强制要求。
“自建还是外购”这种权衡已经没人买账了。什么时候需要自建?什么时候又需要外购?
任何基础架构组件,任何可能是平台组件而非产品组件的东西,在某种程度上已经逐渐开始成为稀松平常的市售产品。亚马逊(或其他厂商)会将这些东西以服务的方式提供给你。
最终你投入大量时间精力维护的东西,别人可以用更低廉的成本以更好的效果获得。
希望自己早知道:如果有人在负责平台类型的某种功能,突然听到亚马逊将这种东西以服务的方式提供,此时肯定会感觉不妙。
你依然试图对使用某些私有的东西,而非使用公有服务的做法的合理性进行辩解。
实际上还有想法截然不同的另一种人。对于有关自建/外购权衡的判断,大家在做法上有很大的差异。
将一切拆解为小的服务,这样大家就可以玩弄权术了。
当你做出的决策依次伤害到公司 > 团队 > 自身的权益时,权术就产生了。
你将个人价值置于团队之上。
团队将团队价值置于公司之上。
权术不仅仅是那些你不喜欢的决策。
为了接纳高度模块化的快速开发方法,需要非常快速地招募人员,并尽可能以最高速度发布功能,这些动机会导致你开始伤害其他对象权益。
当你以“通过多个小规模的成果塑造大规模的成就”这种方式实现自身价值时,此时将很难确定怎样做对公司而言才是最好的。让人吃惊的权衡,对吧。
一切皆须权衡。
希望自己早知道:如何特意做出更好的权衡。
有关这段视频的精彩讨论:HackerNews讨论以及Reddit讨论
作者:Todd Hoff,阅读英文原文:Lessons Learned From Scaling Uber To 2000 Engineers, 1000 Services, And 8000 Git Repositories