acm-header
登录

ACM通信

实践

Hootsuite:追求反应系统


Hootsuite:在追求反应系统,插图

图片来源:Hootsuite Media Inc.

回到顶部

总部位于加拿大温哥华的Hootsuite是应用最广泛的社交媒体管理SaaS(软件即服务)平台。Hootsuite从2008年不起眼的起步到现在,已经成长为一家价值数十亿美元的公司,在全球拥有超过1500万用户。

随着Hootsuite多年来的发展,技术堆栈也在不断发展。一个关键的变化是从LAMP (Linux, Apache, MySQL, PHP)转移到微服务。然而,向微服务的转变并非没有挑战。在这次圆桌聊天中,我们将讨论Scala和Lightbend(它提供了一个响应式应用程序开发平台)如何成为成功过渡的重要组成部分。交换对象包括Lightbend的CTO Jonas Bonér;Terry Coatta,海洋学习系统首席技术官;Edward Steel, Hootsuite的高级Scala开发人员;Hootsuite首席软件开发人员Yanik Berube;以及Hootsuite软件开发高级总监肯•布里顿。

特里COATTA:我很好奇Hootsuite在转向微服务时最初想解决的一系列问题。你能提供一些细节吗?

爱德华。钢:最主要的是,这与我们在Twitter上发生相关事件时向用户移动设备发送通知的能力有关。当我们开始考虑如何处理这个问题时,我们已经为数十万用户提供了服务,每个用户都根据自己的特定兴趣定制了单独的订阅。需要的是能够与Twitter的流媒体终端保持连接的东西。

COATTA:我猜在您做出这一举动的同时,您也采取了一些步骤,从PHP转移到Scala。那开什么?

钢:最初,它与学习其他一些组织使用Scala所取得的成功有很大关系。例如,这是在Twitter决定使用Scala之后,这显然为它提供了很多合法性。另外,第一个使用Scala的团队来自不同的背景。我们有一些人游说使用一种更强类型的函数式语言——某种类似于Haskelland的语言,还有一些人拥有Clojure和Java的经验。考虑到所有这些,我猜Scala似乎满足了大多数要求。

乔纳斯大错:您认为Scala的主要好处是什么?是语言本身的功能特性吗?还是与生态系统中可用的库有更多关系?

钢:语言本身是其中最重要的部分。Scala的主要倡导者当时正在黑莓客户端工作,所以他有很多JVM (Java虚拟机)的知识,但也对Java本身感到沮丧。我猜他只是在寻找更好的方法。我们的另一个想法是构建一个分布式系统,利用Akka作为一个可用的库。这也是决定的重要部分。

事实上,我认为我们能够从一开始就使用一些演员。这是Akka非常早期的版本,但它仍然提供了许多我们认为有用的引人注目的功能。

愚蠢的错误:那时你是否已经考虑过微服务,甚至在它成为流行词之前?还是更喜欢与无共享架构、强隔离和松耦合等相关的反应性原则?

钢:微服务总是在我们的脑海里。我们已经有了一些用PHP编写的批处理流程,那时它们开始运行作业。这种做法是可行的,但它远非一种理想的做事方式。因此,我认为我们已经开始对一个我们可以构建一些服务的系统产生了兴趣,我们的想法是,也许我们可以转向面向服务的体系结构。微服务的概念是在稍晚的时候才出现的,这可能是受到了当时行业中一些热门话题的影响。

COATTA:您已经多次提到了Akka,您能谈谈它在这里的作用吗?

钢:至少在当时,Akka系统对JVM的主要吸引力在于,它为Erlang社区以外的人提供了访问参与者模型的权限。actor的特点是,它们都是基于消息的,而且具有高度的弹性,也就是说,即使它们崩溃了,它们通常也可以以一种有用的方式恢复。这可能解释了为什么Erlang经常被用于开发弹性电信系统。当您讨论分布式系统或希望发送大量消息的系统时,您可以期待参与者模型真正发挥作用。

YANIK BERUBE:就我们当前的基础设施而言,我们应该注意到我们所有的微服务都是由Akka驱动的。在内部,我们有一个通过Akka处理请求和响应的服务器类型库,我们也至少有一个(如果不是更多的话)后端服务,它们使用一组参与者在特定的时间间隔内完成工作。

COATTA:当我想到Erlang参与者系统时,除了您已经提到的独立性之外,我想到的一件事是它非常细粒度。所以我想知道,鉴于你们专注于向用户移动设备发送通知,你们是否可能需要每个用户对应一个actor来处理这个问题?

钢:对我们来说,没有。但在我们第一次建立我们的系统时,这种症状就已经出现了。当我们开始时,我们学习了更多关于我们应该如何构建系统的知识,以及关于角色真正应该如何工作的知识。一开始,我们肯定落入了将太多逻辑放入单个参与者的陷阱,例如,将恢复逻辑放入每个参与者,而不是依赖于监督层次结构,这将使我们的编码防御性更低。事实证明,最好是接受“让它崩溃”的哲学,因为这实际上提供了很多健壮性。


JONAS BONÉR:我发现很有趣的一点是Akka和Erlang似乎是唯一强调拥抱和管理失败的平台或库,也就是说它们基本上是为弹性而设计的。


我们还了解到,当您将关注点划分为单一用途的参与者时,该模型真的非常出色。除了有助于阐明设计之外,它还在部署点的可伸缩性和配置灵活性方面提供了许多机会。

愚蠢的错误:这也适用于微服务。也就是说,关于微服务的定义有很多不同的观点。这个词对你来说是什么意思?这在概念上是如何映射到你如何看待演员的?

BERUBE:在内部,我们仍在努力定义什么是微服务以及它的规模应该是什么。今天,我们的大多数服务只专注于完成一组高度相关的任务。我们的目标是让每个服务都拥有域模型的一部分。例如,每个数据服务将控制自己的数据,其他任何东西都不能访问这些数据。要获得该数据,必须通过数据服务本身。

然后我们还会有功能服务,这些服务本质上是将业务逻辑粘到逻辑的数据部分上。但就这些东西的规模而言,我想说的是,我们仍在努力弄清楚,到目前为止,我们还没有提出任何硬性规定。

愚蠢的错误:我猜你们每个微服务都有自己的数据存储。如果是这样,这些都是状态的,那么你如何能够确保在中断时的弹性?

钢:这些服务都绝对拥有自己的数据。当涉及到更换单片系统的部分时,通常归结为处理LAMP MySQL数据库中的一个表或几个相关表。一般来说,就需要考虑的服务而言,空间是非常小的。它基本上只是一个检索和创建数据的问题。

BERUBE:我想说的是,我们在数据存储方面使用了各种不同的技术。而且,是的,我们确实来自LAMP堆栈,因此仍然严重依赖MySQL,但我们也使用MongoDB和其他数据存储技术。

这些服务通常都封装了数据的某个区域。至少在理论上,它们每个都应该拥有自己的数据,并依赖于专门用于它们的数据存储。因此,为了提高效率和性能,我们最近开始考虑在服务本身中存储更多的数据。

COATTA:那么,明确地说,是否存在一个单独的数据访问层,服务可以从该层拉入它们需要操作的任何信息?

钢:是的,但是您不会看到多个服务访问同一个数据存储。

愚蠢的错误:在推出更新方面,您是如何管理的呢?您是否有部署更新、删除更新和回滚更新的机制?此外,这些服务是独立的吗?如果是,你是怎么做到的?如果没有,你要做什么来减少停机时间?

BERUBE:现在,每个服务都使用代理/工作者基础结构。任何服务都不能访问另一个服务,除非经过代理池,然后代理池会将请求重新分发给多个工作者。这使我们能够通过在经纪人后面安排更多的工人来进行规模扩张。然后,当涉及到部署时,我们可以为这些工作人员跨目标服务器进行滚动部署。通过这种方式,我们能够逐步部署服务的最新版本,而不会影响用户体验,也不会影响在重新部署时需要使用该服务的任何其他服务的体验。

钢:我们最近从LAMP端提取的另一件被证明对频繁发布很有用的事情是特性标记。显然,当我们只使用一堆Web服务器时,这要容易得多,因为我们有一个中心位置来处理它。但最近我们将这些功能迁移到HashiCorp的Consul上,以提供一个分布式的、强一致性的存储,现在我们可以在关闭一切的情况下在Scala端部署代码。

愚蠢的错误:当你在做这些事情的时候,为了保持一个整体,你认为转向微服务的最大好处是什么?

钢:关于如何扩大团队规模,我认为在系统中定义良好的边界要容易得多,因为这意味着您可以与那些只对整体产品如何工作有大致想法的人一起工作,但通过对他们个人负责的特定服务有强大的、深入的知识来增强这种想法。

我还认为微服务方法在操作上给了我们更多的控制权。当这些需求出现时,扩展和替换系统的特定部分变得容易得多。

BERUBE:一个明显的例子是我的团队一直在研究的数据服务。就我们存储的数据量而言,这是一项非常大的服务,我们知道,考虑到我们使用的存储技术,要扩大规模将是一个挑战。隔离数据服务背后的所有数据的能力使我们更容易实现必要的更改。基本上,这给了我们更多的控制权来改变我们在后台使用的持久技术。所以我当然认为这是一个巨大的胜利。


TERRY COATTA:当我想到Erlang参与者系统时,除了它的独立性之外,我想到的一件事是它非常细粒度。


迁移到Lightbend堆栈支持的响应式微服务模型的一些好处几乎是立即显现出来的,因为Hootsuite工程团队开始发现在他们之前在LAMP堆栈上运行的底层物理和虚拟基础设施(在那里每个请求都有一个流程)上缩小规模的机会。因此,很明显,响应式微服务模式下的业务对其资源的压力要小得多。

事实上,Hootsuite的工程师们很快就认识到,如果继续采用LAMP堆栈中有意义的一些实践,他们实际上会失去过多依赖Lightbend堆栈所带来的许多好处。例如,他们发现更多地使用Lightbend堆栈支持的模型类有一个真正的优势,因为这些类配备了数据层知识,可以证明在动态面向web的系统中非常有用。

同样,他们认识到,通过使用单个参与者来运行系统的大量部分,而不是将这些组件分解为参与者组,他们在不知不觉中剥夺了Akka提供的一些功能,这些功能用于分别调优系统的各个部分,并行化它们,然后将工作高效地分配给能够分担负载的多个不同参与者。

他们还学到了一些其他的东西……


EDWARD STEEL:由于我们能够通过配置方式来改变参与者的特征,我们已经能够使这个核心框架适应各种服务的所有类型的有效负载和流量配置文件。


COATTA:到目前为止,我们只讨论了一些一般性问题。现在我想听听你们在工程上遇到的一些更具体的挑战。

愚蠢的错误:我想知道的一件事是,您主要是在做反应式扩展、预测式扩展,还是这两者的某种组合,以优化您的硬件。

BERUBE:至少就目前而言,我们的负荷并没有发生太大变化。或许我该说的是,它们每天都在变化,但可以预见的是,每一天都是如此。我们设计服务的方式是在经纪人之后运行,这意味着我们可以根据需要剥离更多的工人。结合AWS (Amazon Web Services)的一些优秀工具,我们能够快速适应不断变化的工作负载。

钢:我们决定做的一件事是使用ZeroMQ构建一个框架,以支持不同PHP系统之间的进程通信。但后来我们发现,我们可以很容易地把所有这些都引入阿卡。

COATTA:我认为,使用Akka,您将更容易实现坚持参与者范式的目标,同时还可以利用更好的恢复机制和更细粒度的控制。

钢:是的,但我认为关键在于,由于我们能够通过配置方式来改变参与者的特征,我们已经能够使这个核心框架适应各种服务的所有类型的有效负载和流量配置文件。我们可以说,“此服务正在使用阻塞数据库”,这时将提供一个大型线程池。如果服务恰好处理两种不同类型的作业,我们可以将它们分离到不同的执行轨道中。

最近,我们也在一些情况下成功地使用了熔断机制,在一些情况下,由于一些第三方的介入,我们有一些渐进的暂停。但现在我们可以切断连接,继续。其中大部分都是免费的,因为Akka提供了所有的工具。我们已经了解到,通过保持设计尽可能简单,我们可以更好地利用这些工具。

愚蠢的错误:我还发现了一件非常有趣的事情,Akka和Erlang似乎是唯一强调拥抱和管理失败的平台或库,也就是说,它们基本上是为弹性而设计的。获得最大收益的最好方法是,从第一天起它们就成为你申请的一部分。这样,您就可以在架构的核心完全接受失败。

但是,话虽如此,这种新发现的对失败的拥抱在实践中是如何对那些来自其他环境、心态非常不同的开发人员起作用的呢?这是他们能够在很短的时间内接受并开始感到自然的事情吗?

BERUBE:对一些人来说,这实际上需要一个相当大的心态转变。但我认为至少Akkaor的演员模型让我们更容易理解这一点的好处,因为你必须相当明确地表明你作为主管是如何处理失败的。也就是说,作为一个从其他演员身上衍生出来的演员,如果你的一个童星失败了,你必须有适当的规则来规定应该发生什么。但是,是的,人们必须接受教育。即使这样,还是需要一点时间去适应。

现在,随着新开发人员的加入,我们看到他们求助于处理异常的更传统模式。但一旦你意识到把这个问题留给上级管理阶层是多么明智,通常就没有回头路了。通过这种方式,您的代码会变得简单得多,这意味着您可以将重点转向确定每个参与者应该负责什么。

COATTA:抽象地谈论行动者框架是一回事,但一旦实际应用起来,它会是什么样子呢?例如,您如何处理失败的情况?

BERUBE:你可以让actor失败,这意味着它将会死亡,然后将通知发送给监控器。然后,根据失败的严重程度或性质,主管可以决定如何处理这种情况——这是否意味着剥离一个新的参与者或简单地忽略失败。如果问题看起来是系统实际上应该能够处理的,它将使用一个新的参与者本质上再次发送相同的消息。


YANIK BERUBE:从这一切中得到的一个奇妙的教训是,它让我开始思考系统在处理故障和处理我们通过消息进行通信的外部代理方面是如何实际工作的。


但关键是,参与者模型允许您将所有与处理特定故障案例相关的逻辑集中在一个地方。因为参与者系统是分级的,一种可能的情况是,您最终会决定问题并不真的是最初的参与者的责任,而是应该由参与者的主管处理。这是因为创建这些层次结构背后的逻辑不仅决定了在哪里进行处理,还决定了在哪里处理失败——这不仅是组织代码的一种自然方式,而且是一种非常清楚地分离关注点的方法。

愚蠢的错误:我认为这真是一针见血。它归结为区分我们所谓的错误(用户负责处理的事情)和失败。然后,验证错误自然会返回到用户,而较轻的错误则返回到创建服务的组件。这创建了一个更容易推理的模型,而不是在任何可能发生故障的地方用try/catch语句乱扔代码,因为在分布式系统中,故障可以而且将会发生在任何地方。

BERUBE:从这一切中得到的一个奇妙的教训是,它让我开始思考系统实际上是如何工作的,在处理失败和处理通过消息与我们通信的外部代理方面。基本上,我开始思考我们应该如何处理服务之间的通信,围绕我们处理故障的方式。这是我们一直在思考的问题。

实际情况是,每当我们与外部服务通信时,我们都应该预料到一些失败。它们就会发生。这意味着我们不应该指望一些外部服务在短时间内响应。我们希望显式地设置超时。然后,如果我们看到服务开始迅速或频繁地出现故障,我们就知道是时候启动断路器来缓解该服务的压力了,不要让这些故障影响到所有服务。我不得不说,当我们第一次开始与Akka合作时,这是我没有预料到的额外好处。


KEN BRITTON:很明显,在使用微服务时,框架和标准对于开发团队来说是多么重要。


通过使用分布式微服务体系结构来提高系统资源的利用效率是一回事。但是,这会在多大程度上转嫁给程序员额外的负担呢?毕竟,为异步分布式系统编写代码一直被认为是只有最训练有素的绝地武士才敢涉足的领域。

对于更习惯于在同步环境范围内工作的程序员来说,可以做些什么来简化向响应式微服务环境的过渡?他们通常对资源状态所做的所有假设不会经常被违背吗?如何在合理的短时间内让一个庞大的程序员团队提高并发学习曲线?

以下是Hootsuite团队了解到的情况:

COATTA:让我们来谈谈向微服务的转变对开发者的影响。特别是,我认为这意味着你在他们身上投入了比大多数开发人员所习惯的更多的异步性。我想他们现在可能还需要担心更多的数据一致性问题。

BERUBE:尽管还没有完全解决异步问题,但是Scala Futures(用于检索并发操作结果的数据结构)实际上使处理异步计算变得非常容易。我的意思是,还是需要一些时间来适应任何事情都可能失败的事实。但是,有了Scala Futures,相对不太熟悉的程序员实际上很容易学会如何在异步世界中表达自己。

钢:如果您从基于线程的并发性的角度来考虑这个问题,那么对于许多用例来说,这似乎比从future的角度来考虑要可怕得多。另外,当您直接在参与者内部工作时,即使消息是异步传播的,而且系统同时在做上千件事情,您也与同步任何状态修改所需要的操作绝缘,因为参与者一次只处理一条消息。

肯·布里顿:我注意到,当开发人员第一次开始编写Scala时,他们最终得到的是这些深度嵌套的、控制流风格的程序。您可以在命令式语言中看到很多这样的情况,但并不会因此受到惩罚。然而,在强类型函数式语言中,通过复杂的层次结构排列类型要困难得多。开发人员很快就会认识到,编写小的功能块,然后用这些功能块组成程序更好。

Akka在这方面更进一步,鼓励你用消息打破逻辑。我观察到一种常见的演化模式,即开发人员一开始使用这些非常庞大、复杂的参与者,但后来发现他们可以将Future输送到自己的参与者或任何其他参与者。事实上,我已经见证了许多让开发人员顿悟的时刻,这些工具实际上鼓励他们构建更小的可组合的软件单元。

愚蠢的错误:这也符合我的经验。参与者是非常面向对象的,它们封装了状态和行为——我认为所有这些都可以很好地映射到传统的方法。另一方面,期货可以通过所有这些小的、无状态的、一次性的东西来实现功能性思考。但你有没有发现你真的可以让这些来自两个截然不同的宇宙的东西很好地结合在一起?你是把它们混在一起还是分开?

BERUBE:我们已经同时使用了它们,我认为它们这样工作很好。Ken提到了创造未来的想法,然后将它们传送给作为演员的自己或其他演员。我认为这种模式很有效。它既简单又优雅。

钢:我必须承认,我在这种思维转变的早期有点磕磕绊绊。但是,是的,我想说的是,在大多数情况下,我们能够成功地融合演员和未来。

愚蠢的错误:你觉得某些类型的问题更适合于其中一种还是另一种?

钢:在我们更简单的服务中,请求到代码的路由都是基于参与者的,然后实际的业务逻辑通常被编写为对生成Futures的其他事物的调用。我想,当你在考虑基础设施和管道时,很自然地会想到参与者。另一方面,业务逻辑可能更容易映射到功能观点。

布里顿:我们还发现,丰富的面向对象模型对我们的消息传递很有帮助。例如,我们已经开始定义更丰富的成功和失败消息,其中包含足够的细节,以让参与者确切地知道如何响应。因此,现在我们的消息层次结构已经扩展到封装大量信息,我们认为这很好地将面向对象的概念与参与者模型对齐。

COATTA:当您谈到您的环境时,我想到的一件事是,您似乎不仅从单一体系结构转移了,而且从某种意义上说,从单一技术转移到了更广泛的技术阵列。所以我想知道你现在是否觉得在那种环境下工作,以及培训员工在这种环境下工作更加困难。以前可能只需要找到一些精通PHP的新员工就足够了,而现在你有了ZeroMQ、actors和Futures以及其他许多东西让他们思考。毫无疑问,您的环境变得更加复杂。但是,从某些方面来说,它现在实际上也是一个更容易操作的地方吗?

BERUBE:我认为,将逻辑划分为许多不同的自包含服务的做法在某种程度上使推断系统如何工作变得更容易。但我们还没有结束。仍有大量的工作要做,有许多具有挑战性的领域要继续推理。

当然,环境也变得更加复杂了。我不得不同意这一点。但是引入所有这些技术的好处大于坏处,因为我们现在有更多的抽象层可以利用。我们的团队对大局有全面的了解,但主要专注于他们非常了解的一些微服务。随着我们的业务规模不断扩大,这种方法将为我们的业务带来巨大的好处。

布里顿:很明显,在使用微服务时,框架和标准对于开发团队是多么重要。人们经常将微服务提供的灵活性误认为是对每个服务使用不同技术的要求。像所有的开发团队一样,我们仍然需要将我们使用的技术数量保持在最低限度,这样我们就可以轻松地培训新人,维护我们的代码,支持团队之间的移动,等等。

我们还看到了服务规模变小的趋势。我们最初的微服务实际上更像是松散耦合的宏服务。但是,随着时间的推移,我们已经将更多的部署、运行时等内容推进到共享库、工具等中。这确保了服务专注于逻辑而不是管道,同时也坚持团队标准。


版权由作者/所有者持有。授权ACM出版权利。
请求发布的权限permissions@acm.org

数字图书馆是由计算机协会出版的。版权所有©2017 ACM, Inc.


没有发现记录

登录为完全访问
»忘记密码? »创建ACM Web帐号
文章内容:
Baidu
map