【Java】SpringCloud:Eureka、Ribbon、OpenFeign、Hystrix、zuul、 Bus、Gateway

SpringCloud

概述

  1. Spring Cloud是一系列框架的有序集合。
  2. Spring Cloud的本质是在 Spring Boot 的基础上,增加了一堆微服务相关的规范,并对应用上下文 (Application Context )进行了功能增强。
  3. 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
优点缺点
通过服务的原子化拆分,以及微服务的独立打包、部署和升级,小团队的交付周期将缩短,运维成本也将大幅度下降微服务过多,服务治理成本高,不利于系统维护。
微服务遵循单一原则。微服务之间采用Restful等轻量协议传输。分布式系统开发的技术成本高(容错、分布式事务等)。

注意

微服务之间传递参数时 @RequestBody @PathVariable 不能省略!

spring:
  application:
    name: eureka  #微服务项目的名字,会在注册中心中出现

RestTempalte

代码:

消费者代码:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:44
 */
@RestController
@RequestMapping("/conStudent")
public class StudentController {
    @Autowired
    RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;


    /**
     * eureka 获取实例地址方法
     * 
     * @return
     */
    public String serviceUri() {
        // provider-student 服务提供者
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("provider-student");
        if (serviceInstances != null && serviceInstances.size() > 0) {
            return serviceInstances.get(0).getUri() + "/student/";
        }
        return null;
    }


    @GetMapping("/{id}")
    public RespEntity getStuInfo(@PathVariable("id") Integer id) {
        System.out.println("获取一个学生信息");
        //远程调用
        return restTemplate.getForObject(serviceUri() + id,
                RespEntity.class);
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        System.out.println("获取所有学生信息");
        //远程调用
        return restTemplate.getForObject(serviceUri(),
                RespEntity.class);
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println("添加学生");
        System.out.println(student);
        return restTemplate.postForObject(serviceUri(), student, RespEntity.class);
    }
}

提供者代码:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:31
 */
@RestController
@RequestMapping("/student")
public class StudentController {
    @Value("${server.port}")
    private Integer port;

    @GetMapping("/{id}")
    public RespEntity getStudent(@PathVariable("id") Integer id) {
        if (id == 1) {
            return new RespEntity(1, "ok"+port, new Student(1, "test1", 12));
        }
        return new RespEntity(2, "ok"+port, new Student(2, "test2", 11));
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        return new RespEntity(2, "ok"+port, Arrays.asList(
                new Student(1, "test1", 12),
                new Student(2, "test2", 11),
                new Student(3, "test3", 13)
        ));
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println(student);
        return new RespEntity(200, "ok"+port, null);
    }
}

Eureka

概述:

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix中, 实现SpringCloud的服务发现功能。

微服务注册中心

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

常见的注册中心:

组件名语言CAP一致性算法对外暴露接口
EurekaJavaAPHTTP
ConsulGoCPRaftHTTP/DNS
ZookeeperJavaCPPaxos客户端
NacosJavaAPRaftHTTP

CAP:三进二

C:一致性;

A:可用性;

P:分区容错性

在微服务下,分区容错性是必须实现,其一致性和可用性二选一,为了优化用户体验,一般会优先实现可用性,保证服务是可用状态。

基本架构:

Eureka Server :提供服务注册和发现 ;

Eureka Client

Service Provider :服务提供方(将自身服务注册到Eureka ,从而使服务消费方能够找到 )

Service Consumer : 服务消费方 (从Eureka获取注册服务列表,从而能够消费服务)

  1. Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
  2. 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。如果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服 务节点(默认90秒);
  3. 每个Eureka Server同时也是Eureka Client ,多个Eureka Server之间通过复制的方式完成服务注册表的同步;
  4. Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者

自我保护:

  1. 微服务第一次注册成功之后,每30秒会发送一次心跳将服务的实例信息注册到注册中心。通知 Eureka Server该实例仍然存在。
  2. 如果超过90秒没有发送更新,则服务器将从注册信息中将此服务移除。
  3. Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85% ,如果出现低于的情况,Eureka Server会 将当前的实例注册信息保护起来,同时提示这个警告。
  4. 保护模式主要用于一组客户端和Eureka Server 之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务

关闭自我保护功能: eureka.service.enableSelfPreservation=false

服务剔除:

lease-renewal-interval-in-seconds: 服务续约(renew)的间隔, 默认为30秒

lease-expiration-duration-in-seconds: 服务失效时间,默认值90秒

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

服务续约:

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

服务失效:

lease-expiration-duration-in-seconds: 服务失效时间,默认值90秒

代码

pom:

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

yml:

服务端:

server:
  port: 6061
spring:
  application:
    name: eureka #微服务项目的名字,会在注册中心中出现
eureka:
  instance:
    hostname: localhost  #实例名
  client:
#    registerWithEureka: false # 是否将自己挂到注册中心上,单节点可以false,集群true
#    fetchRegistry: false # 是否要去注册获取服务列表
    registerWithEureka: true # 是否将自己挂到注册中心上,单节点可以false,集群true
    fetchRegistry: true # 是否要去注册获取服务列表
    serviceUrl:
      defaultZone: http://localhost:6061/eureka/ #Eureka服务地址
#      defaultZone: http://localhost:6061/eureka/,http://localhost:6062/eureka/ #Eureka服务地址
  server:
    enable-self-preservation: false #禁用自我保护
    #private long evictionIntervalTimerInMs = 60000L;
    #启用删除的默认时间:默认60s启动一次剔除服务,将无效的服务从服务列表删除
    eviction-interval-timer-in-ms: 2000

客户端:

server:
  # 端口
  port: 8081
spring:
  application:
    name: provider-student
eureka:
  client:
    registerWithEureka: true # 是否将自己挂到注册中心上,单节点可以false,集群true
    fetchRegistry: true # 是否要去注册获取服务列表
    serviceUrl:
#      defaultZone: http://localhost:6061/eureka/,http://localhost:6062/eureka/ #Eureka服务地址
      defaultZone: http://localhost:6061/eureka/ #Eureka服务地址
  instance:
    #服务续约时间:客户端每隔30秒发送一个心跳包
    lease-renewal-interval-in-seconds: 1
    #失效时间:  客户端超过90秒没有发送心跳包,服务器就可以认为当前服务下线
    lease-expiration-duration-in-seconds: 2

服务提供者:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:31
 */
@RestController
@RequestMapping("/student")
public class StudentController {
    @Value("${server.port}")
    private Integer port;

    @GetMapping("/{id}")
    public RespEntity getStudent(@PathVariable("id") Integer id) {
        if (id == 1) {
            return new RespEntity(1, "ok"+port, new Student(1, "test1", 12));
        }
        return new RespEntity(2, "ok"+port, new Student(2, "test2", 11));
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        return new RespEntity(2, "ok"+port, Arrays.asList(
                new Student(1, "test1", 12),
                new Student(2, "test2", 11),
                new Student(3, "test3", 13)
        ));
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println(student);
        return new RespEntity(200, "ok"+port, null);
    }
}

服务消费者:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:44
 */
@RestController
@RequestMapping("/conStudent")
public class StudentController {
    @Autowired
    RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;


    /**
     * eureka 获取实例地址方法
     *
     * @return
     */
    public String serviceUri() {
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("provider-student");
        if (serviceInstances != null && serviceInstances.size() > 0) {
            return serviceInstances.get(0).getUri() + "/student/";
        }
        return null;
    }


    @GetMapping("/{id}")
    public RespEntity getStuInfo(@PathVariable("id") Integer id) {
        System.out.println("获取一个学生信息");
        //远程调用
        return restTemplate.getForObject(serviceUri() + id,
                RespEntity.class);
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        System.out.println("获取所有学生信息");
        //远程调用
        return restTemplate.getForObject(serviceUri(),
                RespEntity.class);
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println("添加学生");
        System.out.println(student);
        return restTemplate.postForObject(serviceUri(), student, RespEntity.class);
    }
}

Ribbon

概述:

Ribbon是 Netflix发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。

在 SpringCloud 中,Eureka一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载。

在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务

主要作用:

服务调用负载均衡
基于Ribbon实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进行调用当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址

负载均衡:

两种方式:

服务端客户端
先发送请求到负载均衡服务器或者软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配

负载均衡策略:

com.netflix.loadbalancer.IRule顶级接口
com.netflix.loadbalancer.RoundRobinRule以轮询的方式进行负载均衡。
com.netflix.loadbalancer.RandomRule随机策略
com.netflix.loadbalancer.RetryRule重试策略。
com.netflix.loadbalancer.WeightedResponseTimeRule权重策略。会计算每个服务的权 重,越高的被调用的可能性越大。
com.netflix.loadbalancer.BestAvailableRule最佳策略。遍历所有的服务实例,过滤掉 故障实例,并返回请求数最小的实例返回。
com.netflix.loadbalancer.AvailabilityFilteringRule可用过滤策略。过滤掉故障和请 求

代码:

@Configuration
public class RestTemplateConfiguration {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
@RequestMapping("/consumerstudent")
public class ConsumerStudent {

    @Autowired
    RestTemplate restTemplate;
    
    // 直接使用服务名(provider-student)服务提供者
    public String serviceUrl() {
        return "http://provider-student/student/";
    }
    
    
    @GetMapping("/{id}")
    public RespEntity getStuInfo(@PathVariable("id") Integer id) {
        System.out.println("获取一个学生信息");
        //远程调用
        return restTemplate.getForObject(serviceUri() + id,
                RespEntity.class);
    }
}

OpenFeign

概述:

  1. Feign是Netflix开发的声明式,模板化的HTTP客户端。
  2. Feign可帮助我们更加便捷,优雅的调用HTTP API。
  3. 在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
  4. SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka ,从而让Feign的使用更加方便。

负载均衡:

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

超时控制:

默认Feign客户端只等待1秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了 ,直接返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

可以在ymI文件中开启配置

#微服务调用超时配置
feign:
  client:
    config:
      default:  #default指定对所有的微服务有效,可以指定具体的微服务名
        connectTimeout: 5000
        readTimeout: 5000

日志打印:

配置

#微服务调用超时配置
feign:
  client:
    config:
      default:  #default指定对所有的微服务有效,可以指定具体的微服务名
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full      #OpenFeign输出日志信息
#开启spring日志功能,级别:debug
logging:
  level:
    com.woniuxy.consumer.student.feignclient.StudentFeignClient: debug

代码:

客户端消费者:

pom

        <!--Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

定义声明式接口:

package com.sms.FeignClient;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-27 11:53
 */
@FeignClient("provider-student")
public interface IStudentFeignClient {
    @GetMapping("/student/{id}")
    RespEntity getStudent(@PathVariable("id") Integer id);

    @GetMapping("/student/")
    RespEntity getAllStudent();

    @PostMapping("/student/")
    RespEntity addStudent(@RequestBody Student student);
}

调用接口

package com.sms.controller;

import com.sms.FeignClient.IStudentFeignClient;
import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:44
 */
@RestController
@RequestMapping("/feignstudent")
public class StudentFeignController {
    @Autowired
    IStudentFeignClient iStudentFeignClient;

    @GetMapping("/{id}")
    public RespEntity getStuInfo(@PathVariable("id") Integer id) {
        System.out.println("获取一个学生信息");
        return iStudentFeignClient.getStudent(id);
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        System.out.println("获取所有学生信息");
        return iStudentFeignClient.getAllStudent();
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println("添加学生");
        System.out.println(student);
        return iStudentFeignClient.addStudent(student);
    }
}

客户端提供者:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:31
 */
@RestController
@RequestMapping("/student")
public class StudentController {
    @Value("${server.port}")
    private Integer port;

    @GetMapping("/{id}")
    public RespEntity getStudent(@PathVariable("id") Integer id) {
        if (id == 1) {
            return new RespEntity(1, "ok"+port, new Student(1, "test1", 12));
        }
        return new RespEntity(2, "ok"+port, new Student(2, "test2", 11));
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        return new RespEntity(2, "ok"+port, Arrays.asList(
                new Student(1, "test1", 12),
                new Student(2, "test2", 11),
                new Student(3, "test3", 13)
        ));
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println(student);
        return new RespEntity(200, "ok"+port, null);
    }
}

Hystrix

概述:

Hystrix是一个用于分布式系统的延迟和容错的开源库。

在分布式系统里,许多依赖不可避免的调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。

解决问题:服务雪崩

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sqwieGD-1664275921881)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220927182115524.png)]

服务正常时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hsf6pvum-1664275921882)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220927182124170.png)]

其中一个系统出现问题时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nyqaRm6Z-1664275921883)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220927182128835.png)]
高流情况下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IOrHMLdF-1664275921884)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220927182135143.png)]

主要功能:

服务降级服务熔断服务限流
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback;哪些情况会出发降级:程序运行异常超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示,就是保险丝:服务的降级->进而熔断->恢复调用链路秒杀高并发等操作,严禁一窝蜂的过来拥挤, 大家排队,一秒钟N个,有序进行

熔断是什么?

熔断机制是应对雪崩效应的一种微服务链路保护机制。

当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

当检测到该节点微服务调用响应正常后,恢复调用链路。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtdjvgM5-1664275921884)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220927182851267.png)]

熔断器三种状态?

关闭(Closed):关闭状态(断路器关闭),所有请求都正常访问。

代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。

打开(Open):打开状态(断路器打开),所有请求都会被降级。

Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于20次。

半开(Half Open):半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。

随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则
继续保持打开,再次进行5秒休眠计时。

涉及到断路器的三个重要参数:

  1. 快照时间窗:

    ​ 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒

  2. 请求总数阀值:

    ​ 在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。

  3. 错误百分比阀值:

    ​ 当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

代码:

pom

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

yml

#启用熔断器
feign:
  hystrix:
    enabled: true

启动类

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

定义降级方法

 @HystrixCommand(fallbackMethod = "timeoutfallback")
    @GetMapping("/timeout")
    public ResponseResult timeout() {
        return studentFeignClient.timeout();
    }

    //定义一个降级方法,和对应的方法的方法签名一致(方法名不同,其它都一样)
	// 上面方法出问题时走这个方法
    public ResponseResult timeoutfallback() {
        return new ResponseResult(500, "ttimeout---fallback", null);
    }

降级方法统一配置【局限性较大,不推荐使用】

@DefaultProperties(defaultFallback = "xxxxfallback")
@RestController
@RequestMapping("/consumerstudent")
public class ConsumerStudent {
    
    @HystrixCommand
    @GetMapping("/timeout")
    public ResponseResult timeout() {
        return studentFeignClient.timeout();
    }
    
    @HystrixCommand
    @GetMapping("/timeok")
    public ResponseResult timeok() {
        return studentFeignClient.timeok();
    }
    
    
    public ResponseResult xxxxfallback() {
        return studentFeignClient.timeout();
    }
}

整合OpenFeign

OpenFeign接口指定对应的降级处理类

package com.woniuxy.consumer.student.feignclient;

import com.woniuxy.common.entity.ResponseResult;
import com.woniuxy.common.entity.Student;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author author
 * @create 2022-09-2022/9/27 11:21
 */
@FeignClient( value = "provider-student",fallback = StudentFeignClientFallback.class)
//@RequestMapping("/student")
public interface StudentFeignClient {

    @GetMapping("/student/{sid}")
    public ResponseResult<Student> getStudentBySid(@PathVariable("sid") int sid);

    @GetMapping("/student/")
    public ResponseResult<List<Student>> getAllStudents();


    @PostMapping("/student/")
    public ResponseResult addStudent(Student student);

    @PostMapping("/student/{pageIndex}/{pageSize}")
    public ResponseResult getStudentsPager(
            @PathVariable("pageIndex") int pageIndex,
            @PathVariable("pageSize") int pageSize,
            @RequestBody Student student);

    @GetMapping("/student/timeout")
    public ResponseResult timeout();

    @GetMapping("/student/timeok")
    public ResponseResult timeok();

}

OpenFeign接口的实现类,定义降级业务逻辑

package com.woniuxy.consumer.student.feignclient;

import com.woniuxy.common.entity.ResponseResult;
import com.woniuxy.common.entity.Student;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author author
 * @create 2022-09-2022/9/27 16:32
 */
@Component
public class StudentFeignClientFallback implements StudentFeignClient {
    @Override
    public ResponseResult<Student> getStudentBySid(int sid) {
        return new ResponseResult(500, "getStudentBySid---fallback", null);
    }

    @Override
    public ResponseResult<List<Student>> getAllStudents() {
        return new ResponseResult(500, "getAllStudents---fallback", null);
    }

    @Override
    public ResponseResult addStudent(Student student) {
        return new ResponseResult(500, "addStudent---fallback", null);
    }

    @Override
    public ResponseResult getStudentsPager(int pageIndex, int pageSize, Student student) {
        return new ResponseResult(500, "getStudentsPager---fallback", null);
    }

    @Override
    public ResponseResult timeout() {
        return new ResponseResult(500, "timeout---fallback", null);
    }

    @Override
    public ResponseResult timeok() {
        return new ResponseResult(500, "timeok---fallback", null);
    }
}

Zuul

概述:

网关

作为微服务的统一入口,可以做身份校验

代码:

pom:

       <!--eureka 客户端依赖-->
        <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>

启动类注解:

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

yml:

server:
  port: 9091
spring:
  application:
    name: zuul

#zuul:
#  routes:
#    provider-student:    #路由id,自定义路由名,可以任意,一般使用微服务名
#      path: /student/**   #请求路径
#      url: http://localhost:8081/   #网关转发的真实请求路径
#    consumer-student:    #路由id,自定义路由名,可以任意,一般使用微服务名
#      path: /consumerstudent/**   #请求路径
#      url: http://localhost:7071/   #网关转发的真实请求路径


# 面向服务的路由配置
#zuul:
#  routes:
#    provider-student:    #路由id,自定义路由名,可以任意,一般使用微服务名
#      path: /student/**   #请求路径
#      serviceId: provider-student #通过服务名获取服务地址
#    consumer-student:    #路由id,自定义路由名,可以任意,一般使用微服务名
#      path: /consumerstudent/**   #请求路径
#      serviceId: consumer-student #通过服务名获取服务地址[需要开启eureka注册中心]

#路由id和服务id一致,可以简写 访问时使用服务名直接访问 http://localhost:9091/provider-student/student/1
zuul:
  routes:
    provider-student: /student/**
    consumer-student: /conStudent/**

eureka:
  client:
    registerWithEureka: true # 是否将自己挂到注册中心上,单节点可以false,集群true
    fetchRegistry: true # 是否要去注册获取服务列表
    serviceUrl:
      #      defaultZone: http://localhost:6061/eureka/,http://localhost:6062/eureka/ #Eureka服务地址
      defaultZone: http://localhost:6061/eureka/ #Eureka服务地址

网关过滤:

package com.sms.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.sms.entity.RespEntity;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-29 14:12
 */
@Component
public class LoginFilter extends ZuulFilter {

    /**
     * 过滤器类型:
     * 拦截时机:pre post error routing
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

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

    /**
     * true:执行run();
     * false:放行;
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        System.out.println("shouldFilter--进入");
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String user = request.getParameter("user");
        if (!StringUtils.isEmpty(user)) {
            System.out.println("shouldFilter--放行");
            return false;
        }
        System.out.println("shouldFilter--run");
        return true;

    }

    @Override
    public Object run() throws ZuulException {
        System.out.println("run--进入");
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = request.getParameter("token");
        if (!StringUtils.isEmpty(token)) {
            System.out.println("run--放行");
            return null;
        }
        System.out.println("run--拦截");
        currentContext.setSendZuulResponse(false); // 拦截
		// currentContext.setResponseStatusCode(500); // 返回状态码
        try {
            currentContext.getResponse().setContentType("text/html;charset=utf-8"); // 返回响应体
            currentContext.setResponseBody(new ObjectMapper()
                    .writeValueAsString(new RespEntity(500, "先登录", null)));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }


}

其他:

客户端提供者:

package com.sms.controller;

import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:31
 */
@RefreshScope
@RestController
@RequestMapping("/student")
public class StudentController {
    @Value("${server.port}")
    private Integer port;

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

    @GetMapping("/testconfig")
    public RespEntity testconfig() {

        return new RespEntity(2, "testconfig:"+port, testconfig);
    }

    @GetMapping("/{id}")
    public RespEntity getStudent(@PathVariable("id") Integer id) {
        if (id == 1) {
            return new RespEntity(1, "ok"+port, new Student(1, "test1", 12));
        }
        return new RespEntity(2, "ok"+port, new Student(2, "test2", 11));
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        return new RespEntity(2, "ok"+port, Arrays.asList(
                new Student(1, "test1", 12),
                new Student(2, "test2", 11),
                new Student(3, "test3", 13)
        ));
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println(student);
        return new RespEntity(200, "ok"+port, null);
    }
}

客户端消费者:

package com.sms.controller;

import com.sms.FeignClient.IStudentFeignClient;
import com.sms.entity.RespEntity;
import com.sms.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-23 17:44
 */
@RefreshScope
@RestController
@RequestMapping("/feignstudent")
public class StudentFeignController {
    @Autowired
    IStudentFeignClient iStudentFeignClient;

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


    @GetMapping("/{id}")
    public RespEntity getStuInfo(@PathVariable("id") Integer id) {
        System.out.println("获取一个学生信息");
        return iStudentFeignClient.getStudent(id);
    }

    @GetMapping("/")
    public RespEntity getAllStudent() {
        System.out.println("获取所有学生信息");
        return iStudentFeignClient.getAllStudent();
    }

    @PostMapping("/")
    public RespEntity addStudent(@RequestBody Student student) {
        System.out.println("添加学生");
        System.out.println(student);
        return iStudentFeignClient.addStudent(student);
    }
}

Config

概述:

配置中心

  • 对所有微服务的配置文件中统一管理
  • 配置文件在git上
  • 微服务通过配置中心获取git的地址,就可以加载自己的配置文件
  • 修改配置文件,修改git仓库中的文件

代码:

创建git/gitee管理仓库

server-4041-pom:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <!--config服务端依赖-->
        <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>

server-4041-启动类:

package com.sms;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-29 16:25
 */
@EnableEurekaClient
@EnableConfigServer
@SpringBootApplication
public class ConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }
}

server-4041-yml:

server:
  port: 4041
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          # git仓库地址
          uri: https://gitee.com/sms1234/config.git
eureka:
  client:
    registerWithEureka: true # 是否将自己挂到注册中心上,单节点可以false,集群true
    fetchRegistry: true # 是否要去注册获取服务列表
    serviceUrl:
#      defaultZone: http://localhost:6061/eureka/,http://localhost:6062/eureka/ #Eureka服务地址
      defaultZone: http://localhost:6061/eureka/ #Eureka服务地址

其它微服务的配置文件上传到git:

每个微服务默认的配置文件都是 application.yml
传到git安装规范命名	consumserstudent-dev.yml


命名规范
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties


http://localhost:4041/master/consumerstudent-dev.yml
http://localhost:4041/consumerstudent/dev/master
http://localhost:4041/consumerstudent-dev.yml

配置中心客户端-7071-pom:

        <!--配置中心客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

配置中心客户端-7071-yml:

不需要使用application.yml,使用bootstrap.yml

bootstrap.yml 系统级别的配置文件(添加)
application.yml 用户级别的配置文件(注释,或者删除)

spring:
  cloud:
    config:
      label: master
      name: consumerstudent
      profile: dev
      uri: http://localhost:4041
      #http://localhost:4041/master/consumerstudent-dev.yml

# 暴露刷新头
management:
  endpoints:
    web:
      exposure:
        include: "*"

修改配置文件,不需要重启服务,及时刷新配置文件

pom:

	<!--监控插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

yml:

spring:
  cloud:
    config:
      label: master
      name: consumerstudent
      profile: dev
      uri: http://localhost:4041
      #http://localhost:4041/master/consumerstudent-dev.yml

# 暴露刷新头
management:
  endpoints:
    web:
      exposure:
        include: "*"

controller添加刷新头:

@RefreshScope // 刷新注解
@RestController
@RequestMapping("/feignstudent")
public class StudentFeignController {}

在git/gitee修改配置文件后,发post请求,通知微服务及时刷新,不用重启服务!【需手动刷新】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9anYeffU-1665143060454)(C:\Users\29295\AppData\Roaming\Typora\typora-user-images\image-20220929170537019.png)]

Bus

概述:

消息总线,微服务统一刷新【一般与Config一起使用】

curl -X POST http://localhost:4041/actuator/bus-refresh
只刷新指定服务
curl -X POST http://localhost:4041/actuator/bus-refresh/服务名:端口

代码:

4041-pom:

      <!-- bus-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--监控插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

4041-yml:

  #消息队列
  rabbitmq:
    host: 192.168.247.153
    port: 5672
    username: guest
    password: guest
#设置刷新头
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh

7071/8081:

pom:

        <!-- bus-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--监控插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

yml:

spring:
  cloud:
    config:
      label: master
      name: consumerstudent
      profile: dev
      uri: http://localhost:4041
      #http://localhost:4041/master/consumerstudent-dev.yml
  #消息队列
  rabbitmq:
    host: 192.168.247.153
    port: 5672
    username: guest
    password: guest

# 暴露刷新头
management:
  endpoints:
    web:
      exposure:
        include: "*"

使用命令刷新:

curl -X POST http://localhost:4041/actuator/bus-refresh


Gateway

概述:官网

代码:

pom:

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

yml:

#网关引入注册中心,同样可以通过服务名去注册中心获取服务地址
spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:                       #路由配置
        - id: provider-student      #路由id,名字唯一,建议使用微服务名字
          #          uri: http://localhost:8081  #映射路径
          uri: lb://provider-student
          predicates:                 #断言;判断是否满足指定的条件
            - Path=/student/**         #请求路径
        - id: consumer-student      #路由id,名字唯一,建议使用微服务名字
          #          uri: http://localhost:7071  #映射路径
          uri: lb://consumer-student
#            - AddRequestHeader=h2, v2
#            - AddResponseHeader=test, Blue
          predicates:                 #断言;判断是否满足指定的条件
            - Path=/**         #请求路径
#            - Header=h1, \d+
#            - Cookie=a, ch.p
#            - After=2022-09-30T11:55:19.880+08:00[Asia/Shanghai]
eureka:
  client:
    registerWithEureka: true # 是否将自己挂到注册中心上,单节点可以false,集群true
    fetchRegistry: true # 是否要去注册获取服务列表
    serviceUrl:
      #      defaultZone: http://localhost:6061/eureka/,http://localhost:6062/eureka/ #Eureka服务地址
      defaultZone: http://localhost:6061/eureka/ #Eureka服务地址

server:
  port: 1011


Route Predicate Factories

断言:就是条件

官网地址

GatewayFilter Factories

Authentication 认证 401
Authorization 授权 403

官网地址

自定义全局过滤器

package com.sms.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-30 15:59
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("进入LoginFilter");
        ServerHttpRequest request = exchange.getRequest();
        // request.getParameter()            key--value
        // request.getParameterValues()      key--v1,v2
        // request.getQueryParams().get("user") 获取参数
        if (request.getQueryParams().get("user") == null) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // 返回状态码
            System.out.println("拦截");
            return exchange.getResponse().setComplete();
        }
        System.out.println("放行");
        return chain.filter(exchange);
    }

    /**
     * 级别
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

网关自定义响应体

package com.sms.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sms.entity.RespEntity;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @Author songmingsong
 * @Version V1.0.0
 * @Date 2022-09-30 15:59
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("进入LoginFilter");
        ServerHttpRequest request = exchange.getRequest();
        // request.getParameter()            key--value
        // request.getParameterValues()      key--v1,v2
        // request.getQueryParams().get("user") 获取参数
        if (request.getQueryParams().get("user") == null) {
            System.out.println("拦截");
            // exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // 返回状态码
            // return exchange.getResponse().setComplete();
            // 返回前端统一响应
            return exchange.getResponse().writeWith(Mono.just(result(exchange)));
        }
        System.out.println("放行");
        return chain.filter(exchange);
    }

    /**
     * 过滤器级别
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 封装前端统一返回对象数据
     *
     * @param exchange
     * @return
     */
    public DataBuffer result(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        RespEntity responseResult = new RespEntity(401, "login first", "先登录");
        byte[] bytes = null;
        try {
            bytes = new ObjectMapper()
                    .writeValueAsString(responseResult).getBytes(StandardCharsets.UTF_8);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        return dataBuffer;
    }
}

跨域处理

概述:

官网地址

代码:

yml:

spring:
  application:
    name: gateway
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true   #option跨域
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTION
            allowedHeaders: "*"
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值