写点什么

一些好的规则

  • 2014-08-21
  • 本文字数:6355 字

    阅读完需:约 21 分钟

什么是明智的标准化?

想象一下第一次和特别的人约会。当你到达最喜欢的餐馆时,所有的灯都熄灭了,你身处黑暗之中。奇怪的是,从厨房传来的声音又表明这里像往常一样正在营业中。你听到一位女服务员走来,等待着引导你到没有灯光照射到的座位上。你的同伴不知所措,并且有一点害怕。你是打算留下,还是找个正常点的地方吃饭?

Web 应用就像餐馆一样,人们通过其所提供的体验对其进行评价。即使是短暂的中断也会影响服务提供商的口碑或服务水平。政策和指导方针在防止代价高昂的服务中断中扮演着重要的角色。不幸的是,它们也能导致不理智决策的产生,从而造成更大的损害。比如公司内“DevOps 团队”的建立。这将导致所有的运维知识都被隔离在一个单独的团队中。尽管这样一个管理层指令可能预示着 DevOps 的到来,但它什么都不是。

工程师鄙视无逻辑的、官僚主义的规则。这些规则是前进的障碍物。然而,每家公司至少都会有一些这样的规则。在过去,可能有好的理由在一些问题上制定这样的规则。渐渐地,这些规则过时了。但是,规则制定者不能(或不敢)取消它们。当使用 C++ 代码库时,由于历史原因,被告知不能使用 STL;参与的 Java 项目被坚定地拒绝从 1.4 迁移到新版本。任何有过这样经历的人都明白有些措施可能会对生产力产生消极的影响。

我们应该忘记这些规则吗?

面对这些像障碍物一样的规则,我们都很想将它们废除。不幸的是,“无为”的公司通常都没有成功地废除它们。好的规则是一种重要的交流形式。这种形式关乎到长期策略、从过去吸取到惨痛教训、以及来自用户需求中的发现。理想情况下,一个组织制定的与时俱进的规则,可以帮助个人增强做出正确决定的信心。在实践中, 这样的情况真的发生过吗?

一家公司是否拥有真正有效的指导方针,Netflix 就是一个很好的例子。至少通过阅读他们的博客开源的代码会给人留下这样的印象。比如,即使没有和Netflix 的任何员工聊过,我也能确定,“构建它,运行它”是一个他们如何把开发和运维结合起来的不错的想法。另一个明确的原则是:写代码是为了构建一个可靠的、可扩展的服务,而不是为了其他目的。他们开源了所写的大部分后台软件。这个事实比任何事都更具有说服力。

Netflix 已经构建了 Netflix 内部 Web 服务框架(NIWS)。这是一个自定义的软件栈,用于创建可靠地运行在云上的内部 Web 服务。NIWS 采用了一些不太流行的技术和不太常用的方法。使用这种与最佳实践背道而驰的方法需要有相当强的自信。毫无疑问,部分可以归因于落实的政策。这些政策让工程师可以不受限制地考虑问题。

Netflix 的负载均衡

在 Netflix 如何挑战常规的例子中,我最喜欢的是他们是如何在 NIWS 中实现负载均衡的。面向客户的流量仍使用传统的负载均衡处理器(一个标准的 Amazon EC2 ELB),但对于 Netflix 服务器之间的流量,他们选择了一个完全不同的方案,称作客户端负载均衡(client-side loadbalancing)。基本思想很简单:取消专门用于负载均衡的节点。这些节点用于在 Netflix 服务器间转发流量。客户端本身维持着一张列表,记录了可用的后台节点。当客户端发送请求时,直接与所选的后台实例交互。而这样就没有必要使用专门的负载均衡器。

客户端负载均衡并不是 Netflix 发明的。但是, 它是有名的公司里第一个在基础架构中完全使用这种技术的(公平地说,同一时期内,Twitter 和 Yahoo 也在做基于相同概念的实验)。在多个后台服务器上做均衡的标准方法是:通过一个负载均衡器,如 Amazon EC2 ELB,或者在服务器上运行类似 HAProxy 的软件。对于这么关键的组件,使用保守的方法和一种大多数工程师都熟悉的技术是很有意义的。但是,几乎没有公司在 Netflix 之前试验客户端负载均衡的方法。其真正原因是,他们甚至都没有考虑到这种方法。

对于从事大规模应用程序开发的软件工程师,每天都要和各种库和组件打交道。这有点像鱼和水的关系。在能使用一种特定方法成功地构建系统这么多年后(也许几十年) ,对已经经过考验的方法或者系统的构建模块提出疑问,这看起来是在浪费时间。在许多公司里,这些决定已经被写进政策中。这些政策基本上是不可变的。但是,Netflix 采用了客户端负载均衡的方法,并因此取得了显著的成功。首先,他们从系统中移除了一个单点故障点(对于频繁地在没有警告下就停止服务的 EC2 实例,这是一个重大的胜利)。其次,通过将负载均衡的逻辑集成进客户端,负载均衡的策略可以参考客户端提供的信息。比如,考虑以下的负载均衡规则:

向客户端的 EC2 可访问区域(EC2 Availability Zone)中的可用节点发送请求。如果这样的实例不存在,则在当前区域中找一个运行已超过一天的实例替代。

传统的负载均衡器并没有被设计成用来执行这种自定义逻辑。它们也无法获取太多关于客户端的信息(比如一个客户端所属的 EC2 可访问区域)。自定义负载均衡逻辑变成了应用的一部分,使用相同的语言编写。这意味着编写代码的单元测试用例变得容易。而在传统上,这被认为是“基础设施的东西”。因此,这不仅让制定更复杂、更智能的决策成为可能;也使得人们对工作能如期完成更有信心。从某方面看,NIWS 将 DevOps 带入了下一个层次:开发人员和运维工程师不仅坐在一起,在同一个团队中工作;而且他们使用相同的开发语言,向相同的代码库提交更新。

Prezi 加入客户端负载均衡俱乐部

用一个内部的客户端负载均衡实现替代标准的负载均衡器,这种让 Netflix 受益的技术只适用于 Netflix 吗?不一定。在 prezi.com ,我们对内部流量也采用了这种技术。我们的一些应用服务器运行着若干个服务。当这些服务通信时,我们希望它们优先选择本地的服务实例,而不是向网络中发送请求。然而,如果需要访问的服务没有运行在同一台服务器上,那么就可以访问任何一个该服务的实例。对于 Prezi,获得的好处是,尽可能地避免了网络流量、减少了在 AWS 上的支出和响应时间。目前运行于 prezi.com 产品上的负载均衡逻辑由以下的这段 Scala 代码实现:

复制代码
override def choose(key: scala.Any): Server = Option(getLoadBalancer).map(lb =>
lb.getServerList(true).filter{server =>
server.getHost == config.getHostname && serverIsAvailable(lb, server)
}).getOrElse(Seq()) match {
case Seq() => super.choose(key)
case matchedServers => matchedServers(0)
}

Netflix 的工程师可以设计出 NIWS,而不用担心质疑当前技术所带来的后果。因为公司的规则授权他们这么做。即使任何人都可以获得 NIWS 的技术,只有那些有着类似思维的公司才能够使用这种技术去搭建产品。具体来讲,期望工程师基于技术价值做出决定的公司和完美主义的公司是无法利用这样的技术的。

Netflix 验证(Netflix test)

期望所有的工程师在做决定时不受办公室政治、流行技术和害怕改变的制约,这是不可能的。然而,减少这些方面的影响,对确保开发不会误入歧途有很大的帮助。一堆武断的限制规定会让工程师的设计缺乏创新和效用。相比之下,一些好的规则限制了问题空间、明确了约束、改善了产品的质量。

基于 NIWS 栈的源代码,Netflix 在决定如何实现一个组件时会考虑两件事:

  1. 这个组件发生故障的可能性及后果是什么?
  2. 当这个组件的设想场景发生改变时,是否容易修改这个组件的行为?

我将这些问题称为 Netflix 验证。这两个问题是紧密关联的。甚至可以说,第二个问题包含了第一个问题。这两个问题之所以意义重大,是在于他们如实地镜射出了 Netflix 的商业目标。这个目标就是提供可靠的、可扩展的服务。其他也有相同目标的公司也能从这个验证中受益。但是,这个验证的真正力量在于它没有提到的东西。它没有提到任何具体的技术或者供应商

不适用于完美主义者

真正让我惊讶的是,Netflix 的代码只专注于足够好即可,而无过之。别误解,目前我所看到的代码容易阅读,并且有很高的单元测试覆盖率。即便如此,我也没有预料到Netflix 能专注在足够好这个级别。比如,在代码的许多地方,当后台线程启动之后,就再没有任何操作停止它们。这看起来有很大的问题,直到你意识到Netflix 不在节点上进行软件升级。他们通过启动一个新的EC2 实例集群来部署新版本的应用。当通过监控验证新版本应用运行正常后,就将老集群关闭。如果有人使用了这些部署工具(也是开源的),那么就没有僵尸线程的问题。然而,如果有人在一个像Glassfish 的应用服务器上使用Netflix 的库,那么每次重新部署都将会触发内存泄漏。

代码中包含大量单例模式的类,也是我未预料到的。当我们以一种Netflix 未预见的方式使用一个NIWS 库时,我们很快会发现自己在不断挣扎地使用错综复杂的技术来处理问题。包括使用多个类加载器。

最后,尽管wiki 页面上关于代码的文档有很大的帮助,但是这样的文档太少了,很多细节都没有描述。通常,代码就是文档。有几次,我在github issue tracker 上找到了一些解决NIWS 相关问题的最佳建议。

我的许多同事,在第一次接触Netflix 生态圈时都有点不知所措。他们的第一反应是谴责那些写代码的工程师未经训练或者太懒惰。“应该有一些规则关闭这些没用的线程”,我听他们这样说着。然而,对于Netflix,我们所列出的NIWS 的缺点,都不算是一个真正的问题。用于处理线程关闭的时间被用在了其他更需要的地方。如果有人想要以不支持的方式重用代码,那么单例模式的类只是其中一个会遇到的问题。最后,尽管写文档是一件好事。但是,可读性高的代码和大量内部专业知识让文档成为了一个可选项。Netflix 建立了关于线程管理、恼人的设计模式和最小化文档量的规则。通过建立这些规则,让工程师可以专注于其他主要问题。

事实上,我已经意识到Netflix 的软件栈之所以成功,是因为它有着旺盛的精力。这不仅让Netflix“砍掉了软件栈的一些边角”的事实可以被接受,而且实际上也催生了一个更好的产品。编写了大量描述代码的文档还得保证文档不会过期,因为代码总是在不断的演进。编写不会用到的特性会使开发者失去动力、且难已为团队证明自己,对社区也没有什么好处,因为这部分代码不会在产品中被验证到。在Prezi,我们有一些一直想开源的项目。但由于缺乏时间加入一些我们希望的改进,目前还不能将它们开源。Netflix 成功地开源了大量的代码却没有破产。因为它们一直专注于代码的可读性和单元测试,而不是加入过多的亮点,以及保证其不会过时。Netflix 实施的这些合理规则,使得它的设计开发可以应对不断快速增长的用户;甚至是不断开源所写的代码。

因此所有的特定规则都不好吗?

如果用Netflix 验证再形成一些指导方针,那么这些方针是相当通用的。例如,通过努力获得成功的名言,像“花10% 的时间用于偿还技术债”,或者技术信息,如“0.6.1 版的NodeJS 使我们的Web 应用变得不稳定,别使用它”。如果把从过去失败中总结获取的教训忘了,这难道不是一种浪费吗?

这样的建议,和最佳实践、知名的组件一样,是非常有价值的。在加速开发和简化系统的运维方面,通过多年的验证,这些建议已经获得了工程师的信任。比如在Prezi,大多数后台系统都是用Python 写的,并使用了gunicorn web 服务器、Django web 框架和MySQL 数据库。在公司的初期,这个软件栈使得开发者能够专注于新产品的特性上。多年来,“使用Django 和MySQL 开发服务”就如同“不要在周五下午3 点后部署”一样明确。这些都不是Prezi 成文的规则,但却早已在实行中。

随着注册用户数从0 攀升到4000 万,许多当初采用这个平台的实际情况都已时过境迁。比如,当所有的网站流量都由一个应用处理时,将所有用户数据存入一个MySQL 数据库中是有意义的。如今,Prezi 拥有许多独立的服务。这些服务对响应延时、可靠性和一致性上都有着不同的需求。许多服务运行在EC2 上,将数据库当做键-值存储的容器,通过主键访问数据。第一年制定的技术指导方针,尽管在那时有用,但没有一条能帮助我们应对目前工程上的挑战。

只要标准的技术和特定的规则没有过时,就能够激发工程师的产出。问题在于,当这些特定的规则不再适用时,仍然被强制实施。

固定的接口集

对于已经过时的规范而言,一个问题(而且很常见)是软件接口的过时。我最喜欢的例子是Java Servlet API。即使它并没有真的过时!实际上,它是一个优秀的接口:直观、稳定、有完整的文档、很多不同应用服务器都是使用它实现的。

当Prezi 决定探索JVM,将其作为我们可靠的Django 栈的一个可选平台时,我们选择了一个轻量级的代理应用作为我们的试用项目。我强烈地表明应该使用Jetty 和Servlet API,而不是团队考虑的另一个可选方案。这个方案使用一个不知名的Scala Web 服务器。6 个月之后,我们关闭了原有的代理程序,而用一个基于 Spray (这个技术我是投反对票的)写的程序取而代之。部分原因是:对于我们的用例,使用它可以获得更高的效率。因为在我们的用例中,响应时间主要受发出的 HTTP 请求的响应延时的影响。我开始从代码层面思考:我们想要什么样的目标,想使用什么样的接口。我们如何写单元测试。开发者社区有多大。这正是 Servlet API 在抽象层面解决的问题。我本应该考虑(或谈论)关于它是如何利用硬件资源的。具体来说,瓶颈在于:处理请求时是否需要大量的 CPU 或者 IO 资源。由于在我们的用例中,大部分时间都花费在等待发出的 HTTP 请求的响应上,所以没有这样的资源要求。这就是代理程序的本质。鉴于我们的用例,使用 Servlet 的方法对每一个请求都创建一个专门的线程,不仅毫无必要地限制了处理请求的并行数,而且也无法高效地利用内存。

Servlet API 不适用于这个问题的事实,并不能说明那些常用的接口或 Java 编程语言不好。数以千计的公司使用 Servlet 构建了令人惊叹的产品。其他编程语言也具有相似语义的 Web 服务器接口。这个故事想表明的是,我在使用特定的指导方针时脱离了实际的场景。接口是用来解决某一个特定问题的。当问题不再是你尝试解决的问题时,使用给定的接口不是一个好的选择(无论这个接口有多流行或者多新颖)。

DevOps 的规则

DevOps 能量来自于合作中的人有着完全不同的技能。相比于成员技能单一的团队;一个拥有各种不同技能的团队,包括长满胡子的系统管理员、函数式编程的狂热粉丝,更有可能构建出可靠和可扩展的服务。

成员技术背景的不同使得团队更加需要明确的规则。开发者不需要知道为什么使用的自定义 Linux 内核有着一大串的编译参数。类似地,不是所有人都需要担心代码中有多少单例模式对象的存在。“写 shell 脚本时必须添加 shebang 行”,或者“解析用户数据的代码要有单元测试”。像这样的标准适用于团队中的每一个人,并且会帮助到那些在特定领域内没有足够经验做好事情的人。特定规则只有被适当的使用,才会对团队产生积极的作用。

更通用的规则,像这些 Netflix 验证只适用于制定高层级决策,但是能够应用地更久。管理团队既需要通用的规则也需要高层级的规则。诀窍是要及时发现我们制定的规则是否已不再发挥期望的作用。

如果我们回到文章开头的那个餐馆,打开冰箱门,不同盒子上有着不同的保质期时间。有的可能几个月,比如番茄酱;有的可能几个小时,比如鱼。做饭要用到不同的原料,而每种原料有自己的保质期。保持原料的新鲜,使得最终做出的食物可口,这是一个厨师的责任。同样,不仅在我们决定要将什么进行标准化这件事上需要智慧,在及时发现我们的标准是否已失去意义这件事上也需要真正的智慧。

关于作者

Peter Neumark是 Prezi 的一名 devops 人员。他和妻子 Anna,以及两个儿子住在匈牙利的布达佩斯。当不用调试 pyhton 代码或换尿布时,Peter 喜欢骑自行车。

参考英文原文: A Few Good Rules


感谢赵震一对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-08-21 19:415716

评论

发布
暂无评论
发现更多内容

JavaWeb之JSP技术(三)

爱好编程进阶

Java 程序员 后端开发

容器化|在 S3 备份恢复 RadonDB MySQL 集群数据

RadonDB

MySQL 数据库 Kubernetes 高可用 容器化

JavaWeb之Cookie和Session技术(四)

爱好编程进阶

Java 程序员 后端开发

中国全球GPU市场竞争格局分析

Finovy Cloud

人工智能 gpu 云服务器

大规模并行分布式深度学习

阿里云大数据AI技术

人工智能 深度学习 并行分布式训练

Java Script

爱好编程进阶

Java 程序员 后端开发

web前端培训React调度器原理分析

@零度

前端开发 React

Java8-Stream:2万字20个实例,玩转集合的筛选

爱好编程进阶

Java 程序员 后端开发

想从单体架构演进到分布式架构,SBA 会是一个不错的选择

华为云开发者联盟

架构 微服务架构 分布式架构 单体架构 SBA

hadoop

爱好编程进阶

Java 程序员 后端开发

FusionStorage原理及组件

爱好编程进阶

Java 程序员 后端开发

Docker下Prometheus和Grafana三部曲之三:自定义监控项开发和配置

爱好编程进阶

Java 程序员 后端开发

Dubbo如何处理业务异常,这个一定要知道哦!

爱好编程进阶

Java 程序员 后端开发

HotSpot JVM 内存管理

爱好编程进阶

Java 程序员 后端开发

Java agent还不了解的程序员该反省一下了

爱好编程进阶

Java 程序员 后端开发

JAVA中的位运算与二进制操作

爱好编程进阶

Java 程序员 后端开发

卫剑钒:《大教堂与集市》被过誉了吗?

腾源会

开源 腾源会

28岁程序身价过亿,从字节提前“退休

爱好编程进阶

Java 程序员 后端开发

CXF webservice之手动启动服务方法(restful )

爱好编程进阶

Java 程序员 后端开发

dubbo实战之二:与SpringBoot集成

爱好编程进阶

Java 程序员 后端开发

企业文档协作如何进行?

小炮

文档协同

Hexo 搭建:搭建与配置

爱好编程进阶

Java 程序员 后端开发

JavaWeb快速入门--JSP(2)

爱好编程进阶

Java 程序员 后端开发

Java中的复用类

爱好编程进阶

Java 程序员 后端开发

BS-XX-007基于JSP实现户籍管理系统

爱好编程进阶

Java 程序员 后端开发

@RequestParam、@ModelAttribute、

爱好编程进阶

Java 程序员 后端开发

Docker Swarm从部署到基本操作

爱好编程进阶

Java 程序员 后端开发

DockerFile的编写构建镜像步骤,常用命令和案例

爱好编程进阶

Java 程序员 后端开发

2021最新分享Java面试题库万字精华 github上标星80

爱好编程进阶

Java 程序员 后端开发

Java代理模式,一次复习完4种动态代理实现方式

爱好编程进阶

Java 程序员 后端开发

20年最新金九银十面试必备,教你一份文档吊打面试官,拿到offer

爱好编程进阶

Java 程序员 后端开发

一些好的规则_Scala_Peter Neumark_InfoQ精选文章