SpringCloud入门 (Hystrix、Rabbin、OpenFeign等)

简单的微服务

未使用框架的微服务

我们通过7071端口去调8081端口的服务

8081

/**
 * @author acoffee
 * @create 2022-01-19 17:24
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @RequestMapping("/findAll")
    public ResponseEntity<List<User>> findAll(){
        List<User> users = userService.findAll();
        if (users != null){
            return new ResponseEntity<>(20000,"查询成功",users);
        }else {
            return new ResponseEntity<>(50000,"查询失败",null);
        }
    }

package com.acoffee.springcloud.provider.mapper;

import com.acoffee.springcloud.common.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author acoffee
 * @create 2022-01-19 17:22
 */
@Mapper
@Repository
public interface UserMapper {
    User findById(int id);

    List<User> findAll();

    boolean addUser(User user);

    boolean updateUser(User user);

    boolean deleteUser(int id);
}

7071

配置文件

@Configuration
public class RestTemplateConfiguration {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {

    @Autowired
    RestTemplate restTemplate;

    String url = "http://localhost:8081/user/";

    @RequestMapping("/getUserById/{id}")
    public ResponseEntity<User> getUserById(@PathVariable int id){
        //restTemplate的API实现get远程调用
        return restTemplate.getForObject(url+"/findById/"+id,ResponseEntity.class);
    }

    @GetMapping("/")
    public ResponseEntity<List<User>> finAll(){
        return restTemplate.getForObject(url+"/findAll",ResponseEntity.class);
    }

    @PostMapping("/addUser")
    public ResponseEntity<Boolean> addUser(User user){
        return restTemplate.postForObject(url+"/addUser",user,ResponseEntity.class);
    }
}

8081中要接收7071来的对象必须要写@RequestBody
在这里插入图片描述

执行结果:
在这里插入图片描述

服务注册Eureka基础

微服务的注册中心

注册中心可以说是微服务架构中的通讯录,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。

常见的注册中心

组件名语言CAP对外暴露接口
EurekaJavaAPHTTP
ConsulGoCPHTTP/DNS
ZookeeperJavaCP客户端
NacosJavaAPHTTP

Eureka的概述

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix中, 实现SpringCloud的服务发现功能。
image-20220120105818491
Eureka包含两个组件:Eureka ServerEureka Client ,它们的作用如下:
Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;
Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka ClientEureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;

6061搭建Eureka服务端

依赖(注意与Springboot的版本兼容问题)

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

配置文件

server:
  port: 6061
eureka:
  instance:
    hostname: localhost #配置实例的名字
  client:
    #是否获取服务器上其他服务的注册信息,默认为true,Eureka客户端就用true,
    #Eureka服务端:集群一定要用true,单机版可用false
    fetch-registry: false
    #是否将自己的信息注册到服务器上
    register-with-eureka: false
    service-url:
      #访问的Eureka服务器的地址
      defaultZone: http://localhost:6061/eureka

启动类

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplicaiotn6061 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplicaiotn6061.class,args);
    }
}

8081服务的提供者 (注册信息到注册中心)

依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件

server:
  port: 8081
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test3
    username: root
    password: 7777
  application:
    name: provider-user #注册到注册中心的名字
mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.acoffee.springcloud.common.entity
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:6061/eureka

启动类启用

@EnableEurekaClient
@SpringBootApplication
public class UserProviderApplication8081 {
    public static void main(String[] args) {
        SpringApplication.run(UserProviderApplication8081.class,args);
    }
}

这个时候启动两个服务就可以看到8081已经注册进去了
在这里插入图片描述

7071服务的消费者 (到注册中心获取服务列表)

依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件

server:
  port: 7071

spring:
  application:
    name: provider-consumer
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:6061/eureka

7071使用服务发现通过注册中心获取服务调用地址

@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    DiscoveryClient discoveryClient;

    //目前使用的调用方式是:RestTemplate+注册中心
    String getUrl(){
        List<ServiceInstance> instances = discoveryClient.getInstances("provider-user");
        ServiceInstance serviceInstance = instances.get(0);
        return  "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/";
    }

    @GetMapping("/")
    public ResponseEntity<List<User>> finAll(){
        return restTemplate.getForObject(getUrl()+"/findAll",ResponseEntity.class);
    }
}

一般先启动eureka服务端(先启动客户端得不到连接有可能会报错),同时启动三个服务
在这里插入图片描述
在这里插入图片描述

Eureka集群

Eureka(由瑞卡) 是一个服务治理的模块,主要的功能是服务注册与发现,管理每个服务与服务之间的依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,Eureka包含两个组件:Eureka Server 和 Eureka ClientEureka Server提供服务注册服务,各个微服务节点通过配置启动后,会在Eureka Server中进行注册,服务结点的信息可以在服务界面上看到,Eureka Client又包含消费者和提供者,提供者提供对应的微服务,消费者消费相应的微服务。

Eureka Client会定时连接 Eureka Server ,获取注册表中的信息并缓存到本地。微服务在消费远程API时总是使用本地缓存中的数 据。因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。但如果Eureka Server宕机时,某些微服务也出现了不可用的情况,Eureka Server中的缓存若不被刷新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用。因此,在生成环境中,通常会部署一个高可用的Eureka Server集群。

Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增 量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行为。

7071 和 8081 都挂在6061上

server:
  port: 6061
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
        defaultZone: http://localhost:6062/eureka
spring:
  application:
    name: eureka-6061

server:
  port: 6062
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
        defaultZone: http://localhost:6061/eureka

spring:
  application:
    name: eureka-6062
    
#info可以配置微服务信息

在这里插入图片描述

在这里插入图片描述

Eureka自我保护机制

在这里插入图片描述
一般情况下,我们会选择在 开发环境下关闭自我保护机制,而在生产环境下启动自我保护机制。

eureka:
  server:
    enable-self-preservation: false #禁用自我保护

就是你的注册的服务就算停了,注册在Eureka 的信息仍然不会丢失。 而是保存一段时间。

其实我们用 Eureka 作为注册中心,注册过后会建立心跳,保持通信状态。 而自我保护机制,就是为了避免因为网络波动造成误判,直接把服务剔除,两个连接暂时断开连接了,虽然不能通信,但是其实有可能 服务还在,那么 Eureka 就多等上一点时间,默认就是 90 秒。

其实 Eureka 是一种CAP 中的 AP 的设计思想。A是高可用,P是分区容错性。

启动自动保护机制的条件

网上大多数都是说统计心跳失败的比例在15分钟之内是否低于85% ,如果出现低于的情况,Eureka Server 会将当前的实例注册信息保护起来,这个说法并不是那么准确,很容易误解。

它会先去判断配置自我保护机制没有:
未配置自我保护机制:
客户端每30s续约(发送心跳包)一次,服务端(注册中心)每 60s 扫描一次超过 90s 还没有续约的客户端,就会剔除。
如果配置了自我保护机制:
它会去比对上一分钟收到的心跳数 (实际续约数) 是否小于期望的心跳数 (续约阈值,默认值是期望心跳的85%),如果小于期望值就会开启自我保护机制,触发自我保护机制后,服务端不会剔除任何客户端,它还会每60s执行一次检查,直到发现网络正常,就会自动退出自我保护,此时超过90s还没有续约的客户端才会被剔除掉。

而刷新阈值有三种情况:
① 新服务注册的时候
② 注销服务时
③ 每15分钟计算一次阈值(定时任务)。

这是启动自我保护机制的核心代码

public boolean isLeaseExpirationEnabled() {
    if (!isSelfPreservationModeEnabled()) {
        // The self preservation mode is disabled, hence allowing the instances to expire.
        return true;
    }
    return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}

在这里插入图片描述

我这里有 4 个注册实例,保护系数:0.85,心跳频率:30秒(每分钟两次),计算公式如下:

最小心跳阈值计算公式= 服务总数*60/客户端的心跳间隔)* 自我保护续约百分比阀值因子
Renews threshold = 4 * 2 * 0.85 = 6.8(取整为:6Renews (last min) = 4 * 2 = 8

比如我们现在的扩容因子是 6,而最后一分钟收到的是8,所以不会开启自我保护机制。

续约阀值的更新的三种条件

1.新服务注册进来时(register)(这里省去了register()中其他逻辑)

synchronized(this.lock) {
   if (this.expectedNumberOfClientsSendingRenews > 0) {
       ++this.expectedNumberOfClientsSendingRenews;
       this.updateRenewsPerMinThreshold();
   }
}

当有新服务(实例)注册进来时,expectedNumberOfClientsSendingRenews会增加,然后触发updateRenewsPerMinThreshold()更新threshold

2.注销服务时(cancel)

public boolean cancel(String appName, String id, boolean isReplication) {
    if (super.cancel(appName, id, isReplication)) {
        this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Cancel, appName, id, (InstanceInfo)null, (InstanceStatus)null, isReplication);
        synchronized(this.lock) {
            if (this.expectedNumberOfClientsSendingRenews > 0) {
                --this.expectedNumberOfClientsSendingRenews;
                this.updateRenewsPerMinThreshold();
            }

            return true;
        }
    } else {
        return false;
    }
}

注销时,expectedNumberOfClientsSendingRenews会减少,然后触发updateRenewsPerMinThreshold()更新threshold

3.Task定时任务(默认15分钟)

private void scheduleRenewalThresholdUpdateTask() {
    this.timer.schedule(new TimerTask() {
        public void run() {
            PeerAwareInstanceRegistryImpl.this.updateRenewalThreshold();
        }
    }, (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs(), (long)this.serverConfig.getRenewalThresholdUpdateIntervalMs());
}

服务剔除测试

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求) 。告诉EurekaServer: “我还活着"。这个我们称为服务的续约(renew) ;

有两个重要参数可以修改服务续约的行为;可以在8081 中添加如下配置项: .

lease-renewal-interval-in-seconds: 服务续约(renew)的间隔, 默认为30秒
lease-expiration-duration-in-seconds: 服务失效时间,默认值90秒

也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳, 证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer.in-ms设定的时间,默认60秒) 从服务列表中移除,这两个值在生产环境不要修改,默认即可。

改变6061的配置文件 (将自我保护机制关掉)

server:
  port: 6061
eureka:
  instance:
    hostname: localhost
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
        defaultZone: http://localhost:6062/eureka
  server:
  	#关闭自我检查
    enable-self-preservation: false
    #服务器剔除服务的时间,默认每过60秒启动启动检查
    eviction-interval-timer-in-ms: 2000
spring:
  application:
    name: eureka-6061

修改7071配置文件

server:
  port: 7071

spring:
  application:
    name: provider-consumer
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:6061/eureka
  instance:
    #客户端端默认每隔30s向服务器发送心跳包
    lease-renewal-interval-in-seconds: 1
    #客户端默认连续90s没有向服务器发送心跳包,服务端认为当前服务器宕机
    lease-expiration-duration-in-seconds: 2

在这里插入图片描述
手动关闭7071,一秒之后之后直接剔除7071服务
在这里插入图片描述

Ribbon搭建

RibbonNetflix发布的一个负载均衡器,有助于控制 HTTPTCP客户端行为。在 SpringCloud 中, Eureka一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载。
SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务

Ribbon的主要作用

服务调用
基于Ribbon实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进行调用

负载均衡
当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址

依赖我们上面添加的依赖中以及存在Ribbon 的依赖
在这里插入图片描述
RestTemplate 配置类中添加注解

@Configuration
public class RestTemplateConfiguration {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
//使用Ribbon调用不要服务发现,虽然用了也不报错
//@EnableDiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerStudent7071Application.class,args);
    }
}

String getUrl() {
    //Ribbon的本质就是restTemplate+@LoadBlanced,自动去注册中心通过名字获取调用地址
    return "http://provider-user/user/";
}

查询无误
在这里插入图片描述
客户端负载均衡与服务端负载均衡
服务端负载均衡 (Nginx集中式)
先发送请求到负载均衡服务器或者软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访 问;即在服务器端再进行负载均衡算法分配

客户端负载均衡 (Ribbon进程内)
客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配

Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为com.netflix.loadbalancer.IRule ,实现方式如下

参数介绍
com.netflix.loadbalancer.RoundRobinRule以轮询的方式进行负载均衡。(默认)
com.netflix.loadbalancer.RandomRule随机策略
com.netflix.loadbalancer.RetryRule重试策略。
com.netflix.loadbalancer.WeightedResponseTimeRule权重策略。会计算每个服务的权重,越高的被调用的可能性越大。
com.netflix.loadbalancer.BestAvailableRulecom.netflix.loadbalancer.BestAvailableRule
com.netflix.loadbalancer.AvailabilityFilteringRule可用过滤策略。过滤掉故障

OpenFeign

OpenFeign文档

OpenFeign简介

FeignNetflix开发的声明式,模板化的HTTP客户端。一种调用微服的规范。
Feign可帮助我们更加便捷,优雅的调用HTTP API
SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
SpringCloudFeign进行了增强,使Feign支持了SpringMVC注解,并整合了RibbonEureka ,从而让Feign的使用更加方便。

OpenFeign搭建

<!--OpenFeign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

7071

//定义接口,声明Feign调用,指定要调用的服务名
//接口中定义的方法,就是要调用的8081中的方法和请求路径
@FeignClient("provider-user")
@RequestMapping("/user")
public interface UserFeignClient {
	//使用OpenFeign如果要调用@PathVariable必须有value属性
    @GetMapping("/timeout")
    public ResponseEntity<String> timeout();

    @GetMapping("/ok")
    public ResponseEntity<String> ok();
}

@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {

    @Autowired
    UserFeignClient userFeignClient;
  
    @GetMapping("/timeout")
    public ResponseEntity<String> timeout(){
        return userFeignClient.timeout();
    }

    @GetMapping("/ok")
    public ResponseEntity<String> ok(){
        return userFeignClient.ok();
    }
}

启动类

@EnableFeignClients //扫描Feign
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerStudent7071Application.class,args);
    }
}

OpenFeign负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,也不需要再注册RestTemplate 对象。

OpenFeign日志打印功能

Feign提供了日志打印功能,我们可以通过配来调整日志级别,从而了解Feign中Http请求的细节。

logging.level.project.user.UserClient: DEBUG
日志级别介绍
NONE【性能最佳,适用于生产】:不记录任何日志(默认值)
BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间
HEADERS记录BASIC级别的基础上,记录请求和响应的header。
FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。

OpenFeign超时控制

默认Feign客户端只等待1秒钟,我们模拟服务端处理需要超过1秒钟,导致Feign客户端不想等待了 ,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制或者使用降级方法(后面介绍)。

8081定义服务,7071通过OpenFeign调用
8081

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/timeout")
    public ResponseEntity<String> timeout() {
    	//模拟超时(异常)
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ResponseEntity<>(2000, "ok", "timeout");
    }
    
    @GetMapping("/ok")
    public ResponseEntity<String> ok() {
        return new ResponseEntity<>(2000, "ok", "timeout");
    }
}

在这里插入图片描述

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

在这里插入图片描述

Hystrix

服务雪崩

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者"的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

当这个超时(模拟异常)的服务没有配置延时设置并且访问量较大时,就有可能导致其他没有异常的服务也出现异常,就像多米诺骨牌一样导致其他大量服务也异常,即服务雪崩

对7071的timeout(模拟的异常)进行压测,我们发现ok(正常的方法)方法也异常
在这里插入图片描述

在这里插入图片描述
为了防止服务雪崩,hystrix提供了基于断路器的服务熔断机制

Hystrix

Hystrix (黑丝拽克斯) 是一个用于分布式系统的延迟和容错的开源库。在分布式系统里,许多依赖不可避免的调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。

服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback哪些情况会出发降级:程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级

服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示,就是保险丝:服务的降级 → 进而熔断 → 恢复调用链路

服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤, 大家排队,一秒钟N个,有序进行

服务降级熔断模拟测试

timeout异常,ok也异常,就算我们给timeout添加异常处理,timeout并发高,正常的服务ok方法也会被拖慢或者报异常。

所以这个时候我们可以配置断路器

配置断路器

<!--Hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

配置文件

feign:
  hystrix:
    enabled: true

启动类

//启用Hystrix
@EnableHystrix
//启用OpenFeign
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class ConsumerStudent7071Application {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerStudent7071Application.class,args);
    }
}

定义降级方法

    //定义调用当前方法如果出现异常,执行降级方法,降级方法
    //和原来的方法的方法签名一致(访问修饰符、返回值、参数列表)
    @HystrixCommand(fallbackMethod = "timeoutFallback")
    @GetMapping("/timeout")
    public ResponseEntity<String> timeout(){
        System.out.println("进入timeout");
        return userFeignClient.timeout();
    }

    //定义降级方法
    public ResponseEntity<String> timeoutFallback(){
        System.out.println("进入timeoutFallback");
        return new ResponseEntity<String>(50000,"fallback","timeout-allback");
    }

也可以定义统一降级方法,在类上使用使用@DefaultProperties注解然后直接在方法上使用@HystrixCommand即可,当时这种写法方法签名统一不好满足,所以我们一般不使用

@DefaultProperties(defaultFallback = "ConsumerUserFallback")
@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
public interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

public class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
在这里插入图片描述
通俗来说,服务降级就是请求某个服务执行方法,出现问题,执行降级方法,当失败的调用到一定的阈值 (默认是5秒内20次调用失败) 就会触发熔断机制,一旦触发了熔断机制,请求就不会去执行映射方法了,会直接去执行降级方法。熔断机制的注解是 @HystrixCommand

熔断状态机3个状态:

状态介绍
Closed关闭状态,所有请求都正常访问。
Open打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断, 断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
Half Open半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完全关闭断路器,否则继续保持打开,再次进行休眠计时

通过配置修改熔断策略:

circuitBreaker.requestVolumeThreshold=10 //触发熔断的最小请求次数,默认20
circuitBreaker.sleepWindowInMilliseconds=10000 //休眠时长,默认是5000毫秒
circuitBreaker.errorThresholdPercentage=50 //触发熔断的失败请求最小占比,默认50%

在这里插入图片描述

HystrixDashboard(图形化的监控平台)

Hystrix图形化界面的监控平台,底层要使用SpringBoot监控插件支持

创建监控平台的项目5051

依赖

<dependencies>
	<!--添加监控依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
    </dependency>
</dependencies>

配置端口

server:
  port: 5051
hystrix:
 dashboard:
   proxy-stream-allow-list: "localhost"

启动类

@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboard5051Application {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboard5051Application.class,args);
    }
}

7071中配置类

@Bean
public ServletRegistrationBean getServlet() {
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean =
            new ServletRegistrationBean( streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream" );
    registrationBean.setName("HystrixMetricsStreamServlet" );
    return registrationBean;
}

页面上面的几个参数局域

最上面的输入框: 输入上面所说的三种监控方式的地址,用于访问具体的监控信息页面。
Delay: 该参数用来控制服务器上轮询监控信息的延迟时间,默认2000毫秒。
Title: 该参数对应头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的Url。

在这里插入图片描述
输入http://localhost:7071/hystrix.stream
在这里插入图片描述
hystrix-dashboard页面一直处于加载状态,这里需要给dashboard监控的服务发送一个请求,该请求是所监控的服务的开启了熔断的端点,也就是该请求里面有调用加了@HystrixCommand注解的方法,然后就可以看到数据了。
在这里插入图片描述
在这里插入图片描述
1、圆点:微服务的健康状态,颜色有绿色、黄色、橙色、红色,健康状态依次降低
2、线条:流量变化
3、请求的方法
4、成功请求(绿色)
5、短路请求(蓝色)
6、坏请求(青色)
7、超时请求(黄色)
8、被拒绝的请求(紫色)
9、失败请求(红色)
10、最近10秒钟内请求错误的百分比
11、请求频率
12、熔断器状态
13、数据延迟统计
14、线程池 .

Zuul

ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器。Spring Cloud对Zuul进行了整合和增强。

搭建Zuul网关服务器基本环境

<dependencies>
      <!--网关依赖-->
      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      </dependency>
  </dependencies>

启动类启用

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class Zuul9091Application {
    public static void main(String[] args){
        SpringApplication.run(Zuul9091Application.class,args);
    }
}

配置文件

@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class Zuul9091Application {
    public static void main(String[] args){
        SpringApplication.run(Zuul9091Application.class,args);
    }
}

在这里插入图片描述

面向服务的路由

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

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

配置文件

server:
  port: 9091

spring:
  application:
    name: zuul

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:6061/eureka
#网关配置
zuul:
  routes: #路由配置
    provider-user: #路由ID,可以任意,建议使用微服名
      path: /user/** #请求路径
      url: provider-user #服务名
    consumer-user:
      path: /consumeruser/**
      url: provider-consumer

执行结果:
在这里插入图片描述

简化的路由配置

#面向路由的简化写法,必须使用服务名
zuul:
  routes: #路由配置
    provider-student:  /student/**
    consumer-student:  /consumerstudent/**
#http://localhost:9091/student/student/1
#只要Zuul获取Eureka的服务信息,可以什么都不配置,默认支持通过服务名访问
#http://localhost:9091/provider-student/student/1

Zuul中的过滤器

Zuul它包含了两个核心功能:对请求的路由过滤

过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入ZuulHTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。

Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。

类型说明
PRE这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClientNetfilx Ribbon请求微服务。
POST这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR在其他阶段发生错误时执行该过滤器

在这里插入图片描述

有name参数,就是合法用户,不拦截,放行
http://localhost:9091/user/user/1?name=zs

没有name参数,执行拦截,判断有没有token,有token放行
http://localhost:9091/user/user/1?token=zs
/**
 * Zuul过滤器
 */
@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 拦截类型:拦截器在什么时候执行
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 拦截器的执行顺序:越小优先级高
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否拦截:true:拦截,执行run
     * false:不拦截,不执行run
     * @return
     */
    @Override
    public boolean shouldFilter() {
        //获取请求上下文环境
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String name = request.getParameter("name");
        if(!StringUtils.isEmpty(name)){
            System.out.println("有name不拦截,直接放行");
            return false;
        }
        return true;
    }

    /**
     * 拦截器执行的代码
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        System.out.println("没有name,执行拦截方法,判断token");
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = request.getParameter("token");
        if(StringUtils.isEmpty(token)){
            System.out.println("没有token,拦截,不能访问");
            currentContext.setSendZuulResponse(false);
            //currentContext.setResponseStatusCode(500);
            try {
                //currentContext.getResponse().getWriter().write("");
                currentContext.setResponseBody(new ObjectMapper().writeValueAsString(
                        new ResponseResult<>(401,"authentication","login first")));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println("有token,放行");
        }

        return null;
    }
}

配置中心

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要有配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题。统一管理微服项目所有模块的配置文件,配置文件存储在git仓库,微服连接到配置中心,配置中心连接到git获取真正的配置文件

在这里插入图片描述

配置中心搭建

① 创建git远程仓库
② 依赖

<dependencies>
    <!--配置中心服务端依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

③ yml配置

server:
  port: 4041

eureka:
  client:
    service-url:
      defaultZone: http://localhost:6061/eureka
#配置连接到git
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/acoffee/config #git仓库远程地址
          search-paths: /**   #搜索路径
      label: master #指定分支

④ 启动类

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class Config4041Application {
    public static void main(String[] args) {
        SpringApplication.run(Config4041Application.class, args);
    }
}

⑤ 将7071的配置文件application.yml改名为consumeruser-dev.yml,传到git,删除7071的配置文件
在这里插入图片描述
命名规范

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

⑥ 测试通过配置中心获取远程仓库的配置文件http://localhost:4041/master/consumeruser-dev.yml 结果如下能访问到
在这里插入图片描述
7071controller中将配置文件中的testconfig 的值读出来

@Value("${testconfig}")
String testconfig;

@GetMapping("/testConfig")
public ResponseEntity<String> testConfig(){
    System.out.println("进入timeoutFallback");
    return new ResponseEntity<String>(20000,"testconfig",testconfig);
}

访问7071controller
在这里插入图片描述
7071添加config客户端依赖

<!--添加config客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

⑧ 7071添加配置文件bootstrap.yml
bootstrap.yml就是SpringBoot支持的配置文件,优先级高于application.yml,指定连接配置中心的地址

#连接到4041配置中心
spring:
  cloud:
    config:
      label: master
      name: consumerstudent
      profile: dev
      uri: http://localhost:4041
      #http://localhost:4041/master/consumeruser-dev.yml

启动6061、4041、7071三个项目

⑨ 修改git中的配置文件
在这里插入图片描述
通过注册中心去看已经更改
在这里插入图片描述
重启7071刷新配置我们就可以通过7071controller去访问,发现也已经更改
在这里插入图片描述

自动刷新

上面这种方式只能重启项目,我们可以不重启项目自动刷新配置,不用重启项目,发送post请求,修改的配置文件生效(需要安装curl)

<!--添加监控依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

添加一个刷新监控点,就是一个请求路径的名字,我们通过发送post请求,底层自动刷新配置

7071Controller添加@RefreshScope注解

//添加自动刷新注解在Controller上
@RefreshScope
@RestController
@RequestMapping("/consumeruser")
public class ConsumerUserController {
......

修改了远程仓库的配置文件,不需要重启服务器,发送POST请求,自动刷新,修改testconfig的值
在这里插入图片描述
输入curl -XPOST "http://localhost:7071/actuator/refresh"
在这里插入图片描述
在这里插入图片描述

BUS消息总线

但是上面的方式只能一个一个的刷新,我们可以使用BUS消息总线一次性刷新,它底层使用消息队列,统一刷新所有微服务的配置,本质就是将他们放到mq队列中处理
在这里插入图片描述

4041,7071,8081添加:bus依赖,actuator依赖,添加消息队列的连接配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

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

配置文件
7071

#连接到4041配置中心
spring:
  cloud:
    config:
      label: master
      name: consumeruser
      profile: dev
      uri: http://localhost:4041
  rabbitmq:
    host: 192.168.195.157
    port: 5672
    username: guest
    password: guest

#POST请求的监控点
management:
  endpoints:
    web:
      exposure:
        include: "refresh"

8081

#连接到4041配置中心
spring:
  cloud:
    config:
      label: master
      name: provideruser
      profile: dev
      uri: http://localhost:4041
  rabbitmq:
    host: 192.168.195.157
    port: 5672
    username: guest
    password: guest

#POST请求的监控点
management:
  endpoints:
    web:
      exposure:
        include: "refresh"

4041

server:
  port: 4041

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:6061/eureka

#连接到4041配置中心
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/acoffee/config.git
          search-paths: /**
      label: master
  rabbitmq:
    host: 192.168.195.157
    port: 5672
    username: guest
    password: guest
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh

修改
在这里插入图片描述
在这里插入图片描述
通过curl -XPOST "http://localhost:4041/actuator/bus-refresh刷新,上面的 bus-refresh 就是actuator的刷新机制,相当于提供了一个 /bus-refreshpost方法,当我们调用的时候,执行完成后,配置中心会通过BUS消息总线,发送到所有的客户端,并完成配置的刷新操作。完成了一次修改,广播通知,处处生效的效果

在这里插入图片描述
使用BUS可以指定只刷新一个服务

curl -XPOST "http://localhost:4041/actuator/bus-refresh/服务名:端口号"
curl -XPOST "http://localhost:4041/actuator/bus-refresh/consumer-student:7071"

刷新过后我们看到两个都刷新了
在这里插入图片描述
在这里插入图片描述
我们上面是直接去4041端口去访问的配置文件,我们也可以统一去注册中心访问:

只需要在7071和8081中加一个eureka配置,这个是将其注册到mq中去

#连接到4041配置中心
spring:
  cloud:
    config:
      label: master
      name: consumeruser
      profile: dev
      #uri: http://localhost:4041
      #http://localhost:4041/master/consumerstudent-dev.yml
      uri: http://localhost:6061  #连接注册中心,从注册中心通过config的服务名获取config的地址这个配置文件无法获取注册中心,还要写上注册中心的配置信息
      discovery:
        enabled: true
        service-id: config

  rabbitmq:
    host: 192.168.195.157
    port: 5672
    username: guest
    password: guest

#POST请求的监控点
management:
  endpoints:
    web:
      exposure:
        include: "refresh"
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:6061/eureka

本质上没有什么区别,一个是直接通过4041端口去git中找到对应的配置文件,另外一个是在eureka注册中心中找到4041对应的微服务结点(service-id: config),然后再到git中去找,第二种就多饶了一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值