查看原文
其他

面试不再慌!跟着老司机吃透Spring Cloud

崔皓 51CTO技术栈 2019-11-01

最近和朋友聊天,提到他前几天面试的时候被问到:“能否描述一下Spring Cloud?”他当场就懵了,不知道从何说起。


图片来自 Unsplash


是啊,Spring Cloud 是知名的微服务架构,包含了很多组件,每个组件又有各自的分工。


怎么才能理解 Spring Cloud 架构并且说清楚它到底做了些什么呢?我们今天一起来看一下。


从一个例子开始


对于这样的“大”问题,通常需要拆解成小问题来回答。要说明 Spring Cloud 做了什么,就要说清楚它包含的组件都做了些什么?


如果一个个把组件罗列出来,似乎太过独立,没有关联性,缺少逻辑感。我们就从一个简单的例子开始,把这些组件像串珍珠一样串起来。


假设有一个项目,这个项目有两个服务,分别是“A”和“B”:

  • “A”和“B”的关系是,“A”调用“B”。

  • 然后,有一个客户端“C”调用“A”。

客户端“C”调用服务“A”,服务“A”调用服务“B”


Eureka 服务之间互相认识


在服务端我们已经有了两个服务,“A”和“B”。他们的关系是“A”调用“B”,“B”被“A”调用。


当只有两个服务的时候我们是知道这种关系,并且可以把这种关系记录下来的,但是如果服务一多,我们如何记录这种关系呢?


于是,Eureka 就登场了,它负责“服务注册,服务发现”的工作。Eureka 分成 Eureka Server 和 Eureka Client。


每个微服务架构都会有一个或者多个 Eureka Server 用来保存注册服务的信息。


每个服务都会包含一个Eureka Client,其中会配置Eureka Server的信息,这样当服务启动的时候就能够把自己注册到 Eureka Server 中去了。


同时每个服务也可以通过 Eureka Client 从 Eureka Server 中获取其他服务的信息(Get Registry)。

“A”服务与“B”服务的调用关系


“A”服务和“B”服务首先通过自身集成的 Eureka Client 到 Eureka Server 上注册自身的信息,包括:服务名,地址,端口号等等。


注册完毕以后,“A”服务通过 Eureka Client 从 Eureka Server 获取(Get Registry)服务“B”的信息。


由于,“A”服务调用“B”服务,所以“A”服务称之为“消费者”,“B”服务称之为“生产者”。


Feign 服务之间信息传递


既然“A”“B”两个服务互相认识了,接下来就要轮到“A”服务调用“B”服务了。


由于两个是单独的服务,并且两个服务都在一个网络内,通常会通过 HTTP 请求进行调用。


传统的做法是,“A”服务写好请求的消息,序列化成二进制的串传递给“B”服务,“B”服务收到消息以后反序列化消息进行解析,接着以同样的方式应答“A”服务。


从传统意义上完成这些代码需要大量的工作,而且需要考虑很多编码上面的问题。为了简化上面的过程,Feign 组件就诞生了,它方便了服务之间的调用。


引入 Feign 了以后,在“A”服务中只需要填写简单的 URL,参数,请求方式,就可以调用“B”服务了。调用“B”服务就好像调用本地的一个方法一样简单。

通过 Feign 调用服务的代码片段


我们需要做的就是在“A”服务(消费者)中建立一个 Feign Client,填写我们需要调用的 URL,参数和方式就可以了,其他的事情就交给 Feign 来完成了。

Feign Client 工作流


从上图可以看出,“消费者”开始只需要提供“生产者”的 URL,参数等信息。


Feign Client 会根据这些信息生成对应的 HTTP 请求头和报文,然后发送给生产者。


生产者返回信息以后,Feign Client 同样会返回“消费者”能够读懂的 JavaBean 的信息。


Ribbon 搞定负载均衡


好了,现在“A”“B”服务互相认识了,并且“A”服务可以调用“B”服务了。


假设“B”服务的业务量增大,一个“B”服务无法满足现在的要求,另外又复制了两个“B”服务,连同原来的一个“B”服务,现在一共有三个“B”服务。


虽然三个提供的服务都是一样的,但是“A”服务应该调用哪个“B”服务的复制呢?


这时 Ribbon 就登场了,它用来做负载均衡。“A”服务无需知道调用三个复制中的哪一个,它只用告诉 Ribbon 我要调用“B”服务,Ribbon 会根据策略去调用三个复制中的某一个。

Ribbon 充当负载均衡器的角色


在 Ribbon 的几种负载均衡策略中,随机策略是用的比较多的。例如:“B1”,“B2”,“B3”分别是“B”服务的三个复制。


“A”服务第一次,调用的是“B1”服务,根据随机策略,第二次就访问“B2”服务,第三次访问“B3”服务,第四次又访问“B1”服务,依次类推,循环往复。


下面列出其他几个服务仅供参考,篇幅有限不做赘述:

  • 随机(Random)

  • 轮询(RoundRobin)

  • 一致性哈希(ConsistentHash)

  • 哈希(Hash)

  • 加权(Weighted)


Hystrix 服务出现故障


现在“A”服务知道如何调用“B1”,“B2”,“B3”三个“B”服务的复制了。

假如“B2”服务出现故障,“A”服务还可以访问它吗?为了避免单个服务的故障影响到其他服务,Hystrix 就应运而生了。

调用“B1”服务失败

Hystrix 俗称熔断器(断路器),当服务不可用或者出现故障时,它提供了响应的处理机制。


在微服务架构中,每个服务都有可能依赖其他服务,也有可能多个服务同时依赖一个服务,又或者存在服务之间的连环依赖(A 依赖 B,B 依赖 D)。

一旦被依赖的服务出现故障,Hystrix 可以通过预设的处理机制,调整服务的响应。例如:返回错误信息,用缓存或者兜底信息替代服务返回信息。

单个服务依赖多个服务,依赖服务中有一个出现问题,对整体产生影响。

多个服务依赖单个服务,单个服务出现故障,影响多个服务


应用 Hystrix 只需要两步:

第一步,在“A”服务(消费者)上定义,调用“B”服务(生产者)出现故障时的处理方法。

调用“B”服务故障处理的方法,代码片段

第二步,在“A”服务(消费者)调用“B”服务的方法的 Annotation 上面标注调用失败需要执行的“第一步”的这个方法。

声明调用失败方法,代码片段

在“A”服务中加入 Hystrix

Zuul 如何访问到微服务


上面把服务端的事情说的差不多了,如果“C”客户端需要访问“A”服务,系统通过什么方式告诉它哪个服务是“A”呢?

实际上这里缺少一个网关,把“C”客户端与“A”服务链接起来的网关。

Zuul 就是这个网关,它的责任是过滤和路由。

Zuul 是链接客户端和服务端的网关

还记得上面提到的 Eureka 服务注册和服务发现吗?Zuul 可以和 Eureka 一起合作,完成服务路由的工作。

首先,“A”服务在 Eureka 进行注册,然后“C”客户端向 Zuul 发起请求,访问“A”服务。Zuul 向 Eureka 获取“A”服务的地址,之后访问“A”服务。

Zuul 与 Eureka 协同工作

Zuul 除了可以做路由,还可以做过滤器,针对权限验证,金丝雀测试都可以用到它。这里简单说说 Zuul 内部的运行机制。

Zuul 收到 HTTP 请求以后,会通知 Zuul Servlet 处理,与此同时会生成一个 Request Context 用来记录请求的上下文信息,它会一直保持直到路由结束。

Zuul Filter Runner 接到 Zuul Servlet 的通知以后,会从 Request Context 中取请求的信息,并且交给 Filter Processer 处理,它会维护一套过滤和路由的规则,根据这些规则将请求发送到目标的服务。

Zuul 内部工作原理图


Spring Cloud 微服务架构总结


说完了上面这些是不是对 Spring Cloud 理解更加深了。让我们来回顾一下,Spring Cloud 是一个微服务架构,它为微服务开发提供了丰富的组件。

其中比较重要的五大组件分别是:
  • Eureka:服务发现,服务注册。

  • Feign:服务调用请求。

  • Ribbon:服务之间负载均衡。

  • Hystrix:熔断器。

  • Zuul:服务网关。


如果,用我们上面 ABC 的例子来记忆就是,A 调用 B(Eureka),A 发送请求(Feign),B 做横向扩展以后,A 通过(Ribbon)找到 B,B 出现问题 A 通过熔断机制(Hystrix)保证服务调用正常,C 通过 Zuul 找到 A。

用一张大图来总结一下:

通过 ABC 理解 Spring Cloud 的五大组件

还可以把整个流程总结一下,客户端请求→Zuul→Eureka 获取服务→Feign 通信→Ribbon 负载均衡→Hystrix 熔断:
  • 用户请求会最新发送给 Zuul,Zuul 是用来做 API 网关的。同时它也可以作为过滤器。

  • 微服务的注册操作需要通过 Eureka,作为服务发现和注册中心,一方面记录服务的注册以及健康情况,一方面会协同 Zuul 做好服务访问的工作。

  • 微服务之间通讯,需要把数据打包发送,接受以后也需要解包读取信息。这里可以使用 Feign 作为服务通讯的组件,配合 Ribbon 完成通信工作。

  • Robbin,其负责微服务集群的负载均衡工作。

  • 服务出现故障,例如:业务异常,网络异常等等。需要通过断路器 Hystrix 来实现具体的处理操作,比如通知注册中心服务异常,比如对服务进行降级处理。

    这个时候服务注册发现中心会标记服务异常,再有请求过来就不会发送到有异常的服务上去了。

    同时服务发现注册中心也会定期检查服务的状态,一旦服务恢复状态又把其放到访问队列中。


作者:崔皓

简介:十六年开发和架构经验,曾担任过惠普武汉交付中心技术专家,需求分析师,项目经理,后在创业公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构与研发管理。

编辑:陶家龙、孙淑娟

征稿:有投稿、寻求报道意向技术人请联络 editor@51cto.com

精彩文章推荐:

一次性讲明白Spring Cloud核心组件
神奇的推荐系统:6亿用户音乐场景下的AI思考
在阿里,我如何做好技术项目管理?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存