草庐IT

走向微服务

dotnet-college 2023-03-28 原文

原文出处:https://www.cnblogs.com/Finley/p/16812713.html

上一篇,我们介绍了通过缓存、横向扩容、消息队列、分布式数据库等基础设施来提高系统并发量的方法。在实际开发中业务逻辑比基础设施更加灵活多变且更容易出故障,架构设计不仅需要考虑基础设施的建设,同样需要关注业务开发的便利以及应对业务系统的故障。

还是从博客开始#

还是从我们熟悉的博客网站开始,小明是个喜欢写博客的程序员,他觉得市面上的博客网站都太丑了,就想自己搞一个。说干就干,小明抄起LAMP(Linux-Apache-MySQL-PHP)一把梭三下五除二就把网站搞了起来,网站的名字就暂定为「淘金网」?

由于淘金网界面美观方便好用, 越来越多的网友开始入驻淘金网来耕耘自己的一小片天地。渐渐地用户们觉得只能写博客功能太单一了,有些用户想要把系列文章编辑成电子书、有些用户想要做直播分享,有些用户想要在这里发状态,小明也想要卖点会员补贴一下服务器的支出…… 没关系都可以有,继续一把梭加逻辑就是了:

日子也就这么一天一天的过下去,淘金网逐渐从个人小站发展成了一家小有规模的创业公司。

  1. 博客、电子书、微博每个模块都向用户、订单这些表里塞了一堆字段,一动线上就出 BUG, 谁都不敢动。
  2. 即使上线一个小功能也要发布整个网站,有时候会不小心带上了一些未经测试的代码,有时候会在意想不到的地方出了错误。
  3. 一个业务容量不足需要加机器, 就相当于给所有模块做了扩容,白白支出了其它模块的固定开销(无负载的服务所消耗的资源,比如 Spring 容器消耗的大量内存,后台线程消耗的 CPU)。

这些都还可以忍,直到那个阳光明媚的早晨小明美滋滋打开后台想要看一眼今天的入账时,却发现网站打不开了。。。

检查日志发现,在编写创作者中心的逻辑时不小心写了一个不停向数组中插入元素的死循环,过不了多久服务器就会 OOM 崩溃掉,只有 supervisord 还在徒劳的尝试重启服务器。。。

明总是个未雨绸缪的人, 他觉得BUG 是无法杜绝的,这样的故障早晚会再次发生。 小明灵机一动决定把博客、电子书、会员、直播这些业务拆分成独立的服务端程序,一个模块拉起一个进程,这样任你 OOM 还是 panic 都不会影响其它业务,万一出了什么故障损失也就小得多了。

小明发现把服务拆开后一些老问题也解决了:未测试代码被误上线的情况几乎没有了;各个模块可以按照需求各自规划服务器资源了;而且还有个意外之喜,每个模块可以用不同的技术栈, 前端可以用 node.js 做 BFF, 数据分析可以用 Hadoop, 推荐系统可以用 Python...

还有个问题没有解决,不同模块依旧依赖同一个数据库,表结构牵一发而动全身,每次修改都小心翼翼如履薄冰。小明决定一鼓作气,将数据库也按照业务板块进行拆分,每个模块只允许读写自己的数据库,需要其它模块数据时一律调接口,禁止直接访问数据库。

ok, 现在一切都是那么的完美,岁月如此静好。。。

服务治理的难题#

话说自从服务拆分之后再也没有出现过全站崩溃的事故,小明美滋滋的准备开个年会,大家拿了年终奖回家过年。就在年会上,小明听到程序员们在抱怨:

  • “支付系统一到有活动就扩容,活动结束就把临时加的机器下掉,其它人也得跟着改配置文件才能找到服务地址”
  • “一个请求经过了好几个模块,出了 BUG 找半天都查不清是哪个模块的故障”
  • “一上促销商城那边的调用就特别多,差点把我们的支付压垮,直播的人就来抱怨说没法刷礼物”
  • “会员那边不靠谱,付款失败还照样发会员,损失好多钱”

小明把这些问题一一记录下来,开始寻找答案。

服务发现#

支付系统一到有活动就扩容,活动结束就把临时加的机器下掉,其它人也得跟着改配置文件才能找到服务地址

我们服务部署在不同的服务器上,而且会随着负载情况不时的增删机器,调用方如何及时准确的获得服务的地址?实例之间如何均衡负载?我们将这个问题称为服务发现。

DNS 系统也可以算是一种服务发现,服务提供方的节点在启动后向 DNS 注册自己的地址,节点下线前将自己从 DNS 的节点列表中删除。服务调用方通过域名向DNS查询服务提供方的实际地址,DNS 会在节点列表中按预定策略挑选一个节点的 ip 地址返回给调用方。

在实际使用中更多的还是采用 Zookeeper、Consul、Etcd 等高一致性的 KV 组件做服务发现:

服务发现系统会通过心跳包等机制检查节点健康状态,并屏蔽不健康的节点。这样即使节点在崩溃前没有向配置中心报告故障,服务发现也能避免请求继续到达异常节点:

因为服务发现可以方便的控制调用方访问的节点,所以也常常用来实现灰度发布,A/B测试等功能:

限流、熔断、降级#

一上促销商城那边的调用就特别多,差点把我们的支付压垮,直播的人就来抱怨说没法刷礼物

虽然服务拆分之后单个进程崩溃不会波及其它进程,但是若下层的服务的实际负载超出了最大吞吐量出现响应过慢或超时的情况仍然可能波及其它上层服务。

因此,有必要在服务之间设置保护机制,防止小故障的影响不断扩大,最终造成大面积的雪崩。常用的保护机制有三种:

熔断:当某个服务或节点的调用失败数或调用耗时超过阈值时,调用方应停止继续调用此节点并快速返回失败。防止自身请求大量堆积,整条链路浪费大量资源等待下游响应。

降级:当下游服务停止工作后,如果该服务并非核心业务,则上游服务应该降级,以保证核心业务不中断。

限流:当服务提供方的负载接近最大处理能力时可以丢弃请求并立即返回失败,防止大量堆积的请求将自身压垮。或者当某个上层服务的调用量过大时丢弃它的(部分)请求,避免自身崩溃影响其他上层服务。

链路追踪#

一个请求经过了好几个模块,出了 BUG 找半天都查不清是哪个模块的故障

要想查清故障原因就需要记录一个请求在系统中经过了哪些模块以及模块之间的调用关系,进而找到故障模块的相关日志,这种在服务系统中追踪调用关系的技术称为链路追踪。

链路追踪的原理是在请求进入系统时为它分配一个唯一的 traceID (通常使用雪花算法生成), 这个请求调用过程中产生的所有日志数据都要带上这个 traceID 并上报到统一的日志数据库。事后分析时只要使用 traceID 进行查询就可以找到相关日志了。

比较出名的链路系统是 Google 的 Dapper,有兴趣的朋友可以点链接看一下详细的资料,这里就不展开讨论了。

分布式事务#

会员那边不靠谱,付款失败还照样发会员,损失好多钱

付款和变更订单状态两个操作应该是原子的,要么都执行要么都不执行,不应该出现一个执行另一个不执行的情况。这是典型的数据库事务问题,在我们将数据库拆分之前这个问题可以交给数据库的事务机制解决,但是数据库拆分之后一个事务涉及到多个数据库实例甚至是异构的数据库,这就需要一些分布式事务协调组件来处理了。

常见的分布式事务实现方案有 TCC(try-confirm-catch)事务、MQ 事务消息、Saga 事务等。分布式事务主要有两种实现思路,第一种的典型代表是 TCC 事务,TCC 事务分为三个阶段:

  1. Try 阶段: 事务协调器要求参与方预留并锁定事务所需资源;
  2. Confirm 阶段: 若所有参与方都表示资源充足可以提交,事务协调器会向所有参与方发出 Confirm 指令,要求实际执行事务。
  3. Cancel 阶段: 若 Try 或 Confirm 阶段任一参与者表示无法继续事务协调器会向所有参与方发出 Cancel 指令解锁预留资源并回滚事务。

第二种实现思路的典型代表是 Saga 事务,Saga 事务将一个大事务拆分成多个有序的子事务并且每个子事务都准备了撤销操作,事务协调器会顺序的执行子事务,如果某个步骤失败,则根据相反顺序一次执行一次撤销操作。

上面我们只简单介绍了分布式事务保证原子性的机制,在实际实现中还要考虑分布式事务的一致性(强一致还是最终一致)、隔离性(Saga 事务会暴露事务执行到一半时的状态)、对业务的侵入性、并发量等各种问题,简言之分布式事务是一种非常复杂、成本很高的技术。

由于分布式事务的高成本,在在实际开发中经常使用「对账」的方式来保证多模块事务的最终一致性,即用离线任务定时扫描数据库找出未正确处理的事务,然后按照预定策略进行补偿(比如撤销未成功付款用户的会员身份)或者要求人工介入修复。

微服务时代的基础设施#

容器化和 Kubernetes#

淘金网从最开始便是直接部署在云服务器上的,到了后来做了服务拆分也依旧没有改变。每次扩容都要等待新的云服务器慢慢启动、跑脚本装环境最后拉起服务进程,一等就是半天。需要升级 JRE 的时候还要写脚本一台一台连上去进行升级,有时候还会升级失败需要人工介入进行处理。 还有些时候为了充分利用资源会在一台云服务器上部署好几个服务,这些混部的机器管理起来也是各种麻烦。

这一切都让程序员们苦不堪言,于是小明又开始了调研,这时一种叫「容器化」的新技术吸引他的视线。

我们都知道计算机可以分为三层:硬件、操作系统和应用程序。所谓的云服务器本质上是虚拟机,虚拟机可以模拟硬件的接口,这样做最大的好处是可以在虚拟机上运行与宿主机不同的操作系统程序,比如我们可以 Windows 系统上运行 Linux 虚拟机。但是,操作系统内核的计算量十分庞大,在软件模拟出的硬件上运行其性能可想而知。

对于云服务器而言并不需要再运行一个操作系统内核,云服务器只是需要独立的目录树、进程空间、协议栈就可以了,就是说即使云服务器 A 和 B 运行在同一台的宿主机上 A 的根目录和 B 根目录是独立的。

容器化技术的实质是模拟操作系统内核,实际上运行的只有宿主机一个操作系统内核,但是宿主机上的每个容器都认为自己拥有一个独立的操作系统内核。

Docker 是目前容器技术的事实标准,它使用使用 Linux Namespaces 技术隔离目录树、进程空间、协议栈等,使容器之间互不影响;使用 cgroups 机制分隔宿主机的 CPU、内存等资源。

Docker 的另一个重要贡献是定义了容器镜像的标准。 Docker 镜像使用分层文件系统 AUFS。每层数据一旦提交便不可改变,只能添加一个新层将其覆盖。Docker 镜像的不可变性保证了运行环境的一致,免除了登录云服务器装环境的痛苦,一致的运行环境也减少了「测试环境是好的,怎么一上正式环境就出问题了」的发生。 分层文件系统使得每次打包 Docker 镜像只需要更新业务二进制,比虚拟机镜像小很多。Docker 允许将任何现有容器作为基础镜像来使用,极大的方便了重用。

Docker 只提供了单机上的容器化支持,而我们的生产环境是由很多服务器组成的,有些模块负载不足需要横向扩容多加几个容器,有些宿主机会宕机需要将上面运行的容器换台宿主机重启。解决这个问题的是大名鼎鼎的 Kubernetes, Kubernetes 不仅可以完成服务编排的工作,而且提供了描述集群架构的规范。我们通过编写 yml 定义集群的最终状态,Kubernetes 可以将系统自动达到并维持在这个状态。这种能力将人力从繁重的运维工作中解脱出来,实现了方便可靠的部署和扩缩容。

Service Mesh#

由于微服务需要提供服务发现、熔断限流等服务自治能力,所以微服务框架所要提供的功能比传统的 Web 框架多很多。看到现在仍不少见的 Centos7、Java 5、Struts2 等各种老旧的基础设施就可以想象升级基础框架是一件多么痛苦的事情。

为了解决基础架构组和业务组之间为了升级框架带来的疯狂扯皮,小明找到了一个新的思路。这种方式称为 Service Mesh,它将服务发现、认证授权、调用追踪等服务治理所需的能力放到一个被称为 SideCar 的代理组件中,所有出站入站的流量都通过 SideCar 进行处理和转发, 业务方只需要和 SideCar 进行通信即可。

Service Mesh 中还有个被称为控制面(Control Panel) 的组件来统一管理所有 Sidecar 的配置,SideCar 和业务组成的部分称为数据面(Data Panel), 控制面和数据面共同组成了 Service Mesh 架构。

因为 Side Car 和业务只通过 RPC 进行通信,两者可以独立升级,免去了升级基础框架时需要改动业务代码的种种麻烦。由于 RPC 调用天生可以跨语言,所以只需要开发一次 SideCar 就可以对接多种语言开发的业务系统。

图片来源: Pattern: Service Mesh

ServiceMesh 直译是服务网格,大概是因为架构图比较像网格才起了这个名字吧~

总结#

何谓微服务#

又是一年年终季,小明看着自己搞的这么多东西打算整个 PPT 去行业交流会上吹吹牛。他左翻右找,终于找到了一篇论文:Microservices: a-definition-of-this-new-architectural-term, 原来自己做的这套结构有一个好听的名字:「微服务」:

微服务是一种通过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言,不同的数据存储技术,运行在不同的进程之中。服务采取轻量级的通信机制和自动化的部署机制实现通信与运维。

「什么是微服务」这个问题是典型的一千个人眼里有一千个哈姆雷特,不过回顾「淘金网」的历程我们发现有一些理念已经是业界的共识:

  1. 按照业务板块将单体大服务拆分为多个独立的小服务,通过分割解耦的方式控制代码的复杂度。小服务的独立性赋予了它更高的灵活性,比如采用异构技术和异构架构的自由。分散部署也有效的阻止了局部错误造成大范围的故障。
  2. 数据去中心化: 各个服务独立维护数据库,降低模块之间的耦合程度。
  3. 重视服务治理,但鼓励各模块自治。微服务不仅仅是将服务拆分,而且重视处理拆分后出现的一系列问题,比如控制流量路由的服务发现系统;避免连锁故障的限流、熔断、降级技术;用于调试和排查的链路追踪系统;以及维护事务安全性的分布式事务机制。服务治理能力不是由中心或者基础架构强加给各模块的,而是各模块根据自己的需要灵活选择治理能力和实现方式。
  4. 重视弹性:服务的部署容量不是固定的,而是根据业务需求量随时增加或减少节点数,并且因此促进了 Kubernetes 等弹性平台的广泛使用。
  5. 重视弹性:服务系统应该可以按照业务需要灵活的进行扩缩容。

下集预告:揭开分布式系统的面纱#

很多同学一提到分布式系统便想到 CAP 理论、Paxos 算法、Hadoop 等吓人的名词,甚至失去了继续学习的勇气。「从小白到架构师」 系列的前两篇几乎每句都与分布式系统密切相关,第三篇文章「揭开分布式系统的面纱」我们将一起探索分布式系统的各种技术和问题:如何编写在分布式环境中运行的业务代码?什么是分布式共识问题,又有哪些解决方案?CAP 定理是什么,又有哪些例证?那些经典的分布式数据库又是如何工作的?

 

 

 

 
 

有关走向微服务的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. 【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程 - 2

    这篇文章,主要介绍如何使用SpringCloud微服务组件从0到1搭建一个微服务工程。目录一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件(2)微服务依赖1.2、搭建注册中心(1)引入依赖(2)配置文件(3)启动类1.3、搭建配置中心(1)引入依赖(2)配置文件(3)启动类1.4、搭建API网关(1)引入依赖(2)配置文件(3)启动类1.5、搭建服务提供者(1)引入依赖(2)配置文件(3)启动类1.6、搭建服务消费者(1)引入依赖(2)配置文件(3)启动类1.7、运行测试一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件这里主要是使用的SpringCloudNetflix

  3. 若依框架解读(微服务版)——2.模块间的调用逻辑(ruoyi-api模块)(OpenFeign)(@innerAuth) - 2

    模块之间的关系我们可以了解到一共有这么多服务,我们先启动这三个服务其中rouyi–api模块是远程调用也就是提取出来的openfeign的接口ruoyi–commom是通用工具模块其他几个都是独立的服务ruoyi-api模块api模块当中有几个提取出来的OpenFeign的接口分别为文件,日志,用户服务我们以RemoteUserService接口为例子:其中contextId="remoteUserService"为bean的名称,value=ServiceNameConstants.SYSTEM_SERVICE为接口的描述,fallbackFactory=RemoteUserFallback

  4. 【微服务】ES使用实战·黑马旅游(五) - 2

    🚗Es学习·第五站~🚩Es学习起始站:【微服务】Elasticsearch概述&环境搭建(一)🚩本文已收录至专栏:微服务探索之旅👍希望您能有所收获一.引入综合前几站所学,我们已经对Elasticsearch的使用有了一定的了解,接下来让我们一起通过一个综合实战案例来复习前几站所学内容,体会在实际生产中的作用。我们一起实现如下功能:酒店搜索和分页酒店结果过滤我周边的酒店酒店竞价排名数据聚合筛选选项搜索框自动补全酒店数据的同步二.环境搭建按照第一站的学习部署Elasticsearch并启动运行。按照第二站的学习中的如下步骤,初始化测试项目并在Es导入数据。使用Elasticsearch,肯定离不开

  5. 从大模型走向小模型,谁将是ChatGPT布局to B行业的大赢家? - 2

    ChatGPT淘金热当前,爆发了ChatGPT热潮,吸引众多科技企业陆续加入其中。这与当年美国西部加利福尼亚的淘金热何其相似。历史总会惊人的相似,ChatGPT聊天机器人好比一座数字化时代的金矿。全世界科技淘金人蜂拥而至,从潮起到潮落,潮水退去之时,能生存下来的可能不是淘金人,而是卖铲子、卖牛仔裤等提供淘金基础工具的那批人。 站在ChatGPT的行业风口,除了超大模型的演进发展,还将诞生更多的小模型专注服务垂直领域,普惠千家万户,将是必然趋势之一。从大模型走向小模型,谁将是未来的大赢家?带着这个问题,我们不妨先来捋一捋ChatGPT背后的算力和经济账。01「似乎不止于此」ChatGPT带动了服

  6. 什么是微服务? - 2

    一、什么是微服务?​微服务是一种分布式架构,分布式架构就是把服务做拆分,在我们的传统单体架构中,我们把所有的服务都写在一起,随着业务的扩大我们的代码耦合度会变得越来越高,后期维护起来也很不方便。微服务就是把模块拆分,把我们整个项目拆解分成许多独立的子项目,每个子项目之间独立开发和部署,子项目也有自己独立的功能,这些独立的子项目就形成了微服务,不同的子项目就进而形成一个服务集群。​举例说明:一个商城系统很多模块组成,例如订单模块、用户功能、商品服务、支付模块等,这些模块如果采用单体架构,代码之间的耦合度会非常高,也不便于后期的维护,当一个模块出现问题时整个项目也会受到影响。如果采用微服务,每个模

  7. 【微服务36】分布式事务Seata源码解析四:图解Seata Client 如何与Seata Server建立连接、通信【云原生】 - 2

    文章目录一、前言二、概述三、TM事务管理器初始化1、TM初始化流程图2、TM初始化流程1)获取TmNettyRemotingClient实例1>TmNettyRemotingClient实例化2>AbstractNettyRemotingClient实例化2)初始化TmNettyRemotingClient1>注册一些请求处理组件2>初始化AbstractNettyRemotingClient(1)AbstractNettyRemoting初始化(2)启动netty客户端组件Abs

  8. 【ArchSummit】阿里云原生微服务架构治理最佳实践 - 2

     前言📫作者简介:小明java问道之路,专注于研究Java/Liunx内核/C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。📫热衷分享,喜欢原创~关注我会给你带来一些不一样的认知和成长。🏆InfoQ签约作者、CSDN专家博主/后端领域优质创作者/内容合伙人、阿里云专家/签约博主、51CTO专家🏆🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~本文目录前言本文导读一、云原生微服务的挑战和趋势1、微服务在云原生下的挑战1.1挑战1.2微服务化深入服务治理是难点2、云原生微服务的发展趋

  9. go - 我应该如何避免在微服务架构中多次实现我的方法 - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭3年前。Improvethisquestion我在Golang中从事微服务架构(我的第一个架构)工作,我发现自己在多个服务上复制模型定义。我该怎么做才能避免这种情况?我只能考虑用我所有的模型定义来实现一个共享库,但我无法评估优缺点。你能告诉我解决这个问题的其他方法吗?

  10. go - 如何使用 golang 微服务? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭4年前。Improvethisquestion我的公司使用Go来构建一些HTTPAPI服务。我们希望这些服务共享一个HTTP端口。所以现在的解决方案是我们创建一个名为router的项目,并在router中导入一些模块,每个请求都通过router传递到它们自己的模块。但问题是,如果其中一个模块进程崩溃,路由器就会崩溃。有什么解决办法吗?要求:一个http端口。每项服务都是独立的。我知道go-kit和go-micro

随机推荐