SpringCloud—笔记(二)中级篇

6 篇文章 0 订阅
5 篇文章 0 订阅

在这里插入图片描述

服务注册中心

Eureka服务注册与发现

Eureka基础知识

1、服务治理

Spring Cloud封装了Netflix 公司开发的Eureka模块来实现服务治理


在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

2、服务注册

Eureka采用了C$的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心 跳连接。这样系统的维护人员就可以通过Eureka Server 来监控系统中各个微服务是否正常运行。


在服务注册与发现中,有一个注册中心。 当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供 者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

在这里插入图片描述

3、eureka两组件

Eureka包含两个组件: Eureka Server和Eureka Client

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

Eureka Client通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、 使用轮询(round-robin)负载算法的负载均衡器 。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)(复习GC ROOT)

单机Eureka构建步骤

1、IDEA生成eurekaServer端服务注册中心类7001似物业公司

建Module

cloud-eureka-server7001

改POM

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

        <dependency><!--引入自己定义的api调用包-->
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

        <!--一般配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

写YML

server:
  port: 7001

eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己
    fetch-registry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  # 设置与Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址。

主启动类

@SpringBootApplication
@EnableEurekaServer //表名这是Eureka Server提供服务注册服务
public class EurekaMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class,args);
    }
}

测试

访问:http://localhost:7001/
在这里插入图片描述

2、EurekaClient端cloud-provider-payment8001

将注册进EurekaServer成为服务提供者provider,类似尚硅谷学校对外提供授课服务

cloud-provider-payment8001

改POM

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

写YML

eureka:
  client:
    register-with-eureka: true  # 表示是否将自己注册进EurekaServer默认为true。
    # 是否从EurekaServer抓取已有的注册信息,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动添加@EnableEurekaClient

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

测试

1、先要启动EurekaServer
2、访问http://localhost:7001/
在这里插入图片描述
3、微服务注册名配置说明
这里要保持一致
在这里插入图片描述

自我保护机制

在这里插入图片描述

后面说

3、EurekaClient端cloud-consumer-order80

将注册进EurekaServer成为服务消费者consumer,类似来尚硅谷上课消费的各位同学

cloud-consumer-order80

POM

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

YML

server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    register-with-eureka: true  # 表示是否将自己注册进EurekaServer默认为true。
    # 是否从EurekaServer抓取已有的注册信息,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

主启动加:@EnableEurekaClient

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

测试

1、先要启动EurekaServer, 7001 服务
2、再要启动服务提供者provider, 8001 服务
3、eureka服务器:http://localhost:7001/
在这里插入图片描述

4、bug

Failed to bind properties under 'eureka.client.service -ur' to java.util.Map <java.lang.String, java.lang String>
在这里插入图片描述

集群Eureka构建步骤

相互注册 相互守望

在这里插入图片描述

1、Eureka集群原理说明

服务注册:将服务信息注册进注册中心
服务发现:从注册中心上获取服务信息
实质:存key服务命取value调用地址

  1. 先启动eureka注册中心
  2. 启动服务提供者paymen支付服务
  3. 支付服务启动后会把自身信息(比如服务地址以别名方式注册进eureka)
  4. 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
  5. 消费者获得调用地址后,底层实际是利用HttpClien t技术,实现远程调用
  6. 消费者获得服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新一次服务调用地址

问题:微服务RPC远程服务调用最核心的是什么

高可用,试想你的注册中心只有一个only one,它出故障了那就呵呵(C v~ )" 了,会导致整个为服务环境不可用

解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错

2、EurekaServer集群环境构建步骤

参考cloud-eureka-server7001
新建cloud-eureka-server7002
改POM

修改映射配置

找到C:\Windows\System32\drivers\etc路径下的hosts文件
修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

写YML(以前单机)

server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己
    fetch-registry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/  # 设置与Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址。

主启动

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

测试

http://eureka7001.com:7001/
在这里插入图片描述

http://eureka7002.com:7002/
在这里插入图片描述

3、将支付服务8001微服务发布到上面2台Eureka集群配置中
eureka:
  client:
    register-with-eureka: true  # 表示是否将自己注册进EurekaServer默认为true。
    # 是否从EurekaServer抓取已有的注册信息,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
#      defaultZone: http://localhost:7001/eureka  #单机版

4、将订单服务80微服务发布到上面2台Eureka集群配置中
eureka:
  client:
    register-with-eureka: true  # 表示是否将自己注册进EurekaServer默认为true。
    # 是否从EurekaServer抓取已有的注册信息,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#      def
5、测试01

1、先要启动EurekaServer, 7001/7002服务
2、再要启动服务提供者provider,8001
3、再要启动消费者,80

http://eureka7001.com:7001/
http://eureka7002.com:7002/
在这里插入图片描述
http://localhost/consumer/payment/get/1
在这里插入图片描述

6、支付服务提供者8001集群环境构建

1、创建cloud-provider-payment8002其它跟8001一样
2、修改controller

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

return new CommonResult(200,"插入数据成功,serverport:"+serverPort,result);
return new CommonResult(200,"查询成功,serverport"+serverPort,payment);

http://eureka7001.com:7001/
在这里插入图片描述

7、负载均衡

1、订单业务不能写死
修改80 controller

//    public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

2、使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced   //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

3、ApplicationContextBean

使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

http://localhost/consumer/payment/get/1
在这里插入图片描述
在这里插入图片描述

8、测试02

1、先要启动EurekaServer, 7001/7002服务
2、再要启动服务提供者provider,8001
3、再要启动消费者,80
http://localhost/consumer/payment/get/1
在这里插入图片描述
在这里插入图片描述
负载均衡效果达到

8001/8002端口交替出现

Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了。


actuator微服务信息完善

1、主机名称:服务名称修改

修改cloud-provider- payment8001,8002一样

YML

eureka:
  instance:
    instance-id: payment8001	# 服务名称修改

在这里插入图片描述

2、访问信息有IP信息提示.
eureka:
  instance:
    prefer-ip-address: true  # 访问路径显示IP地址

服务发现Discovery

1、对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
2、修改cloud- provider- payment8001的Controller
@Autowired
    private DiscoveryClient discoveryClient;

@GetMapping(value = "/payment/discovery")
    public Object discovery(){
        List<String> services = discoveryClient.getServices();
        for (String element : services) {   // 快捷键:iter
            log.info("******element"+element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient;
    }
3、8001主启动类

添加@EnableDiscoveryClient标签

@EnableDiscoveryClient
4、自测

1、先要启动EurekaServer
2、再启动8001主启动类

http://localhost:8001/payment/discovery
在这里插入图片描述

eureka自我保护

1、概述

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。

一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。

在这里插入图片描述

自我保护机制:默认情况下EurekaClient定时向EurekaServer端发送心跳包如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包 ,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候EurekaServer会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)

2、故障现象

在这里插入图片描述

3、导致原因

一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存

属于 CAP 里面的AP分支

为什么会产生Eureka自我保护机制?

为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaServer不会
立刻将EurekaClient服务剔除

什么是自我保护模式?

默认情况下,如果EurekaServer在一 定时间内没有接收到某 个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了一因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过 “自我保护模式”来解决这个问题一当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么 这个节点就会进入自我保护模式。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一 种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服:务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

4、怎么禁止自我保护
1、注册中心eureakeServer端7001

1、出厂默认,自我保护机制是开启的 eureka.server.enable-self-preservation=true
2、使用 eureka.server.enable-self-preservation = false 可以禁止自我保护模式

eureka:
  server:
  # 关闭自我保护机制,保证不可用服务被及时踢除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000 # 默认90s,改成2s(时间内没有接收到某 个微服务实例的心跳,EurekaServer将会注销该实例)

3、关闭效果
在这里插入图片描述

4、在eurekaServer端7001处设置关闭自我保护机制

2、生产者客户端eureakeClient端8001
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒( 默认是30秒)
eureka.instance.lease-renewal-interval-in-seconds=30
#Eureka服务端在收到最后一次心跳后 等待时间上限,单位为秒(默认是90秒),起时将剔除服务
eureka.instance.lease-expiration-duration-in-seconds=90

测试:

  1. 先开7001,7002注册中心
  2. 再开8001提供者
  3. 再把8001stop掉
  4. 发现8001在服务中心被删除了

Zookeeper服务注册与发现

SpringCloud整合Zookeeper代替Eureka

1、注册中心Zookeeper

zookeeper是一个分布式协调工具,可以实现注册中心功能

关闭Linux服务器防火墙后启动zookeeper服务器

zookeeper服务器取代Eureka服务器,zk作为服务注册中心

2、服务提供者

新建cloud-provider-payment8004

POM

<!--springboot整合zookeeper客户端-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

YML

#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
  port: 8004

#服务别名----注册zookeeper到注册中心名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.168.111.144:2181

主启动类

@SpringBootApplication
@EnableDiscoveryClient  //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8004.class,args);
    }
}

Controller

@RestController
@Log4j
public class PaymentController {

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

    @RequestMapping(value = "/payment/zk")
    public String paymentzk(){
        return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

启动8004注册进zookeeper

启动zookeeper服务器

jar包冲突异常:zookeeper安装版本和导入的依赖版本冲突

<!--springboot整合zookeeper客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--先排除自带的zookeeper3.5.3-->
    <exclusions>
        <exclusion>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.9</version>
</dependency>

验证测试

http://localhost:8004/payment/zk

验证测试2

获得json串后用在线工具查看试试

get/services/cloud-provider-payment/bb75f135-6743-4346-a4ca-656cdac3bdd3

思考

服务节点是临时节点还是持久节点?

3、服务消费者

新建cloud-consumerzk-order80

POM 跟8004一样

YML 端口号改80,其它和8004一样

主启动

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

业务类

config

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced   //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

controller

@RestController
@Slf4j
public class OrderZKController {

    public static final String INVOKE_URL = "http://cloud-provide-payment";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/zk")
    public String paymentInfo(){
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
        return result;
    }

}

验证测试

ls/server

访问测试地址

localhost://consumer/payment/zk


consul服务注册与发现

Consul简介

是什么

Consul是一开源的分布式服务发现和配置管理系统, 由HashiCorp公司用Go语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要 单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。

能干嘛

  • 服务发现
  • 健康监测
  • KV存储
  • 多数据中心
  • 可视化Web界面

去哪下

https://www.consul.io/downloads

怎么玩

https://www.springcloud.cc/spring-cloud-consul.html

安装并运行Consul

1、官网安装说明:https://learn.hashicorp.com/consul.getting-startedinstall.html
2、下载完成后只有一 个consul.exe文件,硬盘路径下双击运行,查看版本号信息
3、开发模式启动:consul agent -dev
通过以下地址可以访问Consul的首页: http://localhost:8500

服务提供者

新建Module支付服务provider8006 :cloud-providerconsul-payment8006

POM

<!--springcloud consul - server-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

YML

#consul服务端口号
server:
  port: 8006

spring:
  application:
    name: consul-provider-payment
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        # hostname:127.0.0.1
        service-name: ${spring.application.name}

主启动类

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

业务类Controller

@RestController
@Slf4j
public class PaymentController {

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

    @RequestMapping(value = "/payment/consul")
    public String paymentConsul(){
        return "springcloud with consul:"+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

验证测试

http://localhost:8080/
在这里插入图片描述

服务消费者

新建Module消费服务order80 :cloud-consumerconsul-order80

POM 跟8006一样

YML 跟8006一样,ip为80

主启动类

@RestController
@EnableDiscoveryClient
public class OrderConsulMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderConsulMain80.class,args);
    }
}

配置Bean

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

Controller

@RestController
@Slf4j
public class OrderConsulController {
    public static final String INVOKE_URL = "http://consul-provide-payment";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/consul")
    public String paymentInfo(){
        String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
        return result;
    }
}

验证测试

在这里插入图片描述

访问测试地址

http://localhost/consumer/payment/consul

三个注册中心异同点

在这里插入图片描述

C:Consistency ( 强一致性)
A:Availability (可用性)
P:Partition tolerance ( 分区容错性)
CAP理论关注粒度是数据,而不是整体系统设计的策略


服务调用

Ribbon负载均衡服务调用

1、概述

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

已经进入维护

能干嘛

LB(负载均衡):集中式LB;进程内LB

集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

一句话:负载均衡+ RestTemplate调用

2、Ribbon负载均衡演示

1、架构说明
总结: Ribbon其实就是一个软负载均衡的客户端组件,
他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
在这里插入图片描述

Ribbon在工作时分成两步

  • 第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server
  • 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
    其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

2、POM

eureka-client里面自带ribbon

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      <version>2.2.1.RELEASE</version>
      <scope>compile</scope>
</dependency>

在这里插入图片描述

3、二说RestTemplate

1、getForObject方法/getForEntity方法

	//返回对象为响应体中的数据转化的对象,基本上可以理解为json
    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }

    //返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等
    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        if (entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else {
            return new CommonResult<>(444,"操作失败");
        }
    }

2、postForObject/ postForEntity

3、GET请求方法

4、POST请求方法员

3、Ribbon核心组件IRule

1、IRule :根据特定算法中从服务列表中选取一个要访问的服务

  • com.netflix.loadbalancer.RoundRobinRule:轮询
  • com.netflix.loadbalancer.RandomRule:随机
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重
  • WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
  • BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

2、如何替换

修改cloud-consumer-order80

注意配置细节

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

新建package com.wlq.myrule

上面包下新建MySelfRule规则类

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();//定义为随机
    }
}

主启动类添加@RibbonClient

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)

测试

http://localhost/consumer/payment/get/1

4、Ribbon负载均衡算法

负载均衡算法: rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
在这里插入图片描述
源码:

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (server == null && count++ < 10) {
                    List<Server> reachableServers = lb.getReachableServers();
                    List<Server> allServers = lb.getAllServers();
                    int upCount = reachableServers.size();
                    int serverCount = allServers.size();
                    if (upCount != 0 && serverCount != 0) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
            //compareAndSet 自旋锁
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

CAS自旋锁:compareAndSet

手敲轮询算法:

7001/7002集群启动 ,springcloud下新建lb文件夹

8001/8002微服务改造 controller

@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
    return serverPort;
}

80订单微服务改造

1、ApplicationContextBean去掉注解@LoadBalanced

2、LoadBalancer接口重写

public interface LoadBalancer{
	ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

3、MyLB

@Component
public class MyLB implements LoadBalancer{

	private AtomicInteger atomicInteger = new AtomicInteger(0);
	
	public final int getAndIncrement(){
		int current;
		int next;
		do {
			current = this.atomicInteger.get();
			next = current >= 2147483647 ? 0 : current + 1;
		}while(!this.atomicInteger.compareAndset(current,next));
		System.out.println("*****第几次访问,次数:next:"+next);
		return next ;
	}

	@Override
	public ServiceInstance instances(List<ServiceInstance> serviceInstances){
		int index = getAndIncrement() % serviceInstances.size();
		return serviceInstances.get(index); 
	}
}

4、OrderController

@GetMapping(value = "/consumer/ payment/1b")
public string getPaymentLB(){
	List<ServiceInstance> instances = discoveryClient . getInstances( serviceld:"CLOUD-PAYMENT-SERVICE");
	
	if(instances = null | | instances.size() <= 0){
		return nu11 ;
	}
	ServiceInstance serviceInstance = loadBalancer . instances (instances);
	URI uri = serviceInstance.getUri();
	
	return restTemplate.getForobject( url: uri+"/payment/1b" ,String.class);|
}

5、测试:http://localhost/consumer/payment/Ib


服务调用2

OpenFeign服务接口调用

1、概述

Feign是一个声明式WebService客户端。 使用Feign能让编写Web Service客户端更加简单。

它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支 持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装
使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

是什么

Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

GitHub

Feign能干什么

Feign旨在使编写Java Http客户端变得更容易。

前面在使用Ribbon+ RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装, 由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口 上面标注Mapper注解现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是, 通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

2、OpenFeign使用步骤

接口+注解 微服务调用接口+ @FeignClient

新建cloud-consumer-feign-order80

POM

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

YML

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

主启动 : @EnableFeignClients

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

业务类

1、业务逻辑接口+ @FeignClient配置调用provider服务

2、新建PaymentFeignService接口并新增注解@FeignClient

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeginService {
    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}

3、控制层Controller

@RestController
@Slf4j
public class OrderFeignController {

    @Autowired
    private PaymentFeginService paymentFeginService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeginService.getPaymentById(id);
    }
}

测试

1、先启动2个eureka集群7001/7002.
2、再启动2个微服务8001/8002
3、启动OpenFeign启动
4、http://localhost/consumer/payment/get/1
5、Feign自带负载均衡配置项

小总结

在这里插入图片描述

3、OpenFeign超时控制

1.超时设置,故意设置超时演示出错情况

服务提供方8001故意写暂停程序

@GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeout(){
        try {
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return serverPort;
    }

服务消费方80 PaymentFeignService 添加超时方法

	@GetMapping(value = "/payment/feign/timeout")
    public String paymentFeignTimeout();

服务消费方80 OrderFeignController 添加超时方法

@GetMapping(value = "/consumer/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //openfeign-ribbon,客户端一般默认等待1分钟
        return paymentFeginService.paymentFeignTimeout();
    }

测试 启动7001/7002/8001/80

http://localhost/consumer/payment/feign/timeout

错误页面
在这里插入图片描述
OpenFeign默认等待1秒钟,超过后报错

2.是什么,OpenFeign默认支持Ribbon

默认Feign客户端只等待一秒钟, 但是服务端处理需要超过1秒钟,导致Feign喀户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

ymI文件中开启配置

3.YML文件里需要开启OpenFeign客户端超时控制
# 设置feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
  # 指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000
  # 指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000

4、OpenFeign日志打印功能日志打印功能

是什么

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

说白了就是对Feign接口的调用情况进行监控和输出

日志级别

  • NONE :默认的,不显示任何日志;
  • BASIC :仅记录请求方法、URL、 响应状态码及执行时间;
  • HEADERS :除了BASIC定义的信息之外,还有请求和响应的头信息;
  • FULL : 除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

FULL 演示:配置日志bean ,建个config.FeignConfig

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

YML文件里需要开启日志的Feign客户端

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.wlq.springcloud.service.PaymentFeginService: debug

后台日志查看

http://localhost/consumer/payment/get/1
在这里插入图片描述


服务降级

Hystrix断路器

1、概述

分布式系统面临的问题

复杂分布式体系结构中的应用程序有数个依赖关系,每个依赖关系在某些时候将不可避免地失败。

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

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

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

是什么

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

"断路器”本身是一种开关装置, 当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack) ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

能干嘛

服务降级
服务熔断
接近实时的监控

官网资料

https://github.com/Neflix/Hystrix/wiki/How-To-Use

Hystrix官宣,停更进维

2、Hystrix重要概念

1、服务降级。

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示, fallback

哪些情况会出发降级

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级
2、服务熔断

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

就是保险丝:服务的降级->进而熔断->恢复调用链路

3、服务限流

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

3、hystrix案例

1、构建

新建cloud-provider-hystrix-payment8001

POM

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

YML

server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true  # 表示是否将自己注册进EurekaServer默认为true。
    # 是否从EurekaServer抓取已有的注册信息,默认为true。 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
#      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  #集群版
      defaultZone: http://eureka7001.com:7001/eureka  #单机版


主启动

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

业务类 service controller

@Service
public class PaymentService {

    /**
     * 正常访问,肯定ok
     * @param id
     * @return
     */
    public String paymentInfo_OK(Integer id){
        return "线程池: "+Thread.currentThread().getName()+"paymentInfo_OK,id:  "+id+"\t" +"O(∩_∩)O哈哈~";
    }

    public String paymentInfo_TimeOut(Integer id){

        int timeNumber = 3;
        try {

            TimeUnit.SECONDS.sleep(timeNumber);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        return "线程池: "+Thread.currentThread().getName()+"paymentInfo_TimeOut,id:  "+id+"\t" +"O(∩_∩)O哈哈~"+"耗时(秒):"+timeNumber;
    }
}

controller

@RestController
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

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

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("****result:"+result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("****result:"+result);
        return result;
    }
}

正常测试

启动eureka7001

启动cloud-provider-hystrix-payment8001

访问:
http://localhost:8001/payment/hystrix/ok/1
在这里插入图片描述

http://localhost:8001/payment/hystrix/timeout/1
在这里插入图片描述

上述module均OK:以上述为根基平台,从正确->错误->降级熔断->恢复演示

2、高并发测试

上述在非高并发情形下,还能勉强满足 but… 测试高并发

Jmeter压测测试

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
在这里插入图片描述

再来一个访问

http://localhost:8001/payment/hystrix/ok/1

看演示结果

两个都在自己转圈圈

为什么会被卡死

tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

Jmeter压测结论

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

看热闹不嫌弃事大,80新建加入

1、新建cloud-consumer-feign-hystrix-order80

2、POM

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

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

3、YML

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

4、主启动类

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

5、业务类

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
@RestController
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("****result:"+result);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("****result:"+result);
        return result;
    }
}

6、正常测试

http://localhost/consumer/payment/hystrix/ok/1

7、高并发测试

2W个线程压8001

消费端80微服务再去访问正常的Ok微服务8001地址
http://localhost/consumer/payment/hystrix/ok/2

消费者80,o(╥﹏╥)o:要么转圈圈等待,要么消费端报超时错误

3、故障现象和导致原因

8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕

80此时调用8001,客户端访问响应缓慢,转圈圈

4、上诉结论

正因为有上述故障或不佳表现

才有我们的降级/容错/限流等技术诞生

5、如何解决?解决的要求

超时导致服务器变慢(转圈)

超时不再等待

出错(宕机或程序运行出错)

出错要有兜底.

解决.

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己降级处理
6、服务降级

1、降级配置 :@HystrixCommand

2、8001先从自身找问题

设置自身调用超时时间的峰值,峰值内可以正常运行,

超过了需要有兜底的方法处理,作服务降级fallback

3、8001 fallback ;修改cloud-provider-hystrix-payment8001

1、业务类启用,@HystrixCommand报异常后如何处理

一旦调用服务方法失败并抛出了错误信息后,会自动调用@ HystrixCommand标注好的falbaqkMethod调用类中的指定方法

@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {//3秒之内走正常的业务逻辑,超时就执行fallback兜底的方法
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMillinseconds",value = "3000")//设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
    })
    public String paymentInfo_TimeOut(Integer id){

        int timeNumber = 5;
        //int age = 10/0;
        try {

            TimeUnit.SECONDS.sleep(timeNumber);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        return "线程池: "+Thread.currentThread().getName()+"paymentInfo_TimeOut,id:  "+id+"\t" +"O(∩_∩)O哈哈~"+"耗时(秒):"+timeNumber;
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池: "+Thread.currentThread().getName()+"paymentInfo_TimeOutHandler,id:  "+id+"\t" +"o(╥﹏╥)o";
    }

2、主启动类激活,添加新注解@EnableCircuitBreaker

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

http://localhost:8001/payment/hystrix/timeout/1
在这里插入图片描述
上面故意制造两个异常:

  1. int age = 10/0;计算异常
  2. 我们能接受3秒钟,它运行5秒钟,超时异常。

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_ TimeOutHandler

4、80fallback :修改cloud-consumer-feign-hystrix-order80

1、80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护

2、题外话,切记

我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

3、YML

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

feign:
  httpclient:
    enabled: true

4、主启动类 :@EnableHystrix

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

5、业务类

controller

@RestController
@Slf4j
public class OrderHystrixController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
//        log.info("****result:"+result);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
//        log.info("****result:"+result);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "你好消费者80,对方支付系统繁忙请十秒钟后再试o(╥﹏╥)o";
    }
}

6、测试:

http://localhost/consumer/payment/hystrix/timeout/1
在这里插入图片描述
http://localhost:8001/payment/hystrix/timeout/1
在这里插入图片描述

5、目前问题

每个业务方法对应一个兜底的方法,代码膨胀

统一和自定义的分开

6、解决问题

1、每个方法配置一个? ? ?膨胀

feign接口系列

@DefaultProperties(defaultFallback ="")

除了个别重要核心业务有专属,它普通的可以通过@DefaultProperties(defaultFallback= ")统一跳转到统一处理结果页面,通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

controller配置
//放到类名上面
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

//下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global异常,请十秒钟后再试o(╥﹏╥)o";
    }

2、和业务逻辑混一起? ? ?混乱

服务降级,客户端去调用服务端,碰上服务端宕机或关闭

本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系

只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

未来我们要面对的异常

  • 运行
  • 超时
  • 宕机

再看我们的业务类PaymentController

修改cloud-consumer-feign-hystrix-order80

根据cloud-consumer-feign-hystrix- order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口, 统一为接口里面的方法进行异常处理

PaymentFallbackService类实现PaymentHystrixService接口

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "----------PaymentFallbackService---paymentInfo_OKo(╥﹏╥)o";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "----------PaymentFallbackService----paymentInfo_TimeOuto(╥﹏╥)o";
    }
}

YML

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

feign:
  httpclient:
    enabled: true
  hystrix:
    enabled: true	#开启feign的hystrix支持,默认是false

PaymentHystrixService接口:fallback = PaymentFallbackService.class

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

测试

1、单个eureka先启动7001

2、PaymentHystrixMain8001,80启动

3、正常访问测试

http://localhost/consumer/payment/hystrix/ok/1
在这里插入图片描述

4、故意关闭微服务8001
在这里插入图片描述

5、客户端自己调用提示

此时服务端provider已经down了,但是我们做了服务降级处理,

让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

7、服务熔断

在这里插入图片描述

熔断机制概述

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

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

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand.

实操:

修改cloud-provider-hystrix-payment8001

PaymentService :why配置这些参数

//========服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "ture"),   // 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),  // 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),// 时间窗口期
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),    // 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if (id < 0){
            throw new RuntimeException("*****id 不能为负数");
        }
        String serialNumber = IdUtil.simpleUUID();  //生成的是不带-的字符串,类似于: b17f24ff026d40949c85a24f4f375d42

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id 不能为负数,请稍后再试,o(╥﹏╥)o  id:"+id;
    }

PaymentController

//=======服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("****result:"+result);
        return result;
    }

测试,

自测cloud-provider-hystrix-paymnt8001

正确:http://localhost:8001/payment/circuit/1
在这里插入图片描述

错误:http://localhost:8001/payment/circuit/-1
在这里插入图片描述

一次正确一次错误trytry

多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能访问正确的结果

重点测试

原理:(小总结)

1、大神结论
在这里插入图片描述
2、熔断类型:

  • 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR (平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
  • 熔断关闭:熔断关闭不会对服务进行熔断
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

3、涉濒到断路器的三个重要参数
在这里插入图片描述

  • 快照时间窗
    • 1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数阀值
    • 2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阀值。
    • 3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

4、断路器开启或者关闭的条件

  • 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
  • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
  • 到达以上阀值,断路器将会开启
  • 当开启的时候,所有请求都不会进行转发
  • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
8、服务限流

后面高级篇讲解alibaba的Sentinel说明


4、hystrix工作流程

官网:https://github.com/Netflix/Hystrix/wiki/How-it-Works
在这里插入图片描述
首先我们看一下上方的这张图,这个图完整的描述了Hystrix的工作流程:

  1. 每次调用都会创建一个HystrixCommand

  2. 执行execute或queue做同步\异步调用

  3. 判断熔断器是否打开,如果打开跳到步骤8,否则进入步骤4

  4. 判断线程池/信号量是否跑满,如果跑满进入步骤8,否则进入步骤5

  5. 调用HystrixCommand的run方法,如果调用超时进入步骤8

  6. 判断是否调用成功,返回成功调用结果,如果失败进入步骤8

  7. 计算熔断器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态

  8. 降级处理逻辑,根据上方的步骤可以得出以下四种情况会进入降级处理:

    • 熔断器打开
    • 线程池/信号量跑满
    • 调用超时
    • 调用失败
  9. 返回执行成功结果

5、服务监控hystrixDashboard

1、概述:

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

2、仪表盘9001

新建cloud-consumer-hystrix-dashboard9001

POM

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

YML

server:
  port: 9001

主启动:HystrixDashboardMain9001 + 新注解@EnableHystrixDashboard

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

所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置

<!--actuator 监控信息完善-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

http://localhost:9001/hystrix

3、断路器演示

修改cloud-provider-hystrix- payment8001

注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径量
Unable to connect to Command Metric Stream.404

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

    /**
     *  此配置是为了服务监控而配置,与服务容错本身无关, springcloud 升级后的玩
     *  ServletRegistrationBean因为springboot的默认路径不是"/bystrix.stream",
     *  只要在自己的项目里配置上:下面的servlet就可以了
     */

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

监控测试

启动1个eureka或者3个eureka集群均可

观察监控窗口:

1、9001监控8001

http://localhost:8001/hystrix.stream
在这里插入图片描述

2、测试地址

http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1

3、如何看?

先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。
在这里插入图片描述


服务网关

GateWay

1、概述简介

是什么

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuu|网关;但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自 己研发了一个网关替代Zuul,那就是SpringCloud Gateway一句话: gateway是原zuul1.x版的替代
在这里插入图片描述
概述:

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5, Spring Boot 2和Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由, 以及提供一些强大的过滤器功能, 例如: 熔断、限流、重试等

能干嘛

SpringCloud Gateway是Spring Cloud的一个全新项目,基纡Spring 5.0+ Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一 的API路由管理方式。

SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty

Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

一句话:SpringCloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
在这里插入图片描述

微服务结构中网关在哪里

在这里插入图片描述

Spring Cloud Gateway具有如下特性:

  • 基于Spring Framework 5, Project Reactor和Spring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter (过滤器) ;
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器) ;
  • 请求限流功能;
  • 支持路径重写。

Spring Cloud Gateway与Zuul的区别

在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul:

  • 1、Zuul1.x, 是一个基于阻塞I/ 0的API Gateway
  • 2、Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(WebSocket) Zuul的设计模式和Nginx较像,每次|/ O操作都是从工作线程中选择一个执行, 请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul 用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。
  • 3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x 有较大提升在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS (每秒请求数)是Zuul 的1. 6倍。
  • 4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞 API
  • 5、Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有 更好的开发体验

WebFlux是什么:

传统的Web框架,比如说: struts2, springmvc等都是基于Servlet API与Servlet容器基础之上运行的。

但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一 个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty, Undertow及 支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)

Spring WebFlux是Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

2、三大核心概念

1、Route(路由)

路由是构建网关的基本模块,它由ID, 目标URI, 一系列的断言和过滤器组成,如果断言为true则匹配该路由

2、Predicate(断信)

参考的是Java8的java.util.function.Predicate

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

3、Filter(过滤)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

4、总体

在这里插入图片描述

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行些精细化控制。

predicate就是我们的匹配条件;

而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现个具体的路由了

3、Gateway工作流程

在这里插入图片描述

户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。

Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前( "pre”)或之后( “post” )执行业务逻辑。

Filter在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,旧志的输出,流量监控等有着非常重要的作用。

核心逻辑= 路由转发+执行过滤器链

4、入门配置

新建Module :cloud-gateway-gateway9527

POM

    <dependencies>
        <!--geteway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

        <!--一般基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh           # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001  # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**    # 断言,路径相匹配的进行路由

        - id: payment_routh2          # 路由的ID,没有固定规则但要求唯-,建议配合服务名
          uri: http://localhost:8001  # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

业务类 :无

主启动类

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

9527网关如何做路由映射那? ? ?

cloud-provider-payment8001看看controller的访问地址:get,lb

我们目前不想暴露8001端口,希望在8001外面套一层9527

YML新增网关配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh           # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001  # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**    # 断言,路径相匹配的进行路由

        - id: payment_routh2          # 路由的ID,没有固定规则但要求唯-,建议配合服务名
          uri: http://localhost:8001  # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

测试

启动7001

启动8001:cloud-provider-payment8001

启动9527网关

http://eureka7001.com:7001/
在这里插入图片描述

访问说明

添加网关前:http://localhost:8001/payment/get/1
在这里插入图片描述

添加网关后:http://localhost:9527/payment/get/1
在这里插入图片描述
断言路径要和8001对应
在这里插入图片描述

YML配置说明

Gateway网关路由有两种配置方式:

1、在配置文件ymI中配置,见前面的步骤

2、代码中注入RouteLocator的Bean

官网案例
在这里插入图片描述

百度国内新闻网址,需要外网:http://news.baidu.com/guonei

自己写一个

业务需求:通过9527网关访问到外网的百度新闻网址

cloud-gateway-gateway9527

业务实现

config

@Configuration
public class GateWayConfig {
    /**
     * 配置了一个id为route -name的路由规则,
     * 当访问地址http://localhost:9527/guonei时会自动转发到地址: http://news.baidu.com/guonei
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
        RouteLocatorBuilder.Builder routes = builder.routes();

        routes.route("path_route_wlq",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

访问:http://localhost:9527/guonei

5、通过微服务名实现动态路由

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动:一个eureka7001 +两个服务提供者8001/8002

POM

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

YML

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh           # 路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001  # 匹配后提供服务的路由地址,写死了的
          uri: lb://cloud-payment-service # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**    # 断言,路径相匹配的进行路由

        - id: payment_routh2          # 路由的ID,没有固定规则但要求唯-,建议配合服务名
#          uri: http://localhost:8001  # 匹配后提供服务的路由地址
          uri: lb://cloud-payment-service # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**     # 断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试

http://localhost:9527/payment/lb:8001/8002两个端口切换

6、Predicate的使用

是什么,启动我们的gateway9527
在这里插入图片描述

Route Predicate Factories这个是什么东东?

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都 与HTTP请求的不同属性匹配。多个RoutePredicateI厂可以进行组合

Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

常用的Route Predicate

  1. After Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: after_route
6           uri: http://localhost:80017           predicates:
8             - After=2020-04-20T23:57:57.308+08:00[Asia/Shanghai]
		# 所述After断言有一个参数,一个datetime(其是Java ZonedDateTime)。该断言匹配在指定日期时间之后发生的请求
  1. Before Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: before_route
6           uri: http://localhost:80017           predicates:
8             - Before=2020-04-21T23:57:57.308+08:00[Asia/Shanghai]
		# 所述Before断言有一个参数,一个datetime(其是Java ZonedDateTime)。该断言匹配在指定之前发生的请求datetime
  1. Between Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: between_route
6           uri: http://localhost:8001
7           predicates:
8             - Between=2020-04-20T23:57:57.308+08:00[Asia/Shanghai], 2020-04-21T23:57:57.308+08:00[Asia/Shanghai]
		# 该Between断言有两个参数,datetime1并且datetime2 这是Java ZonedDateTime对象。该断言匹配在之后datetime1和之前发生的请求datetime2。该datetime2参数必须是后datetime1
  1. Cookie Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: cookie_route
6           uri: http://localhost:8001
7           predicates:
8             - Cookie=username, xiaoming
		# 所述Cookie断言采用两个参数,该cookie name和regexp(其是Java正则表达式)。该断言匹配具有给定名称且其值与正则表达式匹配的cookie
  1. Header Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: header_route
6           uri: http://localhost:8001
7           predicates:
8             - Header=X-Request-Id, \d+
		# 所述Header断言采用两个参数,报头name和一个regexp(其是Java正则表达式)。该断言与具有给定名称且其值与正则表达式匹配的标头匹配
  1. Host Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: host_route
6           uri: http://localhost:8001
7           predicates:
8             - Host=**.x.com
		# 该Host断言需要一个参数:主机名的列表patterns。该模式是带有.分隔符的Ant样式的模式。断言与Host匹配模式的标头匹配。
  1. Method Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: method_route
6           uri: http://localhost:8001
7           predicates:
8             - Method=GET
		# 所述Method断言需要methods的参数,它是一个或多个参数:HTTP方法来匹配
  1. Path Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: path_route
6           uri: http://localhost:8001
7           predicates:
8             - Path=/payment/get/**
		# 该Path断言有两个参数:春天的列表PathMatcher patterns和一个可选的标志叫matchOptionalTrailingSeparator。
  1. Query Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: query_route
6           uri: http://localhost:8001
7           predicates:
8             - Query=green
		# 所述Query断言采用两个参数:所要求的param和可选的regexp(其是Java正则表达式)
  1. RemoteAddr Route Predicate
1 spring:
2   cloud:
3     gateway:
4       routes:
5         - id: query_route
6           uri: http://localhost:8001
7           predicates:
8             - RemoteAddr=192.168.1.1/24
		# 所述RemoteAddr断言需要的列表sources,其是CIDR的表示法(IPv4或IPv6)的字符串,如192.168.0.1/16(其中192.168.0.1是一个IP地址和16一个子网掩码)
  1. Weight Route Predicate
 1 spring:
 2   gateway:
 3     discovery:
 4       routes:
 5         - id: weight_high
 6           uri: http://localhost:8001
 7           predicates:
 8             - Weight=group1, 8
 9         - id: weight_low
10           uri: http://localhost:8002
11           predicates:
12             - Weight=group1, 2
		# 该Weight断言有两个参数:group和weight(一个int)。权重是按组计算的
  1. 小总结

说白了,Predicate就是为 了实现一组匹配规则,让请求过来找到对应的Route进行处理.

7、Filter的使用

是什么

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。

Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂 类来产生

Spring Cloud Gateway的Filter

生命周期,只有两个

  • pre
  • post

种类,只有两个

  • GatewayFilter(单一的)
  • GlobalFilter(全局的)

常用的GatewayFilter

官网查看:

自定义过滤器

自定义全局GlobalFilter

两个主要接口介绍:implements GlobalFilter,Ordered

能干嘛

案例代码

测试
正确:http://localhost:9527/payment/lb?uname=z3
在这里插入图片描述

错误:http://localhost:9527/payment/lb
在这里插入图片描述


服务配置

SpringCloud Config分配式配置中心

1、概述

分布式系统面临的配置问题

微服务意味着要将单体应用中的业务拆分成一个个子服务 ,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一集中式的、动态的配置管理设施是必不可少的。

SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一 个application.yml, 上百个配置文件的管理…

是什么

在这里插入图片描述

SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置

SpringCloud Config分为服务端和客户端两部分。

服务端也称为分布式配置中心它是一个独立的微服务应用, 用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息, 这样就有助于对环境配置进行版本管理,粗可以通过git客户端工舫便的管理和访问配置内容

能干嘛

  • 集中管理配置文件

  • 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release

  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息

  • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置

  • 将配置信息以REST接口的形式暴露

与GitHub整合配置

由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式

官网

2、Config服务端配置与测试

用你自己的账号在GitHub上新建一个名为springcloud-config的新Repository

由上一步获得刚新建的git地址,git@ github.com:zzyybs/springcloud-config.git

本地硬盘目录上新建git仓库并clone

此时在本地D盘符下D:(44\SpringCloud2020\springcloud-config

新建Module模块cloud-config-center-3344 它即为Cloud的配置中心模块cloudConfig Center

config-dev.yml内容

config:
info: "master branch, springcloud-config/config-dev.yml version=2"

POM

    <dependencies>
        <!--config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

        <!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

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

        <!--actuator 监控信息完善-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--一般基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

YML

server:
  port: 3344

spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: git@github.com:wlq-567/springcloud-config.git  # github上仓库地址
          # 搜索目录
          search-paths:
            - springcloud-config
      # 读取分支
      label: main

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

主启动类

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

windows下修改hosts文件,增加映射 :127.0.0.1 config-3344.com

测试通过Config微服务是否可以从GitHub.上获取配置内容

http://config-3344.com:3344/master/config-dev.yml

配置读取规则

在这里插入图片描述

/{application}-{profile}.yml

http://config-3344.com:3344/application-dev.yml
http://config-3344.com:3344/application-test.yml
http://config-3344.com:3344/application-xxx.yml(不存在的配置)

/{application}/{profile}[/{label}]

http://config-3344.com:3344/application/dev/master
http://config-3344.com:3344/application/test/master
http://config-3344.com:3344/application/xxx/master

/{label}/{application}-{profile}.yml

http://config-3344.com:3344/master/application-dev.yml
http://config-3344.com:3344/master/application-test.yml

master分支

  • http://config-3344.com:3344/master/config-dev.yml
  • http://config-3344.com:3344/master/config-test.yml
  • http://config-3344.com:3344/master/config-prod.yml

dev分支

  • http://config-3344.com:3344/dev/config-dev.yml
  • http://config-3344.com:3344/dev/config-test.yml
  • http://config-3344.com:3344/dev/config-prod.yml

成功实现了用SpringCloud Config通过GitHub获取配置信息

3、Config客户端配置与测试

+新建cloud-config-client-3355

POM

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

        <!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

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

        <!--actuator 监控信息完善-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

bootstrap.yml

applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高

Spring Cloud会创建一个"Bootstrap Context" ,作为Spring应用的Application Context的父上下文。初始化的时候,BootstrapContext’负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。

Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context’和Application Context有着不同的约定所以新增了一个bootstrap.ymI文件,保证Bootstrap Context和Application Context配置的分离。

要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载的.bootstrap.yml优先级高于application.yml

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    # config 客户端配置
    config:
      label: main  # 分支名称
      name: config  # 配置文件名字
      profile: dev   # 读取后缀名称  上述3个综合: master 分支l:config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev. yml
      uri: http://localhost:3344  # 配置中心地址

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version

主启动

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

业务类

@RestController
public class ConfigClientController {

    @Value("$(config.info)") //github读取的值,要与里面内容一致
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

测试

3344自测:
http://config-3344.com:3344/master/config-prod.yml
http://config-3344.com:3344/master/config-dev.yml

启动3355作为Client准备访问:http://localhost:3355/configInfo

成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息

问题随时而来,分布式配置的动态刷新问题

Linux运维修改GitHub_上的配置文件内容做调整

刷新3344,发现ConfigServer配置中心立刻响应

刷新3355,发现ConfigClient客户端没有任何响应

3355没有变化除非自己重启或者重新加载

难到每次运维修改配置文件,客户端都需要重启? ?噩梦

4、Config客户端之动态刷新

避免每次更新配置都要重启客户端微服务3355

动态刷新:步骤

1、修改3355模块
2、POM引入actuator监控

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

3、修改YML,暴露监控端口

#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

4、3355添加@RefreshScope业务类Controller修改

@RestController
@RefreshScope
public class ConfigClientController {
    
    @Value("$(config.info)") //github读取的值,要与里面内容一致
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return configInfo;
    }
}

5、此时修改github—> 3344 —>3355

发现3355并没有改变

6、How

需要运维人员发送Post请求刷新3355:curl -X POST “http://localhost:3355/actuator/refresh”

想想还有什么问题?

假如有多个微服务客户端3355/3366/3377。
每个微服务都要执行一次post请求, 手动刷新?
可否广播, 一次通知,处处生效?
我们想大范围的自动刷新,求方法
消息总线


服务总线

SpringCloud Bus消息总线

1、概述

1、上一讲解的加深和扩充,一言以蔽之

分布式自动刷新配置功能
Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新。

在这里插入图片描述

2、是什么

Bus支持两种消息代理: RabbitMQ 和Kafka

Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,

它整合 了Java的事件处理机制和消息中间件的功能。

Spring Clud Bus目前支持RabbitMQ和Kafka.

3、能干嘛
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器, 可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道

4、为何被称为总线

什么是总线

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题, 并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。

基本原理

ConfigClient实例都监听MQ中同-个topic(默认是springCloudBus)。当-个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

2、RabbitMQ环境配置

1、安装Erlang,地址:https://www.erlang.org/downloads

2、安装RabbitMQ,地址:https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.3/rabbitmq-server-3.8.3.exe

版本对应关系:https://www.rabbitmq.com/which-erlang.html

3、cmd进入RabbitMQ安装目录下的sbin目录,如:D:\devSoft\RabbitMQ Scrverk\rabbitmq_server-3.7.14\sbin

4、输入以下命令启动管理功能

#启动rabbitmq_managemen是管理后台的插件、我们要开启这个插件才能通过浏览器访问登录页面
rabbitmq-plugins enable rabbitmq_management

#启动rabbitmq
rabbitmq-server start

#访问登录页面 用户名和密码都是guest
http://localhost:15672

5、访问地址查看是否安装成功: http://localhost:15672/

6、输入账号密码并登录: guest guest

3、SpringCloud Bus动态刷新全局广播

1、必须先具备良好的RabbitMQ环境先

2、演示广播效果,增加复杂度,再以3355为模板再制作一个3366

新建:cloud-config-client-3366

POM

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

        <!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

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

        <!--actuator 监控信息完善-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

YML

server:
  port: 3366

spring:
  application:
    name: config-client
  cloud:
    # config 客户端配置
    config:
      label: main  # 分支名称
      name: config  # 配置文件名字
      profile: dev   # 读取后缀名称  上述3个综合: master 分支l:config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev. yml
      uri: http://localhost:3344  # 配置中心地址

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/


#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

主启动

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

controller

@RestController
@RefreshScope
public class ConfigClientController {

    @Value("${server.port")
    private String sererPort;

    @Value("$(config.info)") //github读取的值,要与里面内容一致
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return "serverPort"+sererPort+"\t\n\n configInfo:"+configInfo;
    }

}

3、设计思想

  1. 利用消息总线触发一个客 户端/bus/refresh,而刷新所有客户端的配置口
    在这里插入图片描述

  2. 利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
    在这里插入图片描述

图二的架构显然更加适合,图一不适合的原因如下

  • 打破了微服务的职责单- -性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
  • 破坏了微服务各节点的对等性。
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

4、给cloud-config-center-3344配置中心服务端添加消息总线支持

pom

<!--添加消息总线rabbitmq支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

yml

server:
  port: 3344

spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://github.com/wlq-567/springcloud-config.git  # github上仓库地址
          # 搜索目录
          search-paths:
            - springcloud-config
      # 读取分支
      label: main

# rabbitmq相关配置
rabbitmq:
  host: localhost
  port: 5672
  username: guest
  password: guest
  

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
      
# rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints:  # 暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'

5、给cloud-config-client-3355客户端添加消息总线支持

<!--添加消息总线rabbitmq支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    # config 客户端配置
    config:
      label: main  # 分支名称
      name: config  # 配置文件名字
      profile: dev   # 读取后缀名称  上述3个综合: master 分支l:config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev. yml
      uri: http://localhost:3344  # 配置中心地址
      
  # rabbitmq相关配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/


#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

6、给cloud-config-client-3366客户端添加消息总线支持

<!--添加消息总线rabbitmq支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
server:
  port: 3366

spring:
  application:
    name: config-client
  cloud:
    # config 客户端配置
    config:
      label: main  # 分支名称
      name: config  # 配置文件名字
      profile: dev   # 读取后缀名称  上述3个综合: master 分支l:config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev. yml
      uri: http://localhost:3344  # 配置中心地址
      
  # rabbitmq相关配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/


#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

7、测试

发送请求:

curl -X POST "http://localhost:3344/actuator/bus-refresh'

配置中心:
http://config-3344.com:3344/config-dev.yml
http://localhost:3355/configlnfo

客户端:
http://localhost:3366/configInfo

获取配置信息,发现都已经刷新了

8、一次修改,广播通知,处处生效

4、SpringCloud Bus动态刷新定点通知

1、不想全部通知,只想定点通知

2、简单一句话

指定具体某一个实例生效而不是全部
公式: http://localhost:配置 中心的端口号/actuator/bus-refresh/ {destination}

/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例

3、案例

我们这里以刷新运行在3355端口,上的config-client为例

curl -X POST "http://localhost:3344/actuator/bus refresh/config -client:3355"

4、通知总结All
在这里插入图片描述


SpringCloud Stream消息驱动

1、消息驱动概述

是什么

一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

什么是SpringCloudStream

官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。

应用程序通过inputs或者outputs来与Spring Cloud Stream中binder对象交互。

通过我们配置来binding(绑定),而Spring Cloud Stream的binder对象负责与消息中间件交互。

所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。

Spring Cloud Stream为-些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQ、Kafka.

官网:https://spring.io/projects/spring-cloud-stream#overview
中文文档:https://m.wang1314.com/doc/webapp/topic/20971999.html

设计思想

标准MQ:

  • 生产者/消费者之间靠消息媒介传递信息内容
  • 消息必须走特定的通道
  • 消息通道里的消息如何被消费呢,谁负责收发处理:消息通道MessageChannel的子接口SubscribableChannel, 由MessageHandler消息处理器所订阅
    在这里插入图片描述

为什么用Cloud Stream

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中-种, 后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的, 一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

binder:通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

Stream中的消息通信方式遵循了发布-订阅模式

Spring Cloud Stream标准流程套路

在这里插入图片描述

Binder:很方便的连接中间件,屏蔽差异

Channel:通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channe|对队列进行配置

Source和Sink:简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。

编码API和常用注解

在这里插入图片描述

2、案例说明

1、RabbitMQ环境已经OK

2、工程中新建三个子模块

  • cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
  • cloud-stream-rabbitmq-consumer8802,作为消息接收模块
  • cloud-stream-rabbitmq-consumer8803作为消息接收模块

3、消息驱动之生产者

新建Module: cloud-stream-rabbitmq-provider8801

POM

    <dependencies>
        <!--rabbit-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

        <!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

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

        <!--actuator 监控信息完善-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

YML

server:
  port: 8801

spring:
  application:
    name: config-stream-provider
  cloud:
    stream:
      binders:  # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit:  # 表示定义的名称,用于binding整和
          type: rabbit  # 消息组件类型
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        output:   # 通道名称
          destination: studyExchange  # 表示要使用exchange定义
          content-type: application/json  # 设置消息类型
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置


eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    lease-renewal-interval-in-seconds: 2  # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true # 访问的路径变为IP地址

主启动类StreamMQMain8801

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

业务类

service

public interface IMessageProvider {
    public String send();
}
@EnableBinding(Source.class)    //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {

    @Autowired
    private MessageChannel output;  //消息发送管道

    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("*****serial"+serial);
        return serial;
    }
}

controller

@RestController
public class SendMessageController {

    @Autowired
    private IMessageProvider messageProvider;

    @GetMapping(value = "/sendMessage")
    public String sendMessage(){
        return messageProvider.send();
    }
}

测试

启动7001 eureka

启动rabbitmq

启动8801

访问:http://localhost:8801/sendMessage

4、消息驱动之消费者

新建Module:cloud-stream-rabbitmq-consumer8802

POM :跟8801一样

YML :改为 input 其它 和8001一样

input:   # 通道名称
          destination: studyExchange  # 表示要使用exchange定义

主启动类StreamMQMain8802

业务类

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {

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

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message){
        System.out.println("消费者1号,----》接收到消息:"+message.getPayload()+"\t port:"+serverPort);
    }
}

测试8801发送8802接收消息

http://localhost:8801/sendMessage

5、分组消费与持久化

依照8802,clone出来一 份运行8803

启动

  • RabbitMQ
  • 7001:服务注册
  • 8801:消息生产
  • 8802:消息消费
  • 8803:消息消费

运行后有两个问题

  1. 有重复消费问题
  2. 消息持久化问题

消费

目前是8802/8803同时都收到了,存在重复消费问题:如何解决分组和持久化

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,

那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。

这时我们就可以使用Stream中的消息分组来解决

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费次。
不同组是可以全面消费的(重复消费),
同一组内会发生竞争关系,只有其中一个可以消费。
在这里插入图片描述

分组

1、原理:

微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。

不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

2、8802/8803都变成不同组,group两个不同,添加分组group: wlqA;group: wlqB

spring:
  application:
    name: config-stream-consumer
  cloud:
    stream:
      binders:  # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit:  # 表示定义的名称,用于binding整和
          type: rabbit  # 消息组件类型
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input:   # 通道名称
          destination: studyExchange  # 表示要使用exchange定义
          content-type: application/json  # 设置消息类型
          binder: defaultRabbit # 设置要绑定的消息服务的具体设置
          group: wlqA

结果:还是两个都能接受到,因为默认就是不同分组

3、8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。

4、8802/8803都变成相同组,group两个相同

将group改为相同:group: wlqA

持久化

通过上述,解决了重复消费问题,再看看持久化

停止8802/8803并去除掉8802的分组group: wlqA:8803的分组group: wlqA没有去掉

8801先发送4条消息到rabbitmq:http://localhost:8801/sendMessage

再启动8802,group: wlqA已经被删了,无分组属性配置,结果:后台没有打出来消息

再启动8803,有分组属性配置(group: wlqA),后台打印出来了8801先发送4条消息


SpringCloud Sleuth 分布式请求链路跟踪

1、概述

是什么:

https://github.com/spring-cloud/spring-cloud-sleuth

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案

在分布式系统中提供追踪解决方案并且兼容支持了zipkin

2、搭建链路监控步骤

1、zipkin

下载

运行jar

java -jar z ipkin-server-2. 12. 9-exec. jar

运行控制台

在这里插入图片描述
http://localhost:9411/zipkin/
在这里插入图片描述

2、服务提供者

1、修改:cloud-provider-payment8001

2、POM

<!--包含了sleuth+zipkin-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>|

3、YML

spring:
  application: 
    name: cloud-payment-service
    zipkin:
      base-url: http://localhost :9411
    sleuth:
      sampler:
        #采样率值介于0到1之间,1则表示全部采集
      probability: 1

4、业务类PaymentController

@GetMapping("/payment/zipkin")
public String paymentzipkin(){
	return "hi,i'am paymentzipkin server fall back, welcome to atguigu, 0(∩_ n)0哈哈~";
}

3、服务消费者(调用方)

1、修改:cloud-consumer-order80

2、POM

<!--包含了sleuth+zipkin-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>|

3、YML

spring:
  application: 
    name: cloud-order-service
    zipkin:
      base-url: http://localhost:9411
    sleuth:
      sampler:
        #采样率值介于0到1之间,1则表示全部采集
      probability: 1

4、业务类PaymentController

@GetMapping("/consumer/payment/zipkin")
public String paymentzipkin(){
	String result = restTemplate.getForobject("http://localhost :8001"+"/payment/zipkin/", String.class);
	return result; 
}

4、依次启动eureka7001/8001/80

5、打开浏览器访问: http://localhost:9411


POM

    <dependencies>
		<!--包含了sleuth+zipkin-->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-zipkin</artifactId>
		</dependency>|
    
	    <!--rabbit-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

		<!--添加消息总线rabbitmq支持-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        
  	    <!--config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

    	<!--geteway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

    	<!--netflix-dashboard-->
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix_dashboard</artifactId>
        </dependency>

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

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    
    	<!--引入自己定义的api调用包-->
        <dependency>
            <groupId>com.wlq.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

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

		<!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
		<!--actuator 监控信息完善-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

		<!--一般基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值