微服务架构已经成为目前主流的系统架构方式之一。微服务架构是随着IT系统的业务需求和技术需求不断膨胀,为了解决巨型单体应用的性能瓶颈、协作瓶颈等问题而产生的。所以提起微服务架构,我们就不得不提起单体架构,提起单体架构,自然也就会引出分布式架构、分布式系统。同时微服务架构的产生并不是凭空而起,与更早的SOA架构有着很强的联系。因此每当我们谈到系统架构,谈到微服务时,很难只谈微服务,这些概念总是被同时提起并做横向比较,但实际上这些概念之间并不是平行的,每个概念背后所要解决的问题和应用的场景也并不相同。就像Java面试时常被问到的问题:“SpringBoot和SpringCloud有什么区别?“。对这两个框架有了解的肯定都能知道,这俩框架确实有着很强的关系,但却并不是平行的关系,他们要解决的问题是不同的。

本文将结合系统架构的演进历程,对这些概念做进一步的梳理和探讨。

架构演进历程

单体架构

单体架构指的是将整个应用程序作为一个单独的单元进行开发、部署和运行。在单体架构中,所有的功能模块,如用户界面、业务逻辑、数据库访问等,都被打包在一个单一的应用程序中。

在信息化建设的初期,应用系统的复杂度比较低。复杂度低体现在两方面:

  1. 业务复杂度业务体量比较小,业务功能比较简单。

  2. 技术复杂度用户体量小,对系统的性能要求比较低。

在这种场景下,单体架构是完全满足现阶段的需求的。有非常多常见的技术组合:

  • War包+Tomcat+Mysql

  • LAMP(Linux+Apache+Mysql+PHP)

  • LNMP(Linux+Nginx+Mysql+PHP)

以Java Web领域为例,我们的所有功能代码甚至包括前端代码都打包在一个war包里,然后部署在Tomcat的webapp目录之下。在这个过程中,我们的代码只需要一个git仓库就可以管理得很好,部署和运行也只需要对一个war包进行操作。

随着单体架构的不断发展和完善,也有非常多的技术涌现出来:

Struts:基于MVC的web框架

Spring:对象管理框架,无人不知,无人不晓,核心特性是IOC和AOP。

SpringMVC:基于Spring的MVC框架,基本统治了JavaWeb开发。

Hibernate/Mybatis:ORM层框架,提供对数据库的操作能力。

FreeMarker/Thymeleaf:模版引擎,主要是为了替代J2EE中的jsp。是MVC中的View。

SpringBoot:我们的单体应用日渐复杂起来,要集成越来越多的组件(Mybatis、日志框架,RedisClient、模版引擎......),我们开始对SpringMVC中繁琐的XML配置感到厌烦,然后SpringBoot就出现了。他是一个快速开发框架,可以让我们快速的构建一个Spring应用。并且通过将servert容器内置在其中,可以让我们再也不用安装tomcat了。直接将SpringBoot打包为一个jar包,使用java -jar即可运行。

可以看出,这些技术并没有改变单体架构的本质,都是在解决开发效率、开发规范的问题。

集群架构

随着业务的发展和信息化建设的深入,系统用户量的不断增多,对系统性能、吞吐量的要求开始变高。单体架构的性能问题开始凸显出来。为了提高吞吐量,我们一般有两种办法:

  1. 优化现有系统逻辑,提高性能。(困难)

  2. 横向扩展,以集群的形式提供服务(简单)

优化现有系统逻辑是一个比较系统性的话题,跟本文的主旨不符,就不在本文展开。所谓横向扩展就是,同时部署多个一模一样的节点,然后通过一定的负载均衡策略,将用户分配到不同的节点上。这样并发量就可以成倍的提高。

原有的单体架构系统要想以集群的形式部署,一般都需要解决以下问题:

负载均衡

当我们同时部署了多个节点之后,用户的请求如何分配到各个节点上呢?就需要负载均衡器。常见的负载均衡器根据载体的不同,可以分为三类:

  1. 硬件负载均衡:F5、A10

  2. 软件负载均衡:Nginx、LVS

  3. DNS负载均衡:通过DNS来实现负载均衡。

负载均衡一般都可以配置不同的负载均衡算法来将请求分发到合适的节点上,如随机算法、加权随机算法、轮询、加权轮询、源地址Hash和源地址一致性Hash等。

会话共享

在单体架构中,以Tomcat为例,当用户的首次请求Tomcat时,就会在服务端生成一个Session,并且会响应一个Cookie存储在用户的浏览器中,后面的所有请求,浏览器都会自动携带上这个请求,Cookie和Session一一对应。另外在用户登录之后,我们一般都会将用户信息存放在Session中。通过这种方式,我们就可以知道每个请求是哪个用户发起的。

需要注意的是,Session是基于内存的,假如我们有两个节点,用户在第一个节点完成登录后,在做后续操作时被分配到了没有登录的节点,那么用户就会被提示还没有登录。要解决这种问题,有两种思路:

  1. 保证同一个用户的请求都被分发到同一个节点。要想实现这种效果,就要调整我们的负载均衡算法为基于源地址的算法,比如源地址Hash算法和源地址一致性Hash算法。这两种算法都会根据用户的请求源ip进行Hash,然后将Hash值与我们的节点列表进行运算得到最终分配的节点。

  2. 将基于内存的存储转移到公共存储中,比如Redis、数据库。其实就是将我们的后端服务改造成无状态的,前端天然是无状态的。

SOA架构

SOA(Service Oriented Architecture)面向服务的体系结构,是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。

背景

随着企业内部信息化建设的持续深入,系统的数量会越来越多。人力资源系统、财务系统、销售系统等等。随着系统的数量越来越多,我们开始面临下面两个问题:

  1. IT重复建设:以一个电商企业为例,其内部可能会有不同的电商系统,电商系统A和电商系统B都有物流模块的功能,两个系统需要重复开发物流模块的功能。在SOA架构下,这个物流功能就应该被抽象成一个通用的功能,供其他需要的系统来调用。这个通用的物流功能,就是我们SOA面向服务架构中的服务。很多文章在说IT重复建设时,喜欢举用户管理的例子,我认为是不合适的。因为没有一个系统可以没有用户管理的功能,而且用户管理对于99%的系统来说不算是个业务功能。

  2. 系统间的集成:很多业务过程是需要多个系统共同完成的,但是各个独立的系统之间没有标准的实现方式,比如有基于Java的、Php的、.Net的、Python的等等。向外提供服务的方式也会五花八门,可能会有基于xml、基于json的http接口、或者基于SOAP协议的WebService、Mq消息、JMS等。任意两个独立的系统都可能会发生对接,对接过程缺乏统一的标准和管理抓手,另外也会重复开发很多协议的转换之类的重复逻辑。

为了解决这两个问题,SOA提出了很多在当时来看很先进的解决方案:

  1. 服务在SOA中,服务被定义为一种独立的、松散耦合的软件单元,它可以通过网络被调用和使用。每个服务实现一个特定的业务功能,可以被不同的应用程序和系统重用和组合。其核心是可重用性、自治性。

  2. ESBESB(Enterprise Service Bus)企业服务总线,所有的服务都在ESB进行注册和发布,所有系统都通过在ESB集中处理来屏蔽各个系统之间的差异。除此之外在ESB中还可以基于BPM工具对服务进行编排产生更高级的业务功能。

  3. SOAP协议族SOAP协议是SOA中默认的远程调用协议。大家对SOAP协议的认识,可能只是一个比较复杂XML报文,跟普通的Http请求没啥区别,只是报文的格式比较特别。但实际上SOAP协议族除了SOAP协议之外,还包含WSDL(Web Service Description Language)、UDDI(Universal Description Discovery and Integration)。WSDL是基于XML的服务描述协议,用来描述服务具体的调用地址,入参和出参等。UDDI负责服务的注册、发现和治理。类似微服务架构中的注册中心。一般是个实体服务,在Java中也有开源实现:Apache jUDDI、IBM WebSphere UDDI.SOAP报文示例:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ex="http://example.com/">
       <soapenv:Header/>
       <soapenv:Body>
          <ex:GetUserInfo>
             <ex:UserID>12345</ex:UserID>
          </ex:GetUserInfo>
       </soapenv:Body>
    </soapenv:Envelope>
    

总结

SOA重要的是其理念,很多人会抓着ESB不放,认为SOA就只是ESB,ESB大家用的越来越少了,所以SOA已经过时了。这个观点肯定是错误的。

SOA的目标是要指导企业全局的IT建设,是一套自上而下的软件研发方法论,而不是聚焦于单个系统如何建设。但是尴尬的一点是,SOA是出现在企业信息化建设中途,所以它不能仅仅只是制定标准,指导未来的系统建设,还需要兼容存量的IT系统。ESB恰恰就是为了解决存量的IT系统而存在的,屏蔽各个独立系统的技术差异。如果所有系统都重构统一使用SOAP协议,那也就不需要ESB了。而除了ESB,SOA的精华部分实际上现在仍然还在被广泛吸收并采用,微服务架构、中台处处都是SOA的影子。从实际应用情况来看,SOA主要被用在解决系统集成的问题。这虽然和它的伟大愿景有所背离,但这也是无可奈何的事情。

微服务架构

背景

与SOA不同,微服务架构是解决单个单体架构系统的问题,随着信息化建设的再进一步深入和企业业务的不断扩展,单个IT系统的体量不断膨胀。这种膨胀的外化表现是单个系统的代码量越来越大,复杂度越来越高,团队成员数量越来越多。由此产生的开发难度高,协作成本高,部署效率低等问题开始凸显出来。

  1. 复杂度高量变产生质变,当代码量膨胀到一定程度,再厉害的工程师也会望而生畏,任何一个小改动的影响范围都是很难评估的。这种复杂度不仅会影响效率,还会影响质量。

  2. 开发效率低功能和代码量的膨胀带来的是对内存和cpu的消耗,并且启动时间也会边长。这会影响工程师的开发效率。

  3. 部署困难启动时间变长,每次发布只是启动就要十几分钟,对于可用性要求高的场景,这是很难接受的。

  4. 故障无法隔离任何功能的问题都可能导致整个系统不可用,一个边缘功能的bug可能导致核心业务都无法进行下去。

  5. 无法按需扩展只能以系统的粒度去横向扩展,会造成资源的浪费。比如一个电商系统,最容易有性能瓶颈的功能可能是商品查询、下单这些功能,而商品管理,用户管理这种基本不会有性能瓶颈。但是如果想要提高商品查询、下单这些功能的并发能力,就必须整个系统的去横向扩展,不需要扩展的商品管理、用户管理也会跟着扩展。

微服务两大核心问题

为了解决大型单体架构系统的问题,微服务主张对大型单体架构进行拆分。所以微服务其实有两大核心问题:

怎么拆分?

怎么拆分就仁者见仁智者见智,要根据实际情况去分析了。但是原则就是服务的拆分粒度不能过粗,也不能过细。一句非常正确的废话。不过倒是有这么几种常见的拆分方法:

  1. 基于业务逻辑拆分

  2. 基于可扩展拆分

  3. 基于可靠性拆分

  4. 基于性能拆分

拆分之后产生的问题怎么解决?

解决问题之前,首先得知道拆分会产生哪些问题。首当其冲就是拆分出来的微服务之间如何通信,然后就是拆分出来的微服务的集中管理。

服务通信

微服务架构主张采用基于Http、json的RESTFul风格的api作为通信协议,SpringCloud也提供了开箱即用的实现Feign。但通信的问题远不止能够通信这么简单。

服务发现

首先对于单个微服务来讲,我们是可以横向扩展,部署多个节点的。假如A服务依赖B服务的接口,B服务如果部署了3个实例。那么A应该怎么知道B服务的调用地址呢?

在SpringCloud中,A服务通过feign调用B服务,在代码层面是通过服务名去调用的。通过服务名去调用,实际上是因为有注册中心的存在。所有服务在启动后都会将自己的信息注册到注册中心,包含服务名、ip地址、端口和一些自定义信息。每个服务也会从注册中心拿到所有服务的信息缓存在本地,并保持更新。

负载均衡

知道了B服务的所有调用地址,接下来就需要解决调用哪个的问题。SpringCloud中使用客户端负载均衡的方式来解决,对应的组件是Ribbon。Ribbon可以配置不同的负载均衡策略来从可选的实例中选择合适的调用。

雪崩效应和熔断

雪崩效应是指当系统中的一个服务由于某些原因(如资源耗尽、异常、延迟增加等)发生故障或性能下降时,这种不良影响会像雪崩一样迅速蔓延到整个系统中的其他服务,导致整个系统的稳定性和可用性急剧下降。要理解雪崩效应,我们要有这样一个认识:当服务A调用服务B的接口时,在技术层面的表现是,服务A启动一个线程发起Http请求,然后等待服务B的响应。如果服务B资源耗尽或者延迟增加,那么服务A等待的时间就会变长,从而占用服务A的线程资源,而一个服务的线程资源是有限的。如果一个请求的链路再长点儿,比如服务C调用服务A,服务A再调用服务B,那么服务B的异常,会沿着这个请求链,一直将这个延时影响传播到服务C。

理解了雪崩效应之后,我们不然相当解决办法,当发现服务B的响应时间超过平均水平的时候,我们就自动断开。在SpringCloud中也有开箱即用的组件Hystrix,阿里也有开源的组件Sentinel。

集中管理

微服务拆分带来的另一个问题就是服务数量的激增。服务的变多也会带来一些管理上的问题。

配置中心

在单体架构中,我们可能只需要管理一个服务的配置,但是微服务的激增,每个微服务都有一套配置。这些配置最好能做到统一管理。所有就有了配置中心组件。SpringCloud中是SpringCloud Config、阿里的Nacos,还有其他的比如Apollo、Zookeeper。

网关

在单体架构中,所有的接口都在一个服务中,但在微服务中,接口被分散在各个微服务中。不同的微服务有着不同的ip和端口。当我们需要给前端或者外部提供接口时,总不能直接把每个微服务的ip都提供给前端。这样就会把复杂度暴露到外部,并且也会暴露内部的实现。我们希望微服务的拆分只是在后端可见,对外部提供服务的时候,应该是透明的,仍然是一个整体。网关就是为了解决这个问题,所有对后端的请求都会经过网关,由网关统一转发到后端服务,并且会做负载均衡。需要注意的是,微服务之间的调用是不通过网关的,是微服务直连的方式。SpringCloud中有zuul和SpringCloudGateway。zuul是基于同步阻塞IO,已经不再使用了。SpringCloudGateway是基于异步非阻塞IO,是目前比较常用的网关实现。

总结

微服务的兴起除了其本身的先进性之外,容器化技术和DevOps技术的发展和兴起也是微服务不断发展的巨大推力。因为微服务数量的激增,随之而来的也是部署单元的激增。传统的手工化部署面对大规模的微服务集群是肯定无能为力的。以Docker为主的容器化技术给服务部署提供了标准且通用的部署单元,K8s等容器编排技术则将微服务的弹性伸缩能力更好的发挥出来,DevOps则为微服务快速发布、持续部署提供了支持。

微服务架构是为了解决巨型单体架构应用的一系列问题而产生的,这里一定要注意“巨型”这个词。微服务架构的产生时间是在单体架构之后,容易给人的错觉是,微服务架构一定是比单体架构更好的,这种观点实际上是不对的。单体架构的简洁性、便利性是微服务架构无法相比的。只有当单体架构不断膨胀,变成巨型单体应用之后而产生上述那些问题时,我们才可以考虑微服务架构。在正式采用微服务架构之前,还需要考虑我们的团队能否发挥微服务拆分后的隔离带来的效率提升。

分布式架构

分布式架构是一种将系统拆分为多个独立的组件,这些组件可以在不同的计算机或服务器上运行,并通过网络进行通信的架构。它的目标是提高系统的可伸缩性、可靠性和性能。是关于如何构建分布式系统的一套原则、模式和方法。

微服务架构自然也属于一种分布式架构,但分布式架构不一定是微服务架构。

分布式系统

分布式系统是一种计算系统,它通过网络将多个计算节点(计算机、服务器等)连接在一起,使它们协同工作来完成特定任务。这些节点相互通信和协调,形成一个统一的整体系统。

依据这个定义,其实很好判断一个系统是不是分布式系统。比如单机部署的Mysql就不是分布式系统,但如果是主从复制的Mysql双节点,那就是个分布式系统。主服务器负责处理数据的写入操作(事务提交),而从服务器则复制主服务器的数据以实现数据的同步和备份,并能够处理只读的查询请求。再比如上面提到的单体架构应用的集群化部署,其实也可以算是分布式系统。

参考

微服务和DevOps时代-SOA和ESB是否过时

万字长文解析:分布式架构、SOA、微服务架构、API网关、ESB服务总线架构之间的关联及演进

分布式系统架构和微服务架构

SOA,什么是服务?


[重要提示]

所有博客内容,在我的个人博客网站可见,欢迎访问: TwoFish