前言

看到文章的题目了吗?就是这么抽象和笼统的一个问题,确实是我面试中真实被问到的,某共享货车平台的真实面试问题。

SpringCloud确实是用过,但是那是三四年前了,那个时候SpringCloud刚开始流行没多久,我们技术总监让我们调研一下,然后算上我在内的三个同事就一人买了一本SpringCloud的书籍,开始看,开始研究,正好那个时候DDD也比较火,然后我们就一边研究的SpringCloud一边按照DDD的模型搭建自己的项目。

但是这个项目最后做了三个月,才完成了一期。后面二期还没开始,我就撤了。所以SpringCloud总共的使用时间就两三个月,所以对这部分知识掌握的并不扎实,而且入职了新公司之后,都是使用公司自己封装的框架,也已经三年没有用过SpringCloud了,这次是要面试换工作了,所以决定将这方面的知识,总结一下。

服务治理 Spring Cloud Eureka

我们之前在使用服务之间相互调用的时候,一般是靠一些静态配置来完成的。比如服务A,要调用服务B来完成一个业务操作时,为了实现服务B的高可用,一般是通过手动配置来完成服务B的服务实例清单的维护。

随着业务的发展,系统功能越来越复杂,相应的服务不断增加,而且服务的IP还一直在变化,静态配置来维护各服务,就会变得越来越困难。



这个时候就出现了服务治理框架,Spring Cloud Eureka。

Spring Cloud Eureka 主要是围绕着服务注册与服务发现机制来完成对微服务的自动化管理的。

服务注册

Eureka提供了服务端组件,我们也称为注册中心。每个服务都向Eureka的服务注册中心,登记自己提供服务的元数据,包括服务的ip地址、端口号、版本号、通信协议等。这样注册中心,就将各个服务维护在了一个服务清单中(双层Map,第一层key是服务名,第二层key是实例名,value是服务地址加端口)。



服务注册中心,还会以心跳的方式去监听清单中的服务是否可用(默认30秒),若不可用(服务续约时间默认90秒),需从清单中剔除,达到排除故障服务的效果。

Eureka注册中心提供了高可用方案,可以支持集群部署注册中心,然后多个注册中心实例之间又相互注册,这样每个实例中都有一样的服务清单了。

服务发现

Eureka不但提供服务端,还提供了客户端,客户端是在各个服务中运行的。

Eureka客户端主要有两个作用:

  • 向注册中心注册自身提供的服务,并周期性的发送心跳来更新它非服务租约
  • 同时,也能从服务端查询当前注册的服务信息,并把他们缓存到本地,并周期性的刷新服务状态

在Eureka Server中注册的服务,相互之间调用,不再是通过指定的具体实例地址,而是通过向服务名发请求实现调用,因为每个服务服务都是多实例,并且实例地址还有可能经常变。

但是通过服务名称调用,并不知道具体的服务实例位置,因此需要向注册中心咨询,并获取所有服务实例清单,然后实现服务的请求访问。

举例



服务A的一个业务操作,需要调用服务B和服务C来完成。

那么服务A和服务B和服务C都将自己注册到Eureka的注册中心,然后服务A通过咨询注册中心,将注册中心的服务列表清单缓存到自己本地。

通过服务名称获取到服务B和服务C的服务实例地址,最后通过一种轮询策略取出一个具体的服务实例地址来进行调用。

总结一下

Eureka Client : 主要是将服务本身注册到Eureka Server中,同时查询Eureka Server的注册服务列表缓存到本地

Eureka Server:注册中心,保存了所有注册服务的元数据,包括ip地址,端口等信息。

客户端负载均衡 Spring Cloud Ribbon

服务的调用方,在通过Eureka Client缓存到本地的注册表之后,通过服务名称,找到具体的服务对应的实例地址,但是被调用方的服务地址是有多个的,那么该用那个地址去进行调用呢?

服务A:
192.168.12.10:9001
192.168.12.11:9001
192.168.12.12:9001

这个时候Spring Cloud Ribbon就出现了,它是专门解决这个问题的,它的作用就是做负载均衡,会均匀的把请求分发到每台机器上。

Ribbon默认使用Round Ribbon的策略进行负载均衡,具体就是采用轮询的方式进行请求。

Ribbon除了有Round Ribbon这种轮询策略,还有其他策略以及自定义策略。

主要有:

  • RandomRole: 从服务实例清单中随机选择一个服务实例。
  • RoundRobinRule: 按照线性轮询的方式依次选择每个服务实例。
  • RetryRule:根据轮询方式进行,且具备重试机制进行选择实例。
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例。
  • ZoneAvoidanceRule:根据服务方的zone区域和可用性来轮询选择。

Spring Cloud Ribbon具体的执行示例如下:

实例代码

下面的代码就是通过Ribbon调用服务的代码实例。

@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/ribbon-consumer")
public String helloConsumer(){
return restTemplate.getForEntity("http://example-service/index",String.class).getBody();
}
}

可以看到Ribbon也是通过发起http请求,来进行的调用,只不过是通过调用服务名的地址来实现的。虽然说Ribbon不用去具体请求服务实例的ip地址或域名了,但是每调用一个接口都还要手动去发起Http请求,也是比较繁琐的,而且返回类型也比较抽象,所以Spring Cloud对调用方式进行了升级封装。

声明式服务调用 Spring Cloud Feign

Spring Cloud 为了简化服务间的调用,在Ribbon的基础上进行了进一步的封装。单独抽出了一个组件,就是Spring Cloud Feign。在引入Spring Cloud Feign后,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。

Spring Cloud Feign具备可插拔的注解支持,并扩展了Spring MVC的注解支持。

下面我们来看一个具体的例子:

服务方具体的接口定义与实现代码如下:


import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 接口定义
*/
@FeignClient(value="service-hi",fallback = TestFeignServiceImpl.class)
public interface TestFeignService { @RequestMapping(value="/hi",method = RequestMethod.GET)
String sayHi(@RequestParam("name") String name);
}
/**
* 具体的服务实现
*/
@Component
public class TestFeignServiceImpl implements TestFeignService {
@Override
public String sayHi(String name) {
return "你好,"+name;
}
}

调用方的使用代码如下:

@RestController
public class TestController
{
@Resource
private TestFeignService testFeignService; @RequestMapping(value="/hi",method = RequestMethod.GET)
public String sayHi(@RequestParam String name)
{
// 调用远程服务
return testFeignService.sayHi(name);
}
}

通过上面的代码,我们可以看到,调用方通过Feign进程远程服务调用的时候,非常简单,就向是在调用本地服务一样。

像之前的建立连接,构造请求,发起请求,获取响应,解析响应等等操作,对使用者来说都是透明化的,使用者不用关心服务是怎么实现调用的,直接使用即可。

那么Feign是如何实现这套封装逻辑的呢?

其实Feign底层主要是靠动态代理来实现这整个服务的调用过程的。

主要逻辑如下:

  • 如果一个接口上定义了@FeignClient注解,Feign就会根据这个接口生成一个动态代理类。
  • 如果调用方,在调用这个定义了@FeignClient注解的接口时,本质上是会调用Feign生成的代理类。
  • Feign生成的动态代理类,会根据具体接口方法中的@RequestMapping等注解,来动态构造出需要请求的服务地址。
  • 最后针对这个地址,再通过Ribbon发起服务调用,解析响应等操作。



因为Spring Cloud Feign的使用方式比Spring Cloud Ribbon更方便,所以一般项目中都是使用Feign,而且Feign还有继承特性,可以将远程服务接口继承过来然后再进行自己的个性化扩展。因此Feign的使用范围以及普及率更高一些。

服务容错保护 Spring Cloud Hystrix

在微服务架构中,我们将系统拆分成多个服务单元,各个服务之间通过服务注册与订阅的方式互相依赖。

我们以一个电商网站下单的过程来举例,在下单的业务操作过程中需要调用库存服务,支付服务,积分、物流等服务。假设订单服务最多同一时间只能处理50个请求,这个时候如果积分服务挂了,那么每次订单服务去调用积分服务的时候,都会卡这么一段时间,然后才返回超时异常

在这种场景下会有什么问题呢?

如果目前电商网站正在搞活动,进行抢购活动,下单的人非常多,这种高并发的场景下,订单服务的已经同时在处理50个下单请求了,并且都卡在了请求积分服务的过程中。订单服务已经没有能力去处理其他请求了。

那么其他服务再来调用订单服务时,发订单服务无响应,这样就导致订单服务也不可用了。然后其他依赖订单服务的服务,也最终会导致不可用。这就是微服务架构中的服务雪崩。

就上图所示,如果多个服务之间相互调用,而不做任何保护措施的话,那么一个服务挂了,就会产生连锁反应,导致其他服务也挂了。

其实就算是积分服务挂了,也并不应该导致订单服务也挂了,积分服务挂了,我们可以跳过积分服务,或者是放一个默认值,然后继续往下走,等着积分服务恢复了,可以手动恢复一下数据。

那么Spring Cloud Hystrix就是解决这个问题的组件,他主要是起到熔断,隔离,降级的作用。

Spring Cloud Hystrix其实是会为每一个服务开辟一个线程池,然后每个线程池中的线程用于对服务的调用请求。这样就算是积分服务挂了,那也只是调用积分服务的线程池出现问题了,而其他服务的线程池还正常工作。这就是服务的隔离。

这样订单服务在的调用积分服务的时候,如果发现有问题了,积分服务可以通过Hystrix返回一个默认值(默认是5秒内20次调用失败就熔断)。这样订单服务就不用在这里卡住了,可以继续往下调用其他服务进行业务操作了。这就是服务的熔断。

虽然说是积分服务挂了,并且也返回了默认值了,但是后续如果积分服务恢复了,想恢复数据怎么办呢?这个时候积分服务可以将姐收到的请求记录下来,或者是打到日志中,能为后面恢复数据提供依据就行。这就是服务的降级

整个过程大致如下图所示:

API网关服务Spring Cloud Zuul

通过上面几个组件的结合使用,已经能够完成一个基本的微服务架构了。但是当一个系统中微服务的数量逐渐增多时,一些通用的逻辑,例如:权限校验机制,请求过滤,请求路由,限流等等,这些每个服务对外提供能力的时候都要考虑到的逻辑,就会变得冗余。

这个时候API网关的概念应运而生,它类似于面向对象设计模式中的Facade模式(门面模式/外观模式),所有的外部客户端访问都需要经过它来进行调度和过滤。主要实现请求路由、负载均衡、校验过滤、服务限流等功能。

Spring Cloud Zuul就是Spring Cloud提供的API网关组件,它通过与Eureka进行整合,将自身注册为Eureka下的应用,从Eureka下获取所有服务的实例,来进行服务的路由。

Zuul还提供了一套过滤器机制,开发者可以自己指定哪些规则的请求需要执行校验逻辑,只有通过校验逻辑的请求才会被路由到具体服务实例上,否则返回错误提示。

Spring Cloud Zuul的依赖包spring-cloud-starter-zuul本身就包含了对spring-cloud-starter-hystrixspring-cloud-starter-ribbon模块的依赖,所以Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载功能。

Zuul的路由实现是通过Path和serviceId还实现的,path是一个http请求去除ip和端口号后的方法路径,例如:http://192.168.20.12:9001/api-zuul/123,那么path就是/api-zuul/123,Zuul在配置时支持模糊匹配,若123是动态参数,可以将path配置成/pai-zuul/**,serviceId就是服务在Eureka中注册的服务名称。

zuul.routes.api-zuul.path= /api-zuul/**
zuul.routes.api-zuul.serviceId= service-jimoer

有了统一的网关后,再做统一的鉴权、限流、认证授权、安全等方面的工作就会变的更加方便了。

总结

上面总结了Spring Cloud的几个核心组件,其实Spring Cloud 除了这几个组件还有一些其他的组件,例如:

  • 分布式配置中心Spring Cloud Config
  • 消息总线Spring Cloud Bus
  • 消息驱动Spring Cloud Stream
  • 分布式服务跟踪Spring Cloud Sleuth

主要是后面这些组件我们平时用的不多,而且对于微服务来说有些是有替代品的,所以我暂时就没有总结。还有一点毕竟我这次总结是为了解决面试的问题,所以后面如果在实际的工作中用到了剩下的这些组件,我会继续总结的。

好了,总结一下这次的几个组件的内容吧。

  • Spring Cloud Eureka 各个微服务在启动时将自己注册到Eureka Server中,并且各个服务中的Eureka Client又能从注册中心获取各个服务的实例地址清单。
  • Spring Cloud Ribbon 各个服务相互调用的时候,通过Ribbon来进行客户端的负载均衡,从多个实例中根据一定的策略选择一台进行请求。
  • Spring Cloud Feign 基于动态代理机制,根据注解和参数拼接URL,选择具体的服务实例发起请求,简化了服务间相互调用的开发工作。
  • Spring Cloud Hystrix 调用每个服务的时候都是通过线程池中的线程来发起的,不同的服务走不同的线程池,实现了服务的隔离,而且服务不可用时还提供了熔断机制以及支持降低措施。
  • Spring Cloud Zuul 外部请求统一通过Zuul网关来进入,支持自定义路由规则,自定义过滤规则,可以实现同一的鉴权、限流、认证等功能。

最后来一个整体的架构图,将各个组件串起来。

你都用过SpringCloud的哪些组件,它们的原理是什么?的更多相关文章

  1. 描述下什么是springcloud,springcloud中的组件有哪些?分别描述下它的原理?

    1.什么是springcloud,springcloud中的组件有哪些? Spring cloud是一个基于Spring Boot实现的服务治理工具包,在微服务架构中用于管理和协调服务的微服务:就是把 ...

  2. Hadoop基础-Hdfs各个组件的运行原理介绍

    Hadoop基础-Hdfs各个组件的运行原理介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.NameNode工作原理(默认端口号:50070) 1>.什么是NameN ...

  3. SpringCloud 详解配置刷新的原理 使用jasypt自动加解密后 无法使用 springcloud 中的自动刷新/refresh功能

    之所以会查找这篇文章,是因为要解决这样一个问题: 当我使用了jasypt进行配置文件加解密后,如果再使用refresh 去刷新配置,则自动加解密会失效. 原因分析:刷新不是我之前想象的直接调用conf ...

  4. 通俗易懂了解Vue内置组件keep-alive内部原理

    1. 官方介绍及其用法 1.1 组件介绍 要想搞明白<keep-alive>组件的内部实现原理,首先我们得搞明白这个组件怎么用以及为什么要用它,关于<keep-alive>组件 ...

  5. springcloud整合config组件

    config组件 config组件支持两种配置文件获取方式springcould搭建的微服务的配置文件的获取方式有两种.它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中或者本地 ...

  6. Omi框架学习之旅 - 通过omi-id来实现组件通讯 及原理说明

    这个demo是通过omi-id来获取子类的实例,然后更改data属性,之后updata一下就好了. 老规矩:先上demo代码, 然后提出问题, 之后解答问题, 最后源码说明. class Hello ...

  7. MyBatis源码分析(各组件关系+底层原理

    MyBatis源码分析MyBatis流程图 下面将结合代码具体分析. MyBatis具体代码分析 SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例 ...

  8. 一文搞懂AQS及其组件的核心原理

    @ 目录 前言 AbstractQueuedSynchronizer Lock ReentrantLock 加锁 非公平锁/公平锁 lock tryAcquire addWaiter acquireQ ...

  9. .Net 下高性能分表分库组件-连接模式原理

    ShardingCore ShardingCore 一款ef-core下高性能.轻量级针对分表分库读写分离的解决方案,具有零依赖.零学习成本.零业务代码入侵. Github Source Code 助 ...

随机推荐

  1. C++ cin.ignore() 的使用

    cin.sync()的功能是清空缓冲区,而cin.ignore()虽然也是删除缓冲区中数据的作用,但其对缓冲区中的删除数据控制的较精确. 有时候你只想取缓冲区的一部分,而舍弃另一部分,这是就可以使用c ...

  2. Jmeter(1)下载和安装

    一.Jmeter工具安装 1.jmeter安装包下载地址:http://jmeter.apache.org/,下载Binaries包,使用jmeter需要先安装jdk 2.解压后打开/bin目录下的j ...

  3. JavaSE19-IO特殊流和Properties集合

    1.IO特殊操作流 1.1 标准输入流 System类中有两个静态的成员变量 public static final InputStream in:标准输入流.通常该流对应于键盘输入或由主机环境或用户 ...

  4. sonarqube代码质量检查简单使用说明

    本文翻译自sonarqube官网文档,原地址为:https://docs.sonarqube.org/latest/architecture/architecture-integration/ 一,架 ...

  5. Windows远程报错:由于没有远程桌面授权服务器可以提供许可证,远程会话被中断

    故障原因:Windowsserver超过两人的远程连接是收费的,有120天免费试用期,超过这个时间再连接就会报错. 解决方法一: 进行续费 解决方法二: 1,在运行里运行 mstsc /v:ip    ...

  6. 22期老男孩Ptython全栈架构师视频教程

    老男孩Ptython全栈架构师视频教程 Python最新整理完整版22期视频教程 超60G课程容量<ignore_js_op> <ignore_js_op> <ignor ...

  7. 浅谈JAVA代码优化

    JAVA代码的优化分为两个方面: 一.减小代码的体积.二.提高代码的执行效率. ============================================================ ...

  8. Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)

    @ 目录 1.说明 2.代码 关于作者 1.说明 使用正则表达式,匹配客户端的请求头 获取到请求的路径 返回对应请求路径的文字 可以使用打开对应文件的方式去返回对应的文件 2.代码 from sock ...

  9. 怎样用Java 8优雅的开发业务

    怎样用Java 8优雅的开发业务 目录 怎样用Java 8优雅的开发业务 函数式编程 流式编程 基本原理 案例 优雅的空处理 新的并发工具类CompletableFuture 单机批处理多线程执行模型 ...

  10. Python稳基修炼之异常处理

    错误与异常 1.区分错误与异常 两种错误(都必须改正): 语法错误(代码不规范,格式不对或缺少符号).逻辑错误(逻辑不通) 异常: 程序运行时发生错误的信号 2.异常处理与注意事项 异常处理: 程序员 ...