专栏首页Jimoer你都用过SpringCloud的哪些组件,它们的原理是什么?

你都用过SpringCloud的哪些组件,它们的原理是什么?

前言

看到文章的题目了吗?就是这么抽象和笼统的一个问题,确实是我面试中真实被问到的,某共享货车平台的真实面试问题。 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网关来进入,支持自定义路由规则,自定义过滤规则,可以实现同一的鉴权、限流、认证等功能。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 那些伪造IP的软件都是什么原理

    很多人可能都有过这个念头, 如何伪装客户端IP? 还有那些投票刷票的工具是怎么个原理?

    PhoenixZheng
  • Nginx 的负载均衡模式有哪些?它的实现原理是什么?

    Nginx 是后端工程师和运维工程师,以及前端工程师必须要掌握的必备技能,尤其在分布式系统应用越来越广泛的今天,Nginx 已经占据了 Web 服务器的大壁江山...

    码农架构
  • Nginx 的负载均衡模式有哪些?它的实现原理是什么?

    Nginx 是后端工程师和运维工程师,以及前端工程师必须要掌握的必备技能,尤其在分布式系统应用越来越广泛的今天,Nginx 已经占据了 Web 服务器的大壁江山...

    码农架构
  • RDB 和 AOF 持久化的原理是什么?我应该用哪一个?它们的优缺点?

    RDB:生成指定时间间隔内的 Redis 内存中数据快照,是一个二进制文件 dumpr.rdb

    搜云库技术团队
  • SQL优化的意义是什么?你用过哪些优化方式

    随着系统的数据量逐年增加,并发量也成倍增长,SQL性能越来越成为IT系统设计和开发时首要考虑的问题之一。SQL性能问题已经逐步发展成为数据库性...

    陈哈哈
  • 2019年Java面试经典100问,进入BAT不是梦

    3、Math.round(11.5) 等于多少? Math.round(-11.5)等于多少? 【基础】

    Java架构师必看
  • 面试必问:这几个 Spring Cloud 常见面试题!

    单个轻量级服务一般为一个单独微服务,微服务讲究的是 专注某个功能的实现,比如登录系统只专注于用户登录方面功能的实现,讲究的是职责单一,开箱即用,可以独立运行。微...

    架构师修炼
  • mybatis的优点是什么?实际运用过程之中它有哪些缺陷吗

    mybatis对于不了解计算机知识的人来说是陌生的,但是一旦进入了计算机的相应领域,对于mybatis自然也是需要了解的。mybatis实际上是计算机之中的一种...

    用户8739990
  • 微服务总结

    简单举例:看军事新闻的同学应该都知道,一艘航空母舰作战能力虽然很强,但是弱点太明显,就是防御能力太差,单艘的航空母舰很少单独行动,通常航空母舰战斗群才是主要军事...

    后端码匠
  • F版本SpringCloud 2—什么是SpringCloud?SpringCloud版本选择

    上一篇文章中,通过一个简单的小故事,轻松愉快的讲解了架构的演变,以及为啥会有微服务,什么是微服务。不过在最后留下了一个小疑问:将架构设计为微服务需要一整套技术,...

    鹿老师的Java笔记
  • 关于微服务的分享

    微服务应该可以说是这几年比较流行火爆的名词了,很多互联网公司也已经开始采用微服务架构体系,即使比较传统的软件公司也慢慢的开始重新架构现有的应用程序。

    每天学Java
  • 五分钟学Java:一篇文章带你搞懂spring全家桶套餐

    上期我们讲了spring和springmvc两个框架的基础知识和学习路线,而这期内容,我们将围绕着spring全家桶展开来讨论。

    黄小斜学Java
  • F版本SpringCloud 2—什么是SpringCloud?SpringCloud版本选择

    上一篇文章中,通过一个简单的小故事,轻松愉快的讲解了架构的演变,以及为啥会有微服务,什么是微服务。不过在最后留下了一个小疑问:将架构设计为微服务需要一整套技术,...

    鹿老师的Java笔记
  • 最近一百年,全球涌现过哪些最顶尖的、最赚钱的公司?它们的共性是什么?

    作者:挖数 来源:知乎,已获作者授权,拒绝二次转载 题主问的是最赚钱的公司,那应该从利润的维度看更合适。 以下是 2015年全球利润最高的25家公司,利润从...

    顶级程序员
  • 2020年SpringCloud 必知的18道面试题

    Spring cloud流应用程序启动器是基于Spring Boot的Spring集成应用程序,提供与外部系统的集成。Spring cloud Task,一个生...

    程序员白楠楠
  • 微服务 面试

    1、什么是微服务?     就目前而言,对于微服务业界并没有一个统一的,标准的定义。

    庞小明
  • 105道Java面试题,认真思考对你面试很有价值!

    (1)保证被volatile修饰的共享变量对所有线程总是可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。

    程序员追风
  • 什么是Spring Cloud项目,我把它讲清楚了

    最近几年微服务很火,大家都在建设微服务,仿佛不谈点微服务相关的技术,都显得不是那么主流了。

    润森
  • 又到了跳槽季,你们都准备好了吗?我来告诉Java程序员们如何快速全面的复习

    年过完了,大多数同仁们应该已返回并进入了工作状态,估计这个时候,有很多小伙伴也在开始准备年后跳槽的事情了,对于一些做传统项目的同仁,不知道如何复习迎接面试是肯定...

    攻城狮的那点事

扫码关注云+社区

领取腾讯云代金券