「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

一个经过优化的微服务架构案例

2017 年 2 月 13 日

前言

大家都知道,基于单体(Monolith)和微服务(Microservice)架构的争论已经存在多年,正如我们对胖客户端、瘦客户端孰好孰坏的争论一样,有必然的历史演化,也有各自的优缺点。架构师们总是在考虑,我们是要一个中心化、全能多才的单体,还是百花齐放、各自为政的微服务群体,各种基于成本、交互、部署等等的讨论应该不会停下脚步。这里,我们不做过多的深入探讨和介绍,而本文正是这些讨论中的一个很好的例子,供大家思考。

Joakim Tengstrand 近期在他的推特上提到,看到 Robert C. Martin(Bob 大叔)多年前描述的基于 JAR 包微服务的不足之处,于是写下了这篇有趣的称为微单体(micro monolith)的文章,并在Git 上发布了基于 Java 和 Clojure 的两种源码包供下载。接下来将逐一介绍微单体的概念、工作方式、各自的优缺点、示例、实践等环节。

微单体是什么

用成千上万或数百万行代码来编写高质量的软件,可能是开发人员所能承担的最具挑战性和最复杂的任务之一。Tengstrand 利用软件模块化这种非常简单的方式,实现了一个方案并期望能有助于完成这项挑战,提供了基于 Java 和 Clojure 的代码供参考。

随着系统变得越来越大,最终会达到一个临界点,作为一个单体 monolith )它变得难以管理。每一行代码的添加,都会让系统变得更加难以理解、变更和重用。虽然微服务 microservice )试图解决这些问题,但也带来了额外的复杂性以及集成的成本。

微单体架构的核心原则是保持硬件、软件和数据紧密地结合在“一个地方”。这样处理可以简化事情,摆脱不必要的协调工作。如果我们从同一个地方直接访问数据,性能也会得到改善。当设计系统时,可以像微服务一样使用小的、孤立的、可组合的构建块(building blocks),又可以像单体一样通过一个地方执行它们,就能从两方面都达到最优。

从上面的介绍可以看出,单体就像一个胖服务,微单体利用微服务架构的优势将处理工作从逻辑上分拆出去,在此基础上增加一层调度(编排服务)来管理所有的微服务。这样一来,使得业务的完整性和一致性得到了较好的保证,也解决了跨服务集成的问题。用一个简单的例子来描述,单体就好像把所有的文件放在了一个文件夹里,微服务则试图将它们分类并放在不同文件夹中,而微单体的方法是生成了一个叫做 development 的文件夹,里面保存了所有文件的快捷方式(shortcut),这样更易于根据不同的业务场景来管理和访问这些“文件”。

如何工作

在版本控制系统里为每个服务生成一个项目,这样可以得到各自的 JAR 包(假设在 JVM 上运行,或者类似平台)。它们就是构成系统的构建块,并最终组成整个生产系统。生成一个 development 项目,通过服务把所有源代码连接起来,这样就可以在里面直接运行源代码,就像是只有这一个项目一样。

接下来介绍微单体架构的优缺点:

优点

  • 简单性(关注点分离,代码直接调用,消除了网络 API 的复杂度)
  • 卓越的性能(没有访问服务的网络调用)
  • 模块化、可组合的服务(在不同的系统中重复利用)
  • 跨服务事务的数据一致性(无需考虑最终一致性)
  • 减少 DevOps 和硬件 / 托管的费用(在单机上运行的系统)
  • 易于测试(可以对整个系统进行一体化测试)
  • 更快和更有效的开发体验(跨服务导航、重构和调试 + 变更无需重建服务)

缺点

  • 必须在所有服务中使用相同的编程语言(*)
  • development 项目需要一些额外的设置(创建符号链接 symbolic links)
  • 操作系统必须支持符号链接 symbolic links
  • 共享相同路径的资源在所有服务中必须具有唯一的名称(它们有不同的内容)
  • IDE 内置的版本控制失效(因为微单体架构可能不支持 IDE) (*) 并没有强迫只使用一种编程语言,目标是要让 development 项目发挥其优势(例如跨服务的重构和调试),这时最好的选择是使用一种语言。其次是混合使用,可以使用在同一个平台(比如 JVM)上运行的语言,像 Java、Scala、JRuby、 Clojure(如果使用本地接口 JNI ,还可以选择 C),但如果一个服务做了变更,就需要生成一个新的 JAR 包并共享给其他服务。

上面介绍了微单体方法的概念,还没有提到它是如何在实践中工作的,现在让我们通过 Java Clojure 的两个示例来进行演示。所有示例的代码可以在这里找到。注:在实际系统中,它们被存储在单独的资源库里并且彼此隔离,这里为了方便起见,它们被存储在同一个资源库中。

下面 Java 和 Clojure 的例子会实现相同的“解决方案”,利用一个假造的 REST API 来编排一些服务,并暴露findaddresses,douserstuff 和 domoreuserstuff 供调用。

Java - 示例代码

Java 是一种流行语言,这里将展示在面向对象语言里的微单体架构是什么样子的。

在处理 development 项目时,你在大部分时间里会是一个开发人员。虽然所有的服务都被存为各自独立的项目( Git ),这里我们通过一个技巧,利用符号链接把所有源码“放到”一个单独的项目中。IDE 并不关心地址是“真正的”还是一个链接,都采用箭头来标记它们(至少在这个例子里是这样的):

项目在本地check out 后,这些链接在 Linux 或 Unix 上 马上就能工作。如果是其它平台,可以参考这样类似的脚本,手动地创建 development 项目。

建立了这个项目,通常意义的甚至跨服务的开发环境所具备的调试、重构和搜索等等方面的好处,我们都可以实现。这一点非常强大并且节省了时间,每次代码变更不需要重建服务,这样使得工作流程非常高效和快乐。

依赖性

在设计系统时,需要决定是否允许服务获得外部库(external libraries)具体实现的信息。在本文的例子里,我们选择信息透明,最好在所有服务中使用相同版本的外部库。

另一种选择是通过在内部服务与外部库之间添加接口来集成,这样一个服务就不用知道具体库的信息,比如 log4j-1.2.17.jar,只需要生成一个接口 log4j-api,这样编排服务(orchestrator service)就能把它注入到所需的服务中去了。

编排服务(The orchestrator service)

编排服务就是把所有服务都放到一起的那个服务。一个系统可以有多个编排服务,这里的例子只有一个 RestService ,它依赖地址,电子邮件和用户这几个服务,并在 pom.xml 里进行指定。

如果服务 A 需要调用服务 B 里的函数 f,可以通过编排服务把函数 f 注入到服务 A 中。这里没有强制要求一次只注入一个函数,但是这样做可以降低服务间的耦合,增加可变性(changeability)和可测性(testability)。我们将使用“微注入”这个术语,特指一次只注入一个函数的意思。

测试

微单体架构鼓励让测试变得简单、容易,就像微服务一样,要让每个服务的独立测试更方便。相比于微服务, 微单体就像一体化部署在一台机器上一样,它让测试变得更加的简单(比如本例的 REST API )。

例子里包含了一个测试数据生成器,它帮助我们在已知的状态下建立数据库。可以存在一个用户表和一个地址表相关联,然后就得到一个 UserService 和一个 AddressService。测试数据生成器可以方便地设置数据库的已知状态,这有助于编写集成测试。这可以在某个服务中完成并且实现跨服务调用,例如 AddressServiceTest UserServiceTest

Clojure - 示例代码

Clojure 是一个功能强大的语言,能在 JVM 上运行。下面将展示在 Clojure 这样的函数式语言中如何使用微单体架构。所有 Clojure 代码可以在这里找到。

它的 development 项目看起来是这样的:

Clojure 版本的结构基本类似 Java 版本,但函数都存储在不同命名空间而不是类中,也不需要像 Java 一样为地址和电子邮件服务添加额外的 API 层。Clojure 版本更加简洁,可以用大约 200 行代码实现 Java 里 400 行代码能完成的工作。

Clojure 里的微注入会更简单,可以用宏注入(inject macro)的方式来注入函数。这里的例子里,在命名空间 rest.service 的第 8 行,就是用函数 email/send-pdf-email! 替换了原来的 user.service/send-pdf-email!

实践经验

Tengstrand 和他的团队已经把微单体架构应用到了一个真正的生产系统。他们搭建了这样一个架构,每一个服务在 Git 上 有各自的资源库。迁移到微单体是非常顺利的,要做的就是丢弃 30% 的代码,并把所有 REST 服务调用替换成简单的函数调用。这个过程中消失的不仅仅是 REST 部分,还有很多复杂的状态和错误处理。

从一开始,就像生产环境一样设置开发环境,每个服务就是一个 Java 存档(JAR 文件)。缺点是每次的服务变更,必须重建这个 JAR 文件,以便它可以供其他服务使用。另一个缺点是,我们必须重新启动 REPL (是的,使用的是 Clojure!),这样做耗费了时间并把在 REPL 上工作的一些快乐也带走了。

于是,Tengstrand 的团队想出了新的方法来设置 development 项目,只需启动一次 REPL ,然后可以继续工作而不会被打断,这样一来开发人员就幸福多了。另一件事是,他们意识到服务里有一些灰色标记的死掉(dead)的代码,现在也可以去掉了。

另一个设计上的选择是采用 Datomic 数据库,它真的与微单体架构很适用,既简单又强大。你可以在这里了解它的更多架构细节。

他们使用测试数据生成器来处理几乎所有的服务,让集成测试更简单。之前他们发现可以改进服务中的一些变量和函数的命名,但更改之前必须手动搜索和替换所有的服务,这样做费时又容易出错。最后他们并没有做这些小的改动,而是通过development 项目,利用 IDE 对重构的支持瞬间就做到了变量和函数的重命名。

总结

微单体提出了如何构建系统的一个简单模式,虽然和微服务竞争但并不能完全取代它,因为后者肯定有它的位置。如果有需要的话,随时可以同时使用它们。

最后 Tengstrand 建议,如果在构建系统时非常关注简单性和可组合性,那么一定要尝试下微单体架构,享受这个架构带来的高效,以及测试、生产环境的简洁。


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 2 月 13 日 16:2510205
用户头像

发布了 40 篇内容, 共 29.2 次阅读, 收获喜欢 124 次。

关注

评论

发布
暂无评论
发现更多内容

JAVA集合之ConcurrentHashMap

彭阿三

Java JAVA集合

点对点音视频应用场景及优势

anyRTC开发者

音视频 WebRTC 直播 RTC 安卓

10多家公司的Java开发面试常见问题合集

Java架构师迁哥

为什么推广ipv6以及网站ipv6改造基本步骤

MySQL从删库到跑路

Web IP 网络

打造一个全功能的浏览器

Daniel

浅谈滴滴需求响应式公交背后的技术

滴滴技术

滴滴技术 创新公交 路径优化

DàYé玩转数据战略Step By Step

曲水流觞TechRill

数据中台 数字化

信息公交服务在滴滴的应用实践

滴滴技术

滴滴技术 人工只能 信息公交 路径优化

架构师第一期作业(第三周)

Cheer

作业

Go编程(二) 多线程简单斗地主

dongfanger

go 编程 开发

数据挖掘技术在轨迹数据上的应用实践

滴滴技术

人工智能 数据挖掘 滴滴技术 轨道技术 创新公交

倒计时!Pulsar Summit Asia 2020 演讲征集

Apache Pulsar

开源 云原生 pulsar Apache Pulsar 消息中间件

架构师训练营第三周学习总结

邓昀垚

极客大学架构师训练营

奈学:Java 和 JavaScript 是什么关系?

奈学教育

Java

初学源码之——Spring IOC 应用

Java架构师迁哥

比曲婉婷云尽孝更可怕的是:2020年,低收入家庭仍然在被收割

成周

心理学 教育 培训 维权 曲婉婷

StreamNative 宣布开源 MoP:Apache Pulsar 支持原生 MQTT 协议

Apache Pulsar

开源 云原生 mqtt Apache Pulsar 消息中间件

一次注定失败的裸面

escray

ruby 面试题 面经 大龄程序员 面试经历

Go编程(一) 怎么写Go代码

dongfanger

go 编程 开发

java安全编码指南之:异常处理

程序那些事

java安全编码 java安全 java安全编码指南

图解 K8S 源码 - Deployment Controller 篇

郭旭东

Kubernetes Kubernetes源码

国庆假期快来了,打开8天长假的正确方式是...

老胡爱分享

读书 书籍推荐 随笔杂谈

作业帮基于Apache Doris的数仓实践

DorisDB

数据库 数据仓库 数据分析 OLAP 大数据架构

看看别人是怎么面试蚂蚁金服的!社招Java面经分享

Java架构师迁哥

Java 阿里巴巴 面试 蚂蚁金服

不一样的面向对象(三)

书旅

php 面向对象 面向对象编程

一篇文章搞定 Nginx 反向代理与负载均衡

哈喽沃德先生

nginx 负载均衡 反向代理 服务器 正向代理与反向代理

2020面试阿里字节跳动90%被问到的JVM面试题附答案

Java架构师迁哥

Java源码系列1——ArrayList

超超不会飞

Java

奈学:Java 和 JavaScript 是什么关系?

古月木易

Java

聊一下《技术力量-一线技术团队成功启示录》

Man

中台 研发管理

2020年行摄回忆录(上)

穿过生命散发芬芳

生活 摄影

一个经过优化的微服务架构案例-InfoQ