Net Core 中的微服务 笔记 (1)
时间:2023-05-21 00:42:02 | 来源:网站运营
时间:2023-05-21 00:42:02 来源:网站运营
Net Core 中的微服务 笔记 (1):
什么是微服务?
微服务是指,在一个整体系统中,通过远程 API 暴露的一个很小,狭窄,单一,专注的功能的服务。
在传统的系统设计中,我们会把一整套东西做进同一个系统。比如我们来抽象化一个银行;传统模式下,我们会把所有银行功能都打包成一个臃肿的巨型系统。但是在微服务的语境下,迎宾、柜员、身份验证、存取、信贷等等功能都会由一些列的微服务组成。比如存取的微服务,我们就仅仅只专注于“存取”的功能。我需要一个合法的客户身份验证,存取目标账户,和存取金额等前提条件,那么我就可以进行存取操作,完成后,我把工作流交给下一个环节。作为一个微服务,我并不关心我上游是怎么做的,也不关心我的下游会怎么做。我仅仅只关心我自己的环节。
微服务在编程中其实有点类似于现代枪支的模块化发展趋势。
微服务的特点
微服务具有以下特点:
- 单一功能 - 微服务只负责单一功能
- 独立部署 - 微服务可以独立部署,不必和整个系统一起部署
- 独立进程 - 微服务由一个或者多个进程组成
- 维护团队 - 一个小型团队可以维护几个微服务
- 可替换 - 微服务像零件一样可以被替换
我想起读到数据库的时候,书里写到一个数据库内部的几个分层结构,MySQL 的数据驱动模块是可以替换的,有好几种不同的开源或者付费版本可以供选择,这个是类似的概念。
这些特点让微服务具备可以让一个系统拥有以下特征的能力:
- 可塑性 - 微服务可以被替换,所以一个由多个微服务的系统功能是可塑的、灵活的
- 伸缩性 - 根据工作负荷的不同,可以选择适合工作负荷的微服务进行替换
- 坚固性 - 一个微服务失效了,不会对整个系统的其他功能产生影响
总体来说微服务的特征符合软件工程的基本趋势。我们可以看到很多特征都和 OOP 编程的特性符合。
编程中有一个重要原则,就是 SRP,单一职责原则。如果一个编程设计在某个对象中的功能过于复杂,那么想要对这个对象进行维护和升级或者测试将变得极为困难。这个的相反的方向就是,我们设计软件的时候把一个模块做成单一功能,这样就可以反过来让维护和升级等变得简单。比如从 WinForm 的一锅大杂烩到 WPF 的 MVVM 模式的拆分,比如 Node.js 社区所奉行的模块化原则。
为什么要选择微服务?
首先是它使得持续投递(Continuous Delivery)成为可能。微服务的开发和修改很快,而且不影响系统的其他部件。微服务可以独立通过自动化的单元测试。部署也是独立的。运作有效。
我是这样理解持续投递的。对于用户来说,持续投递可能意味着服务提供方可以在“不下线”的前提下,提升服务的质量。一般来说,在线游戏的版本升级或者服务器维护都意味着下线时间。但是我之前听说过一个好玩的事情,就是腾讯的王者荣耀是如何在几乎 24 小时都有在线用户的情况下实现热在线版本升级的,这个故事利用到了网络负载均衡。首先网络负载均衡是一个服务器,它的作用是在请求被发送到负责提供服务的机器之前,分析这些集群哪些比较空闲,哪些比较忙碌,然后自己再分一次请求发到空闲的业务机器,避开忙碌的业务机器。腾讯在版本更新的时候使用的方法是:比如他们有 A B C D 四台机器,A 先进入升级冻结,负载均衡不会把新的请求发往 A 服务器,A 的用户会逐渐减少。A 的用户清零时,进入维护和升级状态。此时 B C D 三台提供服务。A 升级完成,通知负载均衡可以接客。B 进入冻结,等请求自然消退,升级,同样操作再来一次,负载均衡在 A C D 三台之间分配请求,B 完成,开始接客,C 开始同样的循环。这样就可以做到在不影响业务投递的情况下实现全局版本更新。表现就是,我开一局,用的是旧版本,下一局,就是新版本了。
作为微服务的特性,持续投递中,肯定针对局部的更新迭代的下线时间要小于整体系统更新迭代的下线时间。因此它有助于实现持续投递。
微服务的高可维护性也是很好理解的。在一个复杂的,功能多样的大型项目中扒拉扒拉找问题肯定比在一个有限范围的微服务里面找问题要难得多。
耐操性和可伸缩性。和上面一样,使用微服务的架构中,很容易辨认出性能瓶颈,并且进行扩展。同样,一个微服务出现致命问题,并不会导致整个系统的崩坏。
微服务的代价和负面特性
微服务也不是完美的。和单一系统(monolithic systems)相比,它的坑存在于:
- 风险复杂度 - 微服务架构系统是分布式系统,分布式系统整体(注意是整体)不利于通过测试。关于这一点我深有体会。如果测试设计不够完善,是完全可能出现所有微服务单个都没有问题,但是整个系统整体出现异常行为的现象的。而跨越边界,比如应用间的交流,网络间的交流,每一个边界的跨越都伴随着可能出现的复杂度的数量维度上的增加
- 开发复杂度 - 不同的微服务由不同的团队管理,它们的功能迥异。虽然这带来了灵活度的优势,但是也伴随着生产环境搭建的复杂度
- 代码基础(codebase) - 一个微服务系统的每一个微服务可能都是由不同的代码基础构造的。在进行 refactoring 的时候,需要考虑的因素就更多了。每一个微服务的 scope 都必须区分得恰到好处才可以顺滑。
这里单独讲了一个性能的负面效果。我们通过远程调用将一系列微服务串联起来,那么当一个用户请求发出的时候,每一次的远程调用都伴随着延迟和出错的风险。虽然微服务搭建出来的系统架构本身更加灵活,但是如果我们细看微服务间的通讯的话,很可能会发现这些通讯方式(比如 API 调用)缺乏灵活性而且粗糙,不如系统内部的调用来得更加可塑。
单一系统的扣分项
为什么要使用微服务,要回答这个问题我们必须看看单一系统的一些缺陷:
- 代码基础中的耦合过强,牵一发动全身
- 隐性强耦合,不是说代码基础里面的库引用关系,而是工作流和业务逻辑的耦合。比如一些特定的字符串应该如何格式,数据库都某列应该如何被使用,等等
- 部署单一系统的过程漫长,伴随着多方的参与和巨大的下线时间
- 这些系统的功能通常被设计成“瑞士军刀”的类型,在迭代和需求变更中一不小心就成了“缝合怪”。结构臃肿而复杂。如果你想要在单一系统中实现某种架构的连贯性,那么你最简单的功能很容易变成工程设计过度(overengineered)
这一段的书中的章节名字叫做 Greenfield vs. Brownfield,这个很好玩,绿田就是指这个项目还比较年轻,可塑性还很高,还在成长的阶段。褐田就是指,项目已经成熟,定性,趋于稳定,在进行业务的变现,服务的投递了。
其实一个单一系统,完全可以慢慢地剥离自己的子系统或者部件,根据需要,成为微服务。耦合强的情况下,如果一个部件经常出问题或者有被替换或者扩容的需求,那么完全可以花精力把它单独剥离出来,进行去耦合化,成为微服务。
所以这也是存在某种演化的过程的。
代码复用
微服务的代码复用也有坑。很多微服务的代码基础和其他微服务并不通用。因此也就失去了代码复用的可能性了。
微服务中的代码存在上下依赖关系的,需要迁移代码的话,必须自己整理这些依赖关系。而我们的随后的代码导航和 refactoring 也将变得复杂。
每一次的迁移,都伴随着多个代码的复制。我们对其中的任意一份的 refactoring 都代表着其他地方相同的迭代的工作量。这一点和我们在 C# 或者 Java 中为什么要使用依赖注入是一样的。
代码的复用在微服务中破坏了去耦合的目的。比如我们有 A B 两个微服务,共享了同一段代码。这段代码依存相同的库,比如 lib,目前都用的是 1.04 版本。我们假设 A 的 lib 根据需要从 1.04 更新到了 1.06版本;虽然你确定 1.06 和 1.04 版本对 B 服务没有任何区别,但是你要不要把 B 的引用的 lib 也一起更新到 1.06 呢,如果你这次不更新,你如何跟踪这两个服务使用同一个依赖但是版本却不同的事实?如果同步更新了,那么这就是一种耦合。违背了我们使用微服务去耦合的目的。(它这里的耦合不仅仅是代码层面的耦合,而是整个业务系统,包括代码层面,包括通讯,包括工作流连接,包括代码管理,后期维护层面的所有耦合。而微服务也正是被设计成减少和去除这些所有方面的耦合的)
微服务的特点是单一功能。因此我们需要尽可能地保证每一个微服务都是轻量化的。在我们构造一个微服务的时候,即使是从零开始建造,不复用代码,也是可以实现的。而不是在某个之前的微服务的基础上进行功能的扩展,或者说写到一半,参入一些复用的代码。话虽如此,但是依然有很多技巧可以让我们做到类似于代码复用的事情,而不被陷入破坏耦合的坑中。
真实场景的微服务
我们就拿淘宝来说。我们选中一个商品,加入购物车。它会通过一个 API Gateway 到达淘宝服务器集群里面错综复杂的微服务中。我们需要通过数据库,返回商品的参数。调用另外一个数据库,检查库存。另外一个,检查当前价格。然后我们需要把结果整理好之后,再返回给用户的浏览器。
在这个架构中,我们如果假设还希望用户的购买行为在服务器中产生另外的影响,比如说算法推荐。我买了绿色植物,可能会想要买花盆,花洒,铲子,肥料,土壤之类的商品。那么它需要根据我的购买行为为我推荐这些商品。
在单一系统中,加入这些功能是比较痛苦的。我们需要进入我们的整个工作流中,找到购买确认的那一部分,然后从那里开始进行功能拓展。而在微服务的场景下,这仅仅只是意味着多了一个叫做“好物推荐”的微服务罢了(或者一些列的微服务集群,包括机器学习,神经网络,算法推荐那些花里胡哨的东西)
.NET 微服务技术栈
本书中提到的主要有两个技术:Nancy 和 OWIN
这里把我看笑了。他把很多架构所需要进行的前期配置工作形容成“Configuration and ceremony",节日庆典。很诙谐啊,这里主要诟病一些架构需要使用之前的准备工作极为复杂和漫长。
Nancy 架设比较容易,而且可测试性高,在使用方便的同时提供了很多拓展性。
OWIN 的全称时 Open Web Interface for .NET。它是一套标准为网站服务器,致力于针对网站应用进行了去耦合化。一个 OWIN-Compliant 的网站服务器由标准化的接口处理网络请求,并且分配给应用层进行处理。
OWIN 是一套标准,而 Nancy 则是符合 OWIN 标准的一个架构。
XXX-compliant 是指符合 XXX 标准的东西。比如说,我们可以说一款乐高马达上面的板子是 Lego-compliant,因为你可以往上面插 Lego 砖。在这里 Nancy 也是 OWIN Compliant,它可以使用 OWIN 标准的管道流
我写的有点长了,我们单独分一个篇章来讲这一章的例子,跟着来做一下。先断在这里。