Spring Cloud Netflix 学习笔记

Spring Cloud Netflix 学习笔记

1.配置maven父工程

  • 新建 maven 项目,删除其余文件及文件夹仅留下pom.xml

  • pom.xml作下列配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hikw</groupId>
    <artifactId>springcloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>springcloud</name>
    <description>This is the parent project of springcloud</description>

    <!--配置打包格式-->
    <packaging>pom</packaging>

    <!--配置springboot全局版本管理-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
    </parent>

    <!--版本控制-->
    <properties>
        <spring-cloud.version>2020.0.3</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!--配置springcloud全局版本管理-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!--配置打包工具-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>
                        true
                    </fork>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <!--配置阿里云镜像-->
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

2.服务注册与发现

2.1 Eureka

​ Eureka 是 Netflix 旗下的核心模块之一,是基于REST的服务。服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的唯一标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于 Dubbo 的注册中心,比如 Zookeeper 等等。

2.1.1 EurekaServer模块配置
  • 引入 EurekaServer 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • 编写配置文件
# 指定端口号
server.port=8081
# 指定服务名(唯一标识)
spring.application.name=eurekaserver
# 注册中心服务地址
eureka.instance.hostname=127.0.0.1
# 暴露注册中心地址(若是集群部署则逗号隔开多个eurekaserver的url即可)
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
# 是否从eurekaserver获取注册的服务信息(表示自己是一个服务端)
eureka.client.fetch-registry=false
# 是否将自己注册到eurekaserver(表示关闭客户端功能)
eureka.client.register-with-eureka=false
# 其他配置 #
# 是否开启自我保护机制
# eureka.server.enable-self-preservation=true
#自我保护系数(默认0.85)
# eureka.server.renewal-percent-threshold=0.85
# 扫描失效服务时间间隔(单位毫秒,默认60*1000)60秒
# eureka.server.eviction-interval-timer-in-ms=60000
# 设置eureka server同步失败的等待时间,默认5分钟,在这期间它不向客户端提供服务注册信息
# eureka.server.wait-time-in-ms-when-sync-empty=5
# 设置eureka server同步失败的重试次数,默认为5次
# eureka.server.number-of-replication-retries=5
  • 启动类添加@EnableEurekaServer注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaserverApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaserverApplication.class, args);
    }

}
  • 启动EurekaServer模块访问http://127.0.0.1:8081即可
2.1.2 EurekaClient模块配置
  • 引入 EurekaClient 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 编写配置文件
# 指定端口号
server.port=8500
# 指定应用名(唯一标识)
spring.application.name=eurekaclient
# 修改描述信息
eureka.instance.instance-id=eurekaclient
# 指定注册中心地址(若是集群部署则逗号隔开多个eurekaserver的url即可)
eureka.client.service-url.defaultZone=http://127.0.0.1:8081/eureka
  • 启动类添加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient
public class EurekaclientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaclientApplication.class, args);
    }

}
  • 启动EurekaClient模块访问http://127.0.0.1:8081即可发现EurekaClient服务已经注册进EurekaServer
2.1.3 Eureka集群环境配置

注意:我这里采用本地模拟集群,需先将C:\Windows\System32\drivers\etc\hosts目录下的host文件中添加下列内容:

127.0.0.1    hikw01.com
127.0.0.1    hikw02.com
127.0.0.1    hikw03.com
127.0.0.1    hikw04.com
  • 配置3个集群

主要修改端口号主机名,再将多个主机同时设为注册中心地址

主机1

server.port=8081
spring.application.name=eurekaserver
eureka.instance.hostname=hikw01.com
eureka.client.service-url.defaultZone=http://hikw01.com:8081/eureka/,http://hikw02.com:8082/eureka/,http://hikw03.com:8083/eureka/
eureka.client.fetch-registry=false
eureka.client.register-with-eureka=false

主机2

server.port=8082
spring.application.name=eurekaserver
eureka.instance.hostname=hikw02.com
eureka.client.service-url.defaultZone=http://hikw01.com:8081/eureka/,http://hikw02.com:8082/eureka/,http://hikw03.com:8083/eureka/
eureka.client.fetch-registry=false
eureka.client.register-with-eureka=false

主机3

server.port=8083
spring.application.name=eurekaserver
eureka.instance.hostname=hikw03.com
eureka.client.service-url.defaultZone=http://hikw01.com:8081/eureka/,http://hikw02.com:8082/eureka/,http://hikw03.com:8083/eureka/
eureka.client.fetch-registry=false
eureka.client.register-with-eureka=false
  • 配置客户端
server.port=8888
spring.application.name=eurekaclient
eureka.instance.instance-id=eurekaclient
eureka.client.service-url.defaultZone=http://hikw01.com:8081/eureka/,http://hikw02.com:8082/eureka/,http://hikw03.com:8083/eureka/
  • 最后先启动三个集群在启动客户端即可

2.2 Consul

1.Consul官网下载 consul 客户端

2.cmd到文件目录后执行命令consul.exe agent -dev

3.浏览器输入http://localhost:8500即可

注意:由于 consul 本身就是一个服务注册中心,所以只需要专注开发 ConsulClient 就可以了。

2.2.1 ConsulClient模块配置
  • 引入 consulclient 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

<!--consul健康检查依赖  若不引用则无法获取到服务状态-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 配置 consul 客户端
# 指定端口号
server.port=9000
# 指定应用名(唯一标识)
spring.application.name=consulclient
# 指定consul主机
spring.cloud.consul.host=127.0.0.1
# 指定consul端口
spring.cloud.consul.port=8500
# 指定注册的服务名(默认是spring应用名)
spring.cloud.consul.discovery.service-name=${spring.application.name}
# 关闭健康检查(不推荐)
#spring.cloud.consul.discovery.register-health-check=false
  • 启动类添加@EnableDiscoveryClient注解(可加可不加 不影响程序执行)
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsulApplication.class, args);
    }

}

​ 最后浏览器访问http://127.0.0.1:8500即可

3.服务通信

3.1 RestTemplate直接调用

若A服务需要调用B服务中的信息,只需要知道B服务中的请求路径即可,如:

  • A 服务 端口:9001/9011 服务名:users
@RestController
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/user/getAll")
    public String getAll() {
        //指定为B服务的访问地址
        String info = this.restTemplate.getForObject("http://127.0.0.1:9002/product/getAll", String.class);
        return "调用后的信息:" + info;
    }

}
@Configuration
public class BeanConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

  • B服务 端口:9002/9022 服务名:products
@RestController
public class ProductController {

    @Value("${server.port}")
    private int port;

    @GetMapping("/product/getAll")
    public Map<String, Object> getAll() {
        Map<String, Object> map = new HashMap<>();
        map.put("'status", true);
        map.put("message", "查询所有商品信息成功,当前端口:" + port);
        return map;
    }
}
  • 最后通过http://127.0.0.1:9001/user/getAll即可访问到B服务中的http://127.0.0.1:9002/product/getAll路径下的资源信息。

缺点:

1.没有通过注册中心进行访问。

2.没有服务的负载均衡。

3.代码写死不利于维护

4.服务宕机无法检测

3.2 RestTemplate+LoadBalancerClient调用

@RestController
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/user/getAll")
    public String getAll() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("products");//这里是需要调用服务的服务名
        String info = this.restTemplate.getForObject("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() +"/product/getAll",String.class);
        return "调用后的信息:" + info;
    }

}

3.3 RestTemplate+@LoadBalanced调用

  • controller编写
@RestController
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/user/getAll")
    public String getAll() {
        String info = this.restTemplate.getForObject("http://products/product/getAll", String.class);

        return "调用后的信息:" + info;
    }

}
  • 编写配置类
@Configuration
public class BeanConfiguration {
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule iRule() {
        return new RandomRule();//随机策略
//        return new RoundRobinRule();//轮询策略(默认)
//        return new AvailabilityFilteringRule();//首先过滤掉有问题的服务,对剩下的服务进行轮询
//        return new RetryRule();//首先通过轮询获取服务,若获取失败则会在指定时间内进行重试
    }

}

3.4 OpenFeign调用(常用)

官网:OpenFeign官网

简介:Feign是一个声明式的伪http客户端,它可以使http客户端更加简单。只需要接口注解即可实现。Feign默认集成了Ribbon,实现了负载均衡效果。也支持springmvc的直接支持。

# Ribbon+RestTemplate存在问题
- 1.每次调用都需要写大量重复代码,存在大量冗余
- 2.服务地址不利于维护
- 3.使用不灵活
  • 引入OpenFeign依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 启动类添加@EnableFeignClients注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }

}
  • 编写OpenFeign接口
//表示当前接口是一个Feign组件,其中对应的是服务ID
@FeignClient("products")  
public interface ProductClient {

    @GetMapping("/product/getAll")//对应product的路径即可
    String getAllProduct();//方法名随意写(可以和原方法名一致)

}
  • 编写Controllser实现
@RestController
public class UserController {

    @Autowired
    private ProductClient productClient;

    //默认执行负载均衡策略
    @GetMapping("/user/feign/test")
    public String feignTest() {
        log.info("进入feign测试方法...");
        //若是调用统一服务的不同方法且还存在集群部署时,会执行负载均衡策略,两个不同的接口执行的端口不一定一致
        return "调用信息返回:" + productClient.getAllProduct();
    }

}

Feign调用–GET参数传递

重点:@RequestParam()

@RequestParam:地址栏参数传递

  • ProductController
@RestController
public class ProductController {

    @Value("${server.port}")
    private int port;

    @GetMapping("/product/getone")
    public Map<String, Object> getOne(@RequestParam("ProductId") int ProductId) {
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "调用成功");
        map.put("value", ProductId);
        map.put("port", port);
        map.put("status", true);
        return map;
    }
}
  • 编写OpenFeign接口
@FeignClient("products")
public interface ProductClient {

    @GetMapping("/product/getone")
    //@RequestParam("对应原方法中的参数名")
    Map<String, Object> getOne(@RequestParam("ProductId") int ProductId);

}
  • 编写UserController
@RestController
public class UserController {

    @Autowired
    private ProductClient productClient;

    @GetMapping("/user/feign/testget")
    public String feignGet(int id) {
        return "调用信息返回:" + productClient.getOne(id);
    }

}
  • 访问http://127.0.0.1:9001/user/feign/testget?id=10即可

Feign调用–POST参数传递

重点:@RequestBody

@RequestBody:将json格式的字符串转换成对象

  • 在公共模块中创建实体product
@Data
public class Product {

    private Integer id;
    private String name;
    private Double price;
    private String info;

}
  • ProductController
@RestController
public class ProductController {

    @Value("${server.port}")
    private int port;

    @PostMapping("/product/save")
    //@RequestBody 将json格式的字符串转换成对象
    public Map<String, Object> save(@RequestBody Product product) {
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "调用成功");
        map.put("value", product);
        map.put("port", port);
        map.put("status", true);
        return map;
    }
}
  • 编写OpenFeign接口
@FeignClient("products")
public interface ProductClient {

    @PostMapping("/product/save")
    Map<String, Object> save(@RequestBody Product product);
    
}
  • 编写UserController
@RestController
public class UserController {

    @Autowired
    private ProductClient productClient;

    @PostMapping("/user/feign/testpost")
    public Map<String, Object> testPost(Product product) {
        return productClient.save(product);
    }

}
  • 访问http://127.0.0.1:9001/user/feign/testpost?id=21&name=快乐源泉&price=25.12&info=开始调用即可

OpenFeign超时设置

​ OpenFeign的默认超时为1秒,但在企业开发中有时候业务在1秒是不能被完全执行的,所以需要更改超时的时长。

  • 配置服务超时
# 配置指定Feign服务等待时长
feign.client.config.products.connect-timeout=5000
feign.client.config.products.read-timeout=5000
# 配置所有Feign服务等待时长
#feign.client.config.default.connect-timeout=5000
#feign.client.config.default.read-timeout=5000

OpenFeign日志调试

  • 开启Feign日志
# 开启指定Feign日志
feign.client.config.products.logger-level=full
# 开启所有Feign日志
#feign.client.config.default.logger-level=full
logging.level.com.hikw.clients=debug

4.断路器

4.1 Hystrix

官网:Hystrix

简介:hystrix是一个处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常,hystrix能够保证在一个依赖问题的情况选,不会导致整体服务的失败,避免级联故障,一提高分布式系统的弹性.

4.1.1 服务雪崩

​ 在微服务中,由其中某一个发生故障而引起级联服务的故障,称为雪崩效应。导致用户不可用并且逐渐放大的过程。

在这里插入图片描述

​ 若此时A服务的流量波动过大,A能抗住请求但C服务不一定能抗住请求。此时,如果C服务扛不住请求则会不可用,那么B服务也会慢慢的变得不可用,紧接着A服务也会慢慢变得不可用。这也叫扇出效应。如图所示:

在这里插入图片描述

压力测试工具:Apache-JMeter

4.1.2 服务熔断

​ 服务熔断本身是一种开关装置,当某个服务发生故障时,通过故障监控把发生故障的服务直接熔断并向调用方返回一个预期可处理的FallBack(可处理预选响应)。而不用长时间等待或者直接抛出调用方法无法处理的异常。线程也不会被长时间占用,避免了故障在分布式系统中蔓延,乃至雪崩。如果故障服务情况好转则会恢复服务的调用。服务熔断一般在**服务提供方*者实现。

重点注解:

@HystrixCommand(fallbackMethod = "服务熔断方法名")

@HystrixCommand(defaultFallback = "默认服务熔断方法名")

@EnableCircuitBreaker 启用服务熔断组件

在这里插入图片描述

  • 服务提供者引入hystrix依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.8.RELEASE</version>
</dependency>
  • 服务提供者Controller
@RestController
public class ProductController {

    @GetMapping("/product/break")
    //熔断注解  指定熔断方法  当发生熔断时直接执行该方法
    @HystrixCommand(fallbackMethod = "testBreakFallBack")
    public String testBreak(@RequestParam("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("非法参数,ID不能小于0!");
        }
        return "访问成功,当前ID为:" + id;
    }
    
    //testBreak触发熔断的方法
    public String testBreakFallBack(@RequestParam("id") Integer id) {
        return "当前参数ID不合法,触发熔断!";
    }
}
  • 服务提供者启动类添加@EnableCircuitBreaker注解开启服务熔断
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }

}
  • 访问http://127.0.0.1:9002/product/break?id=10正常 当访问http://127.0.0.1:9002/product/break?id=-5时返回熔断方法内容

​ 断路器有半开和全开之分,正常情况下断路器是半开。但当10秒内超过20次请求时断路器则全开,此时即使你是正常求情,也会触发断路器。5秒后会放一个请求,若该请求失败则继续触发断路器,若成功则放行关闭断路器。有效的对调用链路的保护。

在这里插入图片描述

默认服务熔断方法方法

  • 服务提供者Controller
@RestController
public class ProductController {

    @GetMapping("/product/break")
    //熔断注解  指定熔断方法  当发生熔断时直接执行该方法
    @HystrixCommand(defaultFallback = "defaultFallBack")
    public String testBreak(@RequestParam("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("非法参数,ID不能小于0!");
        }
        return "访问成功,当前ID为:" + id;
    }
    
    //默认触发熔断的方法
    public String defaultFallBack() {
        return "服务不可用,触发熔断!";
    }
}
4.1.3 服务降级

​ 服务压力剧增时,根据当前业务情况及流量对一些服务和页面有策略的进行降级,以此缓解服务器的压力,保证核心服务的运行,保证部分甚至大部分的客户得到正确的响应。即当前的求情无法处理或者出错时,给一个默认返回。总而言之就是在服务器压力剧增时,会关闭系统中的边缘服务以保证核心服务的正常运行,称之为降级。服务降级一般在服务消费者实现。

# 服务熔断&服务降级共同点
- 1.目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段
- 2.最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用
- 3.粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改)
- 4.自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段

# 服务熔断&服务降级区别
- 1.触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑
- 2.管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
- 3.实现方式不太一样,这个区别后面会单独来说

# 总结
- 1.服务熔断必定会触发服务降级
- 2.服务熔断针对调用链路保护
- 3.服务降级针对整个系统保护
- 4.服务熔断也是服务降级的一种

服务降级实现(OpenFeign + Hystrix)

  • 服务消费者引入相关依赖
<!--引入OpenFeign依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--引入hystri依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.8.RELEASE</version>
</dependency>
  • 开启Feign支持Hystrix
# 开启Feign支持hystrix
feign.circuitbreaker.enabled=true
  • 编写 FallBack 类

重点内容:

1.类上加@Component注解交给 Spring 管理

2.该类继承服务客户端的Feign接口并实现所有的接口

3.每个方法中返回服务降级后给客户看到的内容

@Component
public class ProductFallBack implements ProductClient {

    @Override
    public String getAllProduct() {
        return "getAllproducts服务被降级...";
    }
  
}
  • 服务消费者OpenFeign编写

重点内容:

1.@FeignClient(value = “serverID”, fallback = xxxFallBack.class)

2.服务熔断与服务降级的返回值要求严格一致

@FeignClient(value = "products", fallback = ProductFallBack.class)
public interface ProductClient {

    @GetMapping("/product/getAll")
    String getAllProduct();

}

5.服务网关

什么是服务网关?

# 简介
- 网关可以统一服务的入口,可以方便对服务的众多服务接口进行管理,对访问服务的身份认证、数据篡改、权限管理等功能进行统一的管理。
# 服务网关
- 服务网关:路由转发 + 过滤器
- 1.路由转发:接收一切外界请求,转发到后端的微服务上去
- 2.过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)

为什么需要网关?

- 1.使用网关可以实现服务的统一管理
- 2.网关可以解决微服务中的代码冗余问题

使用组件:Gateway

组件版本:3.0.3

官网地址:Spring Cloud Gateway

官方文档:Spring Cloud Gateway v3.0.3

执行原理

在这里插入图片描述

5.1 route(路由转发)

application.yml配置文件实现

  • 引入相关依赖

注意:在使用Gateway服务时,需要移除 SpringBoot 的 web 依赖,否者会起冲突无法使用。即移除spring-boot-starter-web依赖。

<!--引入consul依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

<!--consul健康检查依赖  若不引用则无法获取到服务状态-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!--引入Gateway依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 编写application.yml配置文件

route组成部分

  • id:路由ID(唯一标识符)
  • uri:匹配路由转发地址
    • ````http://host:port/``:常规转发
    • lb://serverID:负载均衡转发
  • predicates:配置该路由的谓语、断言([详情参考](####5.2 predicate(谓语、断言)))
  • order:路由的优先级,数字越小,优先级越高。(默认同级0)

常规转发

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    consul:
      port: 8500
      host: 127.0.0.1
      discovery:
        service-name: ${spring.application.name}
# ******************常规路由转发****************** #
    gateway:
      routes:
        - id: user_route
          uri: http://127.0.0.1:9001/
          predicates:
            - Path=/user/**
            
        - id: product_route
          uri: http://127.0.0.1:9002/
          predicates:
            - Path=/product/**
 # ********************************************** #

负载均衡转发

注意:在进行集群部署之后,同一服务部署会有不同的端口,那么在进行转发时则不会对同一服务而端口不同的进行转发,此时则需要进行负载均衡策略进行转发。而进行敷在均衡配置则只需要改动一下配置文件中的uri属性即可。

注意:

  • uri: lb://服务ID

  • discovery.locator.enabled=true配置必须开启

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    consul:
      port: 8500
      host: 127.0.0.1
      discovery:
        service-name: ${spring.application.name}
# *****************负载均衡转发***************** #
    gateway:
      routes:
        - id: user_route
          uri: lb://users
          predicates:
            - Path=/user/**
            
        - id: product_route
          uri: lb://products
          predicates:
            - Path=/product/**
      # 开启根据服务名动态获取路由功能(必须)
      discovery:
        locator:
          enabled: true
# ********************************************** #
  • 入口类添加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

}
  • 启动各个服务后,原本需要http://127.0.0.1:9001/user/feign/test才可以访问的路径现在直接用http://127.0.0.1/user/feign/test即可(80端口可省略)

通过@Bean注入RouteLocator实现(不推荐)

@Configuration
public class RouteConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
        return routeLocatorBuilder.routes()
            .route("user_route", r -> r.path("/user/**").uri("http://127.0.0.1:9001"))
            .route("product_route", r -> r.path("/product/**").uri("http://127.0.0.1:9002"))
            .build();
    }

}

查看路由管理列表

  • 需要添加此配置
# 暴露所有路由规则web端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 访问URL:http://127.0.0.1:80/actuator/gateway/routes

在这里插入图片描述

5.2 predicate(谓语、断言)

获取当前时区时间代码

public static void main(String[] args) {
    ZonedDateTime now = ZonedDateTime.now();
    System.out.println("当前时区时间:" + now);
}
  • 匹配路径转发
- Path=/user/**,/abc/**
  • 匹配主机转发
- Host=**.hikw01.com,**.hikw02.com
  • 在指定时间之后可访问(服务上线等)
- After=2021-07-16T23:19:04.213+08:00[Asia/Shanghai]
  • 在指定时间之前可访问(活动促销、限时抢购等)
- Before=2022-07-16T23:19:04.213+08:00[Asia/Shanghai]
  • 在指定时间之间可访问
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2022-07-16T23:19:04.213+08:00[Asia/Shanghai]
  • 指定Cookie的Key和Value(若不携带Cookie则无法访问)
- Cookie=username, hikw
  • 基于请求头中指定属性的正则匹配路由
- Header=X-Request-Id, \d
  • 指定请求方式访问
- Method=GET,POST
  • 基于查询访问
- Query=green
  • 基于远程地址访问
- RemoteAddr=192.168.1.1/24

配置如下:

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    consul:
      port: 8500
      host: 127.0.0.1
      discovery:
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: user_route
          uri: lb://users
          predicates:
            - Path=/user/**
            #- After=2017-01-20T17:42:47.789-07:00[America/Denver]
            #- Before=2022-07-16T23:19:04.213+08:00[Asia/Shanghai]
            #- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2022-07-16T23:19:04.213+08:00[Asia/Shanghai]
            #- Host=**.hikw01.com,**.hikw02.com
            #- Cookie=username, hikw
            #- Query=green
            #- RemoteAddr=192.168.1.1/24
            #- Method=GET,POST
            #- Header=X-Request-Id, \d

        - id: product_route
          uri: lb://products
          predicates:
            - Path=/product/**
      discovery:
        locator:
          enabled: true

5.2 filter(过滤器)

  • 简介

路由过滤器允许以某种方式修改传入的http请求或传出http响应,路由filter的作用域是特定路由。其中 SpringCloudGateway 包括许多的GatewayFilter工厂。

  • 作用

当有多个服务时,客户端请求各个服务的Api时,每个服务都会做相同的事,如:日志输出、鉴权、限流等等。而有了filter后,这些东西就直接放在filter中进行执行,解决代码冗余。

5.2.1 内置filter
  • 增加请求头(k,v)
- AddRequestHeader=X-Request-red, blue
  • 增加请求参数(k,v)
- AddRequestParameter=id, 34
  • 增加响应头参数(k,v)
- AddResponseHeader=X-Response-Red, Blue

由于SpringCloudGateway内置filter较多,更多使用请参考官网案例:Spring Cloud GatewayFilter

application.yml配置

server:
  port: 80
spring:
  application:
    name: gateway
  cloud:
    consul:
      port: 8500
      host: 127.0.0.1
      discovery:
        service-name: ${spring.application.name}
    gateway:
      routes:
        - id: user_route
          uri: lb://users
          predicates:
            - Path=/user/**
          filters:
            # 增加请求头(k,v)
            #- AddRequestHeader=X-Request-red, blue
            # 增加请求参数(k,v)
            #- AddRequestParameter=id, 34
            # 增加响应头参数(k,v)
            #- AddResponseHeader=X-Response-Red, Blue
            #......
        - id: product_route
          uri: lb://products
          predicates:
            - Path=/product/**
      # 开启根据服务名动态获取路由功能(必须)
      discovery:
        locator:
          enabled: true

management:
  endpoints:
    web:
      exposure:
        include: "*"
5.2.2 自定义filter(使用较多)
@Configuration
@Slf4j
public class GlobalFliterConfiguration implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("进入到自定义filter...");
        if (exchange.getRequest().getQueryParams().get("username") != null) {
            log.info("用户身份信息合法,已放行...");
            return chain.filter(exchange);
        }
        log.info("用户信息不合法,拒绝访问...");
        return exchange.getResponse().setComplete();
    }

    //数字越小filter最先执行  -1最先执行
    @Override
    public int getOrder() {
        log.info("order已执行...");
        return -1;
    }
}

6.配置中心

  • 简介

config即配置中心,也叫统一配置中心。优点在于在日后的大规模集群部署项目时应用相同服务的服务配置一致,日后再修改就只需要统一修改,不需要一个一个修改。之后的每个服务只需要向统一配置中心拉取配置信息即可。

官方文档:Spring Cloud Config

6.1 ConfigServer配置

  • 引入configServer依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>
  • application.properties配置文件

注意:这里需要你首先去 GitHub 注册并创建一个仓库用于存放配置信息。

为了测试,我这里在新建的仓库中新建一个order.properties文件。

server.port=7777
spring.application.name=configserver
spring.cloud.consul.port=8500
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.discovery.service-name=${spring.application.name}

# 配置远程配置中心地址
spring.cloud.config.server.git.uri=你的仓库地址
spring.cloud.config.server.git.username=用户名
spring.cloud.config.server.git.password=密码
#spring.cloud.config.server.git.search-paths=搜索路径

# 修改本地仓库存放地址
spring.cloud.config.server.git.basedir=本地存放配置地址

# 指定远程仓库分支(首先远程配置中心有多分支)
spring.cloud.config.server.git.default-label=dev
  • 拉取远端配置
> 以下任意一种都可以拉取
- 1.http://127.0.0.1:7777/orders-xxx.properties
- 2.http://127.0.0.1:7777/orders-xxx.json
- 3.http://127.0.0.1:7777/orders-xxx.yml
其中xxx的值如下:
- 1.prod  生产环境
- 2.dev   研发环境
- 3.test  测试环境
> 查看详细配置
- 1.http://127.0.0.1:7777/orders/prod
- 2.http://127.0.0.1:7777/orders/dev
- 3.http://127.0.0.1:7777/orders/test
- ......
  • 入口类添加@EnableConfigServer注解开启统一配置中心
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }

}

6.2 ConfigClient配置

这里为了测试我新建一个名为 orders 的Module

  • 在 orders 中引入 config 依赖
<!--引入configclient依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
  • 在远程添加下列配置
- 1.orders.properties                存放公共配置信息
- 2.orders-dev.properties            存放研发配置信息
- 3.orders-prod.properties           存放生产配置信息
- 4.orders-test.properties           存放测试配置信息

orders.properties

spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=${spring.application.name}

orders-prod.properties

server.port=7900

orders-dev.properties

server.port=7910

orders-test.properties

server.port=7920
  • 配置 orders 的bootstrap.(properties|yaml)

bootstrap.(properties|yaml) :启动时预先加载(优先级高于application.(properties|yaml))

application.(properties|yaml):启动时当用到配置文件中的配置时加载

# 配置服务名
spring.application.name=orders
# 开启统一配置中心服务
spring.cloud.config.discovery.enabled=true
# 指定统一配置中心服务的唯一标识
spring.cloud.config.discovery.service-id=configserver
spring.config.import=
# 指定从哪个分支拉取配置文件
spring.cloud.config.label=master
# 指定拉取配置文件的名称
spring.cloud.config.name=orders
# 指定拉取配置文件的环境
spring.cloud.config.profile=dev

注意:若果你使用的是springcloud 2020.*之后的版本,name在启动时会出现如下错误:

***************************
APPLICATION FAILED TO START
***************************

Description:

No spring.config.import property has been defined

Action:

Add a spring.config.import=configserver: property to your configuration.
	If configuration is not required add spring.config.import=optional:configserver: instead.
	To disable this check, set spring.cloud.config.enabled=false or 
	spring.cloud.config.import-check.enabled=false.

错误原因:

由于SpringCloud 2020.* 版本把 bootstrap 禁用了,导致在读取文件的时候读取不到而报错,所以我们只要把bootstrap从新导入进来就会生效了。

解决方法:导入bootstrap依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_何同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值