搭建框架需要工具默认基于 maven 的分布式工程,我们知道在一个项目中,多个微服务是属于同一个工程,只不过是提供不同的服务而已,因此使用 maven 分布式工程来搭建微服务架构。搭建基于 maven 分布式的 Spring Cloud 微服务工程架构。这个是目前比较流行的搭建方式,其实我们可以这样想,服务从原始的RPC开始,至今spring cloud和web service,再到最后soa服务治理。服务的调用拙见完善,目前市场上出现的服务框架dubbo和spring cloud比较流行。

1.dubbo架构

 

 

 

2.spring cloud内部组成:

 

实际上,Spring Cloud是一个全家桶式的技术栈,包含了很多组件。本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理。也就是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。

一、业务场景介绍

先来给大家说一个业务场景,假设咱们现在开发一个跨境电商网站,要实现支付订单的功能,基本流程如下:

  • 创建一个订单后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”
  • 扣减相应的商品库存
  • 通知仓储中心,进行发货
  • 给用户的这次购物增加相应的积分

以上业务流程我们需要有订单服务(order)、库存服务(sku)、仓储服务(erp)、积分服务(point)。整个流程的思路如下: 即使没有用户登录我们然后可以这样操作,只是到添加购物车的时候,我们需要提示用户去登录或者去注册。也是购物车的实现原理

  • 用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态 或者(没有用户登录的情况,我们生产预订单支付提示用户登录或者注册)
  • 订单服务调用库存服务,完成库存的进销存功能
  • 订单服务调用仓储服务,完成物流配送功能
  • 订单服务调用积分服务,完成积分服务功能

至此,整个支付订单的业务流程结束

 

下图随便找张支付流程图,清晰表明了各服务间的调用过程:

重点一:这里最重要的是我们分布式事务管理。针对springboot搭建的项目我们选择 lcn分布式框架进行事务管理。

重点二:针对系统而言我们可能有推送服务,我们建议使用 rabbitmq消息队列进行推送服务和消息通知。

重点三:里面的服务调用通用异步机制,

BATJ面试必问:剖析Spring Cloud底层的工作原理

 

下面我们就开始讲解Spring Cloud微服务架构,在项目中这几个组件如何相互协作,各自发挥的作用以及其背后的原理。

二、Spring Cloud核心组件:Eureka(服务注册和发现)

eureka解决的问题服务如何调用,就上面 订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用呢?

第一个问题:订单服务压根不知道库存服务在哪台机器上,发起一个请求,都不知道发送给谁。

问题解决方案:Spring Cloud Eureka完美解决问题。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。

下面就是eureka工作的整个流程:

BATJ面试必问:剖析Spring Cloud底层的工作原理

重要一:库存服务、仓储服务、积分服务中都有一个Eureka Client组件

这个组件专门负责将这个服务的信息注册到Eureka Server中。说白了,就是告诉Eureka Server,自己在哪台机器上,监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号

订单服务里也有一个Eureka Client组件,这个Eureka Client组件会找Eureka Server问一下:库存服务在哪台机器啊?监听着哪个端口啊?仓储服务呢?积分服务呢?然后就可以把这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。

这时如果订单服务想要调用库存服务,不就可以找自己本地的Eureka Client问一下库存服务在哪台机器?监听哪个端口吗?收到响应后,紧接着就可以发送一个请求过去,调用库存服务扣减库存的那个接口!同理,如果订单服务要调用仓储服务、积分服务,也是如法炮制。

总结:

  • Eureka Client:负责将这个服务的信息注册到Eureka Server中
  • Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号

三、Spring Cloud核心组件:Feign(相比http)

服务各自知道对方的端口和ip后。我们就需要去请求,调用这些服务,这里如果不用feign也可以自定义一个http协议封装一下。

问题一:各个服务之间如何建立网络连接呢?

例如:

BATJ面试必问:剖析Spring Cloud底层的工作原理

解决办法:Feign提供了优雅的解决方案。

例如:订单服务调用库存服务的代码

BATJ面试必问:剖析Spring Cloud底层的工作原理

 

feign为我们省去了底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。Feign Client会在底层根据自定义的注解,跟指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应。

Feign实现原理:Feign的一个关键机制就是使用了动态代理

如下

  • 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
  • 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
  • Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
  • 最后针对这个地址,发起请求、解析响应

BATJ面试必问:剖析Spring Cloud底层的工作原理

下面重点解决分布式服务的解决方案:

 

四、Spring Cloud核心组件:Ribbon(负载均衡)

上面我们解决了,

1.服务各个之间的发现和注册,

2.以及各个服务之间网络通信问题。

下面我们看看分布式各个服务之间如何发现的。

如果库存服务部署在了5台机器上,端口一致,如下所示:

  • 192.168.169:9000
  • 192.168.170:9000
  • 192.168.171:9000
  • 192.168.172:9000
  • 192.168.173:9000

Feign怎么知道该请求哪台机器呢?

我们可以使用 Spring Cloud Ribbon,它的作用是负载均衡,会帮我们在每次请求时选择一台机器,均匀的把请求分发到各个机器上

Ribbon特点:

  • Ribbon的负载均衡默认使用的Round Robin轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。

总结三者工作调用流程:Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体流程如下:

  • 首先Ribbon会从 Eureka Client里获取到对应的服务注册表,这样就知道了所有的服务都部署在了哪些机器上,及端口号。
  • 然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
  • Feign就会针对这台机器,构造并发起请求。

如下图:

BATJ面试必问:剖析Spring Cloud底层的工作原理

五、Spring Cloud核心组件:Hystrix(容错机制)

在微服务架构里,一个系统会有很多的服务。如果其中一个服务挂掉,不能影响其他服务的正常调用。就此案例来说:订单服务如果调用三个服务。现在假设订单服务自己最多只有50个线程可以处理请求。在这其中,积分服务如果挂了,每次订单服务调用积分服务的时候,都会卡住一段时间等候处理,然后抛出—个超时异常。浪费时间。

  1. 如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的50个线程都会卡在请求积分服务这块。导致订单服务没有一个线程可以处理请求
  2. 然后就会导致别人请求订单服务的时候,发现订单服务也挂了,不响应任何请求了,这个地方我们需要用消息队列机制处理,

上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:

BATJ面试必问:剖析Spring Cloud底层的工作原理

一般而言,我们各个服务都应该设置成异步多线程调用的。

如上图,如果其中多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。

这就是里面没有加入消息队列机制处理。如果加入消息队列机制,我们就会发现服务之间出现异常互不影响。这里面还没有我们说的,缓存机制。也可以加进去。

就单单业务来看:支付订单的时候,只要把库存扣减了,然后通知仓库发货就算功能实现了

  • 如果积分服务挂了,大不了等他恢复之后,慢慢人肉手工恢复数据!为啥一定要因为一个积分服务挂了,就直接导致订单服务也挂了呢?不可以接受!

解决方案:

一,加入消息队列机制。异步执行。

二。使用Hystrix。因为Hystrix是隔离、熔断以及降级的一个框架。

Hystrix实现原理:

Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。

打个比方:现在很不幸,积分服务挂了,会咋样?

当然会导致订单服务里那个用来调用积分服务的线程都卡死不能工作了啊!但由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。

这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对积分服务熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!

如果积分服务挂了你就熔断,道理说不通,不能直接就返回!,还需要降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。

梳理一下Hystrix隔离、熔断和降级的全流程:

BATJ面试必问:剖析Spring Cloud底层的工作原理

这个处理方案就比如我们soa治理差不多,只不过,那是dubbo经常用的框架,我们这是springcloud用的框架。

处理机制都差不多。

六、Spring Cloud核心组件:Zuul(网络路由分片)

上面我们几乎把管理搭建springcloud分布式常用的框架说完了,下面我们看看网络分片问题。

Zuul概念:Zuul,就是微服务网关。这个组件是负责网络路由的。

zuul工作流程:

问题一;

假设我们后台部署了上百个服务,如果我们系统是前后端分离。那么前端开发人员如何调用我们这几百个服务呢?

比如:前端开发人员要请求一下库存服务,我们不可能让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?

解决方案:

一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。

七、总结:

总结,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:

  • Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
  • Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
  • Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
  • Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
  • Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

以上就是我们通过一个电商业务场景,阐述了Spring Cloud微服务架构几个核心组件的在现实项目中的实现原理。

Spring Cloud的5个核心组件通过一张图串联起来,包括底层的架构原理:

BATJ面试必问:剖析Spring Cloud底层的工作原理

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐