51

为什么Segment会从微服务退回单体架构?

 5 years ago
source link: http://www.infoq.com/cn/news/2018/07/segment-microservices?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

近年来,我们发布了很多文章介绍企业向微服务迁移的成败经验。最近, Segment的Alexandra Noonan 写了一篇文章,讲述了他们从单体架构迁移到微服务,之后又退回单体应用的经历。文中Alexandra具体介绍了他们从原来的简单架构迁移到微服务的过程:

我们原来有个API负责拦截事件并将它们转发到一个分布式的消息队列中。这里的一个事件指的是由网页或移动应用生成的,包含用户与用户动作信息的JSON对象。当队列中的事件被消费后,系统会检查用户设置来决定接收事件的目标。(……)之后事件被逐个发送到每个目标的API。这样的流程很合理,因为开发者只需要将事件发送到Segment的API这一个目标即可,无需创建几十个集成。

如果事件交付失败,就会被系统重新加入队列,也就是说有时工作者进程要一边发送新的事件,一边尝试重新发送之前失败的事件。这样会导致所有目标都出现延迟,Alexandra解释说:

为解决最紧迫的阻塞问题,团队为每个目标各创建了一个独立的服务和队列。这一新架构包括一个新的路由进程,它会接收入站事件并向每个选定的目标分发一份该事件的拷贝。现在如果某个目标出现问题,只有它自己的队列会回溯,不会影响其它目标。这一微服务化的架构将各个目标独立开来,这样当某个目标又出现常遇到的问题时就会非常有用。

文章之后写到,Segment的开发团队一开始将所有代码存放在一起,但这引发了许多问题:

最大的麻烦在于,只要一个测试崩溃,那么所有目标的测试都会失败。当我们试图部署一项改动时,我们必须先费劲修复崩溃的测试,就算改动与一开始的变动毫无关联也得这样做。为了解决这个问题,我们决定将代码拆分到每个目标各自的存储库里。

这样一来开发团队的灵活性的确改善了许多。然而随着目标数量的增加,存储库的数量也在同步增长。为了让开发者免受维护这么多代码库的麻烦,Segment团队创建了许多共享库,存放所有目标通用的变换和功能。这组共享库大大减轻了他们维护工作的压力。但这个措施也有不太容易发现的负面影响:向共享库更新并测试改动会花费大量时间,还会增加破坏无关目标的风险。最后这些库开始分裂为不同的版本,各自不统一,引发了之前没有想到的一个问题:每个目标的代码库都会依赖不同版本的共享库。Alexandra承认,他们当时可以开发工具来自动将改动更新到这些库中,但那时他们又遇到了这个微服务架构产生的一些新问题。

新出现的问题是,每个服务都有自己的载入模式。有些服务一天处理几个事件,有的服务每秒就能处理几千个。如果目标只处理很少的事件,工作者线程就得在出现载入问题时手动扩展服务以满足需求。

他们的系统集成了自动扩展能力,但因为每个服务都需要指定CPU和内存资源分配,调整自动扩展的设置“更像玄学而非科学”。如前所述,每次存储库的数量增长时他们都要增加目标,最后团队平均每月要增加三个目标,当然还要加上更多的队列和服务。

2017年初, Segment的一个产品 的核心部分使我们达到了峰值负载。当时的情况下我们好像在从微服务的大树上摔下来,一路撞上了所有的树枝。我们这个小团队非但没有提升效率,反而陷入了愈加复杂的泥潭。这个架构的核心优势都成了负担。我们的速度暴降,故障率却在暴增。(……)于是,我们决定回退一步,重新考虑整个流程。

文章最后Noonan回顾了他们如何摆脱这个微服务架构,其中他们还开发了 Centrifuge 来替换所有独立的队列,将所有事件都发送到一个单体服务上。他们还将所有目标的代码都迁移到一个存储库里,不过这一次新增了一些代码管理的规则:所有目标都要使用同一个版本,每次更新时同步更替到新版本。他们再也不用操心各个独立版本之间的差异了,因为所有的目标都在使用一个版本,以后也是如此。对于开发者来说,管理越来越多的目标所花费的时间减少了,风险也降低了。

Noonan的文章还写了很多内容,都是关于他们退回单体服务的经历。感兴趣的读者应该去仔细读一下,文章里面有很多架构细节、关于存储库架构的思考和建立弹性测试集的方法。最后,团队将回退的好处总结如下:

2016年时我们还在使用微服务架构,我们为共享库带来了32项改进。而仅仅今年到现在我们就做出了46项改进。过去半年来我们为库带来的改进比2016年全年都多。因为所有的目标都处于同一服务内,我们可以很好地搭配CPU密集型服务和内存密集型服务,所以扩展服务以满足性能需求变得非常容易。更大的工作者池能载入更多内容,所以我们不再需要将处理少量载入的目标挂起到页面了。

不过这个架构回退过程也有一些负面影响,包括:隔离错误变得更困难(一个目标的错误导致目标崩溃,结果会传染到所有目标);升级一个目标的版本可能会破坏其它一些目标,于是后者也需要升级。Noonan在文章最后写下了诚恳的总结:

在微服务和单体架构之间做选择时,要注意它们各自都有自己需要考虑的因素。我们的架构中有些部分是微服务表现更出色,但服务端的目标迁移到微服务后的一系列麻烦是一个很好的教训,证明这一流行趋势在某些情况下能对生产力和性能有多大负面影响。结果对于我们来说,单体架构才是最终解决方案。

其实他们关于微服务的某些看法是很眼熟的。今年早些时候我们报道说,ThoughtWorks根据观察认为微服务尚未进入普及周期。当时的报道写到:“主要原因之一是很多组织并没有为微服务做好准备,他们缺少一些关于运营和自动化的基础实践”。此外,Jan在另一篇文章中总结了多年来微服务迁移的失败案例。Berico科技的首席软件工程师 Richard Clayton 提到了他们当时遇到的一个问题:

在不同服务之间共享通用功能代码,以消灭各个服务中的重复功能的努力却带来了巨大的负面影响,最终导致了大规模回退。

回到原文,有很多关于这个话题的讨论,比如 Hacker NewsReddit 上的这些;有些讨论者认为与微服务无关的一些因素可能导致了这些问题。比如,有些评论指出Noonan的文章并没有引用CI,只有CD,起码这是一个奇怪的组合。还有 评论 认为不止微服务会引发这些问题,所有的分布式系统都是一个样。关于这一点我们之前也提到过,有人使用SOA时有过类似的经验:

我曾在一个类似的代码库中工作过,那时他们管它叫SOA,云还没开始流行。对服务的每次调用都会启动一个完整的服务实例。我想我们应该强制将网络延迟规定为架构设计的要素之一。

有趣的是很多讨论串谈到了微服务中数据上下文的问题。这个话题我们探讨过很多次,这也是微服务反对者的主要论据之一。HackerNews的一条评论举例说:

比这还糟呢。据我观察多数微服务架构根本就没考虑一致性(“我们才不要乱七八糟的事务!”),盲目地随大流还乐在其中。我搞不懂为啥子人们会觉得,把软件模块拆分开来然后用缓慢不可靠的网络和弱爆的手动连接REST处理串起来,就能神奇地让架构面目一新哩?我觉得人们产生这种生产力幻觉的原因是:”我把这些都搞定啦,现在我也有一套’管它是什么即服务‘的先进玩意儿喽!看看那酷毙的数据面板上闪烁的小绿灯吧,我们可是为了它干了好几个月呢!“

另外,为微服务定义域是多年来我们一直强调的微服务部署关键环节。有一篇PPT介绍了如何使用DDD解构单体应用,Reddit的一个讨论串也谈到了这一点:

建立一个出色的微服务架构是很难的。我现在觉得关键在于恰当地分隔你的域,当系统进化时持续关注这一层面。微服务并不像它的名字那样,它不必非得那么小,但是要搭配适合这个架构的元素。很多人的失败正是因为忽视了这一点。

其他人怎么看?比如说,Segment的微服务架构出现的问题能否用其它方式解决,无需退回单体应用?或者一开始的单体架构是否有办法进化得更好,解决原来的问题,而无需切换到微服务?

查看英文原文: Why Segment Returned to a Monolith from Microservices

感谢冬雨对本文的审校。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK