SpringCloud运用与理解

SpringCloud介绍

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册配置中心、消息总线、负载均衡断路器数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具

SpringCloud官网:http://projects.spring.io/spring-cloud/

相关学习网站:

  • https://springcloud.cc/spring-cloud-netflix.html

  • 中文API文档: https://springcloud.cc/spring-cloud-dalston.html

  • SpringCloud中国社区 http://springcloud.cn/

  • SpringCloud中文网 https://springcloud.cc

优缺点

优点:

1、服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率

2、可以更精准的制定优化服务方案,提高系统的可维护性

3、微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量

4、适于互联网时代,产品迭代周期更短

5、社区活跃度高

缺点:

1、微服务过多,治理成本高,不利于维护系统

2、分布式系统开发的成本高(容错,分布式事务等)对团队挑战大

总的来说优点大过于缺点,目前看来SpringCloud是一套非常完善的分布式框架,目前很多企业开始用微服务

SpringCloud和SpringBoot关系

  • SpringBoot专注于快速方便的开发单个个体微服务
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理服务发现断路器路由微代理事件总线全局锁决策竞选分布式会话等等集成服务
  • SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
  • SpringBoot专注于快速,方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架

SpringCloud和Dubbo的区别

SpringCloudDubbo
服务注册中心SpringCloud Netfilx EureKaZookeeper
服务调用方式REST APIRPC
服务监控Spring Boot AdminDubbo-monitor
断路器Spring Cloud Netflix Hystrix不完善
服务网关Spring Cloud Netfilx Zuul
分布式配置Spring Cloud Config
服务跟踪Spring Cloud Sleuth
消息总线Spring Cloud Bus
数据流Spring Cloud Srream
批量任务Spring Cloud Task

最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用的是最基本的HTTP的REST方式

严格来说,这两种方式各有优劣,虽然从一定程度上来说,后者牺牲看服务调用的性能,但是也避免的上面提到的原生RPC带来的问题,而且REST相比RPC更灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更加合适

他们解决的问题域不一样:Dubbo的定位是一款RPC框架,Spring Cloud的目标是微服务架构下的一站式解决方案

SpringCloud作用

  • Distributed/versioned configuration (分布式/ 版本控制配置)
  • Service registration and discovery (服务注册与发现)
  • Routing(路由)
  • Service-to-service calls (服务到服务的调用)
  • Load balancing (负载均衡配置)
  • Distributed messaging(分布式消息管理)
  • 。。。

SpringCluod的五大组件

  1. 服务发现 ——Netflix Eurek
  2. 客户端负载均衡——Netflix Ribbon
  3. 断路器——Netflix Hystrix
  4. 服务网关——Netflix Zuul
  5. 分布式配置——Spring Cloud Config

引入组件的步骤

  1. 导入依赖
  2. 编写配置文件
  3. 开启这个功能 @EnableXXXXX
  4. 配置类

SpringCloud核心组件的业务运用

在实际微服务开发中,是将项目拆分为一个一个的子服务,如在常见的电商项目中,存在搜索服务,库存服务,订单服务,评价服务,仓储服务,积分服务等服务,微服务使得开发容易和为维护,因为一个服务只关注一个特定的业务,使得业务清晰,代码量少等优点

提出问题1:订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用?订单服务根本儿就不知道库存服务在哪台机器上啊!请求不知道发送给谁

解决问题:使用Eureka将服务都注册到注册中心,就知道每个服务的具体位置

提出问题2:知道了位置,哪怎么调用服务呢?写一堆网络链接代码?

解决问题:使用Feign,Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址

提出问题3:当我们的某个服务需要进行集群时(请求量大,单个服务承载不了,如高峰期的订单服务),那我们此时请求集群的哪个服务器呢?都长得一样呀(都是订单服务)

解决问题:使用Ribbon进行负载均衡,默认时轮询方式,也就是每一个服务都挨个依次访问

提出问题4:如上,基本组成了微服务链路(扇出链路),但是这条链路中某个服务挂了,如评价服务,这业务和其他相比根本不重要,却导致整个项目瘫痪,亏大了

解决问题:使用Hystrix进行熔断,对挂掉的服务做出一个备选处理,时项目正常运行

提出问题5:在我们用户中,我想去使用某个服务,怎么办?记住每一个服务名称和端口号?当服务有上百个呢?当服务又存在着集群呢?显然让我们记住每一个服务名称和端口号?

解决问题: 使用Zuul网关路由,进行服务的统一管理,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务 (这是不必须的)

Eureka

  • Eureka实现服务注册与发现
  • Eureka采用C-S的架构设计,即存在客服端Eureka Client 和 服务端Eureka Server两个组件
  • 每一微服务都有着属于自己的Eureka Client ,向服务端Eureka Server 注册中心注册自己的信息
  • 客服端Eureka Client 链接到服务端Eureka Server并维持心跳(隔一段时间发送信息确保客服端的存活,默认周期30秒),这样系统的维护人员就可以通过Eureka Server监控系统的各个微服务是否正常运行,SpringCloud的一些模块如Zuul就可以通过EurekaServer来发现系统的其他微服务

Eureka Client

ureka Client是一个java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮甸负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果ureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个报务节点移除掉(默认周期为90秒)

Eureka Server

Eureka Server提供服务注册服务,各个节点启动后,会在EurekaServer中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

客服端与服务端的链接

Eureka Client

#使用Eureka将信息注入到注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    instance-id: springcloud-provider-dept8001

Eureka Server

#Eureka Server配置
eureka:
  instance:
    hostname: localhost  #Eureka服务端的实例名称
  client:
    register-with-eureka: false
    #表示是否向eureka注册中心注册自己
    fetch-registry: false
    #是否获取注册表信息,false意味着自己就是注册表
    service-url:  #监控地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

完成注册!

Eureka的自我保护机制

简单来说就是:宁可同时保留所有的微服务(健康和不健康的微服务都会保留),也不会盲目注销任何健康的微服务

目的:使用自我保护机制,可以让Eureka集群更加健壮和稳定

具体介绍

​ Eureka服务端会检查最近15分钟内所有Eureka 实例正常心跳占比,如果低于85%就会触发自我保护机制。触发了保护机制,Eureka将暂时把这些失效的服务保护起来,不让其过期,但这些服务也并不是永远不会过期。Eureka在启动完成后,每隔60秒会检查一次服务健康状态,如果这些被保护起来失效的服务过一段时间后(默认90秒)还是没有恢复,就会把这些服务剔除。如果在此期间服务恢复了并且实例心跳占比高于85%时,就会自动关闭自我保护机制。

为什么会有自我保护机制

  • 防止由于网路通信故障等原因,造成Eureka服务端失去于Eureka客户端的连接,从而形成的不可用。
  • 因为网络通信是可能恢复的,但是Eureka客户端只会在启动时才去服务端注册。如果因为网络的原因而剔除了客户端,将造成客户端无法再注册到服务端。

如何选择是否开启自我保护机制

  • Eureka服务端默认情况下是会开启自我保护机制的。但我们在不同环境应该选择是否开启保护机制。

  • 一般情况下,我们会选择在开发环境下关闭自我保护机制,而在生产环境下启动自我保护机制。

开发环境下

​ 我们启动的服务数量较少而且会经常修改重启。如果开启自我保护机制,很容易触发Eureka客户端心跳占比低于85%的情况。使得Eureka不会剔除我们的服务,从而在我们访问的时候,会访问到可能已经失效的服务,导致请求失败,影响我们的开发。

在生产环境下

​ 我们启动的服务多且不会反复启动修改环境也相对稳定,影响服务正常运行的人为情况较少。适合开启自我保护机制,让Eureka进行管理。

关闭自我保护机制

服务端

eureka:
  server:
    #服务端是否开启自我保护机制 (默认true)
    enable-self-preservation: false
    #扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒
    #让服务端每隔2秒扫描一次,使服务能尽快的剔除。
    eviction-interval-timer-in-ms: 2000

客户端

我们的客户端设置和Eureka服务端的集群相同。我们设置三个客户端服务,服务名(spring.name)分别为client1、client2和client3,并修改端口号(server.port)分别为10000、20000、30000。

server:
  port: 10000
spring:
  application:
    name: client1
eureka:
  client:
    service-url:
      defaultZone: http://cluster1:70700/eureka,http://cluster2:8080/eureka,http://cluster3:9090/eureka
  instance:
  # 客户端向注册中心发送心跳的时间间隔(默认30秒),设置为1秒一次。
    lease-renewal-interval-in-seconds: 1
    #Eureka注册中心(服务端)在收到客户端心跳之后,等待下一次心跳的超时时间,如果在这个时间内没有收到下次心跳,则移除该客户端(默认90秒),设置为2秒。如果超过2秒,移除该客户端。
    lease-expiration-duration-in-seconds: 2
    server:
  port: 20000
spring:
  application:
    name: client2
eureka:
  client:
    service-url:
      defaultZone: http://cluster1:7070/eureka,http://cluster2:8080/eureka,http://cluster3:9090/eureka
  instance:

#客户端向注册中心发送心跳的时间间隔,(默认30秒)

    lease-renewal-interval-in-seconds: 1
    #Eureka注册中心(服务端)在收到客户端心跳之后,等待下一次心跳的超时时间,如果在这个时间内没有收到下次心跳,则移除该客户端。(默认90秒)
    lease-expiration-duration-in-seconds: 2

server:
  port: 30000
spring:
  application:
    name: client3
eureka:
  client:
    service-url:
      defaultZone: http://cluster1:7070/eureka,http://cluster2:8080/eureka,http://cluster3:9090/eureka
  instance:

#客户端向注册中心发送心跳的时间间隔,(默认30秒)

    lease-renewal-interval-in-seconds: 1
    #Eureka注册中心(服务端)在收到客户端心跳之后,等待下一次心跳的超时时间,如果在这个时间内没有收到下次心跳,则移除该客户端。(默认90秒)
    lease-expiration-duration-in-seconds: 2

我们将Eureka服务端集群启动并启动客户端的三个实例。当我们将客户端(client1)实例停掉(按照Eureka心跳占比低于85%规则,停掉一个就可以出发自我保护机制),我们过2-3秒后,查看Eureka服务端的控制台,查看是否将已经失效的服务剔除。

查看自我保护机制是否启动和触发

Eureka是否触发自我保护机制和是否开启自我保护都可以在Eureka控制台看到。

我们先认识一下Eureka控制台的三个参数

Lease expiration enabled 是否启用租约过期 . 当前实例心跳占比不满85%,Eureka自动保护机制启动启动后该值为false,当实例心跳占比满足了85%时,Eureka将会自动关闭自我保护机制,此时此值为true。

Renews thresshold: 续约阀值,既每分钟接收客户端最少的续约数。如果低于这个值,Eureka将进入自我保护机制。

续约: 客户端向服务端发送一次心跳,来证明客户端在线。 理解:相当于服务端和客户端签订了一份合同,到期了就要续约,保证合同能够继续进行。

在Eureka源码中关于Renews thresshold阀值的计算函数如下
    protected void updateRenewsPerMinThreshold() {
        this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfClientsSendingRenews * (60.0D / (double)this.serverConfig.getExpectedClientRenewalIntervalSeconds()) * this.serverConfig.getRenewalPercentThreshold());
    }
expectedNumberOfClientsSendingRenews: 注册的实例数
getExpectedClientRenewalIntervalSeconds:服务端期望的客户端续约间隔,既服务端每分钟期望接收的心跳间隔
getRenewalPercentThreshold(): 阀值系数 默认为0.85


Renews thresshold = (注册的实例数*60/服务端期望的客户端心跳间隔))* 阀值 

注册的实例数:就是包括服务端集群和客户端在内的所有已经注册的实例。在控制台的Instances currently registered with Eureka中显示。

客户端一分钟心跳数:Eureka客户端eureka.instance.ease-renewal-interval-in-seconds配置的值,默认为30秒,既每30秒向服务端发送一次心跳。

阀值:我们在Eureka集群服务端配置的eureka.instance.renewal-percent-threshold. 不需要配置,默认值为0.85.15分钟内Eureka实例的心跳数低于85%时,启动自我保护机制。
Renews(last min): 最后一分钟续约数。 这个值就是所有实例在当前时刻前一分钟的所有心跳数。

我们判断是否进入了自我保护机制,就可以从Renews thresshold和Renews(last min)的对比中看出。

Renews thresshold <= Renews(last min) : 当前最后一分钟的续约数大于续约阀值,这是正常状态,Eureka服务端没有启动自我保护机制。

Renews thresshold > Renews(last min): 当续约阀值大于当前最后一分钟的续约数。此时Eureka将进入自我保护机制。

我将在下面创建一个3个服务端的集群和3个不同服务的客户端,来模拟我们的自我保护机制启动关闭情况。让我们能够从控制台上,确定当前Eureka现在所处的状态。

正常情况
我们可以在控制台上Instances currently registered with Eureka中有6个实例。其中CLUSTEREUREKA是我们的Eureka服务端集群3个。剩下CLIENT1-CLIENT3是客户端实例。

在正常情况下,可以看到Lease expiration enabled 显示为true。

Renews thresshold值为 (我们6个实例每个实例每分钟2次心跳) 0.85 ≈ 10.2 所以我们的Renews thresshold=10.

Renews(last min)值,因为在正常情况下,所以每个实例都是每分钟2次心跳,没有网络连接的异常。

6个实例* 每个实例每分钟2次心跳 = 12.

Renews thresshold < Renews(last min) 当前情况没有达到开启自我保护机制的阀值所以是正常情况。

启动自我保护机制并触发
为了开启自我保护机制,我现在停掉CLIENT1实例。因为一个实例每分钟给服务发送两次心跳请求,当停掉一个实例时,他减少2次心跳。此时Renews (last min) = 10. 这时Renews = Renews threshold 我们来看一下是否触发了自我保护机制。

Eureka服务端默认60秒扫描一次服务,我们过1分钟内可以在控制台看到我已将CLIENT1实例停掉了,但这里依然存在,且Lease expiration enabled 变为了false。Renews thresshold = Renews(last min)并且报出了一下异常。

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. 
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST
TO BE SAFE.
紧急!EUREKA可能错误地声称实例已经启动,而实际上并没有。续订小于阈值,因此实例不会因为安全而过期。

根据我们上面讲到的情况,发现此时已经进入了自我保护机制。

由此可以推断出如果Renews thresshold => Renews(last min)时,我们将进入自我保护机制中。

当我们将CLIENT1重新启动后,1-2分钟后,Renews thresshold < Renews(last min),我们也会自动退出自我保护机制。

关闭自我保护机制
我们可以在服务端application-xxx.yml中配置enable-self-preservation和eviction-interval-timer-in-ms属性

eureka:
  server:
    #服务端是否开启自我保护机制 (默认true) 生产环境下,需要自我保护机制,续注释
    enable-self-preservation: false
    #扫描失效服务的间隔时间(单位毫秒,默认是60*1000)即60秒  生产环境下,需要自我保护机制,需注释
    eviction-interval-timer-in-ms: 2000
  instance:
客户端向注册中心发送心跳的时间间隔,(默认30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka注册中心(服务端)在收到客户端心跳之后,等待下一次心跳的超时时间,如果在这个时间内没有收到下次心跳,则移除该客户端。(默认90秒)
    lease-expiration-duration-in-seconds: 2

在客户端application-xxx.yml中配置lease-renewal-interval-in-seconds和lease-expiration-duration-in-seconds属性

eureka:
  instance:
    # 客户端向注册中心发送心跳的时间间隔,(默认30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka注册中心(服务端)在收到客户端心跳之后,等待下一次心跳的超时时间,如果在这个时间内没有收到下次心跳,则移除该客户端。(默认90秒)
    lease-expiration-duration-in-seconds: 2

这时我们不需要再看Renews threshold和Renews(last min)的值了。因为我们关闭了自我保护机制,不需要在计算是否超过了阀值。

我们可以在控制台上看到如下提示,这个提示就告诉我们现在已经关闭了自我保护机制。

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
自我保存模式已关闭。这可能无法在网络/其他问题时保护实例过期。

这时候,随意关闭一个客户端服务,过几秒后我们将很快发现注册中心已经将失效的服务剔除了。

EurekaServer集群

当只存一个Eureka Server时,当这个崩了,会导致业务瘫痪

现如今有三个Eureka Server,记为Server1,Server2,Server3,端口分别为7001,7002,7003,服务端的实例名称分别为eureka7001.com,eureka7002.com,eureka7003.com

方法:

  • 在Server1中关联Server2,Server3
  • 在Server2中关联Server1,Server3
  • 在Server3中关联Server1,Server2

形成循环

Server1:

#Eureka Server配置
eureka:
  instance:
    hostname: eureka7001.com  #Eureka服务端的实例名称
  client:
    register-with-eureka: false
    #表示是否向eureka注册中心注册自己
    fetch-registry: false
    #是否获取注册表信息,false意味着自己就是注册表
    service-url:  #监控地址,监控Server1,Server2
      defaultZone: http://eureka7002.com:7002/eureka/,
                   http://eureka7003.com:7003/eureka/

Server2,Server#同理

Eureka Client 要同时注册到三个注册中心

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,
      http://eureka7002.com:7002/eureka/,
      http://eureka7003.com:7003/eureka/,
  instance:
    instance-id: springcloud-provider-dept8001

访问其中一个注册中心,都能在该页面的DS Replicas下看到其他两个注册中心,在每一个注册中心都有Eureka Client 注册进来的信息,也就是说,其中一台Server挂掉并不影响业务

Eureka与zookeeper比较

比较Eureka与zookeepe的注册中心服务,先了解什么时CAP原则

CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。

CAP的定义

Consistency (一致性):

“all nodes see the same data at the same time”, 即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性。一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。

Availability (可用性):

可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。

Partition Tolerance (分区容错性):

即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。

二、取舍策略

CAP三个特性只能满足其中两个,那么取舍的策略就共有三种:

CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。

CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。

AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

其中Eureka实现AP原则,Zookeeper实现CP原则,P是分布式一定需要实现的

Ribbon

Ri bbon是基于Netflix Ribbon实现的一套客服端负载均衡的工具

一般负载均衡的方法:轮询、加权、随机

Ribbon的作用:

  • LB(load Balance)负载均衡,在微服务或者分布式集群中经常用的一种应用
  • 负载均衡也就是将用户的请求平摊的分配到多个服务器上,从而达到系统的HA(高可用)
  • 常见的负载均衡软件有Nfinx,Lvs等等

负载均衡有两种

  • 服务端的负载均衡,如Nginx
  • 客服端负载均衡,Ribbon

Ribbon的负载均衡策略

  • RoundRobingRule 轮询(默认)
  • RandomRule 随机
  • AvailabilityFilteringRule 先过滤掉跳闸,访问故障的服务,对剩下的进行轮询
  • RetryRule 会先按照轮询获取服务,如果服务获取失败,则会在指定的时间内进行重试

这些的实现接口 IRule

自定义策略

步骤一:在启动类配置相应的注解

@RibbonClient(name="XXX",configuration=自定义类.class)
//XXX代表微服务的spring.application.name
@Configuration
public class MyRule
@Bean
public IRule myRule(){
    return new MyRuleDemo();
}

calss MyRule extends AbstractLoadBalanceRule{
    .....
}

注:自定义策略(自定义组件)不能在主程序的上下文的@ComponentScan中,否则将由所有的@RibbonClients共享,如果使用@ComponentScan(或@SpringBootApplication),则需要避免包含(例如将其放在一个单独的,不重叠的包中,即和主程序不在同一包或子中)

Feign

问题背景:当我们在分布式微服务开发中,每一个功能模块独立出来,但是怎么实现服务一想与服务二进行通信

  • 订单服务根本就不知道人家库存服务在哪台机器上啊!他就算想要发起一个请求,都不知道发送给谁,有心无力!

  • 这时候,就轮到Spring Cloud Eureka出场了。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。

  • Netflix在设计Eureka时,遵循AP原则

  • Eureka是Nexflix的一个子模块,也是核心模块之一

  • Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移

img

上面的代码没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,Feign全给你干了。

那么问题来了,Feign是如何做到这么神奇的呢?很简单,Feign的一个关键机制就是使用了动态代理。咱们一起来看看下面的图,结合图来分析:

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

img

Hystrix

  • Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
  • “断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩

作用:

  1. 服务降级
  2. 服务熔断
  3. 服务限流
  4. 接近实时的监控
  5. 。。。

问题背景

在分布式系统中面临这样的问题,多个微服务之间调用的时候,假设微服务A调用微服务B和C,微服务B和C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,引起所谓的“雪崩效应”,也就是服务雪崩

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

解决方案

使用SpringCloud中的Hystrix组件,可进行如下操作

  • 熔断模式

这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

  • 隔离模式

这种模式就像对系统请求按类型划分成一个个单位,当某个单位挂掉了,不会影响到其他单位。可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。

  • 限流模式

上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。

服务熔断

概念

​ 当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。

实现

导入依赖

<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

修改配置文件

 instance:
    instance-id: my8001-hystrix
    prefer-ip-address: true

在相应Controller类设置

@RestController
public class DeptController {
    @Autowired
    private DeptService service = null;

    @RequestMapping(value="/dept/get/{id}",method= RequestMethod.GET)
    @HystrixCommand(fallbackMethod = "processHystrix_Get") 
    public Dept get(@PathVariable("id") Long id)
    {
        Dept dept =  this.service.get(id);
        if(null == dept)
        {
            throw new RuntimeException("该ID:"+id+"没有没有对应的信息");
        }
        return dept;
    }

    public Dept processHystrix_Get(@PathVariable("id") Long id)
    {
        return new Dept().setDeptno(id)
                .setDname("该ID:"+id+"没有没有对应的信息,null--@HystrixCommand")
                .setDb_source("no this database in MySQL");
    }
}
//一旦调用GET方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法。但是,此种方式会存在一个问题就是每一个方法都需要一个处理

在启动类加注解

@EnableCircuitBreaker

当异常发生后,直接熔断整个服务,而不是一直等待这个服务直到超时!

服务降级

概念

整体资源不足,将某些服务先关掉,待渡过难关,再重新开启,服务降级是在客户端完成的。

存在Server1,Server2,Server3,分别占资源33.3%,但是访问Server的访问数量有3万条,而访问Server2,Server3的访问量只有100条,50条,可以先”关闭"Server3服务,将所占用的资源交给Server1使用,直到Server1渡过高并发的难关,再开启已"关闭"的服务

注:这里的关闭服务,并不是真正的关闭服务,而是拒绝访问该服务的请求,如果Server自己关闭,则意味着该服务挂掉了

实现

API模块(公共模块)

新建一个实现了FallbackFactory接口的类DeptClientServiceFallbackFactory,该接口是专门处理异常的,与上面的服务熔断处理异常的方式相比,此种方式实现了逻辑与异常处理业务的分离

//降级预备走的类,使得整体服务消耗减小
@Component
public class DeptClientServiceFallbackFactory implements
        FallbackFactory<DeptClientService> {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept get(long id) {
                return new Dept().setDeptno(id)
                        .setDname("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
                        .setDb_source("no this database in MySQL");
            }

            @Override
            public List<Dept> list() {
                return null;
            }

            @Override
            public boolean add(Dept dept) {
                return false;
            }
        };
    }
}

在API模块中的业务接口的注解中添加属性:

@FeignClient(value = "PROVIDER",fallbackFactory=DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
    @RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET)
    public Dept get(@PathVariable("id") long id);

    @RequestMapping(value = "/dept/list",method = RequestMethod.GET)
    public List<Dept> list();

    @RequestMapping(value = "/dept/add",method = RequestMethod.POST)
    public boolean add(Dept dept);
}

Feign模块(消费者模块)

feign: 
  hystrix: 
    enabled: true

服务的降级是重整体考虑,当某个服务熔断之后,服务器将不再被调用,客户端可以利用本地的回调,返回缺省值

熔断与服务降级的区别

  • 服务降级:不管在什么情况下,服务降级的流程都是先调用正常的方法,再调用fallback的方法。 也就是服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个友好提示。
  • 服务熔断:假设服务宕机或者在单位时间内调用服务失败的次数过多,即服务降级的次数太多,那么则服务熔断。 并且熔断以后会跳过正常的方法,会直接调用fallback方法,即所谓“服务熔断后不可用”。 类似于家里常见的保险丝,当达到最大服务访问后,会直接拒绝访问,拉闸限电,然后调用服务降级的fallback方法,返回友好提示。

区别

  • 可以理解成熔断使服务降级的一种特殊情况,熔断主要应对雪崩效应的链路自我保护机制
  • 服务降级的流程使先调用正常方法,在调用fallback的方法,而服务熔断是熔断后不再调用正常方法,直接调用fallback方法,即服务熔断后不可用
  • 服务降级是因为请求异常或者错误导致,服务熔断则是因为服务降级的次数太多,那么则服务熔断
  • 触发原因不一样,服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
  • 管理目标层次不一样,服务熔断是一个框架层次的处理,服务降级是业务层次的处理
  • 实现方式不一样,服务熔断一般是自我熔断恢复,服务降级相当于人工控制
  • 触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;

服务熔断是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。而服务降级则是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。

服务熔断是服务降级的一种特殊情况,他是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流量太大,都会触发该服务的服务熔断措施,链路熔断,返回fallback方法。这是对局部的一种保险措施。

Dashboard监控

  1. 首先在消费者的基础上新建一个项目,引入dashboard的依赖,服务提供者也同样需要(服务提供者在Feign项目基础上)
# 消费者
<!--        dashboard-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>

# 服务提供者
<!--        加入actuator监控-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

  1. 在dashboard的启动类上增加注解@EnableHystrixDashboard开启监控

  2. 启动项目,访问dashboard监控页面

    img

根据监控页面的信息ingle Hystrix App: http://hystrix-app:port/actuator/hystrix.stream,我们访问一下需要监控的地址,出现了页面404的问题

添加如下配置:

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

在服务提供者配置后就可以看到这个页面已经可以看到有ping的信息了,但是ping的数据是空的

img

ping的数据为空,原因是因为没有开启熔断机制,把原来Feign的熔断机制@HystrixCommand和启动类上的@EnableCircuitBreaker熔断支持都开起来,访问一下我们的接口就正常了

img

  1. 把需要监控的信息填写到dashboard的页面上就可以成功了

img

对应图形数字颜色等信息

img

注:第二种写法,注册一个Bean,不添加配置

  1. 首先在Hystrix项目上注册一个Bean(actuator/hystrix.stream就是我们需要监控的地址)

    // 增加一个servlet 注册一个Bean
    @Bean
    public ServletRegistrationBean servletRegistrationBean(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
        // 添加信息
        servletRegistrationBean.addUrlMappings("/actuator/hystrix.stream");
        return servletRegistrationBean;
    }
    
    

    一个是在配置文件上添加配置,一个是在新注册一个Bean,都可以实现

Zuul

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

假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?

上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。

而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。

  • zuul是spring cloud中的微服务网关。
    网关: 是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。
  • Zuul是一个微服务网关,首先是一个微服务。也是会在Eureka注册中心中进行服务的注册和发现。也是一个网关,请求应该通过Zuul来进行路由。
  • Zuul网关不是必要的。是推荐使用的。
    使用Zuul,一般在微服务数量较多(多于10个)的时候推荐使用,对服务的管理有严格要求的时候推荐使用,当微服务权限要求严格的时候推荐使用。
img

实现

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
            
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
     </dependency>
    
    
  2. 在启动类添加注解@EnableZuulProxy

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @SpringBootApplication
    @EnableZuulProxy
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
  3. 修改配置文件

    server:
      port: 8000
    spring:
      application:
        name: zuul
    zuul:
      routes:
        # 标识你服务的名字,这里可以自己定义,一般方便和规范来讲还是跟自己服务的名字一样
        EUREKA-CLIENT-PRODUCER:
          #服务映射的路径,通过这路径就可以从外部访问你的服务了,目的是为了不爆露你机器的IP
          #这里zuul是自动依赖hystrix,ribbon的,不是面向单机
          path: /EUREKA-CLIENT-PROVIDER/**
          #这里一定要是你Eureka注册中心的服务的名称,是因为这里配置serviceId因为跟eureka结合了,
          serviceId: EUREKA-CLIENT-PROVIDER
    eureka:
      client:
        service-url:
          defaultZone: http://admin:123456@127.0.0.1:8761/eureka/
    
    

说明

a. 通过zuul访问服务的,URL地址默认格式为:http://zuulHostIp:port/服务名称/服务中的URL

例如:http://127.0.0.1:8000/EUREKA-CLIENT-PROVIDER/get?name=123
b. 服务名称就是配置文件中的spring.application.name。
c. 服务的URL,就是对应的服务对外提供的URL路径

参考链接:

https://blog.csdn.net/qq_42046105/article/details/83793787?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值