##最小的分布式系统(3)

架构图8

日志的作用有两个:当构建日志的数据块通过消息队列进来时,更新数据库对应行,然后推送它到Pusher用于实时的用户界面更新。

日志块以流的形式在同一个时间从不同的进程中进来,然后被一个进程处理。这个进程每秒最高可处理100个消息。

架构图9

一般情况下这样处理日志流的方式也相当OK,但是这也意味着我们很难处理某些时刻突然增长的日志消息,因此这个唯一的进程对于我们系统的扩展会成为一个很大的障碍。

问题在于,进程是按照这些消息到达消息队列的先后顺序来进行处理的,而Travis CI中的所有事情都依赖于这些消息。

更新数据库里的一条日志流意味更新包含所有日志的一行数据。更新用户界面的日志当然意味着在DOM树上附加一个新的结点。

为了解决这个棘手的问题,我们需要改很多代码。

但是首先,我们需要理清楚什么才是一个更好的解决方案,好的解决方案应该是能够让我们很方便的扩展日志处理的部分。

我们决定让处理的顺序作为消息本身的一个属性,而不是隐含的依赖于它们被放进消息队列的顺序。

架构图10

这个想法是受到Leslie Lamport于1978年发表的一篇论文《Time, Clocks, and the Ordering of Events in a Distributed System》的启发。

在这篇论文中,Lamport描述了在分布式系统中,使用递增计数器来保留事件发生的顺序的方法。当一个消息被发送,发送者会在消息被接收者接收到之前增加计数器的值。

我们可以简化那个想法,因为在我们的场景中一个日志块只能来自一个发送者。进程只要不断增加计数器的值,就可以让之后的日志收集工作变得简单。

剩下的工作就是根据计数器的值来对日志块进行排列了。

困难之处在于,这样设计之后等同于允许向数据库写入小的日志块,这些小日志块只有在对应任务结束后才会写入到完整的日志中。

但是这会直接影响到用户界面。我们不得不面对消息以无序的方式到来。这个变化的确影响的范围大了些,但它反过来简化了很多部分的代码。

从表面看,这个改动似乎无关紧要。但是依赖于你本不需要依赖的顺序会带来更多潜在的复杂性。

我们现在不用依赖于信息是如何传送的,因为现在我们可以在任何时间得到他们的顺序。

我们修改了不少代码,因为那些代码做了一个假设,任何信息都是顺序过来的,而这个假设是完全错误的。在分布式系统中,事件可以以任何顺序在任何时间到达。我们只需要确保之后我们可以将这些片段重新组装回去。

你可以从我们的博客获取这个问题更详细的说明。

到了2013年,我们每天已经在运行45000次构建。我们还是在为早先的设计付出着代价,但是我们也在慢慢的改进设计。

架构图11

我们现在还有一个麻烦。系统所有的组件还是在共享同一个数据库。如果数据库出现问题,自然的所有组件都会出现问题。这个故障上周我们刚刚遇见一次。

这同样意味着日志写入的数量(目前可以达到每秒300次)影响到了我们API的性能,当用户浏览我们的用户界面时可能会慢一点。

另外,当我们从构建任务的数量上考虑时,我们的下一个挑战就是如何去扩展我们的数据容量。

Travis CI在500台构建服务器上运行,这已经不能再算是一个小的分布式系统了。我们现在正着手解决的问题还是从一个相当小的维度来考虑的,但即便在那个维度上,你也能够遇到很多有趣的挑战。根据我们的经验,简单直接的解决方案总是比那些更复杂的要好。