写点什么

与 Jolt 大奖提名图书《Release It!》的作者精彩对话

  • 2008-02-04
  • 本文字数:5398 字

    阅读完需:约 18 分钟

《Release It!: Design and Deploy Production-Ready Software》一书的作者是 Michael Nygard,该书已获得 2008 年度 Jolt 大奖的提名。此书主要围绕怎样开发产品级软件(production-ready software)以及此类软件与功能完备软件(feature-complete software)之间的差别两个话题展开讨论。在 Nygard 个人的网站上,他是这样描述写这本书的源动力的:

这本书凝聚了我多年来与生产系统打交道的经验。我经常因为某些本该 24x7 运作的系统宕机,而在半夜三点受到惊扰。 关于系统设计和架构的书籍往往只告诉你怎样满足功能需求,的确这类书籍对你在 QA 面前过关会有很大帮助。然而这本书中的重点将放在怎样才能使一款软件成为真正的产品这个话题上。如果你不想整日被电子紧箍所束缚,这本书应该正是你所想要的东西。

Nygard 还准备了一些此书的摘录,包括 Sample Patterns、Sample Anti-Patterns。不久前在 InfoQ 上发表的一篇名为《敏捷、架构和凌晨五点的产品问题》的文章亦源自此书。

最近 InfoQ 与 Nygard 就此书所涉及的内容展开了一次对话,期间 Nygard 还回答几个关于此书的哲学怎样与敏捷等概念相适应的问题:

InfoQ: 您能否谈谈产品级软件与功能完备软件有什么不同?

Michael Nygard:首先,虽然不同的人可能会对“功能完备”这个词做出不同的解释,但其中最好的情况也只不过是某个特定版本的所有功能都通过了功能测试。对敏捷团队来说,它还应该意味着通过了所有的验收测试。然而在某种情形下,它甚至仅仅意味着开发人员完成了代码的第一稿然后甩给测试人员。

“产品级别”与“功能完备”是完全不同的两个概念。验收测试的通过或者说测试员的绿色小勾与系统能否承受实际应用中的压力完全是两回事。系统此时有可能表现得一塌糊涂,也有可能非常地棒。

举个例子,我们能保证不存在内存泄漏吗?没人会在测试服务器中在完全模拟实际的负载的情况下对系统进行一个周或者一个月的测试。别说单独用一周时间来进行存活期测试,总共能有一周的测试时间就不错了。因此,通过 QA 并不能保证没有内存泄漏的发生,因此它很容易就被带入到产品中。对的,这就不可避免地带来了操作性问题,因为系统需要频繁重启。我目前见到的内存泄漏情况都是与流量相关的,也就是说,流量越大,内存泄漏的速度就越快。这意味着你根本无法预测什么时候要重启程序,没准就在最繁忙那天的高峰时段。事实上,发生在最高峰时段的可能性非常大,这可真是够糟糕的。

“产品级别”的另一个方面是系统对所谓“瞬时脉冲”的应对能力,也就是对系统的短暂性冲击。不管是你网站上的一个链接上了 Digg.com 的首页,还是一件标价错误的商品被 FatWallet.com 发现了,都会给网站的流量带来惊涛骇浪。许多三层的 Web 应用对并发的大量会话往往无能为力。另一类瞬时脉冲来自于失去与数据库或者其他后台系统的连接。一旦衰退开始,系统崩溃就只是迟早的问题了。

我住在明尼苏达州的明尼阿波利斯市,这可是美国中部最北边的一个州。这里的高速公路系统即使在最好的时期也只是勉强够用。不过车速虽慢,但还能运作下去。你还能够到达想去的目的地,不至于出现类似首都华盛顿或者加利福尼亚湾区的那种上下班时的拥堵。但要知道,这里可是明尼苏达,一年总要下几次雪。一旦下雪,这里的高速公路系统会很快变得非常糟糕,甚至会基本上同时陷入停滞。这时你可能在想象一个更加健壮的高速公路网,它能更好的支持人们的出行,一英寸的降雪只会使车速变慢 10%,而不是目前的 200%。

你应该同样地审视一下你的系统。它们是不是只有在阳光明媚的日子里才撑得住?万一遇到刮风下雪——后台服务的响应延迟从 250 毫秒增加到一分钟的时候,系统能否继续工作?

InfoQ: 那要想将功能完备软件变为产品级软件,我们应该做哪些工作呢?

Michael Nygard:我想应该从改变观念开始。如果你是一个架构师或者开发人员,你就应该为系统设计一套完整的异常处理机制,就像汽车设计师为汽车加上了缓冲溃缩区(crumple zones)一样。你的架构图上的每个框图或者箭头总有靠不住的时候,这是确定无疑的。异常情况一旦发生,尽可能保持系统的功能就是你的责任。

另一项挑战工作是测试,有很多问题是很难通过测试发现的。在书中我讲一个某跨国公司系统瘫痪的案例。这次系统瘫痪曾经造成一定的轰动,为公司带来了巨大的损失。这要从 Oracle 的 JDBC 驱动程序中的一个晦涩动作说起,这个动作仅在集群发生失败转移并进行虚拟 IP 传递的情况下被触发。若干精心编写的异常处理代码和这个动作之间发生了一处微小的相互干扰,最终酿成这次灾难。回顾这个案例时我们发现,找到症结之后,我们都能看到问题所在,也能在模拟中重现错误,但是灾难发生前却完全没有人能对其做出预测。一个稍有规模的系统中各种交互的组合数量之大,连一一想到都不可能,更别说测试了。

InfoQ:你能列举几个大型系统未达产品标准就进行发布的例子吗?它们失败的原因又在哪里呢?

Michael Nygard:首先我举一个零售商的例子,这个公司曾经在几年前建立了一个全新的.com 平台。为了满足预定的访问量指标,我们在发布前对此项目做了三个月的负载测试和性能调优。但是,最终它还是在首次启动后十五分钟就宕了机。怎么会发生这种事情呢?没错,我们是做了负载测试,问题是测试在某种程度上还是太过“温和”了。例如,在测试中所有的 VU 都默认使用 cookie。后来我们发现开发工程师对会话 (session) 的设计存在很大问题,禁用 cookie 的浏览器会制造出大量的冗余会话。在测试中,VU 是不会在一个会话中对同一个 URL 做多次访问的,因此负载大都被合理地分配到多个应用程序服务器上了。事实上,我们没有对刷屏者和代理购买程序采取任何防范措施。

如果你是一个.com 系统的开发工程师,有一天测试人员过来告诉你说“我登记了一个程序错误,因为在短时间内用一个不使用 cookie 的浏览器去重复访问某个页面的时候,系统就挂掉了”。你真的应该感到幸运,因为有人这么早就给你指出了这个问题。

即使测试人员及时报告了此类 bug,它们也可能马上被标为“关闭”,理由是需求计划书上标明了只支持使用 cookie 的用户。你就理所当然地只想着使用 cookie 的用户了。但是要知道,这并不意味着如果有人使用不带 cookie 的脚本访问网站的时候,它就该宕机啊!还就是有这么一批人,他们虽然不知道怎样使用 cookies,但是他们能用"wget" 、 “curl” 或者 VB 编写脚本来访问你的网站。结果就是,不管你是愿意还是不愿意,他们都会来“光顾”的。

InfoQ:在应用程序稳定性方面我们面临哪些共通的问题?怎样解决这些问题?

Michael Nygard:在书中我阐述了很多这类问题,我把它们叫做“反稳定模式”,并且提出了一组“稳定模式”来一一化解。

根本上还是如何平稳退化的问题。错误源自将系统看作统一的实体。从用户的角度和从投资者的角度看,系统实际是一个由相关功能组成的集合,有些功能非常重要,有些却不那么重要。我们所要做的就是尽量保持重要功能的正常运转,同时抛弃掉行为不当引起系统不稳定的功能。

例如,如果你问“在餐厅搜索功能失效的情况下是否还能预定宾馆房间”这样的问题,可能会受到嘲笑。这两个功能看上去并没有直接联系啊!为什么在第三方搜索功能不能正常工作的时候要停止预定服务呢?然而,这些功能是互相耦合的,因为页面请求都是同一个应用服务器上的同一个请求处理线程池来处理的。在搜索服务变慢了或者停止响应了的时候,如果你让所有请求处理线程都阻塞起来等待响应,那就是让核心业务被次要功能所累了。

InfoQ: 在应用程序容量方面我们面临哪些共通的问题?怎样解决这些问题?

Michael Nygard:我也经常见到很多这类问题,我把它们叫做“容量反模式”。它们大都源自于那些忽略了乘法效应的开发人员。

此方面我也将举个例子来说明。每个零售商都会以某种方式来组织其商品目录,并将之以菜单的形式放在自己的网页上作为导航。每当看到这类东西的时候,我都会问“这些目录结构会多久变化一次?它的访问频率是多高呢?“,而答案往往是”一月一次“和”每秒四百次“。既然如此,为什么要动态产生呢?仅仅因为不这样做的话,当其更新后显示新的结构会有几百纳秒的延迟吗?这简直是毫无意义的事情。好的做法是在目录内容变化时生成一次 HTML,然后每次显示页面的时候读取这个缓存起来的片段。(更好的方法是交给 Akamai 公司,让他们帮你通过 Edge 脚本将其填充到你的页面 [译注:Akamai 公司和 Edge Side Includes 标记语言请参见 http://www.akamai.com/html/support/esi.html ])

因此,在系统消耗资源的每一个地方,都要问一下自己它的乘法效应。

InfoQ: 除了稳定性和容量外,还有哪类问题必须注意的?

Michael Nygard:通常我们还会遇到以下两类情形:不透明系统和刚性系统。不透明系统就像生病的金鱼,我们只能任其生死,没法对其做任何补救工作。对此类的系统,你无从了解它是怎样运行的,也无法知道它是否在正常运行。

幸运的是情况正在发生变化,诸如 Nagios 和 Zenoss 之类的免费监控软件陆续出现,对系统进行监控的能力日益变得强大,。

刚性系统就是无法演进或者很难演进的系统,只能停止服务才能实施更改的系统也属于其中。刚性系统需要重启才能部署新代码或者更新其内容。此类系统通常会进行版本锁定,这往往会对企业信息系统中的诸多方面产生影响,因为对其进行更新,不仅仅需要承担很大的风险,而且需要协调受影响的各部门以统一升级时间。

InfoQ: 运营人员是怎样与产品级软件相配合的呢?他们是否牵扯进开发过程,是否帮助定义产品,是否是在产品接近发布的时候接受训练?

Michael Nygard:运营人员被整合进创建产品级软件的过程。开发和构架有一种趋势,就是在很长时间里都过于抽象。我认为把部署的架构具体化一些是非常有用的,运营人员在此方面可以起到作用。你需要设计好目录结构。你要如何发布代码才能容易地回滚呢?你如何分别激活不同的代码?只要与运营人员进行一些沟通和商议,这些问题都有简单的答案。

举个简单的解决案例,假如工作在 UNIX 平台,你可以用目录名来解决发布版本问题,用一个符号连接指向当前的版本。如果某网上店铺的目前的版本是 1.5.2,你可以用“store_1_5_2”来命名发布目录,然后用一个名为“store”的符号连接指向它。如果版本发展到了“1.6.0”, 你就应该将其发布到“store_1_6_0”目录,等待运营人员更新符号连接来切换应用服务器。建立诸如此类的构建机制应该不是一件非常困难的事情。

还有一件事情值得注意,那就是你需要支持很多运营部门已有的系统。比如他们很可能已经有一个监控方案,因此你必须让新系统能与之好好地配合。他们甚至可能已经采用配置管理数据库(CMDB)来控制版本和应用之间的依赖关系的情况。

总之,你对运营部门支持得越好,他们对你的系统支持得就越好。如果你想睡个囫囵觉的话,就好好做到这一点吧。

InfoQ: 开发产品级软件中倡导的预先工作的原则似乎与敏捷思想中的需要的时候再动手和必要时再重构的原则有些冲突,你是怎么看这个问题的?

Michael Nygard:作为一个敏捷开发人员,我也时常会面对这个矛盾。对此问题,目前还没有好的解决方法,但是要看到,这两个原则的目的是一致的,那就是设计出好的面向对象的产品。

当完成一段逻辑代码及其单元测试代码后——不管你先写的是哪个——为了改善质量,重构是免不了的。嗯,“改善”,你能告诉我“改善”的具体涵义吗?这是否意味着,对面向对象的设计你心中必须有一个好坏的标准?是的,而这个标准就是 Martin Fowler《重构》一书中所说的“代码的坏味道”。“代码的坏味道”只是一个定性的衡量。这里硬要塞进一个严格的定量衡量标准是没有必要的。

我认为对于架构同样如此。对我来说,一个没有时间限制的远程调用,一个未加限制地取回客户所有订单的 SOAP 调用或者 REST 调用,都可以认为是“架构的坏味道”。

因此,虽然不赞成预先做大的设计,或者说“大范围的预先架构”,但我认为在系统内部定义合理的边界,设计合理的异常处理机制并且排除“架构的坏味道”是非常有必要的。

InfoQ: 你是否认为某些软件或者 API(例如 Hibernate Shards、Puppet)会有助于开发产品级别的软件?如果答案是否定的,那么你认为是否会出现那样的软件或 API?也就是说开发产品级别的软件是一个架构问题,还是一个学习某软件的问题?

Michael Nygard:这是一个卖广告的好时机,不过我并没有这样的软件。我认为两方面的问题是正交的。无论框架开发人员,产品厂商的开发人员还是应用开发人员……我们都是搞开发的。在产品型软件的代码和框架代码中遇到的产品安全问题,在应用型软件的代码中也会同样遇到。因此,框架对编写产品级软件也就无所谓是否能提供帮助了。

好的框架的确能提高产品的稳定性,例如 Doug Lea 的线程类库就显著提高了 Java 的线程安全性和同步功能。但是,我们最终还是要从产品和框架本身上来获取信心。验证过的总比没有验证的好,开放的总比封闭的好,经过长期市场验证的各类产品比其他任何的说辞都来得实在。

查看英文原文: Book Excerpt and Review: Release It! - - - - - -

译者简介:孙涛,华中科技大学物理电子学专业硕士毕业。现就职于 9spaces 网络中国分部 ( www.9spaces.cn ),从事 Java EE 应用开发和管理工作。目前主要关注基于 SOA 的软件架构设计,RIA 以及息检索(Information Retrieval)和信息抽取(Information Extraction)等技术的发展和进步。曾发表有关电磁波算法应用类论文两篇,其中一篇被 SCI 索引收录。

2008-02-04 03:071777
用户头像

发布了 23 篇内容, 共 47287 次阅读, 收获喜欢 3 次。

关注

评论

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

分治(详解残缺棋盘 —— Java代码实现)

若尘

算法 分治 java代码 6月日更

手把手教你在IDEA中配置Maven

打工人!

Java maven 6月日更

Dubbo SPI

青年IT男

dubbo

react源码解析4.源码目录结构和调试

全栈潇晨

React Hooks react源码

面试系列-3 限流场景实践

李阿柯

php lua redis 面试 限流算法

详解Camtasia的注释功能

淋雨

视频剪辑 Camtasia 录屏

情指勤一体化指挥调度平台搭建,情报研判分析系统搭建

深圳首辆数字人民币主题观光巴士亮相

CECBC

致恰达耶夫,致鸿蒙

脑极体

React Hooks - 如何安全地使用state

蛋先生DX

大前端 React React Hooks JavaScrip 6月日更

直击Huawei Mate 40产线背后的华为云IoT智能制造

华为云开发者联盟

IoT 数字化转型 数字孪生 华为云IoT

有点难的 webpack 知识点:Dependency Graph 深度解析

范文杰

webpack 6月日更

“扯皮”终结者,区块链帮农民工计薪水

CECBC

微博评论的高性能高可用计算架构设计

唐高为

Redis数据结构

邱学喆

数据库 redis 跳跃表

实现多级缓存架构设计方案

xcbeyond

缓存 缓存架构 6月日更

GrowingIO 前端团队对于 GraphQL 的实践总结

GrowingIO技术专栏

大前端 graphql

在Spring Bean实例过程中,如何使用反射和递归处理的Bean属性填充?

小傅哥

Java spring 小傅哥 反射调用 属性填充

HarmonyOS 2正式发布 硬件生态品牌HarmonyOS Connect一同亮相

科技汇

BZZ算力挖矿系统开发功能丨BZZ算力挖矿源码设计

系统开发咨询1357O98O718

你们公司的数据库出过问题么?

escray

学习 极客时间 朱赟的技术管理课 6月日更

ARTS- 日常打卡5

pjw

Spring Boot FatJar类加载机制简要分析

luojiahu

Spring Boot 类加载 ClassLoader FatJar

中断Hwi:提高鸿蒙轻内核系统实时性及执行效率的秘密武器

华为云开发者联盟

鸿蒙 硬件 中断 鸿蒙轻内核 中断信号

Hello Python! 第一天学 Pyhton 语言

在即

6月日更

k8s 插件管理工具之krew使用

雪雷

6月日更

面试系列-2 redis列表场景分析实践

李阿柯

php 面试 redis cluster

关于第四次财富狂潮的思考,区块链如猛虎出笼?

CECBC

【Flutter 专题】114 图解自定义 ACEProgressPainter 对比进度图

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 6月日更

一文了解预训练语言模型!

博文视点Broadview

【Vue2.x 源码学习】第二篇 - Vue的初始化流程

Brave

源码 vue2 6月日更

与Jolt大奖提名图书《Release It!》的作者精彩对话_Java_Ryan Slobojan_InfoQ精选文章