最全微服务,Goodbye!服务器端我更愿意选择相信单体应用(1),2024年最新Golang黑科技保活实现原理揭秘

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

本文翻译自Alexandra Noonan 的 Goodbye Microservices: From 100s of problem children to 1 superstar。内容是描述 Segment 的架构如何从 「单体应用」 -> 「微服务」 -> 「140+ 微服务」 -> 「单体应用」 的一个历程。翻译比较粗糙,如有疏漏,请不吝指教。

注:下文说的目的地就是对应的不同的数据平台(例如Google Analytics, Optimizely)

除非你生活在石器时代,不然你一定知道「微服务」是当世最流行的架构。我们Segment早在2015年就开始实践这一架构。这让我们在一些方面上吃了不少甜头,但很快我们发现:在其他场景,他时不时让我们吃了苦头。

简而言之,微服务的主要宣传点在于:模块化优化,减少测试负担,更好的功能组成,环境独立,而且开发团队是自治的(因为每一个服务的内部逻辑是自洽且独立的)。而另一头的单体应用:「巨大无比且难以测试,而且服务只能作为一个整理来伸缩(如果你要提高某一个服务的性能,只能把服务器整体提高)」

2017 早期,我们陷入了僵局,复杂的微服务树让我们的开发效率骤减,并且每一个开发小组都发现自己每次实现都会陷入巨大的复杂之中,此时,我们的缺陷率也迅速上升。

最终,我们不得不用三个全职工程师来维护每一个微服务系统的正常运行。这次我们意识到改变必须发生了,本文会讲述我们如何后退一步,让团队需要和产品需求完全一致的方法。

为什么微服务曾经可行?

Segment 的客户数据基础设施吸收每秒成百上千个事件,将每一个伙伴服务的API 请求结果一个个返回给对应的服务端的「目的地」。而「目的地」有上百种类别,例如Google Analytics, Optimizely,或者是一些自定义的webhook。

几年前,当产品初步发布,当时架构很简单。仅仅是一个接收事件并且转发的消息队列。在这个情况下,事件是由Web或移动应用程序生成的JSON对象,例子如下:

{

“type”: “identify”,

“traits”: {

“name”: “Alex Noonan”,

“email”: “anoonan@segment.com”,

“company”: “Segment”,

“title”: “Software Engineer”

},

“userId”: “97980cfea0067”

}

事件是从队列中消耗的,客户的设置会决定这个事件将会发送到哪个目的地。这个事件被纷纷发送

到每个目的地的API,这很有用,开发人员只需要将他们的事件发送到一个特定的目的地——也就是

Segment 的API,而不是你自己实现几十个项目集成。

如果一个请求失败了,有时候我们会稍后重试这个事件。一些失败的重试是安全的,但有些则不。可重试的错误可能会对事件目的地不造成改变,例如:50x错误,速率限制,请求超时等。不可重试的错误一般是这个请求我们确定永远都不会被目的地接受的。例如:请求包含无效的认证亦或是缺少必要的字段。

微服务,Goodbye!服务器端我更愿意选择相信单体应用

此时,一个简单的队列包含了新的事件请求以及若干个重试请求,彼此之间事件的目的地纵横交错,会导致的结果显而易见:队头阻塞。意味着在这个特定的场景下,如果一个目的地变慢了或者挂掉了,重试请求将会充斥这个队列,从而整个请求队列会被拖慢。

想象下我们有一个 目的地 X 遇到一个临时问题导致每一个请求都会超时。这不仅会产生大量尚未到达目的地 X的请求,而且每一个失败的事件将会被送往重试的队列。即便我们的系统会根据负载进行弹性伸缩,但是请求队列深度突然间的增长会超过我们伸缩的能力,结果就是新的时间推送会延迟。发送时间到每一个目的地的时间将会增加因为目的地X 有一个短暂的停止服务(因为临时问题)。客户依赖于我们的实时性,所以我们无法承受任何程度上的缓慢。

微服务,Goodbye!服务器端我更愿意选择相信单体应用

为了解决这个队头阻塞问题,我们团队给每一个目的地都分开实现了一个队列,这种新架构由一个额外的路由器进程组成,该进程接收入站事件并将事件的副本分发给每个选定的目标。现在如果一个目的地有超时问题,那么也仅仅是这个队列会进入阻塞而不会影响整体。这种「微服务风格」的架构分离把目的地彼此分开,当一个目的地老出问题,这种设计就显得很关键了。

微服务,Goodbye!服务器端我更愿意选择相信单体应用

个人Repo 的例子

每一个目的地的API 的请求格式都不同,需要自定义的代码去转换事件来匹配格式。一个简单的例子:还是目的地X,有一个更新生日的接口,作为请求内容的格式字段为 dob ,API 会对你要求字段为 birthday,那么转换代码就会如下:

const traits = {}

traits.dob = segmentEvent.birthday

许多现代的目的地终点都用了Segment 的请求格式,所以转换会很简单。但是,这些转换也可能会

十分复杂,取决于目的地API 的结构。

起初,目的地分成几个拆分的服务的时候,所有的代码都会在一个repo 里。一个巨大的挫折点就是一个测试的失败常常会导致整个项目测试无法跑通。我们可能会为此付出大量的时间只是为了让他像之前一样正常运行通过测试。为了解决这个问题,我们把每一个服务都拆分成一个单独的repo,所有的目的地的测试错误都只会影响自己,这个过渡十分自然。

拆分出来的repo 来隔离开每一个目的地会让测试的实现变得更容易,这种隔离允许开发团队快速开发以及维护每一个目的地。

伸缩微服务和Repo 们

随着时间的偏移,我们加了50多个新的目的地,这意味着有50个新的repo。为了减轻开发和维护这些codebase 的负担,我们创建一个共享的代码库来做实现一些通用的转换和功能,例如HTTP 请求的处理,不同目的地之间代码实现更具有一致性。

例如:如果我们要一个事件中用户的名字,event.name() 可以是任何一个目的地里头的调用。共享的类库会去尝试判断event 里的 name 或者 Name 属性,如果没有,他会去查 first name,那么就回去查找first_name 和 FirstName,往下推:last name 也会做这样的事情。然后吧first name 和last name 组合成full name.

Identify.prototype.name = function() {

var name = this.proxy(‘traits.name’); if (typeof name === ‘string’) { return trim(name) } var firstName = this.firstName(); var lastName = this.lastName(); if (firstName && lastName) { return trim(firstName + ’ ’ + lastName) }}

共享的代码库让我们能快速完成新的目的地的实现,他们之间的相似性带给我们一致性的实现而且维护上也让我们减少了不少头疼的地方。

尽管如此,一个新的问题开始发生并蔓延。共享库代码改变后的测试和部署会影响所有的目的地。这开始让我们需要大量时间精力来维护它。修改或者优化代码库,我们得先测试和部署几十个服务,这其中会带来巨大的风险。时间紧迫的时候,工程师只会在某个特定的目的地去更新特定版本的共享库代码。

紧接着,这些共享库的版本开始在不同的目标代码库中发生分歧。微服务起初带给我们的种种好处,在我们给每一个目的地都做了定制实现后开始反转。最终,所有的微服务都在使用不同版本的共享库——我们本可以用自动化地发布最新的修改。但在此时,不仅仅是开发团队在开发中受阻,我们还在其他方面遇到了微服务的弊端。

这额外的问题就是每一个服务都有一个明确的负载模式。一些服务每天仅处理寥寥几个请求,但有的服务每秒就要处理上千个请求。对于处理事件较少的目的地,当负载出现意外峰值时,运维必须手动伸缩服务以满足需求。(编者注,肯定有解决方案,但原作者突出的还是复杂度和成本。)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值