SpringCloudNetflix Spring

2021-11-17 约 22073 字 阅读时长45 分钟

SpringcloudNetflix

简介

架构的演化

集中式架构:传统架构,所有的功能一个项目完成,可以进行集群;一个war包或jar包

分布式架构:对一个完整的项目进行拆分,通过rpc,http等调用共同完成工作;多个war包或jar包

微服务架构:属于分布式的一种,对系统进行拆分,每个服务间使用http的restful风格进行调用;每个服务都可以独立的进行测试、开发、部署;每个服务专注于自己的功能,并且可以使用不同的语言进行开发

分布式和微服务的区别?

分布式目的:拆分项目为各个模块,部署在不同机器上,增加访问量

微服务目的:拆分项目为各个模块(服务),各服务间的升级,重构不会影响到其他的服务

两者类似,目的不同

springcloud

Spring Cloud是一套工具的集合,用于快速构建分布式项目以及解决分布式项目中的一些常见问题(配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等)

整体架构

整体架构

Service Provider:暴露服务的服务提供方

Service Consumer:调用远程服务的服务消费方

Eureka Server:服务注册中心和服务发现中心

起步

新建springboot项目,导入springcloud依赖,并申明为父项目,供后续服务springcloud的版本统一

xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 4    <modelVersion>4.0.0</modelVersion>
 5    <groupId>org.lei</groupId>
 6    <artifactId>spring-cloud-netflix</artifactId>
 7    <version>0.0.1-SNAPSHOT</version>
 8    <packaging>pom</packaging>
 9    <name>spring-cloud-netflix</name>
10    <description>spring-cloud-netflix</description>
11    <properties>
12        <java.version>11</java.version>
13        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
14        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
15        <spring-boot.version>2.7.6</spring-boot.version>
16    </properties>
17    <dependencyManagement>
18        <dependencies>
19            <dependency>
20                <groupId>org.springframework.boot</groupId>
21                <artifactId>spring-boot-dependencies</artifactId>
22                <version>${spring-boot.version}</version>
23                <type>pom</type>
24                <scope>import</scope>
25            </dependency>
26            <dependency>
27                <groupId>org.springframework.cloud</groupId>
28                <artifactId>spring-cloud-dependencies</artifactId>
29                <version>2021.0.9</version>
30                <type>pom</type>
31                <scope>import</scope>
32            </dependency>
33        </dependencies>
34    </dependencyManagement>
35</project>

Eureka

Eureka介绍

Eureka Server 主要对外提供了三个功能:

  • 服务注册:所有的服务都注册到 Eureka Server 上面来
  • 提供注册表:注册表是所有注册上来的服务的列表,Eureka Client 在调用服务时,需要获取这个注册表,一般来说,这个注册表会被缓存下来,如果缓存失效,则直接获取最新的注册表
  • 同步状态:Eureka Client 通过注册、心跳等机制,和 Eureka Server 同步当前客户端的状态

Eureka Client 主要用来简化每个服务和 Eureka Server 的交互。 Eureka Client 会自动拉取、更新以及缓存 Eureka Server 中的信息,这样,即使 Eureka Server 所有节点都宕机,Eureka Client 依然能够获取到想要调用服务的地址(地址可能不准确)

  • 服务注册

    服务提供者将自己注册到注册中心(Eureka Server),需要注意,所谓的服务提供者,只是一个业务上的分类,本质上它就是一个 Eureka Client 。当 Eureka Client 向 Eureka Server 注册时,它需要提供一些自身的元数据信息,如 IP 地址、端口、名称、运行状态等

  • 服务续约

    Eureka Client 注册到 Eureka Server 上之后,事情没有结束,刚刚开始而已。注册成功后,默认情况下,Eureka Client 每隔 30 秒就要向 Eureka Server 发行一条心跳信息,告诉 Eureka Server 我还在运行

  • 服务下线

    当 Eureka Client 下线时,它会主动发送一条信息,告诉 Eureka Server ,我下线了

  • 获取注册表信息

    Eureka Client 从 Eureka Server 上获取服务的注册信息,并将其缓存到本地。本地客户端,在需要调用远程服务时,会从该信息中查找远程服务所对应的的 IP 地址、端口等信息。Eureka Client 上缓存的服务注册信息会定期更新(30 秒),如果 Eureka Server 返回的注册表信息与本地缓存的注册表信息不同的话,Eureka Client 会自动处理

EurekaServer搭建

  1. 新建项目添加依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    4</dependency>
  2. 配置文件配置

    properties
     1#服务端口
     2server.port=8761
     3#服务名称
     4spring.application.name=eurekaServer
     5#指定Eureka服务端的IP、域名
     6eureka.instance.hostname=127.0.0.1
     7#是否将服务注册到Eureka服务端(自己就是eureka服务端,一般不需要注册,默认为true)
     8eureka.client.register-with-eureka=false
     9#是否从Eureka服务端获取服务信息(自己就是eureka服务端,一般不需要注册,默认为true)
    10eureka.client.fetch-registry=false
    11#开启注册中心的保护机制,默认是开启
    12eureka.server.enable-self-preservation=true
    13#设置保护机制的阈值,默认是0.85。
    14eureka.server.renewal-percent-threshold=0.5
  3. 启动类添加注解@EnableEurekaServer

    java
    1@EnableEurekaServer
    2@SpringBootApplication
    3public class Application {
    4    public static void main(String[] args) {
    5        SpringApplication.run(Application.class);
    6    }
    7}
  4. 访问 127.0.0.1:8761 可以看到如下界面:

    image-20211108162337623

EurekaClient

  1. 导入EurekaClient客户端依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    4</dependency>
  2. 配置Eureka-clientapplication配置文件

    properties
     1#服务端口
     2server.port=7082
     3#服务名称,eureka通过服务名称识别是否为同一服务
     4spring.application.name=portal-service
     5#当前服务器的服务地址(ip或域名)
     6eureka.instance.hostname=localhost
     7#注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔
     8eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka
     9#心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端,不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活
    10eureka.instance.lease-renewal-interval-in-seconds=5
    11#心跳超时时间10s,默认90s。从client端最后一次发出心跳后,达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除
    12eureka.instance.lease-expiration-duration-in-seconds=10
  3. Application启动类添加注解@EnableEurekaClient,启动Eureka客户端,用于注册当前服务到EurekaServer

远程服务调用

注册RestTemplate模板,并添加@LoadBalanced注解

java
 1@SpringBootApplication
 2@EnableEurekaClient
 3public class PortalApplication {
 4    @Bean
 5    @LoadBalanced
 6    public RestTemplate restTemplate(){return new RestTemplate();}
 7    public static void main(String[] args) {
 8        SpringApplication.run(PortalApplication.class, args);
 9    }
10}

通过ip直接调用

java
 1@Autowired
 2private RestTemplate restTemplate;
 3@RequestMapping("/getGoods")
 4public WrapperResponse getGoods(){
 5    MultiValueMap<String, Object> param= new LinkedMultiValueMap<>();
 6    param.add("total",100);
 7    //这个的地址参数更换为需要调用的服务名(该服务已注册在EurekaServer)
 8    ResponseEntity<WrapperResponse> responseEntity = restTemplate.postForEntity("http://127.0.0.1/goods/getGoods",param,WrapperResponse.class);
 9    return responseEntity.getBody();
10}

通过注册到eurekaServer中心的服务名调用

java
 1@Autowired
 2private RestTemplate restTemplate;
 3@RequestMapping("/getGoods")
 4public WrapperResponse getGoods(){
 5    MultiValueMap<String, Object> param= new LinkedMultiValueMap<>();
 6    param.add("total",100);
 7    //这个的地址参数更换为需要调用的服务名(该服务已注册在EurekaServer)
 8    ResponseEntity<WrapperResponse> responseEntity = restTemplate.postForEntity("http://GOODS/goods/getGoods",param,WrapperResponse.class);
 9    return responseEntity.getBody();
10}

Eureka集群

所谓的Eureka集群,就是启动多个服务端,相互进行注册即可

Eureka 分区:

  • region:地理上的不同区域
  • zone:具体的机房

同一个分区内的 Eureka Server 和 Eureka Client 会优先进行心跳同步

Eureka集群架构中,Eureka Server 之间通过 Replicate 进行数据同步,不同的 Eureka Server 之间不区分主从节点,所有节点都是平等的。节点之间,通过置顶 serviceUrl 来相互注册,形成一个集群,进而提高节点的可用性

**注意:**本地搭建集群环境需要更改host文件,eureka默认使用eureka.instance.hostname主机名注册以及通信

服务端配置文件大概如下:

properties
 1#服务端口
 2server.port=8761
 3#服务名称
 4spring.application.name=eurekaServer
 5#指定Eureka服务端的IP、域名
 6eureka.instance.hostname=eure8761
 7#是否将服务注册到Eureka服务端(一般来说客户端需要开启,server需要关闭,但是这里搭建eureka集群)
 8eureka.client.register-with-eureka=true
 9#是否从Eureka服务端获取服务信息
10eureka.client.fetch-registry=true
11#开启注册中心的保护机制,默认是开启
12eureka.server.enable-self-preservation=true
13#设置保护机制的阈值,默认是0.85
14eureka.server.renewal-percent-threshold=0.5
15
16#注册中心路径,如果有多个eureka server,在这里需要配置其他eureka server的地址,用","进行区分,如"http://address:8888/eureka,http://address:8887/eureka"
17eureka.client.service-url.defaultZone=http://eure8762:8762/eureka/
18
19#本地测试时,可能需要忽略虚拟网卡,否则会识别错误
20spring.cloud.inetutils.ignored-interfaces=VMware.*
21spring.cloud.inetutils.use-only-site-local-interfaces=true
22#优先使用ip地址
23spring.cloud.inetutils.preferred-networks=10.0.15.94

客户端配置文件:

properties
1#服务端口
2server.port=7082
3#服务名称
4spring.application.name=portal-service
5#注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔
6eureka.client.serviceUrl.defaultZone=http://eure01:8761/eureka,http://eure02:8762/eureka,http://eure03:8763/eureka

通过上面配置既实现了Eureka集群

image-20220502165448668

本地测试

准备三个application.properties配置文件,分别配置eureka-server的配置,然后通过参数指定配置文件启动服务

java -jar myproject.jar --spring.profiles.active=test 用于springboot指定配置文件applicaiton-test启动

打包

打包时通过parent进行打包

将最后的成功配置好的Eureka-server进行打包,然后部署到服务器上,便后续使用

单机测试部署时,修改hosts文件,并编写shell脚本

shell
1#!/bin/bash
2nohup java -jar springcloud-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=eure8761 > ./log/eure8761 &
3nohup java -jar springcloud-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=eure8762 > ./log/eure8762 &

Eureka-server的自我保护机制

properties
1#开启注册中心的保护机制,默认是开启
2eureka.server.enable-self-preservation=true

Eureka-server的保护机制,默认开启的;作用是:当出现eure-server在一段时间没收到服务(多个服务)的心跳时,Eureka-server会认为时自己网络出现问题,不会将服务进行剔除,而是将服务保护起来等待恢复

例如:两个客户端实例 C1 和 C2 的连通性是良好的,但是由于网络故障,C2 未能及时向 Eureka 发送心跳续约,这时候 Eureka 不能简单的将 C2 从注册表中剔除。因为如果剔除了,C1 就无法从 Eureka 服务器中获取 C2 注册的服务,但是这时候 C2 服务是可用的

Eureka和zookeeper比较

CAP理论,一致性、可用性、分区容错性;分区容错性是分布式系统必须要保证的,因此只能在一致性和可用性之间进行取舍

zookeeper保证服务的AP,当zookeeper集群中某台机器宕机时,zookeeper会选举出新的leader,然后才对外提供服务

Eureka保证服务的CP,当Eureka集群中某台机器宕机时,Eureka Client 会自动切换到新的 Eureka Server 上。每个 Eureka Server 节点,都会互相同步数据

负载均衡Ribbon

Ribbon是一个开源的运行在消费端的客户端/进程内负载均衡器

消费端从eureka获取所有服务,并在内部采用Ribbon实现负载均衡,对多个服务进行调用

image-20211121162159619

Ribbon与nginxs

Ribbon是在请求发起前,对所有服务进行负载均衡后,选择服务进行调用

Nginx是在收到客户端发起的请求,进行负载均衡

注意Request 的位置,在 Nginx 中请求是先进入负载均衡器,而在 Ribbon 中是先在客户端进行负载均衡才进行请求的

Ribbon负载均衡算法

Ribbon负载均衡算法都实现了IRule接口,可以通过IDEA自带的工具Diagrams查看UML图,如下:

image-20211121164420294

ILoadBalancer接口:

java
 1public interface ILoadBalancer {
 2    void addServers(List<Server> var1);
 3    Server chooseServer(Object var1);
 4    void markServerDown(Server var1);
 5    /** @deprecated */
 6    @Deprecated
 7    List<Server> getServerList(boolean var1);
 8    List<Server> getReachableServers();
 9    List<Server> getAllServers();
10}

通过debug该接口的实现类的chooseServer方法,可以查看当前选择的规则;默认规则实现为ZoneAvoidanceRule

Ribbon的基本使用

使用注解,将@LoadBalanced注解加到RestTemplate Bean上,当使用RestTemplate进行http访问时,会将请求进行拦截,然后通过Ribbon进行负载均衡后,再将请求发出

java
1@Bean
2@LoadBalanced
3public RestTemplate restTemplate(){
4    return new RestTemplate();
5}

修改默认负载均衡

java
1@Bean
2public IRule iRule(){
3    //修改robbin负载策略为 轮询
4    return new RoundRobinRule();
5}

修改后可以通过debug查看ILoadBalancer接口实现类的chooseServer方法,可以查看到当前规则的实现

负载均衡策略

所有策略实现IRule接口

策略名称策略对应的类名实现原理
轮询策略(默认)RoundRobinRule轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个provider,第 1 次取第 1 个,第 2次取第 2 个,第 3 次取第 3 个,以此类推
权重轮询策略WeightedResponseTimeRule1.根据每个 provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。2.原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,当信息足够时,给每个 provider附上一个权重,并按权重随机选择provider,高权越重的 provider会被高概率选中。
随机策略RandomRule从 provider 列表中随机选择一个provider
最少并发数策略BestAvailableRule选择正在请求中的并发数最小的 provider,除非这个provider 在熔断中。
在“选定的负载均衡策略”基础上进行重试机制RetryRule1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择 provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider
可用性敏感策略AvailabilityFilteringRule过滤性能差的 provider,有 2种:第一种:过滤掉在 eureka 中处于一直连接失败 provider 第二种:过滤掉高并发的 provider
区域敏感性策略ZoneAvoidanceRule1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重。

默认实现为ZoneAvoidanceRule策略

远程调用OpenFeign

介绍

通过 RestTemplate 进行远程调用时,不够优雅,即使注册到eureka后,仍需要填写远程服务名,如下:

java
1public WrapperResponse getGoods(){
2    MultiValueMap<String, Object> param= new LinkedMultiValueMap<>();
3    param.add("total",100);
4    ResponseEntity<WrapperResponse> responseEntity = restTemplate.postForEntity("http://GOODS-SERVICE/goods/getGoods",param,WrapperResponse.class);
5    return responseEntity.getBody();
6}

因此引入了Open Feign,利用了OpenFeign的声明式方式定义Web服务客户端;可以将远程服务像本地服务一样进行调用

使用

  1. 添加spring-cloud-starter-openfeign起步依赖

    xml
    1<!-- 因为继承了springcloud 依赖, 它申明了openfeign版本-->
    2<!-- 起步依赖集成了 openfeign-core 等依赖 -->
    3<dependency>
    4    <groupId>org.springframework.cloud</groupId>
    5    <artifactId>spring-cloud-starter-openfeign</artifactId>
    6</dependency>
  2. Applicaition启动类添加注解 @EnableFeignClients

  3. 编写接口,用于远程调用服务;注意:定义的接口必须包含在 Applicaiton 同级或子包下,或者在启动类的注解上指定接口地址 @EnableFeignClients(basePackages = "com.lei.api")

    java
    1//FeignClient(服务名),它会自动转换为从 eureka 获得的服务IP地址
    2@FeignClient("GOODS-SERVICE")
    3public interface GoodsClient {
    4    //方法签名需和远程服务的方法签名一致
    5    // 传参方式一:RequestParam 指定参数名
    6    @PostMapping("/goods/getGoods")
    7    WrapperResponse getGoods(@RequestParam("total") Integer total);
    8}
  4. 编写测试类,此时可以像本地开发一样,进行远程服务调用

    java
     1@RestController
     2@RequestMapping("/portal")
     3public class PortalController {
     4    @Autowired
     5    private GoodsClient goodsClient;
     6
     7    @RequestMapping("/getGoodsFign")
     8    public WrapperResponse geGoodsFign(){
     9        //通过声明的 Feign客户端,进行远程调用
    10        return goodsClient.getGoods(100);
    11    }
    12}

传参方式

默认情况下,Feign会将标有@RequestParam注解的参数转换成字符串添加到URL中,将没有注解的参数通过Jackson转换成json放到请求体中。注意,如果在@RequetMapping中的method将请求方式指定为POST,那么所有未标注解的参数将会被忽略

  1. @RequestParam(org.springframework.web.bind.annotation.RequestParam)用于将指定的请求参数赋值给方法中的形参

    java
    1//Feign客户端
    2@FeignClient("GOODS-SERVICE")
    3public interface GoodsClient {
    4    @PostMapping("/goods/getGoods")
    5    WrapperResponse getGoods(@RequestParam("total") Integer total);
    6}
    7
    8//服务提供方 直接获取就行因为参数拼接在URL中

    此时请求:http://GOODS/goods/getGoods?total=100,它会将参数拼接在URL后

  2. 没有注解时,相当于@RequestBody,参数通过Jackson转换成json放到请求体中;可以在服务提供方使用@RequestBody从请求体中获取参数

    java
     1//Feign客户端
     2@FeignClient("GOODS-SERVICE")
     3public interface GoodsClient {
     4    @PostMapping("/goods/getGoods")
     5    WrapperResponse getGoods(Map map);
     6}
     7
     8//服务提供方,使用@RequestBody取得参数
     9public WrapperResponse getGoods(@RequestBody Map map){
    10    return WrapperResponse.success(map);
    11}
  3. @PathVariable使用RestFul进行参数传递

    java
     1//Feign客户端
     2@FeignClient("GOODS-SERVICE")
     3public interface GoodsClient {
     4    @PostMapping("/goods/getGoods/{total}")
     5    WrapperResponse getGoods(@PathVariable("total")Integer total);
     6}
     7
     8//服务提供者
     9@PostMapping("/getGoods/{total}")
    10public WrapperResponse getGoods(@PathVariable("total") Integer total){
    11    return WrapperResponse.success(total);
    12}

服务熔断与降级 Hystrix

介绍

Hystrix 提供了熔断降级请求限流

服务降级是指当某个微服务响应时间过长,发生异常,或者服务不可用了,我们不能把错误信息返回回来,或者让它一直卡在那里,所以要准备一个对应的策略(一个方法),当发生这种问题时,我们直接调用这个备用的方法来快速返回一个默认的结果,让请求得到快速响应,而不是一直卡在那里

Hystrix目的

  • 防止复杂分布式系统中出现级联故障
  • 快速失败和快速恢复
  • 在允许的情况下,提供退路对服务进行优雅的降级
  • 提供近实时监控、报警和操作控制

降级是作用

  1. 可以监听你的请求有没有超时;(默认是1秒,时间可以改)
  2. 异常或报错了可以快速让请求返回,不会一直等待;(避免线程累积)
  3. 系统马上大量并发时,可以考虑先关闭一些不重要的微服务(在降级方法中返回一个比较友好的信息),把资源让给核心微服务,待高峰流量过去,再开启回来。

Hystrix使用

  1. 导入已经整合的 start 依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    4</dependency>
  2. 在启动类上添加注解@EnableCircuitBreaker启动断路器

    java
    1@SpringBootApplication
    2@EnableEurekaClient
    3@EnableFeignClients
    4@EnableCircuitBreaker
    5public class PortalApplication {
    6    //......
    7}

    可以观察到启动类上又很多注解,可以使用@SpringCloudApplication注解代替@SpringBootApplication、@EnableEurekaClient、@EnableCircuitBreaker,该注解内部

    java
    1@Target({ElementType.TYPE})
    2@Retention(RetentionPolicy.RUNTIME)
    3@Documented
    4@Inherited
    5@SpringBootApplication
    6@EnableDiscoveryClient
    7@EnableCircuitBreaker
    8public @interface SpringCloudApplication {
    9}
  3. 在调用远程服务的方法上加上注解@HystrixCommand(fallbackMethod = "fallback"),hystrix 默认超时时间是 1000 毫秒,如果该方法响应时间超过此时间,就会触发断路器,可以修改hustrix的默认超时时间

    注解方式配置超时时间

    java
     1@RequestMapping("/geGoodsHystrix")
     2@HystrixCommand(fallbackMethod = "getDetailFallback",    //超时调用方法
     3    commandProperties={
     4        @HystrixProperty(name="execution.timeout.enabled", value="true"),   //默认启动
     5        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000")     //修改超时时间为5s
     6    })
     7public WrapperResponse geGoodsHystrix(){
     8    try {
     9        Thread.sleep(3000);
    10    } catch (InterruptedException e) {
    11        e.printStackTrace();
    12    }
    13    return goodsClient.getGoods(999);	//调用远程方法
    14}
    15public WrapperResponse getDetailFallback(){
    16    return WrapperResponse.fail("服务降级了");
    17}

    配置文件方式配置超时时间

    properties
    1#ribbon默认连接超时时间和默认超时时间
    2ribbon.ConnectTimeout=1000
    3ribbon.ReadTimeout=2000
    4
    5#hystrix是否启动,以及调用远程方法的超时时间
    6hystrix.command.default.execution.timeout.enabled=true
    7hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=4000

    注意:ribbon不仅有负载均衡的作用,还会有失败重试,但实例1宕机或者读超时,ribbon会把请求转发到另外一台实例;如果hystrix的熔断时间小于ribbon的超时时间的话,就有可能hystrix在ribbon进行重试的时候就熔断了,所以ribbon的重试配置就没有实际作用了

    因此hystrix超时时间应该大于rabbin所有配置时间,如下例子:

    yaml
    1ribbon:
    2  ReadTimeout: 5000
    3  ConnectTimeout: 2000
    4  MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
    5  MaxAutoRetriesNextServer: 2 #重试负载均衡其他的实例最大重试次数,不包括首次调用
    6  OkToRetryOnAllOperations: false  #是否所有操作都重试?默认get请求起作用,如果想其他类型的请求起作用,需要配置true,但是! post请求一般是create,如果接口没做幂等性会有并发问题,所以慎重配置。
    7
    8 #上面rabbin的配置有:
    9 ## 1(首次访问) + 1(MaxAutoRetries一次) + 1 (负载到实例2之后请求一次) + 1 (实例2重试一次) + 1(负载到实例3之后请求一次)+ 1(实例3重试一次)= 6次,那么rabbin总共工作时间为 42s的时间,此时hystrix熔断时间应该配置为大于42s

Hystrix异常处理

默认情况下方法抛了异常会自动进行服务降级,交给服务降级中的方法去处理; 当发生异常后,只需要在服务降级方法中添加一个 Throwable 类型的参数就能够获取到抛出的异常的类型,如下

java
 1@RequestMapping("/geGoodsHystrix")
 2@HystrixCommand(fallbackMethod = "getDetailFallback")
 3public WrapperResponse geGoodsHystrix() throws Exception {
 4    if (true){
 5        throw new Exception("异常了!!!");
 6    }
 7    return goodsClient.getGoods(999);
 8}
 9//降级方法添加参数 Throwable ,就可以获取到方法抛出的异常
10public WrapperResponse getDetailFallback(Throwable th){
11    return WrapperResponse.fail(th.getMessage());
12}

Hystrix限流

限流的方案

  • Nginx
  • Redis + Lua
  • Sentinel
  • 基于限流算法自己实现(令牌桶、漏桶算法)

hystrix限流就是限制你某个微服务的使用量(可用线程数、信号量);hystrix通过线程池的方式来管理微服务的调用,它默认是一个线程池(大小10个) 管理你的所有微服务,你可以给某个微服务开辟新的线程池

java
1@RequestMapping("/geGoodsHystrix")
2@HystrixCommand(fallbackMethod = "getDetailFallback",
3                threadPoolKey = "goods",
4                threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "2"), 		                                                     @HystrixProperty(name = "maxQueueSize", value = "1")}
5               )
6public WrapperResponse geGoodsHystrix() throws Exception {
7    Thread.sleep(2000);
8    return goodsClient.getGoods(99);
9}

threadPoolKey 是线程池唯一标识, hystrix 会使用该标识来计数,看线程占用是否超过了, 超过了就会直接降级该次调用;这里coreSize给他值为2 那么假设你这个方法调用时间是1s执行完, 那么在1s内如果有超过2个请求进来的话,剩下的请求则全部降级;其中maxQueueSize是一个线程队列,里面只能放一个请求线程,本来线程数有2个,队列里面允许放一个,那么总共只能有3个请求线程执行,如果超过了就会限流走降级方法,并能捕获到如下错误信息:

bash
1Task java.util.concurrent.FutureTask@2d934502[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@965faa0[Wrapped task = null]] rejected from java.util.concurrent.ThreadPoolExecutor@174a1fe4[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 14]

Feign整合Hystrix

  1. feign 默认是支持hystrix的,需要在配置文件中开启

    properties
    1feign.hystrix.enabled=true
  2. @FeignClient添加fallback,值为class

    java
    1@FeignClient(value = "GOODS",fallback = GoodsClientFallback.class)
    2public interface GoodsClient {
    3    @PostMapping("/goods/getGoods/{total}")
    4    WrapperResponse getGoods(@PathVariable("total")Integer total);
    5}

    GoodsClientFallback类

    java
    1@Component
    2public class GoodsClientFallback implements GoodsClient {
    3    @Override
    4    public WrapperResponse getGoods(Integer total) {
    5        return WrapperResponse.fail("feign降级方法!!!");
    6    }
    7}
  3. 如果需要获取异常信息,需要在@FeignClient指定fallbackFactory ,值为class

    java
    1@FeignClient(value="GOODS", fallbackFactory = GoodsClientFallbackFactory.class)
    2public interface GoodsClient {
    3    @PostMapping("/goods/getGoods/{total}")
    4    WrapperResponse getGoods(@PathVariable("total")Integer total);
    5}

    GoodsClientFallbackFactory类

    java
     1@Component
     2public class GoodsClientFallbackFactory implements FallbackFactory<GoodsClient> {
     3    @Override
     4    public GoodsClient create(Throwable throwable) {
     5        return new GoodsClient() {
     6            @Override
     7            public WrapperResponse getGoods() {
     8                String message = throwable.getMessage();
     9                return WrapperResponse.fail(message);
    10            }
    11        };
    12    }
    13}

Spring Cloud Feign超时时间设置

Feign调用服务的默认时长是1秒钟,也就是如果超过1秒没连接上或者超过1秒没响应,那么会相应的报错。而实际情况是因为业务的不同可能出现超出1秒的情况,这时我们需要调整超时时间

Feign 的负载均衡底层用的就是 Ribbon在application.properties中添加如下配置,超过5秒没连接上报连接超时,如果超过5秒没有响应,报请求超时

properties
1#请求连接超时时间
2ribbon.ConnectTimeout=2000
3#请求处理的超时时间
4ribbon.ReadTimeout=5000

ribbon还有MaxAutoRetries对当前实例的重试次数

MaxAutoRetriesNextServer对切换实例的重试次数

如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作;由于ribbon的重试机制,通常熔断hystrix的超时时间需要配置的比ReadTimeout长,ReadTimeout比ConnectTimeout长,否则还未重试,就熔断了

为了确保重试机制的正常运作,理论上建议hystrix的超时时间为:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout

hystrix相关配置

Execution相关的属性的配置

  • hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore(信号量)

  • hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms

  • hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true

  • hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true

  • hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使

  • ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。semaphore应该占整个容器(tomcat)的线程池的一小部分。 Fallback相关的属性 这些参数可以应用于Hystrix的THREAD和SEMAPHORE策略;

  • hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequest如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10

  • hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true

Circuit Breaker相关的属性

  • hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
  • hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求, 即使19个请求都失败,也不会触发circuit break。默认20
  • hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。 默认5000
  • hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该 值,circuit会被打开,并短路所有请求触发fallback。默认50
  • hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所 有request,默认false
  • hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将 一直关闭且忽略circuitBreaker.errorThresholdPercentage

Metrics相关参数

  • hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒, 则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
  • hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数 量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
  • hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪, 默认true
  • hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
  • hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
  • hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window =10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序 的开销。默认100
  • hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用 来统计成功和错误绿)的间隔,默认500ms

Request Context 相关参数

  • hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不 缓存
  • hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true
  • Collapser Properties 相关参数
  • hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认 Integer.MAX_VALU
  • hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间 +该值,默认10
  • hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true

ThreadPool 相关参数 线程数默认值10适用于大部分情况(有时可以设置得更小),如果需要设置得更大,那有个基本得公式可以 follow: requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room 每秒最大支撑的请求数 (99%平均响应时间 + 缓存值) 比如:每秒能处理1000个请求,99%的请求响应时间是60ms,那么公式是: 1000 (0.060+0.012)基本得原则时保持线程池尽可能小,他主要是为了释放压力,防止资源被阻塞。 当一切都是正常的时候,线程池一般仅会有1到2个线程激活来提供服务

  • hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
  • hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
  • hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到 queueSizeRejectionThreshold该值后,请求也会被拒绝
  • hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默 认10000
  • hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个 buckets,默认10

Hystrix 仪表盘监控

Hystrix 仪表盘主要用来监控 Hystrix 的实时运行状态,通过它可以看到 Hystrix 的各项指标信息

**搭建一个 Hystrix Dashboard 服务用于监控Hystrix **

  1. 新建springboot工程,导入依赖,依然需要继承springcloud父依赖项目

    xml
     1<parent>
     2    <artifactId>ch01-springcloud-parent</artifactId>
     3    <groupId>com.lei</groupId>
     4    <version>1.0-SNAPSHOT</version>
     5    <relativePath>../ch01-springcloud-parent/pom.xml</relativePath>
     6</parent>
     7<dependencies>
     8    <dependency>
     9        <groupId>org.springframework.boot</groupId>
    10        <artifactId>spring-boot-starter-web</artifactId>
    11    </dependency>
    12    <dependency>
    13        <groupId>org.springframework.cloud</groupId>
    14        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    15    </dependency>
    16</dependencies>
  2. 配置文件配置服务端口,启动使用注解启动仪表盘功能@EnableHystrixDashboard

    properties
    1server.port=3721
    2#允许仪表盘监控的地址,否则会无法监控,这里监控的本地随意指定的localhost
    3hystrix.dashboard.proxy-stream-allow-list=localhost
    java
    1@SpringBootApplication
    2@EnableHystrixDashboard
    3public class HystrixDashboardApplication {
    4    public static void main(String[] args) {
    5        SpringApplication.run(HystrixDashboardApplication.class, args);
    6    }
    7}
  3. 访问仪表盘:http://localhost:3721/hystrix

    image-20211204141108695

  4. 将需要监控hystrix的服务,暴露监控接口

    在portal消费者服务中,导入springboot提供的监控接口

    xml
    1<!--  springboot 提供的监控接口-->
    2<dependency>
    3    <groupId>org.springframework.boot</groupId>
    4    <artifactId>spring-boot-starter-actuator</artifactId>
    5</dependency>

    配置spring boot 监控端点

    properties
    1#配置spring boot 监控端点,暴露 endpoints 的,由于 endpoints 中会包含很多敏感信息
    2#除了 health 和 info 两个支持直接访问外,其他的默认不能直接访问,所以我们让它都能访问
    3management.endpoints.web.exposure.include=*

    访问监控接口:http://localhost:7091/actuator/hystrix.stream;注意:需要先访问一次hystrix熔断接口,否则该监控接口会一直输出ping;然后在仪表盘输入地址,就可以看到仪表盘监控了

    hystrix

  5. 多个hystrix服务的监控,需要整合,新建一个turbine的springboot项目,注册到注册中心,整合多个hystrix服务,然后仪表盘通过监控turbine项目提供的接口进行监控多个服务

    image-20211204152501124

    导入依赖:同时该项目依然依赖于自建的springcloud父依赖

    xml
    1<!-- 用于汇集hystrix监控  -->
    2<dependency>
    3    <groupId>org.springframework.cloud</groupId>
    4    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
    5</dependency>
    6<dependency>
    7    <groupId>org.springframework.cloud</groupId>
    8    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    9</dependency>

    配置文件

    properties
    1server.port=3722
    2#不向注册中心注册自己
    3eureka.client.register-with-eureka=false
    4#注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔
    5eureka.client.serviceUrl.defaultZone=http://139.155.239.250:8761/eureka,http://139.155.239.250:8762/eureka,http://139.155.239.250:8763/eureka
    6#配置turbine,这里指定服务名
    7turbine.app-config=portal,portal2
    8#需要有这个,没有的话聚合不了多个项目
    9turbine.cluster-name-expression="default"

    启动类上添加注解@EnableTurbine,用于聚合监控

    java
    1@EnableTurbine
    2@SpringBootApplication
    3public class TurbineApplication {
    4    public static void main(String[] args) {SpringApplication.run(TurbineApplication.class, args);}
    5}

    访问:http://localhost:3722/turbine.stream,就可以监控多个hystrix服务了

网关Zuul

介绍

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

其中路由功能负责将外部的请求转发到具体的微服务实例上,实现外部访问统一入口的基础;过滤功能是对外部的请求进行校验、服务聚合等功能的基础

Zuul和eureka进行整合,将自身注册为eureka服务治理下,同时从eureka获取其他微服务的信息,以后访问微服务都可以通过zuul跳转后获得

Zuul服务搭建

  1. 导入依赖

    xml
     1<!--spring-cloud-starter-netflix-eureka-client-->
     2<dependency>
     3    <groupId>org.springframework.cloud</groupId>
     4    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
     5</dependency>
     6
     7<!-- spring-cloud-starter-netflix-zuul -->
     8<dependency>
     9    <groupId>org.springframework.cloud</groupId>
    10    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    11</dependency>
  2. 启动类添加注解,@EnableZuulProxy简单理解为@EnableZuulServer的增强版

    java
    1@EnableZuulProxy   //启动zuul网关
    2public class ZuulApplication {
    3    public static void main(String[] args) {
    4        SpringApplication.run(ZuulApplication.class, args);
    5    }
    6
    7}
  3. zuul服务已经搭建成功,然后通过zuul服务地址+微服务名+请求地址,即可访问具体微服务的方法实现;例如:http://localhost/portal-service/portal/geGoodsFign

路由

Zuul网关搭建好后,可以通过:zuul服务地址+微服务名+请求地址访问微服务

  1. 自定义访问微服务得路径,而不是通过微服务名;此时可以通过:http://localhost/portal/portal/geGoodsFign 访问

    properties
    1## 超时时间
    2zuul.host.connect-timeout-millis=5000
    3
    4## 路由规则配置
    5## / **代表是所有(多个)层级   /cloud/goodsFeignHystrix
    6## / * 是代表一层
    7zuul.routes.portal.service-id=portal-service
    8zuul.routes.portal.path=/portal/**
  2. 屏蔽使用微服务名进行访问

    properties
    1#屏蔽使用微服务名进行访问;*代表屏蔽所有微服务名,这里可以指定微服务名
    2zuul.ignored-services=*
  3. 统一前缀,添加访问前缀;此时通过: 进行访问

    properties
    1#前缀,当请求匹配前缀时会进行代理
    2zuul.prefix=/zuul
    3#代理前缀默认会从请求路径中移除;即localhost/api/UserService -> localhost:8080
    4zuul.strip-prefix=true

通配符含义

通配符含义举例说明
匹配任意单个字符/portal-service/?匹配 /portal-service/a
*匹配任意数量的字符/portal-service/*匹配 /portal-service/aaa,无法匹配 /portal-service/a/b/c
**匹配任意数量的字符/portal-service/**匹配 /portal-service/aaa, /portal-service/a/b/c

过滤器

限流、权限验证、记录日志

过滤器(filter)是zuul得核心组件,zuul大部分功能都通过过滤器实现;zuul中定义了四种过滤器类型,这些过滤器分别对应请求得生命周期

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

1639894902678

编写过滤器

  1. 编写过滤器需要继承ZuulFilter类,并重写其中的方法;需要声明为Spring Bean

    java
     1@Component
     2public class LogFilter extends ZuulFilter {
     3    /**
     4     * 过滤器类型,pre、route、post、error
     5     */
     6    @Override
     7    public String filterType() {
     8        return FilterConstants.ROUTE_TYPE;
     9    }
    10
    11    /**
    12     * 过滤器,过滤器执行顺序,不同的过滤器可以返回相同的数字,越小越先执行
    13     */
    14    @Override
    15    public int filterOrder() {
    16        return 1;
    17    }
    18
    19    /**
    20     * 该过滤器是否执行,true 表示执行、false 表示不执行
    21     */
    22    @Override
    23    public boolean shouldFilter() {
    24        return true;
    25    }
    26
    27    /**
    28     * 过滤器运行逻辑,返回值暂无意义
    29     */
    30    @Override
    31    public Object run() throws ZuulException {
    32        RequestContext currentContext = RequestContext.getCurrentContext();
    33        HttpServletRequest request = currentContext.getRequest();
    34        System.out.println("访问地址:"+request.getServerName()+request.getRequestURI());
    35        return null;
    36    }
    37}
  2. 过滤器禁用

    properties
    1#过滤器禁用 zuul.<SimpleClassName>.<filterType>.disable = true
    2zuul.LogFilter.route.disable=true

异常处理

正常情况下,zuul过滤器按顺序 PRE–>ROUTE 然后由POST返回response;如果 PRE 阶段或者 ROUTE 阶段发生异常,会执行ERROR过滤器,统一处理异常

  1. 禁用zuul提供的默认异常过滤器 SendErrorFilter过滤器

    properties
    1#过滤器禁用 zuul.<SimpleClassName>.<filterType>.disable = true
    2zuul.SendErrorFilter.error.disable=true
  2. 过滤器编写,只要其他过滤器执行发生异常,就会进入异常过滤器

    java
     1@Component
     2public class ErrorFilter extends ZuulFilter {
     3    @Override
     4    public String filterType() {
     5        return FilterConstants.ERROR_TYPE;
     6    }
     7
     8    @Override
     9    public int filterOrder() {
    10        return 0;
    11    }
    12
    13    @Override
    14    public boolean shouldFilter() {
    15        return true;
    16    }
    17
    18    @Override
    19    public Object run() throws ZuulException {
    20        try {
    21            RequestContext context = RequestContext.getCurrentContext();
    22            ZuulException exception = (ZuulException)context.getThrowable();
    23            System.out.println("进入系统异常拦截" + exception.getMessage());
    24            HttpServletResponse response = context.getResponse();
    25            response.setContentType("application/json; charset=utf8");
    26            response.setStatus(exception.nStatusCode);
    27            PrintWriter writer = null;
    28            try {
    29                writer = response.getWriter();
    30                writer.print("{code:"+ exception.nStatusCode +",message:\""+
    31                        exception.getMessage() +"\"}");
    32            } catch (IOException e) {
    33                e.printStackTrace();
    34            } finally {
    35                if(writer!=null){
    36                    writer.close();
    37                }
    38            }
    39        } catch (Exception e) {
    40            ReflectionUtils.rethrowRuntimeException(e);
    41        }
    42        return null;
    43    }
    44}
  3. 此时错误也返回的也是友好页面,全局统一异常拦截

zuul服务降级熔断

zuul是一个代理服务,但如果被代理的服务突然断了,这个时候zuul上面会有出错信息,例如,停止了被调用的微服务;

一般服务方自己会进行服务的熔断降级,但对于zuul本身,也应该进行zuul的降级处理;

配置降级后,错误页面友好;当调用微服务超时或异常时,就会进入配置的zuul降级方法,返回友好的错误提示

zuul服务降级,实现如下

java
 1@Component
 2public class ProviderFailback implements FallbackProvider {
 3    /**
 4     * 哪个微服务提供降级,* 匹配所有
 5     */
 6    @Override
 7    public String getRoute() {
 8        return "*";
 9    }
10
11    /**
12     * 响应逻辑;route为当前微服务名称
13     */
14    @Override
15    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
16        //通过匿名内部类得方式返回 ClientHttpResponse 响应对象
17        return new ClientHttpResponse() {
18            @Override
19            public HttpHeaders getHeaders() {
20                HttpHeaders headers = new HttpHeaders();
21                headers.set("Content-Type", "text/html; charset=UTF-8");
22                return headers;
23            }
24            @Override
25            public InputStream getBody() throws IOException {
26                // 响应体
27                return new ByteArrayInputStream("服务正在维护,请稍后再试.".getBytes());
28            }
29
30            @Override
31            public HttpStatus getStatusCode() throws IOException {
32                //状态码相关
33                return HttpStatus.BAD_REQUEST;
34            }
35            @Override
36            public int getRawStatusCode() throws IOException {
37                //状态码相关
38                return HttpStatus.BAD_REQUEST.value();
39            }
40            @Override
41            public String getStatusText() throws IOException {
42                //状态码相关
43                return HttpStatus.BAD_REQUEST.getReasonPhrase();
44            }
45            @Override
46            public void close() { }
47        };
48    }
49}

配置中心Config

简介

为什么使用配置中心

  • 安全性:配置跟随源代码保存在代码库中,容易造成配置泄漏
  • 时效性:修改配置,需要重启服务才能生效
  • 局限性:无法支持动态调整:例如日志开关、功能开关

常用分布式配置中心框架

  • Apollo(阿波罗):携程框架部门研发的分布式配置中心
  • diamond : 淘宝开源的持久配置中心,支持各种持久信息发布和订阅
  • XDiamond:全局配置中心,存储应用的配置项,解决配置混乱分散的问题
  • Qconf:奇虎360内部分布式配置管理工具
  • Disconf:百度的分布式配置管理平台
  • Spring Cloud Config:Spring Cloud微服务开发的配置中心,提供服务端和客户端支持

什么是Spring Cloud Config

Spring Cloud Config 是一个解决分布式系统的配置管理方案。它包含 Client和 Server 两个部分,Server 提供配置文件的存储、以接口的形式将配置文件的内容提供出去,Client 通过接口获取数据、并依据此数据初始化自己的应用。 Spring cloud config使用 git 或 svn 、也可以是本地存放配置文件,默认情况下使用 git

Springcloud config 的工作原理

img

  1. 创建一个远程 Git 仓库

  2. 每当 Config Server访问远程 Git 仓库时,都会克隆一份到本地,这样当远程仓库无法连接时,就直接使用本地存储的配置信息

  3. 微服务 A、微服务 B 则是我们的具体应用,这些应用在启动的时会从 Config

    Server 中获取相应的配置信息

  4. 当微服务 A、微服务 B 尝试从 Config Server 中加载配置信息的时候,Config Server 会先通过 git clone 命令克隆一份配置文件保存到本地

  5. 由于配置文件是存储在 Git 仓库中,所以配置文件天然具有版本管理功能

Config Server构建

  1. 构建springboot项目,并添加依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-config-server</artifactId>
    4</dependency>
  2. 启动类上使用@EnableConfigServer注解方式,启动配置中心

    java
    1@SpringBootApplication
    2@EnableConfigServer
    3public class ConfigApplication {
    4    public static void main(String[] args) {
    5        SpringApplication.run(ConfigApplication.class, args);
    6    }
    7}
  3. 创建远程仓库,目录结构如下

    tex
    1ch01
    2	--goods
    3		---application-goods1.properties
    4		---application-goods2.properties
    5	--portal
    6		---application-portal1.properties
    7		---application-portal2.properties
  4. 编写配置文件application-portal1.properties

    properties
     1#服务端口
     2server.port=7091
     3#服务名称
     4spring.application.name=portal-service
     5#这里配置当前微服务地址,eure点击微服务可跳转,当前服务器的服务地址(ip或域名)
     6eureka.instance.hostname=localhost
     7
     8#开启feign支持hystrix
     9feign.hystrix.enabled=true
    10
    11#ribbon默认连接超时时间和默认超时时间
    12ribbon.ConnectTimeout=1500
    13ribbon.ReadTimeout=3000
    14
    15hystrix.command.default.execution.timeout.enabled=true
    16hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
    17
    18#服务检索开启
    19eureka.client.fetch-registry=true
    20#注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔
    21eureka.client.serviceUrl.defaultZone=http://eure8761:8761/eureka/,http://eure8762:8762/eureka/
    22#心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端,不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活
    23eureka.instance.lease-renewal-interval-in-seconds=6
    24#心跳超时时间10s,默认90s。从client端最后一次发出心跳后,达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除
    25eureka.instance.lease-expiration-duration-in-seconds=10
    26#配置spring boot 监控端点,暴露 endpoints 的,由于 endpoints 中会包含很多敏感信息
    27#除了 health 和 info 两个支持直接访问外,其他的默认不能直接访问,所以我们让它都能访问
    28
    29management.endpoints.web.exposure.include=*
  5. 在application.properties配置文件中,配置仓库信息

    properties
     1server.port=8888
     2spring.application.name=config-service
     3#uri 表示配置中心所在仓库的位置
     4spring.cloud.config.server.git.uri=https://gitee.com/lei142857/springcloud-config.git
     5#search-paths 表示仓库下的子目录
     6spring.cloud.config.server.git.search-paths=ch01/portal,ch01/goods
     7#username 表示你的 GitHub 用户名
     8spring.cloud.config.server.git.username=19882445846
     9#password 表示你的 GitHub 密码
    10spring.cloud.config.server.git.password=tl869257546
  6. 此时启动配置中心,可通过以下方式访问配置文件

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

    {application} 表示配置文件的名字,对应的配置文件即 application {profile} 表示环境,有 dev、test、online 及默认 {label} 表示分支,默认我们放在 master 分支上

    例如:http://localhost:8888/master/application.properties

    返回json数据

    • name 表示配置文件名 application 部分
    • profiles 表示环境部分
    • label 表示分支
    • version 表示 GitHub 上提交时产生的版本号
  7. 当有微服务获取配置中心文件时,配置中心首先会自动从远程仓库pull最新配置文件,否则使用本地缓存配置文件

Config Client构建

修改微服务配置文件,让微服务自动从配置中心加载配置

  1. 导入依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-starter-config</artifactId>
    4</dependency> 
  2. 创建 bootstrap.properties配置文件,用于获取远程配置信息

    properties
    1## name 对应配置文件中的 application 部分,这个也是应用名
    2## profile 对应了 profile 部分
    3## label 对应了 label 部分
    4## uri 表示配置中心的地址
    5spring.application.name=application
    6spring.cloud.config.profile=portal1
    7spring.cloud.config.label=master
    8spring.cloud.config.uri=http://localhost:8888/

    Spring Cloud有一个引导上下文的概念,这是主应用程序的父上下文。引导上下文负责从配置服务器加载配置属性,以及解密外部配置文件中的属性。和主应用程序加载application(yml或 properties)中的属性不同,引导上下文加载(bootstrap)中的属性。配置在 bootstrap中的属性有更高的优先级,因此默认情况下它们不能被本地配置覆盖

  3. 创建controller测试是否获取到远程配置信息

    java
     1@RestController
     2@RequestMapping("/test")
     3public class TestController {
     4    @Value("${eureka.client.serviceUrl.defaultZone}")
     5    private String defaultZone;
     6    @RequestMapping("/getDefaultZone")
     7    public String getDefaultZone(){
     8        return defaultZone;
     9    }
    10}

    访问接口可获得返回值:http://eure8761:8761/eureka/,http://eure8762:8762/eureka/;表示已经从配置中心获取到配置信息了

配置信息的加解密处理

image-20220105170751012

对称加密

  1. 配置中心,创建bootstrap.properties配置文件,然后配置密钥

    properties
    1#设置对称密钥,用这个密钥进行加密和解密
    2encrypt.key=cat
  2. config server提供了加密与解密的接口

    • 加密接口:http://localhost:8888/encrypt
    • 解密接口:http://localhost:8888/decrypt (这个接口我们不需要用)
    • 解密是config-server自动完成的
  3. 通过postman,post调用传入明文,则可获得加密后的密文

    image-20220105165641658

  4. 然后将远程仓库配置信息配置成密文,然后配置中心获取配置文件时会自动进行解密;使用 {cipher} 表示该属性值为密文

    properties
    1#......
    2eureka.instance.lease-renewal-interval-in-seconds={cipher}10616d2f78c5fdf7ed15033fe64f3a2c0994366d0f19304cb521a72ca3458311
    3#......
  5. 通过http://localhost:8888/master/application.properties访问配置中心,看是否能得到原文

配置中心局部刷新

局部刷新通过Spring Boot 的actuator提供了一个刷新端点/refresh

  1. 在客户端添加springboot提供的监控依赖

    xml
    1<!--  springboot 提供的监控接口起步依赖-->
    2<dependency>
    3    <groupId>org.springframework.boot</groupId>
    4    <artifactId>spring-boot-starter-actuator</artifactId>
    5</dependency>
  2. 修改配置文件,放行刷新端点

    properties
    1#配置spring boot 监控端点,暴露 endpoints 的,由于 endpoints 中会包含很多敏感信息
    2#除了 health 和 info 两个支持直接访问外,其他的默认不能直接访问,所以我们让它都能访问
    3management.endpoints.web.exposure.include=*
  3. 在Controller上添加注解@RefreshScope,添加这个注解的类会在配置更新时得到特殊的处理

    java
     1@RefreshScope
     2@RestController
     3@RequestMapping("/config")
     4public class ConfigTestController {
     5    @Value("${eureka.instance.lease-renewal-interval-in-seconds}")
     6    private String eurekaInstanceId;
     7    @RequestMapping("/getEurekaInstance")
     8    public Object getEurekaInstance(){
     9        return eurekaInstanceId;
    10    }
    11}
  4. 修改远程仓库配置文件,访问客户端的刷新端点访问http://localhost:8080/actuator/refresh 进行手动刷新配置;当访问刷新端点时,配置中心会自动读取远程仓库最新的配置文件,并缓存到本地

  5. 访问controller,可以看到获取到的配置信息已经被刷新了

局限性:这种方式的刷新,就是你对每个微服务分别进行刷新,也一个一个操作,如果你有80个微服务,那么就需要手动刷新这80个微服务

通过Bus消息总线全局刷新

image-20220122235323214

通过消息队列,实现全局刷新,发送一条刷新消息到每个微服务,然后微服务会自动去拉去最新配置

  1. 新建专门刷新全局配置得项目或直接使用任意微服务项目

  2. 导入springcloud消息总线rabbitMQ起步依赖,springboot监控起步依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.cloud</groupId>
    3    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    4</dependency>
    5<dependency>
    6    <groupId>org.springframework.boot</groupId>
    7    <artifactId>spring-boot-starter-actuator</artifactId>
    8</dependency>
  3. 修改配置文件

    properties
     1#配置rabbitmq
     2spring.rabbitmq.host=192.168.10.131
     3spring.rabbitmq.port=5672
     4spring.rabbitmq.username=root
     5spring.rabbitmq.password=123456
     6
     7#开启spring cloud bus,默认是开启的,也可以省略该配置
     8spring.cloud.bus.enabled=true
     9
    10#打开所有的web访问端点
    11management.endpoints.web.exposure.include=*
  4. 在其他微服务中集成springcloud消息总线,导入依赖并修改配置文件

  5. 访问:http://localhost:8888/actuator/bus-refresh通过消息总线进行全局配置刷新,RabbitMQ将收到消息,然后微服务会消费消息,config的所有客户端的微服务配置都会动态刷新

config高可用

Spring Cloud Config的高可用机制解决方式非常简单,把Spring Cloud Config注册到Eureka就搞定了,此时用户访问的时候不是直接从配置中心获取配置信息,而是先通过eureka中获取配置中心的地址,然后再从配置中心获取具体服务的配置信息

注意:这里需要将eureka的连接信息等配置到bootstrap.properties文件中

Spring cloud config 高可用第二种方式:使用Nginx

spring.cloud.config.uri=http://nginx的ip:nginx端口/, 这个url写成访问nginx,然后通过nginx负载均衡转发到3个config服务

bootstrap.properties配置文件示例:

properties
 1spring.application.name=application
 2spring.cloud.config.profile=goods1
 3spring.cloud.config.label=master
 4#将配置中心注册到注册中心时,可通过配置中心的服务名访问
 5spring.cloud.config.discovery.enabled=true
 6spring.cloud.config.discovery.service-id=CONFIG-SERVICE
 7#配置中心未注册到注册中心时可直接通过地址访问
 8#spring.cloud.config.uri=http://localhost:8888/
 9
10#访问配置中心的账号和密码
11spring.cloud.config.username=lei
12spring.cloud.config.password=123456

安全认证

Config安全认证

  1. 配置中心添加依赖

    xml
    1<!-- spring-boot-starter-security -->
    2<dependency>
    3    <groupId>org.springframework.boot</groupId>
    4    <artifactId>spring-boot-starter-security</artifactId>
    5</dependency>
  2. 在配置文件中配置账号和密码

    properties
    1#配置安全认证的账号和密码
    2spring.security.user.name=lei
    3spring.security.user.password=123456
  3. 需要从配置中心获取配置文件的客户端,在bootstrap.properties中配置访问账号和密码

    properties
    1#访问配置中心的账号和密码
    2spring.cloud.config.username=lei
    3spring.cloud.config.password=123456

Eureka安全认证

一般情况下Eureka 都会在一个内网环境中,但免不了在某些项目中需要让其他外网的服务注册到Eureka,这个时候就有必要让Eureka增加一套安全认证机制了,让所有服务提供者通过安全认证后才能注册进来

  1. eureka服务端添加依赖

    xml
    1<!-- spring-boot-starter-security -->
    2<dependency>
    3    <groupId>org.springframework.boot</groupId>
    4    <artifactId>spring-boot-starter-security</artifactId>
    5</dependency>
  2. 配置文件配置用户名和密码

    properties
    1#访问注册中心的账号和密码
    2spring.security.user.name=lei
    3spring.security.user.password=123456
  3. 在eureka服务端编写配置类EurekaSecurityConfig,重写configure方法,把csrf劫持置为不可用,让服务能被接收和注册

    java
     1/**
     2 * csrf置为不可用
     3 */
     4@Configuration
     5@EnableWebSecurity
     6public class EurekaSecurityConfig extends WebSecurityConfigurerAdapter {
     7    @Override
     8    protected void configure(HttpSecurity http) throws Exception {
     9        http.csrf().disable();
    10        super.configure(http);
    11    }
    12}
  4. 客户端配置访问注册中心的url中添加账户和密码

    properties
    1#eureka注册中心的连接地址
    2eureka.client.service-url.defaultZone=http://lei:123456@localhost:8761/eureka

分布式链路跟踪 Sleuth

分布式链路跟踪概述

分布式链路跟踪可以 串联整个调用链路,快速定位问题;理清各个微服务之间的依赖关系;进行各个微服务接口的性能分折;跟踪整个业务流程的调用处理顺序

Spring Cloud Sleuth [sluːθ]为 spring Cloud提供了分布式跟踪的解决方案

Spring Cloud Sleuth可以追踪10种类型的组件:async、Hystrix,messaging,websocket,rxjava,scheduling,web(Spring MVC Controller,Servlet),webclient(Spring RestTemplate)、Feign、Zuul

Springcloud Sleuth一些术语

  • 跨度(span):基本工作单元。span用一个64位的id唯一标识,span被启动和停止时,记录了时间信息。初始化 span被称为"rootspan",该 span的 id和 trace的 ID相等
  • trace(跟踪):形成树状结构的一组跨度,一组共享"rootspan"的 span组成的树状结构称为 trace, trace也用一个64位的 ID唯一标识, trace中的所有 span都共享该 trace的 ID
  • annotation(标注):用于及时记录事件的存在,核心annotation用来定义请求的开始和结束
    • CS (Client sent客户端发送):客户端发起一个请求,该annotation描述了span的开始
    • SR (server Received服务器端接收):服务器端获得请求并准备处理它。如果用 SR减去 CS时间戳,就能得到网络延迟
    • SS (server sent服务器端发送):该annotation表明完成请求处理(当响应发回客户端时)。如果用 SS减去 SR时间戳,就能得到服务器端处理请求所需的时间
    • CR (Client Received客户端接收): span结束的标识。客户端成功接收到服务器端的响应。如果 CR减去 CS时间戳,就能得到从客户端发送请求到服务器响应的所需的时间

Zipkin

Zipkin主要是分析追踪数据

SpringCloud Sleuth 分布式链路跟踪仅仅只会生成一堆数据,这些数据不便于阅读;通常情况,会将Sleuth产生的数据上传到Zipkin服务器上,Zipkin Server会解析Sleuth数据,方便于阅读

Sleuth和ZipkinServer联动

搭建ZipkinServer

本地搭建失败,直接使用docker运行 docker run -d -p 9411:9411 openzipkin/zipkin

也可以在github上下载源码,打包jar运行

微服务整合Sleuth,并上传数据到ZipkinServer

  1. 服务中导入依赖

    xml
     1<!-- spring-cloud-starter-sleuth -->
     2<dependency>
     3    <groupId>org.springframework.cloud</groupId>
     4    <artifactId>spring-cloud-starter-sleuth</artifactId>
     5</dependency>
     6
     7<!-- spring-cloud-starter-zipkin -->
     8<dependency>
     9    <groupId>org.springframework.cloud</groupId>
    10    <artifactId>spring-cloud-starter-zipkin</artifactId>
    11</dependency>
  2. 配置文件,添加下面配置

    properties
    1#指定Zipkin server地址
    2spring.zipkin.base-url=http://192.168.10.131:9411
    3#发送跟踪数据到zipkin的类型web(http)
    4spring.zipkin.sender.type=web
    5#request采样的数量 默认是0.1 也即是10%,即采样10%的请求数据;
    6#因为在分布式系统中,数据量可能会非常大,因此采样非常重要我们示例数据少最好配置为1全采样,100%的采集会稍微影响一点性能
    7spring.sleuth.sampler.probability=1.0
  3. 此时访问各个服务,zipkinServer就可以捕获到链路信息

Sleuth 目前只要是服务重启,所有的链路跟踪数据都会丢失,可以集成数据库或者Elasticsearch,此时数据就可以被持久化保存在第三方介质上,当Sleuth重启时,以前的链路信息就会得以保存下来

Spring Cloud Stream

简介

Spring Cloud Stream来整合消息中间件,这样就可以降低微服务和消息中间件的耦合性,做到轻松在不同消息中间件间切换,目前Spring Cloud Stream只支持rabbitmq 和 kafka

Spring Cloud Stream 可以说是在消息中间件上抽象了一层;它负责和不同的消息消息中间件交互,而应用程序负责和Stream提供的接口进行交互

image-20220325100942966

应用程序通过input(相当于消费者consumer)、output(相当于生产者producer)来与Spring Cloud Stream中Binder交互,而Binder负责与消息中间件交互

组成说明
BinderBinder是应用与消息中间件之间的封装,目前实现了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现;
@Input该注解标识输入通道,通过该输入通道接收消息进入应用程序
@Output该注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding将信道channel和exchange绑定在一起

Stream整合rabbitMQ

image-20220408164408627

  1. 添加依赖

    xml
    1<!-- spring-cloud-starter-stream-rabbit -->
    2<dependency>
    3    <groupId>org.springframework.cloud</groupId>
    4    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    5</dependency>
  2. 生产者和消费者的配置

    properties
     1#对接具体的消息中间件
     2#binders:这是一组binder的集合,这里配置了一个名为test的binder,这个binder中是包含了一个rabbit的连接信息
     3spring.cloud.stream.binders.test.type=rabbit
     4spring.cloud.stream.binders.test.environment.spring.rabbitmq.host=192.168.10.131
     5spring.cloud.stream.binders.test.environment.spring.rabbitmq.port=5672
     6spring.cloud.stream.binders.test.environment.spring.rabbitmq.username=root
     7spring.cloud.stream.binders.test.environment.spring.rabbitmq.password=123456
     8spring.cloud.stream.binders.test.environment.spring.rabbitmq.virtual-host=/
     9
    10#bindings:这是一组binding的集合,这里配置了一个名为testOutPut的binding,这个binding中配置了指向名test的binder下的一个交换机testRabbit
    11spring.cloud.stream.bindings.testOutPut.destination=testRabbit
    12#设置要绑定的消息服务的binder
    13spring.cloud.stream.bindings.testOutPut.binder=test
    14
    15#消费者,与生产者相比 binding的名字不同,也是通道与交换机绑定的关键
    16spring.cloud.stream.bindings.testInPut.destination=testRabbit
    17spring.cloud.stream.bindings.testInPut.binder=test
  3. 发送消息

    java
     1//创建信道;这里的名称要和配置文件中配置的bindings一致
     2public interface MqMessageSource{
     3    String TEST_OUT_PUT="testOutPut";
     4    @Output(TEST_OUT_PUT)
     5    MessageChannel testOutPut();
     6}
     7
     8//发送消息
     9@EnableBinding(MqMessageSource.class)
    10public class MessageSender {
    11    @Resource
    12    @Output(MqMessageSource.TEST_OUT_PUT)
    13    private MessageChannel output; // 消息的发送管道
    14
    15    public void publish(String msg) {
    16        output.send(MessageBuilder.withPayload(msg).build());
    17        System.out.println("消息发送:<" + msg + "> 完成,时间:" + new Date());
    18    }
    19}
  4. 接收消息

    java
     1//创建信道;这里的名称要和配置文件中配置的bindings一致
     2public interface MqMessageSink {
     3    String TEST_IN_PUT = "testInPut";
     4    @Input(TEST_IN_PUT)
     5    SubscribableChannel testInPut();
     6}
     7
     8//接收消息
     9@EnableBinding(MqMessageSink.class)
    10public class MessageReceiver {
    11    @StreamListener(MqMessageSink.TEST_IN_PUT)
    12    public void input(Message message) {
    13        System.out.println("消息接收:<" + message.getPayload()  + "> 完成,时间:" + new Date());
    14    }
    15
    16}

注意:默认情况,对于rabbitMQ来说创建的是临时队列,并且交换机类型是topic,路由键默认为#,即所有消费者都会创建队列,并能接收到消息

交换机:

image-20220409135859966

队列:

image-20220409135932830

绑定信息:

image-20220409140132042

Stream分组与持久化

消息分组,对于rabbitMQ来说即创建了指定名称的队列;此时消费者可以指定到一个分组(队列)中,则消息只会被消费一次;并且队列不再是临时队列,因此消息可以持久化保存

配置文件

properties
1spring.cloud.stream.bindings.testOutPut.destination=testRabbit
2spring.cloud.stream.bindings.testOutPut.binder=test
3#指定分组,可以进行消息的持久化
4spring.cloud.stream.bindings.testOutPut.group=myGroup
5
6spring.cloud.stream.bindings.testInPut.destination=testRabbit
7spring.cloud.stream.bindings.testInPut.binder=test
8#指定分组,可以进行消息的持久化
9spring.cloud.stream.bindings.testInPut.group=myGroup

此时创建的队列:

image-20220409140357346

为持久化队列,当没有任何监听者监听队列时,队列不会被删除,消息可以持久化保存

Stream设置路由键

路由键默认为 #,可以手动设置路由键,仅适用于rabbitMQ;在消费端设置

配置文件

properties
 1spring.cloud.stream.bindings.testOutPut.destination=testRabbit
 2spring.cloud.stream.bindings.testOutPut.binder=test
 3spring.cloud.stream.bindings.testOutPut.group=myGroup
 4#设置发送消息的路由键,该属性也可以实现动态路由键
 5spring.cloud.stream.rabbit.bindings.testOutPut.producer.routing-key-expression="key.asdas"
 6
 7
 8spring.cloud.stream.bindings.testInPut.destination=testRabbit
 9spring.cloud.stream.bindings.testInPut.group=myGroup
10#设置一个RoutingKey路由key,默认是#,我们可以指定
11spring.cloud.stream.rabbit.bindings.testInPut.consumer.binding-routing-key=key.#

动态路由键实现

properties
 1spring.cloud.stream.bindings.testOutPut.destination=testRabbit
 2spring.cloud.stream.bindings.testOutPut.binder=test
 3spring.cloud.stream.bindings.testOutPut.group=myGroup
 4#这里设置路由键表达式为headers.routekey,在发送消息时,设置headers的routekey属性,则就会使用该属性作为路由键
 5spring.cloud.stream.rabbit.bindings.testOutPut.producer.routing-key-expression=headers.routekey
 6
 7
 8spring.cloud.stream.bindings.testInPut.destination=testRabbit
 9spring.cloud.stream.bindings.testInPut.group=myGroup
10#设置一个RoutingKey路由key,默认是#,我们可以指定
11spring.cloud.stream.rabbit.bindings.testInPut.consumer.binding-routing-key=key.#

发送者

java
 1@EnableBinding(MqMessageSource.class)
 2public class MessageSender {
 3    @Resource
 4    @Output(MqMessageSource.TEST_OUT_PUT)
 5    private MessageChannel output; // 消息的发送管道
 6
 7    public void publish(String msg) {
 8        //这里设置headers中routekey的属性值,配置文件中路由键表达式会读取到,并作为消息的路由键
 9        output.send(MessageBuilder.withPayload(msg).setHeader("routekey","key.123").build());
10        System.out.println("消息发送:<" + msg + "> 完成,时间:" + new Date());
11    }
12
13}

阿波罗配置中心

安装apollo

  1. github 上下载Quick Start安装包,解压后大概目录结构如下

    tex
    1-- client
    2-- portal
    3-- service
    4-- sql
    5-- demo.sh
  2. 上传到linux,解压 unzip …

    shell
    1$ unzip apollo-build-scripts-master.zip
  3. 先根据sql初始化库表,然后运行

    shell
    1## 修改数据库连接
    2$ vim demo.sh
    3	#这儿修改数据库连接
    4$ ./demo.sh start
  4. 启动成功后,访问:http://ip:8070 登录账号默认是: apollo/admin

    • Config-service 8080端口
    • Admin-service 8090端口
    • Portal管控台 8070端口

集成apollo配置中心

  1. 导入依赖

    xml
    1<!-- apollo-client -->
    2<dependency>
    3    <groupId>com.ctrip.framework.apollo</groupId>
    4    <artifactId>apollo-client</artifactId>
    5    <version>1.9.2</version>
    6</dependency>
  2. 配置文件

    properties
    1app.id=YOUR-APP-ID
    2apollo.meta=http://config-service-url
  3. 启动类添加,开启apollo配置支持

    java
    1@EnableApolloConfig //开启apollo配置支持

Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置,不影响应用正常运行

Apollo客户端的实现原理

image-20220503113439835

  1. 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送(通过Http Long Polling实现)
  2. 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置,这是一个fallback机制,为了防止推送机制失效导致配置不更新
  3. 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified;定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟
  4. 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中,客户端会把从服务端获取到的配置在本地文件系统缓存一份;在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
  5. 应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知

Apollo模块

  • Config Service:提供配置获取接口、提供配置更新推送接口(基于Http long polling)
  • Admin Service:提供配置管理接口、提供配置修改发布等接口、接口服务对象为Portal
  • Meta Server:Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port)、Client通过域名访问Meta Server获取Config Service服务列表(IP+Port)、Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client;增设一个Meta Server的角色主要是为了封装服务发现的细节
  • Eureka:基于Eureka和Spring Cloud Netflix提供服务注册和发现、Config Service和Admin Service会向Eureka注册服务,并保持心跳
  • portal:提供Web界面供用户管理配置
  • client:Apollo提供的客户端程序,为应用提供配置获取、实时更新等功能

实战

说明

版本号:基于 spring-cloud-netflix 3.1.8 搭建;与前文教程版本有些许不一样

搭建的环境:基于 docker 搭建

整体架构

Eureka 集群搭建

使用滚轮缩放
按住拖动