分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
“扇出”
。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.eg:80服务调用了8001,8001调用8002,8002调用8004,8004调用8006,这样挨个调用使得链路越来越长,只要其中一个出事了就会导致所有的服务出现问题。
延迟
和容错
的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
。向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常
,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-provider-hystrix-payment8001artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.angenin.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#集群版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#单机版
defaultZone: http://eureka7001.com:7001/eureka
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
package com.angenin.springcloud.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
//正常访问方法
public String paymentInfo_OK(Integer id){
//如果正常访问则,返回当前线程池的名字、传入的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;
}
}
package com.angenin.springcloud.controller;
import com.angenin.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Slf4j
@RestController
public class PaymentController {
@Resource
PaymentService paymentService;
@Value("${server.port}") //spring的@Value注解
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
访问:都能正确访问并返回结果。
success的方法:http://localhost:8001/payment/hystrix/ok/4
每次调用耗费3秒钟:http://localhost:8001/payment/hystrix/timeout/4
上述module均OK:以上述为根基平台,从正确->错误->降级熔断->恢复
开启Jmeter,来2万个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
保存:
http://localhost:8001/payment/hystrix/timeout/4
启动eureka7001
启动cloud-provider-hystrix-payment8001
分别访问2个方法,查看演示结果(自测)
结果:
提供者8001自己测试
,假如此时外部的消费者80也来访问,那消费者
只能干等,最终导致消费端80不满意,服务端8001直接被拖死新建cloud-consumer-feign-hystrix-order80
POM
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.angenin.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //激活feifn
public class OrderHystrixMain80
{
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
package com.angenin.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@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);
}
-----------------------------------------------------------
package com.angenin.springcloud.controller;
import com.angenin.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystirxController {
@Resource
private PaymentHystrixService paymentHystrixService;
//调用ok方法
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
//调用超时方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
正常测试
启动7001,8001,80
访问ok:http://localhost/consumer/payment/hystrix/ok/4(速度同样很快)
高并发测试
生产者降级:超时方法默认峰值为3秒,现在睡眠了5秒所以要进行降级,执行兜底的方法。
package com.angenin.springcloud.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
//正常访问方法
public String paymentInfo_OK(Integer id){
//如果正常访问则,返回当前线程池的名字、传入的id、表情包
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
/**
* @HystrixCommand:启用
* 超时访问方法
* fallbackMethod:此方法出现问题了,执行哪个兜底的方法
* HystrixProperty:此方法线程的超时时间为3秒钟,我们现在睡眠5秒说明超时了,就走兜底的方法
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
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()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
}
}
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker //激活
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
自测:启动7001,8001
http://localhost:8001/payment/hystrix/timeout/1
消费者降级:生产者的超时峰值为5秒睡眠了3秒,现在消费者调用要求的时间为1.5秒等不了3秒,所以服务降级调用兜底的方法。
@HystrixCommand
内属性的修改建议重启微服务feign:
hystrix:
enabled: true
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients //激活feifn
@EnableHystrix //激活
public class OrderHystrixMain80
{
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
package com.angenin.springcloud.controller;
import com.angenin.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystirxController {
@Resource
private PaymentHystrixService paymentHystrixService;
//调用ok方法
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_OK(id);
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) {
int age = 10/0; //测试出现异常同样要执行兜底的方法
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
把生产者端修改正常:生产者方峰值为5秒,睡眠3秒,此时消费方峰值为1.5秒等不了3秒,所以执行兜底方法。
测试:启动7001,8001,80
http://localhost/consumer/payment/hystrix/timeout/1
问题1:每个业务方法对应一个兜底的方法,代码膨胀
问题2:业务逻辑方法和兜底的方法混在了一块,代码的耦合度极高。
解决:统一和自定义的分开
解决问题1:每个方法配置一个???膨胀
feign接口系列
@DefaultProperties(defaultFallback = “”)
通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量,O(∩_∩)O哈哈~
controller配置
package com.angenin.springcloud.controller;
import com.angenin.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
/**
* 没有配置过定制的@HystrixCommand(fallbackMethod)方法就走这个全局定义的@DefaultProperties(defaultFallback)方法,
* 如果配置了就走自定义的@HystrixCommand(fallbackMethod)兜底方法。
*/
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystirxController {
@Resource
private PaymentHystrixService paymentHystrixService;
//调用ok方法
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
//调用超时方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})*/
/**
* 注意:需要注释的是这个@HystrixCommand注解中的属性而不是整个注解
* 注释掉注解:代表不使用服务降级,正确就正确,错误就错误
* 使用@HystrixProperty注解:不指定fallbackMethod属性表示使用全局的,指定代表使用定制的。
*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
int age = 10/0; //测试出现异常同样要执行兜底的方法
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
//下面是全局fallback兜底方法
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}
http://localhost/consumer/payment/hystrix/timeout/1
解决问题2:和业务逻辑混一起???混乱
在客户端80实现完成的
,与服务端8001没有关系只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦package com.angenin.springcloud.service;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
package com.angenin.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
/**
* value:调用的微服务名
* 过程:fallback:相当于去找CLOUD-PROVIDER-HYSTRIX-PAYMENT这个微服务的名字,去调用下面已有的方法,
* 假如出事了去调用PaymentFallbackService里面的方法
*/
@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);
}
http://localhost/consumer/payment/hystrix/ok/31
断路器:一句话就是家里的保险丝
熔断机制概述
修改
cloud-provider-hystrix-payment8001
//=========服务熔断
/**
* commandProperties中配置的4个注解的含义:
* true使用断路器,假设在时间窗口期10秒钟内,10次请求有
* 超过60%都是失败的,那么这个断路器将起作用。
*
*/
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 是否开启断路器
@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) {
/* 业务逻辑:
* 如果输入的id大于等于0,则输出流水号,如果输入的id是个负数抛出异常,
* 那么根据上面注解配置的熔断机制执行降级的兜底方法paymentCircuitBreaker_fallback
* */
if(id<0){
throw new RuntimeException("******id 不能负数");
}
/**
* IdUtil.simpleUUID()类似于 UUID.randomUUID().toString().replaceAll("-", "")
* 它来自于之前在父项目中引入的hutool依赖
* hutool是个功能强大的JAVA工具包(中国人编写的),官网:https://hutool.cn/
*/
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
//降级的兜底方法
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
https://github.com/Netflix/Hystrix/wiki/How-it-Works#CircuitBreaker
//=========服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
测试:
启动:cloud-eureka-server7001、cloud-provider-hystrix-payment8001
自测cloud-provider-hystrix-payment8001
正确:http://localhost:8001/payment/circuit/31
错误:http://localhost:8001/payment/circuit/-31
一次正确一次错误trytry
重点测试:多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行
大神结论
熔断类型
官网断路器流程图
官网步骤
断路器在什么情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值
。
断路器开启或者关闭的条件
断路器打开之后
All配置:如下
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
return "hello 2020";
}
public String str_fallbackMethod()
{
return "*****fall back str_fallbackMethod";
}
https://github.com/Netflix/Hystrix/wiki/How-it-Works
Hystrix工作流程
官网图例
步骤说明
1)创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。
2)命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
3)若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。
4)检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。
5)线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。
6)Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。
7)Hystrix会将 “成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”。
8)当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。
9)当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。
tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。
准实时的调用监控(Hystrix Dashboard)
,Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-hystrix-dashboard9001artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 9001
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard //启用Hystrix仪表盘
public class HystrixDashboardMain9001
{
public static void main(String[] args)
{
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
演示:以之前的案例8001服务熔断为例,查看9001监控8001会产生哪些图标。
它需要自己搭建一个9001的监控测试平台比较麻烦,Hystrix被国内阿里的sentienl替代后直接可以登陆一个网站进行监控。
注意事项:
package com.angenin.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker //激活
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
启动1个eureka或者3个eureka集群均可
观察监控窗口
9001监控8001
测试地址(这是上面8001测试服务熔断的2个地址,10.3.7目录)
http://localhost:8001/payment/circuit/31 (输入正数id,返回正确页面)
http://localhost:8001/payment/circuit/-31 输入负数id,返回错误页面)
上述测试通过
ok
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。
监控结果,成功
监控结果,失败
如何看?
7色
1圈
故障实例和高压力实例
。1线
整图说明
整图说明2
搞懂一个才能看懂复杂的
为什么需要网关
网关的技术实现
官网
上一代zuul 1.X:https://github.com/Netflix/zuul/wiki
当前gateway:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
是什么
概述
一句话:
能干嘛
微服务架构中网关在哪里
有Zuul了怎么又出来了gateway
我们为什么选择Gateway?
neflix不太靠谱,zuul2.0一直跳票,迟迟不发布 。
SpringCloud Gateway具有如下特性:
SpringCloud Gateway 与 Zuul的区别
Zuul1.x模型
GateWay模型
WebFlux是什么
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-new-framework
说明:传统的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 来实现响应式流规范。
Route(路由)
Predicate(断言)
如果请求与断言相匹配则进行路由
Filter(过滤)
总体:
官网总结
核心逻辑:路由转发+执行过滤器链
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-gateway-gateway9527artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.angenin.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #以单机版为例,不然集群启动太慢
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
以 cloud-provider-payment8001为例
cloud-provider-payment8001看看controller的访问地址
get方法:测试查询功能
lib:测试自定义负载均衡的规则
我们目前不想暴露8001端口,希望在8001外面套一层9527,这样比较安全,别人不能直接攻击8001外面有一层网关阻挡。
server:
port: 9527
spring:
application:
name: cloud-gateway
#网关配置:
cloud:
gateway:
routes: #routes表示可以路由多个,可以为某个controller里面的所有rest接口都可以做路由
#第一个路由
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #以单机版为例,不然集群启动太慢
启动cloud-eureka-server7001,cloud-provider-payment8001,cloud-gateway-gateway9527
引入jar包的小bug,gateway网关不需要引入web的相关依赖,如果引入启动会报错
访问说明
添加网关前:http://localhost:8001/payment/get/4
添加网关后:http://localhost:9527/payment/get/4,也能正确访问
方式一:在配置文件yml中配置,见前面的步骤
方式二:代码中注入RouteLocator的Bean
百度国内新闻网址,需要外网:http://news.baidu.com/guonei(现在好像没有这个网址了)
自己写一个
百度新闻
业务需求:通过9527网关访问到外网的百度新闻网址
编码
测试:
配置类内容:
package com.angenin.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig {
/**
* 配置了一个id为route-name的路由规则,
* 当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
* @param
* @return
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
当前存在的问题:地址写死了,服务的提供者可能有多个所以需要进行负载均衡
架构
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
启动:一个eureka7001(cloud-eureka-server7001)+ 两个服务提供者8001/8002(cloud-provider-payment8001,cloud-provider-payment8002)
POM:之前已经添加过此依赖,把服务注册到注册中心
YML
server:
port: 9527
spring:
application:
name: cloud-gateway
#网关配置:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes: #routes表示可以路由多个,可以为某个controller里面的所有rest接口都可以做路由
#第一个路由
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址(8001 8002集群组成的微服务名称) 替换为动态的
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址 替换为动态的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #以单机版为例,不然集群启动太慢
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
官网案例:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
问题:使用直接复制这行配置即可,但是自己如何写出想要的这个时间格式呢,它的时区又怎么写呢???
自己测试:
import java.time.ZonedDateTime;
public class T2 {
public static void main(String[] args) {
//获取默认时区的当前时间
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
//2023-08-27T19:32:02.975+08:00[Asia/Shanghai]
System.out.println(zbj);
// 用指定时区获取当前时间(美国/纽约)
//ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York"));
//System.out.println(zny);
}
}
server:
port: 9527
spring:
application:
name: cloud-gateway
#网关配置:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes: #routes表示可以路由多个,可以为某个controller里面的所有rest接口都可以做路由
#第一个路由
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址(8001 8002集群组成的微服务名称) 替换为动态的
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址 替换为动态的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2023-08-27T19:32:02.975+08:00[Asia/Shanghai] #这个匹配的请求要在指定的时间之后,路由地址lb才生效(亚洲/上海)
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #以单机版为例,不然集群启动太慢
# 在指定的时间之前生效
- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# 在指定的2个时间内生效
- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
自己测试:
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址 替换为动态的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2023-08-27T19:32:02.975+08:00[Asia/Shanghai] #这个匹配的请求要在指定的时间之后,路由地址lb才生效(亚洲/上海)
#- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] #在指定时间之前生效
#- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai] #在指定的2个时间之内生效
- Cookie=username,zzyy #满足cookie名和正则表达式格式
不带cookies访问,发送get请求:curl http://localhost:9527/payment/lb
带上cookies访问:curl http://localhost:9527/payment/lb --cookie "username=zzyy"
https://blog.csdn.net/leedee/article/details/82685636
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
自己测试
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址 替换为动态的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
#- After=2023-08-27T19:32:02.975+08:00[Asia/Shanghai] #这个匹配的请求要在指定的时间之后,路由地址lb才生效(亚洲/上海)
#- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] #在指定时间之前生效
#- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai] #在指定的2个时间之内生效
#- Cookie=username,zzyy #满足cookie名和正则表达式格式
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
测试
- Host=**.atguigu.com #根据主机地址进行匹配
自己测试:
- Method=GET #根据请求方式进行匹配
测试
自己测试
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
server:
port: 9527
spring:
application:
name: cloud-gateway
#网关配置:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes: #routes表示可以路由多个,可以为某个controller里面的所有rest接口都可以做路由
#第一个路由
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址(8001 8002集群组成的微服务名称) 替换为动态的
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
#第二个路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址 写死的
uri: lb://cloud-payment-service #匹配后提供服务的路由地址 替换为动态的
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
#- After=2023-08-27T19:32:02.975+08:00[Asia/Shanghai] #这个匹配的请求要在指定的时间之后,路由地址lb才生效(亚洲/上海)
#- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] #在指定时间之前生效
#- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai] #在指定的2个时间之内生效
#- Cookie=username,zzyy #满足cookie名和正则表达式格式
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET #根据请求方式进行匹配
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka #以单机版为例,不然集群启动太慢
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
生命周期,Only Two
种类,Only Two:
GatewayFilter :单一的
GlobalFilter:全局的
具体查看官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
implements GlobalFilter,Ordered
package com.angenin.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("**************come in MyLogGateWayFilter:" + new Date());
//获取request中的uname参数
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null){
log.info("*******用户名为null,非法用户!!");
//设置 response 状态码 因为在请求之前过滤的,so就算是返回NOT_FOUND 也不会返回错误页面
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
//完成请求调用
return exchange.getResponse().setComplete();
}
//过滤链放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
//返回值是过滤器的优先级,越小优先级越高(最小-2147483648,最大2147483648)
return 0;
}
}
启动
正确:http://localhost:9527/payment/lb?uname=z3(8001 8002来回切换)
错误
9527控制台日志
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理…
/(ㄒoㄒ)/~~
是什么
各个不同微服务应用
的所有环境提供了一个中心化的外部配置
。怎么玩
服务端和客户端两部分
。分布式配置中心,它是一个独立的微服务应用
,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口举例:
用你自己的账号在GitHub上新建一个名为springcloud-config的新Repository远程仓库
由上一步获得刚新建的git地址:https://github.com/123shuai-aa/springcloud-config.git
本地硬盘目录上新建git本地仓库,并clone远程仓库到本地
本地仓库位置:E:\Git-bendiku\SpringCloud2020
打开命令行终端
命令行终端输入克隆命令:git clone https://github.com/123shuai-aa/springcloud-config.git
效果:
在springcloud-config目录下创建3个yml配置文件:保存格式必须为UTF-8(默认就是)
开发环境:config-dev.yml
生产环境:config-pro.yml
测试环境:config-test.yml
config-dev.yml:
config:
info: "master branch,springcloud-config/config-dev.yml version=1"
config:
info: "master branch,springcloud-config/config-prod.yml version=1"
config:
info: "master branch,springcloud-config/config-test.yml version=1"
查看本地库状态:git status,可以看到3个yml文件
添加到暂存区:git add 文件名
提交本地库:git commit -m “日志信息” 文件名(注意为英文符号)
远程库链接地址太长了不好记忆,所以给链接起个别名,将来推送和拉取代码的时候更加方便(语法:git remote add 别名 远程库链接地址
)
推送本地库分支到远程仓库:
语法:git push 别名 分支(默认分支名是主分支:main)
git push origin main
之后会弹出一下窗口,选择浏览器账号登录
。
输入GitHub账号密码
添加到凭据管理器中
命令行效果:
刷新浏览器的git页面,此时发现已将我们main分支上的内容推送到GitHub 创建的远程仓库
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-center-3344artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/123shuai-aa/springcloud-config.git #git的仓库地址
search-paths: #搜索目录
- springcloud-config
label: main #读取的分支
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka #服务注册到的eureka地址
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344
{
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class, args);
}
}
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/#_quick_start
讲解其中的3种:
{}
http://config-3344.com:3344/config/dev/main
http://config-3344.com:3344/config/test/main
http://config-3344.com:3344/config/test/dev
名词解释:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-client-3355artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
是什么:
优先级更加高
Application Context
的父上下文。初始化的时候,Bootstrap Context
负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment
。Bootstrap
属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context
和Application Context
有着不同的约定,所以新增了一个bootstrap.yml
文件,保证Bootstrap Context
和Application Context
配置的分离。内容:
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#过程:表示3355通过3344间接读取到配置的主分支下面的config-dev配置文件。
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class,args);
}
}
在前面13.1.3能干嘛中讲过:
package com.angenin.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
@Value("${config.info}")
读取到的是GitHub中配置文件的前缀启动7001服务注册中心
启动Config配置中心3344微服务并自测
http://config-3344.com:3344/main/config-pro.yml
http://config-3344.com:3344/main/config-dev.yml
启动3355作为Client准备访问
结论:成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息
Linux运维修改GitHub上的配置文件内容做调整
刷新3344浏览器页面,发现ConfigServer配置中心立刻响应
刷新3355,发现ConfigClient客户端没有任何响应
3355没有变化除非自己重启或者重新加载
难到每次运维修改配置文件,客户端都需要重启??噩梦
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#过程:表示3355通过3344间接读取到配置的主分支下面的config-dev配置文件。
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
package com.angenin.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //实现刷新功能
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}
需要运维人员发送Post请求刷新3355
curl -X POST "http://localhost:3355/actuator/refresh"
(命令行窗口)再次访问3355,此时虽然没有重启3355,但是发现版本号已经发生变化,说明配置生效了。
假如有多个微服务客户端3355/3366/3377,每个微服务都要执行一次post请求,手动刷新?
可否广播,一次通知,处处生效?我们想大范围的自动刷新,求方法
解决:以上一些列问题,批量进行通知、精确通知可以使用----SpringCloud Bus消息总线来解决
上一讲解的加深和扩充,一言以蔽之
是什么
它整合了Java的事件处理机制和消息中间件的功能。
能干嘛
为何被称为总线
轻量级的消息代理
来构建一个共用的消息主题
,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线
。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。杨哥B站ActiveMQ课程:https://www.bilibili.com/video/av55976700?from=search&seid=15010075915728605208
http://192.168.10.140:15672/
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-config-client-3366artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 3366
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#过程:表示3366通过3344间接读取到配置的主分支下面的config-dev配置文件。
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3366 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3366.class,args);
}
}
package com.angenin.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String configInfo() {
return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
}
}
利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
图二的架构显然更加适合,图一不适合的原因如下
给cloud-config-center-3344配置中心服务端
添加消息总线支持:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/123shuai-aa/springcloud-config.git #git的仓库地址
search-paths: #搜索目录
- springcloud-config
label: main #读取的分支
#rabbitmq相关配置
rabbitmq:
host: 192.168.10.140 #linux上的主机ip
port: 5672 #15672是图形管理界面的端口,5672是client访问端口
username: admin
password: 123456
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka #服务注册到的eureka地址
#rabbitmq相关配置,暴露bus刷新配置的端点
#凡事要暴露监控刷新的操作,3344的pom文件一定要引入actuator依赖
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
给cloud-config-client-3355客户端
添加消息总线支持
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#过程:表示3355通过3344间接读取到配置的主分支下面的config-dev配置文件。
#rabbitmq相关配置
rabbitmq:
host: 192.168.10.140 #linux上的主机ip
port: 5672 #15672是图形管理界面的端口,5672是client访问端口
username: admin
password: 123456
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
给cloud-config-client-3366客户端
添加消息总线支持
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
server:
port: 3366
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#过程:表示3366通过3344间接读取到配置的主分支下面的config-dev配置文件。
#rabbitmq相关配置
rabbitmq:
host: 192.168.10.140 #linux上的主机ip
port: 5672 #15672是图形管理界面的端口,5672是client访问端口
username: admin
password: 123456
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
启动7001、3344、3355、3366
运维工程师
修改Github上配置文件增加版本号
发送POST请求(刷新的是3344配置中心
)
curl -X POST “http://localhost:3344/actuator/bus-refresh”
一次发送,处处生效
配置中心
客户端
http://localhost:3355/configInfo
http://localhost:3366/configInfo
没有重启服务,再次获取配置信息,发现都已经刷新了
一次修改,广播通知,处处生效
登录RabbitMQ后台管理界面:
http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
案例
我们这里以刷新运行在3355端口上的config-client为例
修改GitHub配置文件
刷新:
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
即:使用刷新3344通知指定的服务3355
bus-refresh
config-client:3355:
效果:3344,3355刷新了,3366没有刷新。(不重启)
http://config-3344.com:3344/main/config-dev.yml
http://localhost:3355/configInfo
http://localhost:3366/configInfo
场景:
目的:
常用的消息中间件:
什么是SpringCloudStream
目前仅支持RabbitMQ、Kafka。
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
官网:
https://spring.io/projects/spring-cloud-stream#overview
官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/
Spring Cloud Stream中文指导手册
标准MQ
消息
媒介传递信息内容通道
为什么用Cloud Stream
一大堆东西都要重新推倒重新做
,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。应用程序与消息中间件细节之间的隔离。
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离
。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
INPUT对应于消费者
OUTPUT对应于生产者
Stream中的消息通信方式遵循了发布-订阅模式
组成 | 说明 |
---|---|
Middleware | 中间件,目前只支FRabbitMQ和Kafka |
Binder | Binder是应用与消息中间件之间的封装,目前实行了KafKa和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现 |
@Input | 注解标识输入通道,通过该输入通接收到的消息息进入应用程序 |
@Output | 注解标识输出通道,发布的消息将通过该通道离开应用程序 |
@StreamListener | 监听队列,用于消费者的队列的消息接收 |
@EnableBinding | 指信道channel和exchange绑定在一起 |
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-stream-rabbitmq-provider8801artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.10.140
port: 5672
username: admin
password: 123456
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称 生产者使用output,消费者使用input
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost: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地址
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class,args);
}
}
package com.angenin.springcloud.service;
public interface IMessageProvider {
String send() ;
}
package com.angenin.springcloud.service.impl;
import com.angenin.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import javax.annotation.Resource;
import java.util.UUID;
/**
* 不再是以前crud操作数据库了:业务类上使用@service了,类里面注入dao层对象
* 现在操作的是消息中间件Rabbitmq:使用的是 SpringCloud Stream消息驱动中的注解
*/
@EnableBinding(Source.class) // 可以理解为定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; // 消息的发送管道
@Override
public String send() {
//定义发送的消息
String serial = UUID.randomUUID().toString();
//使用绑定器将消息绑定起来
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("***serial: "+serial);
return null;
}
}
package com.angenin.springcloud.controller;
import com.angenin.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;
//业务:每调用一次就发送一次流水号到Rabbitmq
@GetMapping(value = "/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-stream-rabbitmq-consumer8802artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.10.140
port: 5672
username: admin
password: 123456
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称 生产者使用output,消费者使用input
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8802
{
public static void main(String[] args)
{
SpringApplication.run(StreamMQMain8802.class,args);
}
}
package com.angenin.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class) //指信道channel和exchange绑定在一起
public class ReceiveMessageListener {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT) //监听队列,用于消费者的队列的消息接收
public void input(Message<String> message) {
System.out.println("消费者1号,----->接受到的消息: "+message.getPayload()+"\t port: "+serverPort);
}
}
cloud-stream-rabbitmq-consumer8803
目录结构:
pom
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020artifactId>
<groupId>com.angenin.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-stream-rabbitmq-consumer8803artifactId>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-stream-rabbitartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.10.140
port: 5672
username: admin
password: 123456
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称 生产者使用output,消费者使用input
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8803.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
package com.angenin.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8803 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8803.class,args);
}
}
package com.angenin.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener
{
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message message)
{
System.out.println("消费者2号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
}
}
目前是8802/8803同时都收到了,存在重复消费问题
http://localhost:8801/sendMessage:8801生产者发送消息,发现消费者8802和8803都接收到了消息。
如何解决
分组和持久化属性group
生产实际案例
默认8802,8803是不同的分组所以可以重复消费。
原理
不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
8802/8803都变成不同组,group两个不同
group: atguiguA、atguiguB
8802修改YML:group: atguiguA
8803修改YML:group: atguiguB
重启8802,8803生效
我们自己配置
消费组
的概念。结论:还是重复消费
生产者发送消息,点击3次
生产者控制台8801
消费者控制台8802
消费者控制台8803
8802/8803都变成相同组,group两个相同
group: atguiguA
8802修改YML:atguiguA
8803修改YML:atguiguA
重启生效:
我们自己配置:
查看Rabbitmq管理界面发现2个分组:atguiguA,atguiguB。atguiguB是之前的历史记录和它没有关系。
atguiguA组里面有8802和8803两个消费者,这个时候消息发到A组里面每次产生竞争关系,只允许同一组下面的一个实例拿到,这样就避免了重复消费。
点击atguiguA:可以看到里面有2个消费者
点击atguiguB:消费者数量为0
结论:同一个组的多个微服务实例,每次只会有一个拿到
生产者发送消息,点击2次
生产者控制台8801
消费者控制台8802
消费者控制台8803
8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
通过上述,解决了重复消费问题,再看看持久化
停止8802/8803并去除掉8802的分组group: atguiguA
8801先发送4条消息到rabbitmq:http://localhost:8801/sendMessage
先启动8802,无分组属性配置,后台没有打出来消息
再启动8803,有分组属性配置,后台打出来了MQ上的消息
总结:消费者因为各种问题导致宕机,此时生产者持续发送消息,之后重新启动消费者,如果消费者配置了分组
则会
消费Rabbitmq中未曾消费的消息,如果消费者没有配置分组
则不会
消费因为故障问题未曾被消费的消息,这样就导致了消息丢失。
https://github.com/spring-cloud/spring-cloud-sleuth
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
在分布式系统中提供追踪解决方案并且兼容支持了zipkin
sleuth 负责跟踪,而zipkin负责展示
SpringCloud从F版起已不需要自己构建Zipkin Server服务端了,只需调用jar包即可
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/
zipkin-server-2.24.3-exec.jar
http://localhost:9411/zipkin/
术语
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
spring:
application:
#微服务名称,将此服务项目入住到注册中心,那么就需要给此项目取个名字
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411 # zipkin 地址
sleuth:
sampler:
probability: 1 #采样率值介于0到1之间,1则表示全部采集(一般不为1,不然高并发性能会有影响)
@GetMapping("/payment/zipkin")
public String paymentZipkin() {
return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411 # zipkin 地址
sleuth:
sampler:
# 采样率值 介于0-1之间 ,1表示全部采集
probability: 1
// ====================> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin() {
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
return result;
}
依次启动eureka7001/8001/80
80调用8001多次测试下:http://localhost/consumer/payment/zipkin
打开浏览器访问:http://localhost:9411
会出现以下界面
查找痕迹
查看依赖关系