2018年1月

在过去几年里,微服务成为一个非常热门的话题。“微服务热”是这样的:

Netflix很擅长devops。Netfix做微型服务。因此:如果我做微服务,我就很把控好devops。

在许多情况下,人们都在努力采用微服务模式,而不一定了解成本和收益,也并不知道微服务是否适用于当前问题的具体情况。

我将详细描述什么是微服务,为什么这种模式如此吸引人,以及它们所带来的一些关键挑战。

最后,我将给出一组简单的问题,当您正在考虑微服务是否是适合您的模式时,可能会问自己一些有价值的问题。问题在文章的结尾。
pic1

什么是微服务,它为什么如此流行

让我们从基础开始。下面是设想的一个视频共享平台的实现方式,首先以Monolith(单个大型单元)的形式实现,然后以微服务的形式实现:
Monolith and microservice
这两种系统的不同之处在于,第一种是单一的大单位;第二种是一组小型的、特定的服务。每个服务都有一个特定的角色。

当在这个细节层次上绘制图表时,很容易看到它的吸引力。有许多潜在的好处:

独立开发:小的、独立的组件可以由小的、独立的团队构建。一个组可以对“上传”服务进行更改,而不干扰“Transcode”服务,甚至不知道它。学习组件的时间大大减少,开发新特性也更容易。

独立部署:每个单独的组件都可以独立部署。这使得新特性能够以更高的速度和更少的风险发布。可以在不需要部署其他组件的情况下部署“流”组件的修复或功能。
独立的可伸缩性:每个组件可以彼此独立地缩放。在新节目发布的繁忙时期,可以扩大“下载”组件,以处理增加的负载,而不必增加每个组件的规模,这使得弹性缩放更可行,降低了成本。

可重用性:组件实现一个小的、特定的功能。这意味着它们可以更容易地适应于其他系统、服务或产品的使用。“Transcode”组件可以被其他业务单位使用,甚至可以变成新业务,可能为其他组提供代码转换服务。

在这个层次上来比较,微服务模型比单一模型的好处似乎是显而易见的。但是,如果是这样的话-为什么这种模式只是最近才流行起来呢?我这辈子之前都在搞飞机吗?

既然这么好,为什么以前没做过呢?

这个问题有两个答案。其一是它已经用尽了我们最大的技术能力,其二是最近的技术进步使我们能够把它提升到一个新的水平。

当我开始写这个问题的答案时,它变成了一个很长的描述,所以我实际上要把它分成另一篇文章,稍后再发表。在这个阶段,我将跳过从单个程序到许多程序的演变过程,忽略ESB和面向服务的体系结构、组件设计和有界上下文等等。

那些有兴趣的人可以单独阅读更多关于演变过程的内容。相反,我要说,在许多方面,我们已经这样做了一段时间,但是随着容器技术(特别是Docker)和编排技术(例如Kubernetes,Mesos)的流行程度的最近激增。从技术角度来看,这种模式已经变得更加可行。

因此,如果们搞一个微服务项目,我们就先需要仔细考虑有没有使用微服务的必要。我们已经看到了高层理论上的优点,但实际工作中又会遇到什么样的挑战呢?

微型服务有什么问题?

如果微型服务这么好,为什么我们之前没有使用它?以下是我所见过的一些最大的问题。

增加了开发人员的复杂性

对于开发人员来说,事情会变得更加艰难。如果开发人员希望在可能跨越许多服务的过程中工作,则该开发人员必须在其机器上运行所有这些服务,或连接到它们。这通常比简单地运行一个程序更复杂。

这个挑战可以通过工具缓解,但是随着组成一个系统的服务数量的增加,开发人员在运行整个系统时将面临更多的挑战。

运维者的复杂性增加

对于那些不开发服务但维护服务的团队来说,潜在的复杂性会急剧增加。他们管理的不是几个正在运行的服务,而是管理几十个、数百个或数千个运行中的服务。有更多的服务,更多的通信路径,更多的潜在故障区域。

增加了devops的复杂性

阅读以上两点,它可能会抱怨操作和开发是分开处理的,特别是考虑到devops作为一种实践的流行(我是这一实践的大力支持者)。不能减轻这一点吗?

面临的挑战是,许多组织仍然采用独立的开发和运营团队--而一个这样做的组织更有可能在采用微服务的问题上苦苦挣扎。

对于那些已经接受了“devops”的组织来说,这仍然很难。作为一个开发人员和一个运维人员已经很难了(但是对于构建好的软件来说至关重要),但是也必须理解容器编排系统的细微差别,特别是那些正在快速发展的系统,这是非常困难的。这就引出了下一个问题。

它需要认真的专业知识

如果由专家来做,结果会是非常好的。但是,想象一下,一个组织,也许事情在单一的单一系统下不顺利地运行。增加系统的数量,增加操作的复杂性,会有什么原因使事情变得更好呢?

是的,有了有效的自动化、监控、编排等等,这一切都是可能的。但挑战很少是技术--挑战在于找到能够有效利用技术的人。这些技能目前需求量很大,可能很难找到。

现实世界系统的边界往往定义得很差

在我们用来描述微服务好处的所有例子中,我们谈到了独立的组件。然而,在许多情况下,组件是不独立的。在纸面上,某些域看起来可能是有界的,但是当你进入到模糊的细节中时,你可能会发现它们比你预期的更具有挑战性。

这就是事情会变得极其复杂的地方。如果您的边界实际上没有很好地定义,那么所发生的情况是,即使理论上服务可以单独部署,但是您发现由于服务之间的相互依赖关系,您必须将服务集合作为一个组部署。

这就意味着您需要管理在一起工作时经过验证和测试的一致版本的服务,实际上您没有一个独立的可部署系统,因为要部署一个新特性,您需要仔细安排多个服务的同时部署。

状态的复杂性常常被忽视

在前面的示例中,我提到了特性部署可能需要同时推出多个服务的同步版本。我们很容易认为,合理的部署技术会减轻这种情况,例如蓝色/绿色部署(大多数服务编排平台只需花费很少的精力就能处理这些部署),或者一个服务的多个版本并行运行,使用的通道决定使用哪个版本。

如果服务是无状态(serverless)的,这些技术可以减轻大量的挑战。无状态服务是相当直白的,和他们打交道很容易。事实上,如果您有无状态服务,那么我倾向于考虑跳过微服务,并考虑使用无服务器模型。

实际上,许多服务都需要状态。我们的视频共享平台的一个例子可能是订阅服务。订阅服务的新版本可能以不同的形式将数据存储在订阅数据库中。如果同时运行两个服务,则同时运行两个模式的系统。如果您执行蓝色绿色部署,而其他服务依赖于新形状的数据,则必须同时更新它们,如果订阅服务部署失败并回滚,它们可能也需要回滚,从而产生级联后果。

同样,我们可能会很容易认为,对于NoSQL数据库,这些模式问题消失了,但它们没有。不执行模式的数据库不会导致无模式系统--它们只是意味着模式倾向于在应用程序级别而不是在数据库级别进行管理。理解数据的形状及其演变的基本挑战是无法消除的。

沟通的复杂性往往被忽略

当你建立一个相互依赖的大型服务网络时,可能会有很多的服务间通信。这导致了一些挑战。首先,有很多事情可能会失败。我们必须期望网络呼叫将失败,这意味着当一个服务呼叫另一个服务时,它应该至少需要重试几次。现在当一个服务可能调用很多服务时,我们最终会遇到一个复杂的情况。

用户上传视频共享服务中的视频。我们可能需要运行上传服务,将数据传递到转码服务,更新订阅,更新建议等等。所有这些调用都需要一定程度的协调,如果事情失败,我们需要重试。

这个重试逻辑可能难以管理。试图同步做事往往会导致站不住脚,失败点太多。在这种情况下,更可靠的解决方案是使用异步模式来处理通信。这里面临的挑战是异步模式固有地使系统具有状态性。如前所述,分布式状态系统和系统很难处理。

当一个微服务系统使用消息队列进行服务内通信时,你基本上有一个大的数据库(消息队列或代理)将这些服务粘合在一起。同样,虽然起初看起来似乎不是一个挑战,但其实不然。X版本的服务可能会写入某种格式的消息,当发送服务更改发送的消息的详细信息时,依赖于该消息的服务也将需要更新。

可以有许多不同格式的消息处理服务,但这很难管理。现在,在部署新版本的服务时,您可能会有两次不同版本的服务尝试处理来自同一队列的消息,甚至可能是由不同版本的发送服务发送的消息。这可能会导致复杂的边缘情况。为了避免这些边缘情况,仅允许特定版本的消息存在可能更容易,这意味着您需要将一组服务的一组版本作为一个整体来部署,以确保先前版本的消息得到适当的排除。

这再次突出表明,独立部署的想法可能不会像预期的那样持有细节。

版本控制可能很难

为了缓解前面提到的挑战,版本控制需要非常谨慎的管理。再次,可以有一种趋势,假设遵循像semver这样的标准将解决这个问题。它不。Semver是一个明智的使用惯例,但是您仍然需要跟踪可以一起工作的服务和API的版本。

这可能会非常迅速地变得非常具有挑战性,并且可能会导致您不知道哪个版本的服务实际上可以一起正常工作。

在软件系统中管理依赖关系是非常困难的,无论是节点模块,Java模块,C库还是其他。当一个实体消费独立组件之间的冲突的挑战是很难处理的。

当依赖关系是静态的时候,这些挑战是很难处理的,并且可以进行修补,更新,编辑等,但是如果依赖关系本身是实时服务,那么您可能无法更新它们 - 您可能需要运行许多版本(已经描述过这些挑战),或者直到整个系统得到修复。

分布式事务

在需要跨操作交易完整性的情况下,微服务可能会非常痛苦。分布式状态很难处理,很多小的单位可能会很难进行编排交易。

试图通过使操作幂等性,提供重试机制等来避免这个问题可能是诱人的,在许多情况下这可能起作用。但是你可能有一些情况,你只需要一个事务失败或成功,而不会处于中间状态。解决这个问题或者在微服务模型中实现它的努力可能是非常高的。

微服务可能是变相的庞然大物

是的,单独的服务和组件可能是孤立部署的,但是在大多数情况下,您将不得不运行某种编排平台,比如Kubernetes。如果您使用的是托管服务,例如Google的GKE 5或Amazon的EKS 6,则会为您处理管理群集的大量复杂性。

但是,如果您要自己管理集群,那么您正在管理一个庞大而复杂的关键任务系统。虽然单个服务可能具有前面所述的所有优点,但您需要非常小心地管理群集。这个系统的部署可能很难,更新可能很难,故障转移可能很困难等等。

在许多情况下,整体效益仍然存在,但重要的是不要轻视或低估管理另一个庞大而复杂的系统的额外复杂性。托管服务可能会有所帮助,但在很多情况下,这些服务都是新兴的(例如,Amazon EKS只是在2017年底才宣布)。

微服务热之死!

通过仔细考虑决定避免疯狂。为了帮助解决这个问题,我已经注意到了一些你可能想问自己的问题,以及答案可能表明什么:
微服务热之死
你可以在这里下载PDF副本:microservice-questions.pdf

最后的想法:不要混淆微服务和架构

我故意避免这篇文章中的“a”字。但是,我的朋友佐尔坦(Zoltan)在证明这篇文章的时候提到了一个很好的观点。

没有微服务体系结构。微服务只是组件的另一种模式或实现,只不过是另一件事。无论是否存在于系统中,都不意味着系统的体系结构得到了解决。

微服务在许多方面与包装和操作的技术过程有关,而不是系统的固有设计。组件的适当边界仍然是工程系统中最重要的挑战之一。

无论您的服务是否在Docker容器中,您总是需要仔细考虑如何将系统放在一起。没有正确的答案,并有很多选择。

本文翻译自http://www.dwmkerr.com/the-death-of-microservice-madness-in-2018/

使用WebAssembly
如果您只是对使用WebAssembly感兴趣的开发人员,则建议的启动方式是使用Emscripten(SDK)。 Emscripten是一个已经用于编译asm.js中的C / C ++的工具链,它是与WebAssembly类似的目标发明的JavaScript子集。 使用Emscripten,您可以更轻松地使用前面提到的Binaryen,并将其与自己的链结合起来。

一旦你安装了Emscripten,或者你已经从源代码编译了,你必须安装binaryen。

# these commands should be executed inside the emsdk folder
# on Linux or Mac OS X
# this step might take a while
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit
 
# on Windows
# this step might take a while
# if you are using Visual Studio 2017, append --vs2017
emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit

然后激活编译环境,并确保设置正确的路径和变量,每次启动以下命令。

# the command should be executed inside the emsdk folder
# on Linux or Mac OS X
source ./emsdk_env.sh --build=Release
 
# on Windows
emsdk_env.bat --build=Release

终于,可以开始编码了。
WebAssemblyExample.c

#include <stdio.h>
 
int factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}
 
int main(int argc, char ** argv) {
  int number = 5;
  int fact = factorial(number);
  printf("The factorial of %d is %d", number, fact);
}

然后编译到WebAssembly并在浏览器中看到它。

emcc WebAssemblyExample.c -s WASM=1 -o WebAssemblyExample.html
# launch the local web server included with emscripten
emrun --no_browser --port 8080 .

第一个命令将生成三个文件:一个WASM模块,一个显示代码的HTML文件以及一个设置模块的JS文件,并负责运行所需的所有内容。 WASM = 1向Emscripten表明我们想要生成一个WASM模块,而不是一个asm.js文件。
webassembly.png
您还可以使用--shell-file选项在自定义模板中输出代码。 Emscripten SDK安装包含此位置的基本模板(esmdk-folder)/emscripten/incoming/src/shell_minimal.html。 将该文件复制到您的项目中并根据需要进行调整(例如,添加其余的JS代码)。

# We renamed it WebAssemblyTemplate.html
emcc -o WebAssemblyExample.html WebAssemblyExample.c -O3 -s WASM=1 --shell-file WebAssemblyTemplate.html

你也可以直接输出一个JS文件,但目前不推荐。 那是因为你需要代码来处理内存分配,内存泄漏等低级问题。

最后的目标是使加载WebAssembly模块的过程变得简单,就是使用<script type ='module'> HTML代码加载JavaScript代码,但是我们还没有加载。

C和JavaScript之间的互相操作
C和JavaScript之间的互相操作是Emscripten的一个问题。 你必须做的第一件事是包含emscripten的头文件。

#include <emscripten.h>

调用JavaScript的简单方法就是调用函数emscripten_run_script:

// it is equivalent to call eval() in JavaScript
emscripten_run_script("alert('hello')");

从JavaScript调用C函数稍微复杂一些。 首先,您必须从C / C ++代码中提供它,因为默认情况下Emscripten不能使用除主函数之外的所有C函数。 所以,您必须将修饰符EMSCRIPTEN_KEEP_ALIVE添加到您要在JavaScript中使用的所有函数中。

int EMSCRIPTEN_KEEPALIVE factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}

如果你用C ++编写的话,记得把你希望在extern'C'块中提供的任何函数放在一起,为了避免C ++弄错函数的名字(这是C ++所做的事,它不是WebAssembly或Emscripten的错误)。

其次,您必须使用NO_EXIT_RUNTIME选项来编译WebAssembly模块,这样可以避免在main函数的退出时关闭运行时,从而无法从JavaScript调用C代码。

emcc -o WebAssemblyExample.html WebAssemblyExample.c -O3 -s WASM=1 -s NO_EXIT_RUNTIME=1 --shell-file WebAssemblyTemplate.html

第三,你不能直接调用你的C函数,但是你必须使用下面的语法。

Module.ccall('factorial', // name of C function
             'number', // return type
             ['number'], // argument types
             [4] // arguments
);

类型可以是三个之一:数字,字符串和数组。

如果您需要在JavaScript代码中多次使用该函数,则可以使用cwrap函数来包装它。

factorial = Module.cwrap('factorial', 'number', ['number'])
factorial(4);

您可以轻松地在控制台中尝试。
webssembly_console.png

结语
我们已经看到了WebAssembly的简短介绍:它是什么,为什么你应该关心以及如何使用它。 它将成为网络进一步发展的一个很好的平台:它将使网络的开发变得更容易,更高效。

它的发展得到了Mozilla,微软,Google和苹果的支持。 对工具的关注是WebAssembly重要性的另一个证明:它将快速改变Web。

您现在可以使用WebAssembly,并在MDN网站上查看更深入的文档。 如果你有兴趣了解更多关于格式的细节,你可以在官方网站上阅读。 您也可以查看Emscripten文档来了解与JavaScript和C / C ++代码之间的互相操作相关的问题。