Apache Storm 最近成为了ASF的顶级项目,这对于该项目和我个人而言是一个重大的里程碑。很难想像4年前Storm只是我脑海中的一个想法,但现在却成为了一个有着大社区支持并被无数企业使用的繁荣项目。在此我将在本文中回首Storm的成长历程及其经验教训。
我会根据我当初必须要克服的主要挑战来涵盖Storm历史的相关主题。本文前25%是关于Storm是如何构思并初创的, 所以主要讨论促使我开发这个项目的技术问题。其余部分是关于Storm的发布并由活跃用户和开发者社区将其发展成一个广泛使用的项目的发展过程。本文主要讨论了Storm的营销,传播和社区的发展。 任何成功的项目都要满足两个条件: 1. 它解决了一个实用的问题 2. 你有足够的能力说服很多人使他们相信你的项目是解决他们问题的最佳方案。 我认为很多开发者难以理解的是实现第二个条件与构建项目本身一样困难和有趣。我希望在阅读Storm的历史时能给你一些启发。 Storm来源Storm来自于我在BackType的工作. 在BackType我们的工作是产品分析以帮助用户实时的了解他们的产品对社交媒体的影响,当然也能查询到历史记录. 在Storm之前,实时部分的实现用的是标准的队列和worker的方法. 比如, 我们向一个队列集合里面写入Twitter firehose, 再用Python worker从这个队列集合读取tweets并处理他们. 通常情况下这些worker需要通过另一个队列集合向另一个worker集合发送消息来进一步处理这些tweets. 我们非常不满意这种处理方式. 这种方法不稳定--我们必须要保证所有的队列和worker一直处于工作状态--并且在构建apps它也显得很笨重. 我们写的大部分逻辑都集中在从哪发送/获取信息和怎样序列化/反序列化这些消息等等. 但是在实际的业务逻辑里面它只是代码库的一小部分.再加上一个应用的正确逻辑应该是可以跨多个worker,并且这些worker之间是可以独立部署的. 一个应用的逻辑也应该是自我约束的. 初探在2010年12月,我完成了第一个重大实现。也就是在那时我想出了将"stream"作为分布式抽象的想法。stream会被并行地产生和处理,但它们可以在一个程序中被表示为一个单独的抽象。这使我产生了"spout"和"bolt"的想法-spout生产全新的stream, 而bolt将产生的stream作为输入并产出stream。这就是spout和bolt的并行本质, 它与hadoop中mapper和reducer的并行原理相似。bolt只需简单地对其要进行处理的stream进行注册,并指出接入的stream在bolt中的划分方式。最后,我所想到的顶级抽象就是"topology"-由spout和bolt组成的网络。
我在BackType测试了这些抽象的用例,并且它们之间契合地非常好。我对于它的结果非常满意:我们之前需要处理的繁重工作-发送/接收消息,序列化,部署等都能通过这些新的抽象实现自动化。 在开始构建Storm之前,我想用更广泛的用例集来验证我的想法。所以我发了这条微博:
有一群人回应了我,并且我们通过邮件来相互交流。很明显,我的抽象非常非常合理。
然后我开始了Storm的设计。在我尝试找出spout和bolt间传递消息的方式时我很快就被卡住了。我最初的想法是模仿我们之前采用的队列和工人方法并使用一个像 RabbitMQ 的消息代理来传递中间消息。我实际花费了大量时间来研究RabbitMQ用于此目的的方案和操作上的影响。但是,为中间消息使用消息代理的想法似乎并不好,于是我决定暂时搁置Storm直到我能想到更好的方法。 再探我认为需要那些中间消息代理的原因是为数据的处理提供保障。如果一个bolt处理消息时失败了,它可以从取得该消息的代理中重试。但是对于中间消息代理,有很多问题困扰着我:
直觉告诉我,还有一种不使用中间消息代理也能实现消息处理保障的方式。所以我花费了很长时间思考在spout和bolt间直接传递消息时该如何保障消息的处理。不便用中间消息持久化,这意味着需要从消息来源(spout)中进行重试。棘手的是失败可能发生在spout下游的任何地方或另一台服务器上,并且这些失败需要精准检测到。 在苦思冥想了几周后我突然灵光一现。我开发了一个基于随机数和异或运算的算法,它只需大约20字节就可以跟踪每个spout tuple, 不论下游触发了多少处理过程。它是我研究出的最优算法之一,它也是在我生涯中有限的几次,可以说如果没有接受良好的计算机科学教育我是不会想出的算法。 在想出这个算法之后,我知道我已经取得了重大突破。因为避免了上面提及的所有问题,所以它大大简化了storm系统的设计,并提供了一种更加高效的方式。(有趣的是,在我想出这个算法的当天,我还有一个跟最近认识的女孩的约会。但我对该发现是如此激动以致于在整个约会期间我都心不在焉。不用说,我对不住那女孩.) 构建第一个版本在下面的的5个月里,我构建了Storm的第一个版本。从一开始我就知道我会开源,因此一开始我在心里就做了一些关键的决定。首先,我用Java实现了Storm的所有API,但用Clojure来实现Storm。通过将Storm的API 100%的Java实现,以确保它有一个非常大的潜在用户群体。而使用Clojure来实现,我能够更高效以使项目进展地更快。 一开始时我也计划在非JVM的语言中使用Storm。拓扑被定义为Thrift的数据结构并提交了一个Thrift的API。除此之外,我设计了一个协议使得spouts和bolts可以在任何语言中的实现。Storm可以应用在其他语言让更多的人使用了项目。它让人们迁移到Storm中更容易,因为他们不必用 JAVA 重写现有的实时处理器。相反,他们可以迁移现有的代码运行在Storm的多语言的API上。 我是Hadoop的长期用户,用我已有的Hadoop经验来设计Storm使得Storm会更好.比如, 在Hadoop的workers不关闭,所有的进程不做任何操作的情况下。这些”僵死进程“积累到一定程度用尽了所有的资源,最终会导致这个集群停止不能运作--Hadoop最严重的问题之一. 这个问题的关键在于Hadoop中关闭worker的工作是由它自身负责。但是有时会因为其他很多原因导致worker自我关闭失败. 所以在Storm的设计里面,我把关闭worker的任务交给第一次启动这个worker的daemon负责.最后也证明Storm的这种设计比Hadoop更鲁棒,也不存在”僵尸进程”的问题.
我在Hadoop中遇到的另一个问题就是如果JobTracker因为某种原因停掉,那么在这个JobTracker跑的所有的任务都会终止.真正让人着急的是已经跑了几天的任务就这样被停止了.在Storm里面不会存在单个节点失败的问题,因为“topologies"是一旦开始就不会停止的。因为设计Storm时就加入了”进程容错“机制:一个Storm daemon的停止和重启对一个正在运行的topologies绝对不会有影响. 当然这种考量会使设计面临更多的挑战,因为你必须要考虑到进程可能在任何时候用kill -9强行停止和重启的情况,但是这样也使它更健壮。 在开发阶段的早期我做的一个很关键性的决定就是让我们的一个实习生--Jason Jackson-- 在AWS上做一个Storm的自动部署工具.这个工具在很大程度加快了Storm的开发,因为它能够让我很容易的测试不同大小的集群和配置, 并且迭代更快. |