微服务入门系列:微服务介绍

这是下面七篇系列文章的第一篇:

  1. 微服务入门系列:微服务介绍(本文)
  2. 微服务入门系列:使用 API 网关
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/introduction-to-microservices/
原文链接:https://www.nginx.com/blog/introduction-to-microservices/
作者:Chris Richardson
翻译:董干

这 7 篇文章是很不错的微服务入门系列文章,介绍了微服务所需要的主要技术,是当年(本系列文章写于2015年,但现在仍然不过时)带领译者入门微服务的文章,强烈建议深入微服务架构之前先了解本系列。


微服务正在引领一股热潮,有关微服务的话题在各处吸引着注意力:文章、博客、社交媒体上的讨论以及会议演讲等。它们正迅速向着 Gartner 技术成熟度曲线的“过高期望峰值”发展。与此同时,软件开发社区也有怀疑的声音认为微服务没有什么新鲜的。这些怀疑论者声称这种思想不过是 SOA 的重新包装。然而,不论是热捧还是怀疑,微服务架构模式都有着很大的优势 —— 尤其是当采用敏捷开发交付复杂的企业级应用时。

这篇文章是关于设计、构建和部署微服务的七篇系列文章的第一篇。你将了解实践微服务的方法和它和传统的单体架构模式之间的对比。本系列描述微服务架构的各个元素。你将了解微服务架构模式的优缺点、它是否适合你的项目以及如何实践微服务架构模式。

我们先来看一下为什么你应该考虑使用微服务。

构建单体应用

假设你即将要构建一个新的打车应用,与 Uber 和 Hailo 进行竞争。一系列初步的会议和需求收集后,你开始或手动,或使用 Rails,Spring Boot,Play 或者 Maven 提供的代码生成器创建一个新的项目。这个新的应用将使用模块化的六边形架构,类似下图这样:

Richardson-microservices-part1-1_monolithic-architecture

应用程序的核心是业务逻辑,由定义服务、领域对象和事件的模块所实现。与外部世界连接的接口适配器围绕核心所构建。数据库访问组件、生产和消费消息的消息组件以及暴露 API 或实现 UI 的 web 组件等,都是这些适配器的例子。

尽管有着模块化的架构,应用程序打包和部署成为了单体应用。实际的形式取决于应用程序使用的语言和框架。例如,许多 Java 应用程序都打包成 WAR 文件并部署在像 Tomcat 或者 Jetty 这样的应用程序服务器上。还有一些 Java 应用打包成了自包含的可执行 JAR 文件。类似地,Rails 和 Node.js 应用程序打包成目录结构。

这种风格编写的应用程序非常普遍。它们易于开发,因为我们的 IDE 和其他工具都聚焦于构建单个应用程序。这类应用同样易于测试。你可以通过简单地启动应用并且使用 Selenium 测试 UI 来进行端到端测试。单体应用也很容易部署。你仅仅需要将打包好的应用拷贝到服务器上即可。你也可以通过在负载均衡下面运行多份应用拷贝来进行扩容。在项目的早期,这种方式能够很好地工作。

迈向单体地狱

不幸的是,这种简单的方式有一个非常大的限制。成功的应用程序都有个习惯,它们会随着时间长大并且最终变得巨大。你的开发团队在每个 sprint 都会实现更多的 story,这意味着添加很多行代码。几年过后,小的简单应用逐渐变成了一个巨大的单体应用。举个极端的例子,最近我和一个开发者聊天,他正在开发用来分析上百万行代码应用的几千个 JAR 的依赖关系。我确信创建这样一个怪兽一样的应用是许许多多开发者多年以来齐心协力的结果。

当你的应用成为一个巨大的复杂单体应用时,你所在的开发组织可能会处在痛苦的海洋中。任何敏捷开发和交付的尝试都会困难重重。一个主要的问题就是应用实在太复杂,以至于没有一个开发者能够完全理解它。结果就是正确地修复 bug 和实现新功能成为困难,并且非常耗时。而且,这将会引发恶性循环,逐渐向下沉沦。如果代码库很难理解,就很难正确地添加新的改动。最终你将得到一个可怕的、无法理解的巨大的泥球

应用程序巨大的体量也会导致开发进程缓慢。应用越大,启动所花的时间就越久。举个例子,最近在一则调查中,一些开发者报告说他们的应用启动时间长达12分钟。我还听说过一些轶事,说有的应用程序会启动长达40分钟。如果开发者需要经常重启应用,那么一天的大部分时间都将用来等待启动,将极大影响他们的开发效率。

大型复杂单体应用的另一个问题是持续部署较为困难。如今的 SaaS 应用发展已经处于每天会推多次改动到生产环境。这对于复杂的单体应用来说极为困难,因为无论更新了多小的一个部分,你都必须重新部署整个应用。前面提到的启动时间过长也加剧了这个问题。另外,由于你通常不会很深入了解一个改动所带来的所有影响,你很可能需要做广泛的手工测试。因此,持续部署几乎成为了不可能的事。

当不同的模块有互相冲突的资源需求时,单体应用也将难于扩容。例如,一个模块可能实现了一个 CPU 密集型图像处理逻辑,理想情况下应该部署在 AWS 的计算优化型 EC2 实例上。但是另外一个模块可能是一个嵌入式的内存数据库,因此更适合使用内存优化型实例。尽管如此,由于这些模块将部署为一个整体,你不得不在选择硬件上做出一些妥协。

单体应用的另外一个问题是可靠性。由于所有的模块都运行在同一个进程中,任何模块的一个 Bug,例如内存泄露,都有可能会弄挂整个进程。而且,因为所有部署的应用实例都是等同的,这个 Bug 将会影响整个应用的可用性。

最后,但并非最不重要的一点是,单体应用使得新框架和新语言的采用变得极度困难。举个例子,想象你有一个使用某 XYZ 框架开发了两百万行代码的应用。将整个应用重写并替换为一个更好的框架 ABC 的代价(无论是时间上还是金钱上)将会极高,即使这个新的框架相当程度地比原来的好。结果就是,采用新技术会有巨大的障碍。你将会停滞在项目一开始你不论做的什么技术选型上。

总结一下:你有一个成功的业务攸关的应用发展成为了一个只有极少数或者根本没有开发者能够理解的巨型的单体应用。它使用了陈旧的低效技术,使得难于招聘有才能的开发者。这个应用难于扩容,并且不是很可靠。因此,应用程序的敏捷开发和交付成为不可能的事。

那么,你可以做什么呢?

微服务 —— 解决复杂性

很多公司,例如 Amazon,eBay 和 Netflix ,通过引入当前被称为微服务架构模式的方式来解决这个问题。不同于构建一个单一的巨大的单体应用,这种模式的思想是将应用程序分割为一些更小的互联的服务。

一个服务通常实现一些独立的功能或特性,例如订单管理,客户管理等等。每个微服务都是一个迷你的应用程序,拥有由业务逻辑和各种适配器组成自己的六边形架构。一些微服务会暴露供其他微服务或应用程序的客户端消费的 API。还有一些微服务可能会实现 web UI。在运行时,每个实例通常都是一个云服务的 VM 或是一个 Docker 容器。

举个例子,刚描述的系统的一种可能的拆解如下图所示:

Richardson-microservices-part1-2_microservices-architecture

应用程序的每个功能区域现在由各自的微服务实现。而且,网页应用程序分成了一组更简单的应用(例如在打车应用的例子中分别分为乘客和司机端页面)。这种做法使得为特别的用户、设备或者使用场景部署不同的体验更为简单。

每个后端服务暴露 REST API,大部分服务同时也调用其他服务提供的 API。例如,Driver Management 服务使用 Notification 服务通知空闲的司机潜在的行程订单。UI 服务调用其他服务从而渲染出网页。这些服务也可能使用异步的,基于消息的通信。这个系列后面的文章会涵盖关于服务之间的通信方式更多的细节。

有些 REST API 也会暴露给司机和乘客使用的移动端应用。但是,移动端应用不会直接访问后端服务。它们之间的通信通过一个称为API 网关的中间媒介而中继。API 网关负责诸如负载均衡、缓存、访问控制、API 访问量监测以及监控等,也可以通过使用 NGINX 来有效地实现。本系列后面的文章将会涵盖 API 网关

Richardson-microservices-part1-3_scale-cube

微服务架构模式对应上图扩容立方体中的Y轴,扩容立方体是来自于《扩容的艺术》一书中的一个 3D 模型。另外两个扩展轴分别是X轴(水平扩容),由在负载均衡下运行多个等同的应用程序拷贝组成,和Z轴(数据分区),在这种扩容方式下,请求的某个属性(例如某一行的主键,或客户的标识)用来决定路由到的服务实例。

应用程序通常会结合三种类型的扩容一起使用。Y轴扩容将应用划分为本节第一段图中所示的多个微服务。在运行时,X轴扩容将每个服务运行多个实例,挂在负载均衡下,提高吞吐率和可用性。一些应用也会使用Z轴扩容的方式将服务划分为多个分区。下图示意了 Trip Management 服务使用 Docker 运行在 Amazon EC2 上可能的部署方式。

Richardson-microservices-part1-4_dockerized-application

在运行时,Trip Management 服务由多个服务实例组成。每个服务实例运行在一个 Docker 容器中。为了实现高可用性,容器运行在云服务的多个 VM 上。在服务实例的前面是一个像 NGINX 这样的负载均衡器,将请求分布在不同的实例中。负载均衡器可能也负责处理其他诸如缓存访问控制API 访问计数监控等功能。

微服务架构模式对程序和数据库之间的关系产生了显著的影响。不同于与其他服务共享同一个数据库 schema,每个服务拥有自己的数据库 schema。一方面,这种方法与企业范围内统一数据模型的思想相左。另一方面,它也会导致一些数据冗余。然而,如果你想充分发挥微服务的优势,每个服务拥有自己独立的数据库 schema 至关重要,因为它保证了松耦合。下图展示了示例程序的数据库架构。

intro-microservices

每个服务都拥有自己的数据库。甚至,每个服务都可以用最适合自己需求的数据库类型,所谓的异构持久层架构。例如,用来寻找潜在乘客附近的司机的 Driver Management 服务,必须使用支持高效地理位置检索的数据库。

从表面上看,微服务架构模式有点像 SOA。两种方式都由一组服务的集合组成。不过,可以认为微服务架构模式是没有商业软件和能觉察到的 web service 规范( WS-* )和企业服务总线(ESB)的 SOA。基于微服务的应用程序更喜欢简单、轻量的协议,比如 REST,而不是 WS-* 。它们同样避免使用 ESB,而在微服务本身中实现类似的功能。微服务架构模式也拒绝 SOA 的其他部分,例如权威性的 schema 概念。

微服务的优点

微服务架构模式有很多重要的优势。第一,它处理了复杂性问题。它将巨大的单体应用分解为一组服务。应用总的功能没有变化,但是应用被拆解成为可管理的区块,即服务。每个服务有着定义良好的边界,这些边界的形式为 RPC 或是消息驱动的 API。微服务架构模式使得单体应用在实际中很难做到的一定程度的模块化成为可能并得以强制实施。正因如此,单个服务可以开发得更快,并且更容易弄懂和维护。

第二,这种架构使得每个服务可以被聚焦于这个服务的开发团队独立开发。开发者可以自由选择任何适用的技术,只要服务之间遵守 API 的约定即可。当然,大部分组织希望避免完全使用单一和有限的技术选择。而这种自由意味着开发者不用必须使用项目已开始时采用的可能已经陈旧过时的技术。当写一个新的服务时,他们可以选择使用当前的技术。甚至,由于服务相对较小,使用当前的技术重写老的服务也成为可行的选项。

第三,微服务架构模式使得每个服务都可以被单独部署。开发者部署他的服务自身的改动时不需要所有人的协调和同步。这类改动只要通过测试即可立即部署上线。例如,UI 团队可以进行 A/B 测试并迅速迭代 UI 的改动。微服务架构模式使得持续部署成为可能。

最后,微服务架构模式使得每个服务可以独立扩容。你可以部署恰好能满足每个服务容量和可用性约束那么多的实例数。甚至,你可以使用能最好满足某个服务资源要求的硬件对它进行部署。例如,你可以将 CPU 密集型图像处理服务部署在 EC2 计算优化型实例上,将内存型数据库服务部署在 EC2 的内存优化型实例上。

微服务的缺点

正如 Fred Brooks 30年前所写的,没有银弹。如同其他所有技术一样,微服务架构也有缺点。一个缺点在于它的名字中。“微服务”这个术语过多强调了服务的大小。实际上,有些开发者倡导构建极小的细粒度的 10-100 行的服务。尽管小的服务更好,记住这是个达到的目的的手段而非最终目的很重要。微服务的目的是将应用分解为足够小的组件,使得敏捷应用开发和部署更为容易。

另一个主要的缺点是微服务应用程序是个分布式系统这一事实所带来的复杂度的上升。开发者需要选择实现一种进程间通信的机制,无论是基于消息或是 RPC。而且,他们必须为了处理请求部分失败写一些错误处理代码,因为请求的远端可能响应慢或是不可用。虽说这些并不是高精尖的技术,但是比所有模块之间调用都通过语言级别的方法调用的单体应用复杂的多。

微服务的另一个挑战是数据库分区架构。业务事务需要更新多个业务实体的场景十分常见。这类事务在单体应用中由于使用单一数据库因此很简单。而在基于微服务的应用中,你需要更新多个服务下的多个数据库。不仅仅是由于 CAP 理论的原因,使用分布式事务通常不是一个可选项。很多当今的高扩展性 NoSQL 数据库以及消息队列都不支持。你需要使用某种基于最终一致性的方案,这对于开发者来说更难一些。

测试微服务程序也非常复杂。例如,使用像 Spring Boot 这样的现代的框架,可以很容易为单体 web 应用写测试类并自动启动服务以测试其 REST API。与此不同的是,给一个服务开发的类似的测试类需要不仅启动这个服务,还要启动这个服务所依赖的其他服务(或者至少配置这些服务的 stub)。同样地,这不是什么高精尖的科技,但是不要低估做这些工作的复杂性。

微服务架构模式的另外一个主要的挑战是跨多个服务的改动。举个例子,想象你正在实现一个功能,需要对服务 A、B 和 C 做出改动,其中,A 依赖 B,B 依赖 C。在单体应用中,你可以简单地只改动相应的模块,集成这些改动,并一次性地部署它们。然而,在微服务架构模式中,你需要仔细计划并协调改动涉及到的服务。例如,你可能需要先更新服务 C,然后服务 B,最后再更新服务 A。幸运的是,大部分改动通常只影响到一个服务,需要涉及协调多个服务的这种跨多个服务的改动相对较少。

基于微服务的应用部署也更复杂。单体应用通常部署到传统负载均衡下的一组相同的服务器上。每个实例配置像数据库和消息队列这样的基础设施服务的位置(主机名和端口号)。而微服务应用通常由很多服务组成。举个例子,Hailo 有160多个不同的服务,Netflix 根据 Adrian Cockcroft 所说有超过600个服务。每个服务在运行时都会启动多个实例。这意味着更多的可变性需要被配置、部署、扩容和监控。而且,你也需要实现一种服务发现机制(后面的文章将会讨论),使得一个服务可以发现它所需要通信的其他服务的位置(主机名和端口)。传统的基于开问题 ticket 和人工运维的方式应对不了这种级别的复杂度。因此,成功地部署微服务需要一套开发者提供的更细粒度控制并且高度自动化的部署方法。

一种实现自动化的方式是使用现成的 PaaS 服务,例如 Cloud Foundry。PaaS 给开发者提供了一种部署和管理微服务的简单的方式。它将申请获得和配置 IT 资源的关注点分离。与此同时,配置 PaaS 服务的在系统和网络上更专业的工程师可以保证遵循最佳实践以及公司策略。另外一种自动化部署微服务的方式是开发自己所需的 PaaS。使用像 Kubernetes 这样的集群方案,配合像 Docker 这样的容器技术,是一个典型的方式。这个系列后面的文章我们会看一下像 NGINX 这样的能容易地处理缓存、访问控制、API计数和监控的基于软件的应用交付方式能如何帮助解决这个问题。

总结

众所周知构建复杂的应用程序很难。单体架构只适用于简单的轻量的应用程序。如果你用它来构建复杂的应用,最终你会很痛苦。对于复杂、不断演化的应用程序来说,微服务架构模式是更好的选择,尽管它也有些缺陷和挑战。

后面的文章,我会深入微服务架构模式每个角度的细节,并讨论服务发现、服务部署方式,以及将单体应用重构为服务的一些策略等话题。

请保持关注……


这是下面七篇系列文章的第一篇:

  1. 微服务入门系列:微服务介绍(本文)
  2. 微服务入门系列:使用 API 网关
  3. 微服务入门系列:微服务架构的进程间通信
  4. 微服务入门系列:微服务架构的服务发现
  5. 微服务入门系列:基于事件驱动的数据管理
  6. 微服务入门系列:微服务的部署策略选择
  7. 微服务入门系列:单体应用的微服务重构

出自微服务部落:https://blog.idevfun.io/introduction-to-microservices/
原文链接:https://www.nginx.com/blog/introduction-to-microservices/
作者:Chris Richardson
翻译:董干

Show Comments

Get the latest posts delivered right to your inbox.