分布式:架构演进,核心问题,共识算法
分布式系统应使资源易于访问,隐藏资源分布在网络上的事实,开放的,可伸缩的。重在资源共享与加快计算机速度,强调的是服务化以及服务的分散化。微服务架构倡导将软件应用设计成多个独立开发、可配置、可运行和可维护的子服务。重在于松耦合和高内聚的效果,使每个模块独立,强调服务的专业化和精细分工。
【问题】分布式是什么?分布式是指将一个计算机系统或应用拆分成多个独立的子系统,这些子系统分布在不同的计算机或服务器上,并通过网络连接互相通信,协调工作,以完成一致的任务目标的一种计算机技术。
【问题】为什么要分布式呢?首先,分布式技术可以将大型系统分解成小型组件,从而降低单个组件的复杂度,便于开发和维护。其次,分布式技术可以实现负载均衡,提高整个系统的性能和可伸缩性。此外,分布式系统还具备容错性,即当一个节点出现故障时,其他节点仍然可以继续运行,从而提高了系统的可靠性。
【问题】分布式又会有哪些问题?
- 数据一致性问题:由于数据分散在不同的节点上,如何保证数据的一致性成为一个难题。
- 网络延迟和通信故障问题:分布式环境下,节点之间的通信必须通过网络完成,网络通信故障或延迟会严重影响系统的性能和可靠性。
- 节点故障问题:分布式系统中,每个节点都可能出现故障,如何保证在这种情况下系统仍能正常运行也是需要考虑的问题。
【问题】分布式系统是如何实现事物的?分布式系统实现事务一般采用两阶段提交(2PC)协议。该协议可以确保分布式环境下所有节点都能提交或回滚一个事务,从而保证系统的数据一致性。在 2PC 协议中,一个事务分为两个阶段,即投票阶段和提交阶段。投票阶段中,协调者询问参与者是否可以提交事务,并等待参与者的响应;如果所有参与者都可以提交,则进入提交阶段,否则回滚事务。在提交阶段中,协调者通知所有参与者提交事务,如果所有参与者都成功的提交了事务,则整个事务提交完成,否则回滚事务,以保证系统的一致性。
1,架构演进
1.1,单体JEE架构(JSP+Servlet)
JEE(也叫:J2EE)以面向对象的Java编程语言为基础,扩展了Java平台的标准,是Java平台企业版的简称。它作为企业级软件开发的运行时和开发平台,极大促进了企业开发和定制信息化系统的进展。
JEE将企业级软件架构分为三个层次:Web层、业务逻辑层和数据存取层(MVC),每个层次的职责分别如下:
- Web层:负责与用户交互或者对外提供接口。
- 业务逻辑层:为了实现业务逻辑而设计的流程处理和计算处理模块。
- 数据存取层:将业务逻辑层处理的结果持久化以待后续查询,并维护领域模型中对象的生命周期。
JEE平台将不同的模块化组件聚合后运行在通用的应用服务器上,例如:WebLogic、WebSphere、JBoss等,这也包含Tomcat,但Tomcat仅仅是实现了JEE Web规范的Web容器。JEE平台是典型的二八原则的一个应用场景,它将80%通用的与业务无关的逻辑和流程封装在应用服务器的模块化组件里,通过配置的模式提供给应用程序访问,应用程序实现20%的专用逻辑,并通过配置的形式来访问应用服务器提供的模块化组件。事实上,应用服务器提供的对象关系映射关系、数据持久服务、事务服务、安全服务、消息服务等通过简单的配置即可在应用中使用。(JSP+Servelt仅仅需要配置web.xml就可以就可简单使用)
- JEE 单体架构虽分层,但未真正解耦:虽然逻辑上实现了分层,并划分了职能团队,但多个业务逻辑依然集中在同一个项目和 JVM 中运行。
- 随着复杂度增长,耦合问题加剧:由于开发迭代频繁、新手工程师为赶进度跨层调用组件,导致原本隔离的业务组件、UI 层、数据层耦合严重。
- 后期维护困难:难以明确组件边界,系统完全耦合,造成新功能迭代和系统维护困难。
- 部署形式限制了解耦能力:尽管 JEE 支持容器分离部署,但实际多数项目仍部署在同一服务器、运行在同一 JVM 中,进一步限制了解耦与扩展能力。
1.2,单体SSH架构(SSM,SpringBoot)
在JEE开始流行但没有完全奠定其地位时,开源软件Struts2、Spring和Hibernate开始崭露头角,很快成为行业内企业开发的开源框架标配,JEE规范中的各种技术如EJB由于学习成本高,XML配置文件多,难以做单元测试等问题迅速失去了发展的机会。WebMVC框架Struts在用户交互的UI层进一步划分了前端的职责,将用户交互划分为视图、模型和控制器三大模块(MVC)。MVC详解
SSH时代,Struts MVC模型几乎服务于大多数企业服务的Web项目。后来,开源软件Spring的发布,更加改变了JEE一开始制定的战略目标。Spring框架作为逻辑层实现的核心容器,使用起来简单、方便又灵活,几乎大部分开发者完全倒向了Spring开源派。Spring框架有两个核心思想:IOC和AOP。
Spring IOC:指的是控制反转,将传统EJB基于容器的开发改造成普通的Java组件的开发,并且在运行时由Spring容器统一管理和串联,服务于不同的流程,在开发过程中对Spring容器没有强依赖,便于开发、测试、验证和迁移。使用EJB实现一个服务化组件Bean时,需要依赖多个容器接口,并需要根据容器的规则进行复杂的XML配置,测试需要依赖于应用服务器的环境,有诸多不便;使用Spring框架则不然,开发业务逻辑时每个业务逻辑的服务组件都是独立的,而不依赖于Spring框架,借助于Spring容器对单元测试的支持,通过对下层依赖服务进行Mock,每个业务组件都可以在一定范围内进行单元化测试,而不需要启动重型的容器来测试。
Spring AOP:AOP代表面向切面编程,通常适用于使用面向对象方法无法抽象的业务逻辑,例如:日志、安全、事务、应用程序性能管理(APM)等,使用它们的场景并不能用面向对象的方法来表达和实现,而需要使用面向切面来表达,因为它们可能穿插在程序的任何一个角落里。在Java的世界里,AOP的实现方式有如下三种:
- 对Java字节码进行重新编译,将切面插入字节码的某些点和面上,可以使用cglib库实现。
- 定制类加载器,在类加载时对字节码进行补充,在字节码中插入切面,增加了除业务逻辑外的功能,JVM自身提供的Java Agent机制就是在加载类的字节码时,通过增加切面来实现AOP的。
- JVM本身提供了动态代理组件,可以通过它实现任意对象的代理模式,在代理的过程中可以插入切面的逻辑。可以使用Java提供的APIProxy.newProxyInstance()和InvocationHandler来实现。
另外,AspectJ是实现AOP的专业框架和平台,通过AspectJ可以实现任意方式的字节码切面,Spring框架完全支持AspectJ。
到现在为止,SSH开源标配框架中有了UI交互层的Struts框架和业务逻辑实现层的Spring框架,由于面向对象领域的模型与关系型数据库存在天然的屏障,所以对象模型和关系模型之间需要一个纽带框架,也就是我们常说的ORM框架(链接),它能够转化关系,也可以将关系转化成对象,于是,Hibernate框架出现了。Hibernate通过配置对象与关系表之间的映射关系,来指导框架对对象进行持久化和查询,并且可以让应用层开发者像执行SQL一样执行对象查找。这大大减少了应用层开发人员写SQL的时间。然而,随着时间的发展,高度抽象的ORM框架被证明性能有瓶颈,因此,后来大家更倾向于使用更加灵活的MyBatis来实现ORM层。
这一时代SSH框架与JEE时代架构类似,可分为三个层次:实现交互UI接口的Web MVC层、实现业务逻辑的Spring层及实现对象关系映射的Hibernate层,每个层级的实现比JEE对应的层次更简单、更轻量级,不需要开启整个应用服务器即可测试和验证,极大提高了开发效率,这得益于Spring框架的控制反转理念。
由于SSH时代的企业级软件服务的对象仍然是企业,用户量并不大,因此,大多数企业里的SSH架构最终会被打包到同一个JEE规范的War包里,并且部署在Apache Tomcat Web容器里,因此,整个结构还是趋向于传统的单体架构,业务逻辑仍然耦合在一个项目中,即使通过规范来约束模块化组件的耦合度,效果也往往适得其反。
SSH时代的职能团队的划分仍然停留在层次上,与JEE架构的智能团队划分类似,分为前端团队、后端业务逻辑研发团队和DBA团队。
1.3,服务化架构(SOA)
从JEE时代到SSH时代,服务的特点仍然是单体化,服务的粒度抽象为模块化组件,所有组件耦合在一个开发项目中,并且配置和运行在一个JVM进程中。如果某个模块化组件需要升级上线,则会导致其他没有变更模块化组件同样上线,在严重情况下,对某个模块化组件编程,由于种种原因,会导致其他模块化组件出现问题。
另外,在互联网异军突起的环境下,传统JEE和SSH无法满足对海量用户发起的高并发请求进行处理请求,无法突破耦合在一起的模块化组件的性能瓶颈,单一进程已经无法满足需求,并且水平扩展的能力也很有限的。
为了解决上面的问题,SOA出现了。SOA代表面向服务的架构,俗称服务化。SOA将应用程序的模块化组件通过定义明确的接口和契约联系起来,接口是采用中立的方式进行定义的,独立于某种语言、硬件和操作系统,通常通过网络通信来完成,但是并不局限与某种网络协议,可以是底层的TCP/IP,可以是应用层的HTTP,也可以是消息队列协议,甚至可以是约定的某种数据库存储形式。这使得各种各样的系统中的模块化组件可以以一种统一和通用的方式进行交互。
对比JEE和SSH时代的模块化组件后发现,SOA将模块化组件从单一进程中进一步拆分,形成独立的对外提供服务的网络化组件,每个网络化组件通过某种网络协议对外提供服务,特点如下:
- SOA定义了良好的对外接口,通过网络协议对外提供服务,服务之间表现为松耦合性,松耦合性具有灵活的特点,可以对服务流程进行灵活组装和编排,而不需要对服务本身做改变。
- 组合整个业务流程的每个服务的内部结构和实现在发生改变时,不影响整个流程对外提供服务,只要对外接口保持不变,则改变服务内部的实现机制对外部来说可以是透明的。
- SAO通过定义标准定义标准的对外接口,可以让底层通用服务进行下沉,供多个上层的使用方同时使用,增加了服务的可重用性。
- SOA通过定义标准的对外接口,可以让底层通用服务进行下沉,供多个上层的使用方同时使用,增加了服务的可重用性。
- SOA可以让企业最大化地使用内部和外部的公共服务,避免重复造轮子,例如:通过SOA从外部获取时间服务。
SOA实现方式:Web Service和ESB。
Web Service:Web Service技术是SOA服务化的一种实现方式,它使得运行在不同的机器及操作系统上的服务的互相发现和调用成为可能,并且可以通过某种协议交换数据。
从图中可以看出,每个服务之间是对等的,并且互相是解耦的,通过WSDL定义的服务发现接口进行访问,并通过SOAP协议进行通信。SOAP协议通常是一种在HTTP或者HTTPS通道上传输XML数据来实现的协议,但是每个服务都要依赖中心化Web Service目录来发现现存的服务。
Web Service的工作原理如下:
- 服务提供者Web Service 2和Web Service 3通过UDDI协议将服务注册到Web Service目录服务中。
- 服务消费者Web Service 1通过UDDI协议从Web Service目录中查询服务,并获得服务的WSDL服务描述文件。
- 服务消费者Web Service 1通过WSDL语言远程调用和消费Web Service 2和Web Service 3提供的服务。
通过这个过程,要改造一个新的业务流程,可以从Web Service目录中发现现有的服务,并最大限度地重用现有的服务,经过服务流程的编排来服务新的业务。
ESB:ESB是企业服务总线的简称,是用于设计和实现网络化服务交互和通信的软件模型,是SOA的另一种实现方式,主要用于企业信息化系统的继承服务场景中。Mule是企业服务总线的一个实现。
在SOA服务化发展之前,企业对信息化系统进行了初步建设,这些企业信息化系统由异构技术栈实现:不同的开发语言、操作系统和系统软件为了快速响应新的市场,完全使用新技术重建所有的信息化系统是不现实的,在现有的服务系统上增加新的功能或者叠加新的服务化系统的方法更加可行,这需要对这些现有的信息化系统和新增的信息化系统进行组合。SOA凭借其松耦合的特性,正好应用于这一场景,使得企业可以按照服务化的方式来添加新服务或升级现有服务来解决新业务对流程编排的需要,甚至可以通过不同的取到来获得外部服务,并与企业的现有应用进行组合,来提供新的业务场景所需的信息化流程。
ESB也适用于事件处理、数据转换和映射、消息和事件异步队列顺序处理、安全和异常处理、协议转换和保证通信服务的质量等场景。
ESB服务没有中心化服务节点,每个服务提供者都是通过总线的模式插入系统,总线根据流程的编排负责将服务的输出进行转换并发送给流程要求的下一个服务。
ESB的核心在于企业服务总线的功能和职责:
- 监控和控制服务之间的消息路由。
- 控制可插拔的服务化的功能和版本。
- 解析服务之间的交互和通信的内容和格式。
- 通过组合服务、资源和消息处理器来统一编排业务需要的信息处理流程。
- 使用冗余来提供服务的备份能力。
企业服务总线是ESB的核心要素,所有服务都可以在总线上插拔,并通过总线的流程编排和协议转接能力来组合实现业务处理能力。
1.4,微服务架构(SpringCloud)
随着互联网的不断发展,互联网产品需要服务的用户量逐渐增加,海量用户发起的大规模、高并发请求是企业不得不面对的,虽然SOA服务化系统能够分解任务,让每个服务更简单、职责单一、更易于扩展,但无论是Web Service还是ESB,都有时代遗留的问题。
Web Service的问题:
- 依赖中心化的服务发现机制。
- 使用SOAP通信协议,通常使用XML格式来序列化通信数据,XML格式的数据冗余太大,协议太重。
- 服务管理和治理设施并不完善。
ESB的问题:
- ESB虽然是SOA实现的一种方式,却更多地体现了系统集成的便利性,通过统一的服务总线将服务组合在一起,并提供组合的业务流程服务。
- 组合在ESB上的服务本身可能是一个过重的整体服务,或者是传统的JEE服务等。
- ESB视图通过总线来隐藏系统内部的复杂性,但系统内部的复杂性仍然存在。
- 对于总线本身的中心化的管理模型,系统变更影响的范围经常会随之扩大。
过于严格的规范定义带来过度的复杂性。而构建在 SOAP 基础之上的 ESB、BPM、SCA、SDO 等诸多上层建筑,进一步加剧了这种复杂性。开发信息系统毕竟不是作八股文章,过于精密的流程和理论也需要懂得复杂概念的专业人员才能够驾驭。SOA 诞生的那一天起,就已经注定了它只能是少数系统阳春白雪式的精致奢侈品,它可以实现多个异构大型系统之间的复杂集成交互,却很难作为一种具有广泛普适性的软件架构风格来推广。SOA 最终没有获得成功的致命伤与当年的EJB如出一辙,尽管有 Sun Microsystems 和 IBM 等一众巨头在背后力挺,EJB 仍然败于以 Spring、Hibernate 为代表的“草根框架”,可见一旦脱离人民群众,终究会淹没在群众的海洋之中,连信息技术也不曾例外过。
由于上述问题,近年来服务化架构设计得到了进一步的演化和发展,微服务架构已经出现在不同公司的讨论和设计中,并且在实践的过程中证明,微服务设计模式起到了正向作用,以至于在不同的公司里,每个人都在以微服务来设计系统架构,但并不是所有同行都意识到他们使用的就是微服务架构模式,而是在默默享受微服务给软件设计、实现和运行带来的好处。
微服务架构倡导将软件应用设计成多个独立开发、可配置、可运行和可维护的子服务,子服务之间通过良好的接口定义通信机制,通常使用 RESTful 风格的 API 形式来通信,因为RESTful 风格的API通常是在 HTTP 或者 HTTPS 通道上传输 JSON 格式的数据来实现的,HTTP 协议有跨语言、跨异构系统的优点,当然,也可以通过底层的二进制协议、消息队列协议等进行交互。这些服务不需要中心化的统一管理,每个服务的功能可自治,并且可以由不同的语言、系统和平台实现。
微服务架构致力于松耦合和高内聚的效果,与SOA和ESB相比,不再强调服务总线和通信机制的多样性,常常通过 RESTful 风格的 API 和轻量级的消息通信协议来完成。
微服务架构也并不是一个全新的概念,最早可追溯到UNIX操作系统的实现。在 UNIX 操作系统的脚本中,每个技术人员都会使用管道,管道连接前后两个命令,前面命令的输出通过管道传送给后一个命令作为输入,每个命令“各自为政”,完成各自的功能需求。从对比来看,管道中的每一个代表微服务,它们高度自治并完成自己的职责,然后将结果输出。管道类似于微服务的网络通信协议,负责微服务之间的交互。
微服务架构不是为了拆分而拆分,真正的目的是通过对微服务进行水平扩展解决传统的单体应用在业务急剧增长时遇到的问题,而且由于拆分的微服务系统中专业的人做专业的事,人员和项目的职责单一、低耦合、高内聚,所以产生的问题概率会降到最小。
微服务架构:
- 微服务把每一个职责单一的功能放在一个独立的服务中。
- 每个服务运行在一个单独的进程中。
- 每个服务有多个实例在运行,每个实例可以运行在容器化平台内,达到平滑伸缩的效果。
- 每个服务都有自己的数据存储,实际上,每个服务应该有自己独享的数据库、缓存、消息队列等资源。
- 每个服务应该有自己的运行平台,以及独享的运营人员,这包括技术运维和业务运营人员;每个服务高度自治,内部的变化对外透明。
- 每个服务都可根据性能需求独立地进行水平伸缩。
传统单体架构:
- 传统单体架构将所有模块化组件混合后运行在同一个服务JVM进程中。
- 可对包含多个模块化组件的整体JVM进行进行水平扩展,而无法对某个模块化组件进行水平扩展。
- 某个模块化组件发生变化时,需要所有模块化组件进行编译、打包和上线。
- 久而久之,模块间的依赖将会不清晰,互相耦合、互相依赖成为家常便饭。
通过对比,微服务架构更灵活并且可水平伸缩,可以让专业的人来做专业的事。
微服务架构与SOA服务化架构有一些相似的特点,事实上微服务与SOA服务化架构并不冲突,它们一脉相承,微服务架构是服务化架构响应特定历史时期的使用场景的延续,是服务化进行升华并落地的一种实现方式。SOA服务化的理念在微服务架构中仍然有效,微服务在SOA服务化上进行了演进和叠加,形成了适合现代化应用场景的一个方法论。其中的区别:
目的不同:
- SOA服务化涉及的范围更广一些,强调不同的异构服务之间的协作和契约,并强调有效集成、业务流程编排、历史应用集成等,典型的代表为Web Service和ESB。
- 微服务使用一系列的微小服务来实现整体的业务流程,目的是有效地拆分应用,实现敏捷开发和部署,在每个微小服务团队里,减少了跨团队的沟通,让专业人做专业的事,缩小变更和迭代影响的范围,并达到单一微服务更容易水平扩展的目的。
部署方式不同:
- 微服务将完整的应用拆分成多个细小的服务,通常使用敏捷扩容、缩容的Docker技术来实现自动化的容器管理,每个微服务运行在单一的进程内,微服务中的部署互相独立、互不影响。
- SOA服务化通常将多个业务服务通过组件化模式方式打包在一个War包里,然后统一部署在一个应用服务器上。
服务粒度不同:
- 微服务倡导将服务拆分成更细的粒度,通过多个服务组合来实现业务流程的处理,拆分到职责单一,甚至小到不能再进行拆分。
- SOA对粒度没有要求,在实践中服务通常是粗粒度的,强调接口契约的规范化,内部实现可以更粗粒度。
1.5,后服务时代(云原生,Istio)
从软件层面独力应对微服务架构问题,发展到软、硬一体,合力应对架构问题的时代,此即为“后微服务时代”。分布式系统中的注册发现、跟踪治理、负载均衡、通信安全等问题并不是微服务时代独有的,早在 SOA 乃至更早期的分布式架构中就已存在,是分布式架构的固有难题。事实上,这些问题在传统架构中大多依赖硬件设施来解决,如通过增加服务器实现扩容,通过部署负载均衡器实现流量分发,通过配置 TLS 和 DNS 保障通信与发现。但随着应用服务灵活性需求提升,硬件手段难以快速适应,软件层的动态编排、弹性部署成为现实选择。因此,微服务选择“软件解决基础设施问题”,源于对灵活性与效率的追求。
微服务的发展成果离不开 Docker 为代表的容器化技术。然而在早期,容器更多是为单体应用提供快速部署的运行环境,并未真正用于解决分布式架构中的核心问题。尽管早期已有 Docker Swarm、Mesos、SDN、SDS 等方案,但行业广泛承认“虚拟化基础设施介入分布式问题”的转折点,是从 2017 年 Kubernetes 的胜出开始。当虚拟化能力从单个容器扩展到整个服务集群、通信网络与存储设施时,软硬件的界限逐渐模糊。一旦硬件基础设施的灵活性能够媲美软件,传统需要软件显式处理的分布式技术问题(如服务发现、网络配置、存储管理等),就有可能被下沉、封装于底层“看不见”的基础设施中,从而释放软件开发者的关注点,聚焦于业务本身。这一愿景让多个经典概念逐步变为现实:
DCE 中未竟的“透明分布式应用”有望实现;
Martin Fowler 提出的“凤凰服务器”不再只是理想;
Chad Fowler 所推崇的“不可变基础设施”逐渐落地。
这一转变标志着:分布式架构的应对方式从“软件单打独斗”进化为“软硬协同作战”,这种融合趋势正是“云原生”理念的本质内涵。
Kubernetes 的胜出标志着后微服务时代的到来,但它并未解决所有分布式系统中的难题,尤其是应用系统与基础设施交界处的问题。相比之下,Spring Cloud 在某些功能处理上反而更为灵活。比如在服务熔断场景中,如果微服务 A 同时调用 B 的两个接口 B1 和 B2,当 B2 持续报错而 B1 正常时,仅依赖基础设施层的熔断机制难以做到细粒度控制——要么全部切断 A 到 B 的通信,影响正常请求;要么不切断,又无法隔离故障服务,从而难以避免雪崩效应。这种边缘场景正是 Kubernetes 的“不完美”所在。
在基于 Spring Cloud 等应用层方案构建的微服务架构中,像熔断这类问题通过程序代码即可灵活处理,只要逻辑清晰、能力足够,开发者几乎可以实现任意功能。而基础设施如 Kubernetes 由于是以容器为管理单位,粒度较粗,难以精细控制到单个远程服务。例如,在服务监控、认证授权、安全策略或负载均衡等方面,往往需要根据业务流量特征进行更细致的管理和调整,如变更负载均衡算法或策略层级,而这些需求是 DNS 等基础设施方案难以胜任的。类似问题的存在,正凸显了仅依赖基础设施层面对分布式架构进行治理的局限性。
为解决基础设施粒度过粗的问题,虚拟化技术完成了第二次演进,催生出“服务网格”(Service Mesh)架构,核心是“边车代理模式”(Sidecar Proxy)。它的做法是在每个服务容器(如 Kubernetes 的 Pod)中自动注入一个代理进程,像摩托车边车一样紧贴主服务运行。这个代理在应用无感知的前提下接管所有对外通信,一方面完成服务间的正常通信(数据平面),另一方面接受控制指令(控制平面),动态地对通信内容执行熔断、认证、监控、负载均衡等操作。
组成部分 说明 数据平面 由一组轻量级的sidecar代理组成(如 Envoy),与每个服务实例一起部署,负责拦截进出服务的所有网络请求 控制平面 管理和配置所有的 sidecar 代理,例如定义服务之间的路由规则、安全策略(如 Istio 的 Pilot、Citadel 等) 这种方式无需修改业务代码,却能提供接近应用层的精细治理能力,弥补了传统基础设施难以深入的短板。
假设你有一个超大型快递分拣中心,里面有很多部门(对应微服务),每个部门负责处理不同种类的快递:
A部门:负责处理电子产品
B部门:负责服装类
C部门:负责生鲜食品
快递包裹(请求)需要在部门间传递、转发,有时候还会有特殊要求,比如:
包裹必须优先送到VIP客户的订单(流量控制)
包裹运输过程需要用特殊的安全箱(安全通信)
快递包裹丢失了,需要自动重试(重试机制)
需要有人记录每个包裹的处理路径(可观测性)
这时快递中心装了“智能小助手”(服务网格的 sidecar)
每个部门外面都有一个“智能小助手”帮忙:
负责接收和转发包裹,确保按照规则分配
自动给包裹贴安全标签,保证安全运输
记录包裹每一步去向,方便追踪
处理临时异常,重发包裹,不影响部门本身工作
而且这些小助手都由一个中央调度大脑(控制平面)统一管理,按时下达最新的分拣规则和安全策略。服务网格的出现,就是为了把这些"非业务逻辑"抽离出来,并集中控制。这样,部门专注于处理业务(分拣快递),不用操心如何转发、重试、安全和监控,由“智能小助手”和“中央大脑”统一管理,效率和安全性都大大提升。
虽然难以界定与应用共享容器资源的代理服务究竟属于软件还是基础设施,但只要它能在不改动应用代码的前提下实现服务治理,这种透明性就已足够。
1.6,无服务时代
分布式架构最初是为了解决单机性能瓶颈的问题,尽管之后引入了容错、异构、职责划分等多种因素,但“追求更高性能”始终是架构设计的核心动力。如果单机性能无限,今天的分布式、容器化、微服务等形态可能根本不会出现或不至于如此重要。虽然无限性能在绝对意义上不存在,但云计算的普及让“相对无限性能”成为现实。2012 年 Iron.io 提出“无服务”概念,2014 年 AWS Lambda 商业化后推动其发展,至 2018 年,国内云厂商也纷纷跟进,使得“无服务”成为技术圈的热门趋势。
无服务只涉及两块内容:后端设施(Backend)和函数(Function):
- 后端设施是指数据库、消息队列、日志、存储,等等这一类用于支撑业务逻辑运行,但本身无业务含义的技术组件,这些后端设施都运行在云中,无服务中称其为“后端即服务”。
- 函数是指业务逻辑代码,这里函数的概念与粒度,都已经很接近于程序编码角度的函数了,其区别是无服务中的函数运行在云端,不必考虑算力问题,不必考虑容量规划(从技术角度可以不考虑,从计费的角度你的钱包够不够用还是要掂量一下的),无服务中称其为“函数即服务”。
无服务的愿景是让开发者只需要纯粹地关注业务,不需要考虑技术组件,后端的技术组件是现成的,可以直接取用,没有采购、版权和选型的烦恼;不需要考虑如何部署,部署过程完全是托管到云端的,工作由云端自动完成;不需要考虑算力,有整个数据中心支撑,算力可以认为是无限的;也不需要操心运维,维护系统持续平稳运行是云计算服务商的责任而不再是开发者的责任。
虽然无服务架构有广阔的远期前景,但从现实角度看,其在中短期内的普及持谨慎态度。其结构特性决定了它难以像单体或微服务那样成为通用架构模式,除非未来出现重大技术变革。
- 无服务架构适用于短链接、无状态、事件驱动的场景,如离线计算、Web 资讯类网站、小程序、公共 API、移动端服务等,这些场景可以有效降低开发和运维成本。
- 对于业务逻辑复杂、需要长连接、服务端状态依赖强、响应性能要求高的应用(如信息管理系统、网络游戏等),当前无服务架构并不适合。这主要由于其按量计费模式带来的冷启动问题与状态管理限制,导致响应延迟不可忽视,尤其对如 Java 等启动慢的应用影响更大。
2,核心问题
2.1,分布式和微服务
分布式:
- 首先,分布式系统是计算元素的集合,每个计算元素都能够相互独立地工作。通常将计算元素称为节点,它可以是硬件设备或软件进程。
- 第二个因素是用户(无论是人还是应用程序)认为他们在处理一个系统。这意味着自治节点以某种方式需要协同和作。如何建立这种协作是开发分布式系统的核心。
微服务:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。
【目的不同】
- 分布式系统应使资源易于访问,隐藏资源分布在网络上的事实,开放的,可伸缩的。重在资源共享与加快计算机速度,强调的是服务化以及服务的分散化。
- 微服务架构倡导将软件应用设计成多个独立开发、可配置、可运行和可维护的子服务。重在于松耦合和高内聚的效果,使每个模块独立,强调服务的专业化和精细分工。
【主流观点】
- 分布式属于微服务:分布式只是一种手段,把不同的机器分散在不同的地方,然后这些机器间相互协助完成业务。微服务是一种特殊的分布式,换句话说,微服务架构是分布式服务架构的子集。微服务架构通过更细粒度的服务切分,使得整个系统的迭代速度并行程度更高,但是运维的复杂度和性能会随着服务的粒度更细而增加。
- 微服务属于分布式:微服务的意思也就是将模块拆分成一个独立的服务单元通过接口来实现数据的交互。但是微服务不一定是分布式,因为微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器。
个人观点:微服务是一种实际应用技术,而分布式是一个很广的理论概念。微服务在思想上基于分布式计算,在物理部署上通常也是分布式的,当然也不排除少数一定要部到同一台机器上。如果硬要归结关系,我认为微服务是分布式系统设计和架构的理念之一,也是分布式系统实际落地应用的一种体现。
2.2,RPC & REST
【RPC】远程服务调用,让计算机能够跟调用本地方法一样去调用远程方法。
【进程间通信】
- 管道:管道类似于两个进程间的桥梁,可通过管道在进程间传递少量的字符流或字节流。普通管道只用于有亲缘关系进程(由一个进程启动的另外一个进程)间的通信,具名管道摆脱了普通管道没有名字的限制,除具有管道所有的功能外,它还允许无亲缘关系进程间的通信。管道典型的应用就是命令行中的
|操作符,譬如:# ps与grep都有独立的进程,以上命令就通过管道操作符|将ps命令的标准输出连接到grep命令的标准输入上。 ps -ef | grep java
- 信号:信号用于通知目标进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程自身。信号的典型应用是 kill 命令,譬如:
# 由 Shell 进程向指定 PID 的进程发送 SIGKILL 信号。 kill -9 pid
- 信号量:信号量用于两个进程之间同步协作手段,它相当于操作系统提供的一个特殊变量,程序可以在上面进行 wait() 和 notify() 操作。
- 消息队列:以上三种方式只适合传递少量信息,POSIX 标准中定义了消息队列用于进程间数据量较多的通信。进程可以向队列添加消息,被赋予读权限的进程则可以从队列消费消息。消息队列克服了信号承载信息量少,管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限。
- 共享内存:允许多个进程访问同一块公共的内存空间,这是效率最高的进程间通信形式。原本每个进程的内存地址空间都是相互隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的程序接口。当一块内存被多进程共享时,各个进程往往会与其它通信机制,譬如信号量结合使用,来达到进程间同步及互斥的协调操作。
- 套接字接口:消息队列和共享内存只适合单机多进程间的通信,套接字接口是更为普适的进程间通信机制,可用于不同机器之间的进程通信。出于效率考虑,当仅限于本机进程间通信时,套接字接口是被优化过的,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等操作,只是简单地将应用层数据从一个进程拷贝到另一个进程。
RESTful:用统一的 URL + HTTP 方法(GET/POST/PUT/DELETE)来操作资源。
2.3,Java 体系中的分布式
【Dubbo】Dubbo是阿里巴巴开源的一个分布式服务框架,不但提供了高性能和透明化的RPC远程服务调用,还提供了基本的服务监控、服务治理和服务调度等能力。它默认支持多种序列化协议和通信编码协议,默认使用Dubbo协议传输Hessian序列化的数据。Dubbo使用 ZooKeeper 作为注册中心来注册和发现服务,并通过客户端负载均衡来路由请求,负载均衡算法包括:随机、轮询、最少活跃调用数、一致性哈希等。它基于Java语言,不能与其他语言的服务化平台互相调用。Dubbo服务框架在互联网企业得到了广泛应用,几乎每个中小型公司都开始使用Dubbo完成服务化的使命。然而,Dubbo也有如下缺点:
- Dubbo开发的较早,近些年已经没有开发者维护和升级。
- 早期留下的Bug一直没有得到修复,需要使用者自己发现和修复。
- Dubbo没有经过全面优化,在服务量到达一定程度时,会出现通知系统携带过多的冗余信息,在极端情况下会导致网络广播风暴。
- Dubbo服务框架是SOA服务化时代的产物,对微服务化提出的各种概念如熔断、限流、服务隔离等没有做精细的设计和实现。
- Dubbo的监控和服务治理模块比较简单,难以满足复杂业务的需求。
【SpringCloud Netflix】SpringCloud Netflix集成了SpringBoot对微服务敏捷启动和发布的功能,以及Netflix提供的微服务化管理和治理能力,成为一个完美的微服务解决方案。在SpringCloud Netflix平台下,开发人员通过几个简单的注解配置即可完成一个大规模分布式系统的发布工作。SpringCloud Netflix 包括服务发现组件Eureka、容错性组件Hystrix、只能路由组件Zuul和客户端负载均衡组件Ribbon。
Netflix中的交互流程如下:
- 服务在 Eureka 服务器实力上注册。
- Zuul 作为一个特殊的服务在 Eureka 上注册并发现服务。
- Zuul 作为网关,将发现的服务导出给 PC 网站、App 和开放平台使用。
- RestTemplace 和 FeignClient 使用简单的服务调用的方法调用服务1、服务2等。
Netflix的特点:
- 服务在 Eureka 实例中注册,由 Spring 管理的 Bean 来发现和调用。
- 通过配置的方式可以启动嵌入式的 Eureka 服务器。
- Feign 客户端通过声明方式即可导入服务代理。
- Zuul 使用 Ribbon 服务实现客户端的负载均衡。
- 通过声明的方式即可插入 Hystrix 的客户端。
- 通过配置的方式即可启动 Hystrix 面板的服务器。
- 在 Spring 环境中可以直接配置Netflix的组件。
- Zuul 可以自动注册过滤器和路由器,形成一个反向代理服务器。
- Hystrix 面板可以对服务的状态进行监控,并提出容错机制。
SpringCloud Netflix 是当前最流行的微服务架构的落地和实现,发布的时间较晚,理念较新。
3,共识算法
【状态机】状态机(State Machine) 是一种计算模型:通过确定的操作将源状态转换为目标状态。相同初始状态 + 相同操作序列 → 相同最终状态。
3.1,Paxos
Paxos 算法的所有节点都是平等的,它们都可以承担以上某一种或者多种的角色,不过为了便于确保有明确的多数派,决策节点的数量应该被设定为奇数个,且在系统初始化时,网络中每个节点都知道整个网络所有决策节点的数量、地址等信息。
假设有 5 个同事要一起订晚餐,他们必须 在不同餐厅中选一个(比如寿司、披萨、火锅)。
但是大家不能随时开会面对面商量,而是只能通过 邮件投票。【节点角色】
- 提案节点 Proposer,提出对某个值进行设置操作的节点,设置值这个行为就被称之为提案,值一旦设置成功,就是不会丢失也不可变的。谁先发起「要不要吃寿司?」的邮件。
- 决策节点 Acceptor,是应答提案的节点,决定该提案是否可被投票、是否可被接受。提案一旦得到过半数决策节点的接受,即称该提案被批准(Accept),提案被批准即意味着该值不能再被更改,也不会丢失,且最终所有节点都会接受该它。收到邮件的人,他们会决定是否接受这个提议。
- 记录节点 Learner,不参与提案,也不参与决策,只是单纯地从提案、决策节点中学习已经达成共识的提案,譬如少数派节点从网络分区中恢复时,将会进入这种状态。最终所有人都知道选了哪家餐厅。
【算法流程】
- 准备:如果某个提案节点准备发起提案,必须先向所有的决策节点广播一个许可申请(称为 Prepare 请求)。小王(Proposer)发邮件说:“大家先别急着选,我编号 5,想发起一次投票,请问你们有没有已经答应过的餐厅?”。提案节点的 Prepare 请求中会附带一个全局唯一的数字 n 作为提案 ID,决策节点收到后,将会给予提案节点两个承诺与一个应答。
- 承诺不会再接受提案 ID 小于或等于 n 的 Prepare 请求。
- 承诺不会再接受提案 ID 小于 n 的 Accept 请求。
- 应答:不违背以前作出的承诺的前提下,回复已经批准过的提案中 ID 最大的那个提案所设定的值和提案 ID,如果该值从来没有被任何提案设定过,则返回空值。如果违反此前做出的承诺,即收到的提案 ID 并不是决策节点收到过的最大的,那允许直接对此 Prepare 请求不予理会。
每个同事(Acceptor)收到后,如果他们之前没答应过编号更大的请求,就会回复「目前没有答应过」或者「我答应过某个餐厅」。
- 批准:
- 如果提案节点发现所有响应的决策节点此前都没有批准过该值(即为空),那说明它是第一个设置值的节点,可以随意地决定要设定的值,将自己选定的值与提案 ID,构成一个二元组“(id, value)”,再次广播给全部的决策节点(称为 Accept 请求)。
- 如果提案节点发现响应的决策节点中,已经有至少一个节点的应答中包含有值了,那它就不能够随意取值了,必须无条件地从应答中找出提案 ID 最大的那个值并接受,构成一个二元组“(id, maxAcceptValue)”,再次广播给全部的决策节点(称为 Accept 请求)。
同事们答应小王:“好的,我承诺不再接受比编号 5 更小的提议。”,这样保证不会有更小编号的提议插进来。
- 接受:当每一个决策节点收到 Accept 请求时,都会在不违背以前作出的承诺的前提下,接收并持久化对当前提案 ID 和提案附带的值。如果违反此前做出的承诺,即收到的提案 ID 并不是决策节点收到过的最大的,那允许直接对此 Accept 请求不予理会。
小王收到大家的回复,发现大家之前都没答应过别的餐厅,于是发出:“那我们吃寿司吧(编号 1 提议)。”同事们收到后,觉得符合他们之前的承诺(没有比编号 1 更大的),于是答应:“好,我接受吃寿司。”一旦 多数人(超过一半)同意,结果就被确定:这次选的是寿司。
- 学习:所有人都会被通知最终选择是寿司,即使有些人掉线,最后也能得知结论。
假设在小王发起提议时,小李也发起了一个(编号 6 的提议,选火锅)。
因为编号 6 比编号 5 大,Acceptor 们在收到小李的邮件时会承诺只接受编号 ≥6 的提议。
小王的寿司提议可能因此被打断,最终多数人接受了火锅。
这个机制保证了:
只有一个最终结果(要么寿司要么火锅,不会一半人吃寿司一半人吃火锅)。
只要多数人正常运作,最终一定能选出餐厅。
【工作实例】假设一个分布式系统有五个节点,分别命名为 S1、S2、S3、S4、S5。全部节点都同时扮演着提案节点和决策节点的身份。此时,有两个并发的请求分别希望将同一个值分别设定为 X(由 S1作为提案节点提出)和 Y(由 S5作为提案节点提出),以 P 代表准备阶段,以 A 代表批准阶段。
【情况1】S1选定的提案 ID 是 3.1(全局唯一 ID 加上节点编号),先取得了多数派决策节点的 Promise 和 Accepted 应答,此时 S5选定提案 ID 是 4.5,发起 Prepare 请求,收到的多数派应答中至少会包含 1 个此前应答过 S1的决策节点,假设是 S3,那么 S3提供的 Promise 中必将包含 S1已设定好的值 X,S5就必须无条件地用 X 代替 Y 作为自己提案的值,由此整个系统对“取值为 X”这个事实达成一致。
【情况2】事实上,对于情况一,X 被选定为最终值是必然结果,但 X 被选定为最终值并不是必定需要多数派的共同批准,只取决于 S5提案时 Promise 应答中是否已包含了批准过 X 的决策节点,S5发起提案的 Prepare 请求时,X 并未获得多数派批准,但由于 S3已经批准的关系,最终共识的结果仍然是 X。
【情况3】当然,另外一种可能的结果是 S5提案时 Promise 应答中并未包含批准过 X 的决策节点,譬如应答 S5 提案时,节点 S1 已经批准了 X,节点 S2;S3未批准但返回了 Promise 应答,此时 S5以更大的提案 ID 获得了 S3、S4、S5的 Promise,这三个节点均未批准过任何值,那么 S3将不会再接收来自 S1的 Accept 请求,因为它的提案 ID 已经不是最大的了,这三个节点将批准 Y 的取值,整个系统最终会对“取值为 Y”达成一致。
【情况4】从情况三可以推导出另一种极端的情况,如果两个提案节点交替使用更大的提案 ID 使得准备阶段成功,但是批准阶段失败的话,这个过程理论上可以无限持续下去,形成活锁(Live Lock)。在算法实现中会引入随机超时时间来避免活锁的产生。
3.2,Muti Paxos
Multi-Paxos 的关键改进在于 引入“选主”机制:
节点通过心跳机制判断是否存在主节点。
若主节点失联,节点触发 一次 Basic Paxos 的完整两轮交互(准备 + 批准) 来竞选主节点。
当选主节点后,该节点成为唯一能提出提案的主节点,其他节点仅转发请求。
因为主节点已完成准备阶段,所以后续提案只需 一次批准交互 即可达成共识。
👉 本质:通过选主消除了提案并发竞争,把多轮 Paxos 转化为一次选举 + 多次快速批准,从而提升性能。
二元组(id, value)已经变成了三元组(id, i, value),这是因为需要给主节点增加一个“任期编号”,这个编号必须是严格单调递增的,以应付主节点陷入网络分区后重新恢复,但另外一部分节点仍然有多数派,且已经完成了重新选主的情况,此时必须以任期编号大的主节点为准。
【问题】如何把数据复制到各个节点上?
在正常情况下,客户端向主节点发起操作请求后,主节点会先将操作写入日志但不提交,然后通过心跳将变更信息广播给从节点并要求确认。从节点收到后写入日志并回复确认,主节点在收到多数派确认后提交变更,向客户端返回结果,并向从节点广播提交指令,从节点再执行提交。
在异常情况下,网络出现了分区,部分节点失联,但只要仍能正常工作的节点的数量能够满足多数派(过半数)的要求,分布式系统就仍然可以正常工作。假设有 S1、S2、S3、S4、S5五个节点,S1是主节点,由于网络故障,导致 S1、S2和 S3、S4、S5之间彼此无法通信,形成网络分区。
一段时间后,S3、S4、S5三个节点中的某一个(譬如是 S3)最先达到心跳超时的阈值,获知当前分区中已经不存在主节点了,它向所有节点发出自己要竞选的广播,并收到了 S4、S5节点的批准响应,加上自己一共三票,即得到了多数派的批准,竞选成功,此时系统中同时存在 S1和 S3两个主节点,但由于网络分区,它们不会知道对方的存在。
这种情况下,客户端发起操作请求:
- 如果客户端连接到了 S1、S2之一,都将由 S1处理,但由于操作只能获得最多两个节点的响应,不构成多数派的批准,所以任何变更都无法成功提交。
- 如果客户端连接到了 S3、S4、S5之一,都将由 S3处理,此时操作可以获得最多三个节点的响应,构成多数派的批准,是有效的,变更可以被提交,即系统可以继续提供服务。
假设现在故障恢复,分区解除,五个节点可以重新通信了:
- S1 和 S3 都向所有节点发送心跳包,从各自的心跳中可以得知两个主节点里 S3 的任期编号更大,它是最新的,此时五个节点均只承认 S3 是唯一的主节点。
- S1、S2 回滚它们所有未被提交的变更。
- S1、S2 从主节点发送的心跳包中获得它们失联期间发生的所有变更,将变更提交写入本地磁盘。
- 此时分布式系统各节点的状态达成最终一致。
3.3,Gossip 协议
【步骤 1】如果有某一项信息需要在整个网络中所有节点中传播,那从信息源开始,选择一个固定的传播周期(譬如 1 秒),随机选择它相连接的 k 个节点(称为 Fan-Out)来传播消息。
【步骤 2】每一个节点收到消息后,如果这个消息是它之前没有收到过的,将在下一个周期内,选择除了发送消息给它的那个节点外的其他相邻 k 个节点发送相同的消息,直到最终网络中所有节点都收到了消息,尽管这个过程需要一定时间,但是理论上最终网络的所有节点都会拥有相同的消息。
对网络连通性要求低:不要求全连接,也能容忍部分节点宕机、重启、动态增删,最终仍能同步完成。
高度去中心化:没有主节点,每个节点地位平等,增强系统的鲁棒性,适合在公网环境中使用。
更多推荐



















所有评论(0)