来自红色星球的思考

顶部

关注 Nathan

搜索 精选文章

最新文章

所有文章

« Storm's 1st birthday | Main | Early access edition of my book is available » 星期一 2月6日2012

Suffering-oriented programming

Date星期一,2012年2月6日 前几天有人问我一个有趣的问题:“当你在一个 startup 工作时,你如何证明在构建 Storm 上冒这么大的风险是合理的?”(Storm 是一个实时计算系统)。 我可以看出,从局外人的角度来看,投资这样一个大型项目对于一家 startup 来说似乎风险极大。 然而,从我的角度来看,构建 Storm 根本没有风险。 这具有挑战性,但没有风险。 我遵循一种开发风格,可以大大降低像 Storm 这样的大型项目的风险。 我称这种风格为“面向痛苦编程 (suffering-oriented programming)”。 面向痛苦编程 (suffering-oriented programming) 可以概括如下:除非你感到没有它带来的痛苦,否则不要构建技术。 它适用于大的架构决策以及较小的日常编程决策。 面向痛苦编程 (suffering-oriented programming) 通过确保你始终在处理重要的事情来大大降低风险,并确保你在尝试进行大量投资之前精通问题领域。 我有一个面向痛苦编程 (suffering-oriented programming) 的信条:“首先使其成为可能。 然后让它变得美丽。 然后让它变快。”

首先使其成为可能

当遇到你不熟悉的问题领域时,一开始就尝试构建“通用”或“可扩展”的解决方案是一个错误。 你只是对问题领域的了解不够,无法预测你未来的需求。 你会使不需要的东西变得通用,增加复杂性并浪费时间。 最好只是“hack things out”,并且非常直接地解决你手头的问题。 这使你可以完成需要完成的工作,并避免浪费工作。 在你 hacking things out 时,你将越来越多地了解问题领域的复杂性。 Storm 的“使其成为可能”阶段是使用队列和 workers hacking 出一个流处理系统的一年。 我们学习了使用“ack”协议来保证数据处理。 我们学习了如何使用队列和 workers 集群来扩展我们的实时计算。 我们了解到,有时你需要以不同的方式对消息流进行分区,有时是随机的,有时是使用哈希/mod 技术,该技术可确保同一实体始终转到同一 worker。 我们甚至不知道我们处于“使其成为可能”阶段。 我们只是专注于构建我们的产品。 但是,队列和 workers 系统带来的痛苦很快变得剧烈。 扩展队列和 workers 系统非常繁琐,并且容错能力远未达到我们想要的水平。 显然,队列和 workers 范例不在正确的抽象级别上,因为我们的大多数代码都与路由消息和序列化有关,而与我们关心的实际业务逻辑无关。 与此同时,开发我们的产品促使我们发现了“实时计算”问题领域中的新用例。 我们为我们的产品构建了一个功能,该功能将计算 Twitter 上 URL 的 reach。 Reach 是在 Twitter 上接触到 URL 的唯一人数。 这是一个困难的计算,可能需要数百个数据库调用和数千万次展示才能区分出一次计算。 我们在单台机器上运行的原始实现对于硬 URL 将花费超过一分钟的时间,并且很明显我们需要某种分布式系统来并行化计算以使其更快。 引发 Storm 的关键认识之一是,“reach 问题”和“流处理”问题可以通过一个简单的抽象来统一。

然后让它变得美丽

通过 hacking things out 来探索问题空间时,你会开发该问题的“map”。 随着时间的流逝,你会在问题域中获得越来越多的用例,并对构建这些系统的复杂性有深刻的了解。 这种深刻的理解可以指导创建“美丽”的技术来替换你现有的系统,减轻你的痛苦,并启用以前难以构建的新系统/功能。 开发“美丽”解决方案的关键是找出可以解决你已经拥有的具体用例的最简单的抽象集。 尝试预测你实际上没有的用例是一个错误,否则你最终会过度设计你的解决方案。 根据经验,你尝试进行的投资越大,你需要对问题领域的了解就越深入,并且你的用例需要越多。 否则,你将冒着second-system effect 的风险。 “使其变得美丽”是你使用你的设计和抽象技能将问题空间提炼成可以组合在一起的简单抽象的地方。 我认为开发美丽的抽象类似于统计回归:你在图表上有一组点(你的用例),并且你正在寻找适合这些点的最简单的曲线(一组抽象)。 你拥有的用例越多,你就能越好地找到适合这些点的正确曲线。 如果你没有足够的点,则很可能会过度拟合或拟合不足该图,从而导致浪费工作和过度设计。 使其变得美丽的一大组成部分是了解问题空间的性能和资源特性。 这是你在“使其成为可能”阶段学习到的复杂性之一,在设计你美丽的解决方案时,你应该利用这种学习。 对于 Storm,我将实时计算问题域提炼成一小部分抽象:streams, spouts, bolts, 和 topologies。 我设计了一种新的算法来保证数据处理,该算法消除了对中间消息代理的需求,而中间消息代理是我们系统中导致最大复杂性和痛苦的部分。 流处理和 reach 这两个表面上截然不同的问题都可以如此优雅地映射到 Storm,这强烈表明我正在做一些大事。 我采取了其他步骤来获取 Storm 的更多用例并验证我的设计。 我与其他工程师联系,以了解他们正在处理的实时问题的细节。 我不仅仅问我认识的人。 我还在推特上发消息说,我正在开发一个新的实时系统,并且想了解其他人的用例。 这导致了很多有趣的讨论,使我更加了解该问题领域并验证了我的设计思想。

然后让它变快

构建出你美丽的设计后,你可以安全地投入时间进行分析和优化。 过早地进行优化只会浪费时间,因为你仍然可能会重新考虑该设计。 这称为过早优化。 “使其变快”与系统的高级性能特征无关。 对这些问题的理解应该在“使其成为可能”阶段获得,并在“使其变得美丽”阶段进行设计。 “使其变快”是关于微优化和收紧代码以使其更具资源效率。 因此,你可能会在“使其变得美丽”阶段担心渐近复杂性,而在“使其变快”阶段则专注于恒定时间因子。

冲洗并重复

面向痛苦编程 (suffering-oriented programming) 是一个持续的过程。 你构建的美丽系统为你提供了新的功能,使你可以在问题空间中新的,更深入的领域中“使其成为可能”。 这会将学习反馈给技术。 你通常必须调整或添加到你已经提出的抽象中,以处理越来越多的用例。 Storm 经历了许多这样的迭代。 当我们第一次开始使用 Storm 时,我们发现我们需要能够从单个组件发出多个独立的流。 我们发现,添加一种称为“direct stream”的特殊流将使 Storm 能够将元组批处理为具体单元。 最近,我开发了“transactional topologies”,它超越了 Storm 的至少一次处理保证,并允许为几乎任意的实时计算实现精确一次的消息传递语义。 就其本质而言,在你不太了解的问题领域中 hacking things out 并不断迭代会导致一些马虎的代码。 面向痛苦编程 (suffering-oriented programming) 程序员最重要的特征是对重构的不懈关注。 这对于防止accidental complexity破坏代码库至关重要。

结论

用例在面向痛苦编程 (suffering-oriented programming) 中至关重要。 它们的价值堪比黄金。 获取用例的唯一方法是通过 hacking 获得经验。 大多数程序员都会经历一定的进化。 你开始努力使事情正常运转,并且你的代码绝对没有结构。 代码马虎,并且普遍存在复制/粘贴。 最终,你将了解结构化编程和尽可能多地共享逻辑的好处。 然后,你将学习制作通用抽象和使用封装以使推理系统更容易。 然后,你会沉迷于使所有代码都通用,并使事物可扩展以实现程序的前瞻性。 面向痛苦编程 (suffering-oriented programming) 拒绝你可以有效地预测你当前没有的需求。 它认识到,在没有对问题领域有深刻了解的情况下尝试使事物通用化将导致复杂性和浪费。 设计必须始终由真实的,切实的用例驱动。

你应该在 Twitter 上关注我here 28 Comments | Share ArticleShare Article

Post发表新评论

在下面输入你的信息以添加新评论。 作者:(忘记存储的信息) 作者电子邮件(可选): 作者 URL(可选): 发表: | 允许使用一些 HTML: 通过电子邮件通知我后续评论。 版权所有©2012-2019,Nathan Marz。 版权所有。