目录
1. SpringCloud Hystrix(服务熔断与降级组件 / 服务容错与保护组件)
2. SpringCloud Gateway(网关组件)
3. SpringCloud Config(分布式配置组件)
1. SpringCloud Hystrix(服务熔断与降级组件 / 服务容错与保护组件)
在微服务架构中,一个应用往往由多个服务组成,这些服务之间相互依赖,依赖关系错综复杂。
通常情况下,一个用户请求往往需要多个服务配合才能完成。例如上图所示,在所有服务都处于可用状态时,请求 1 需要调用 A、D、E、F 四个服务才能完成,请求 2 需要调用 B、E、D 三个服务才能完成,请求 3 需要调用服务 C、F、E、D 四个服务才能完成。
当服务 E 发生故障或网络延迟时,会出现以下情况:
1. 即使其他所有服务都可用,由于服务 E 的不可用,那么用户请求 1、2、3 都会处于阻塞状态,等待服务 E 的响应。在高并发的场景下,会导致整个服务器的线程资源在短时间内迅速消耗殆尽。
2. 所有依赖于服务 E 的其他服务,例如服务 B、D 以及 F 也都会处于线程阻塞状态,等待服务 E 的响应,导致这些服务的不可用。
3. 所有依赖服务B、D 和 F 的服务,例如服务 A 和服务 C 也会处于线程阻塞状态,以等待服务 D 和服务 F 的响应,导致服务 A 和服务 C 也不可用。
当微服务系统的一个服务出现故障时,故障会沿着服务的调用链路在系统中疯狂蔓延,最终导致整个微服务系统的瘫痪,这就是【雪崩效应】。为了防止此类事件的发生,微服务架构引入了“熔断器”的一系列服务容错和保护机制。
熔断器(Circuit Breaker)
与物理学中的熔断器作用(当线路出现故障时,迅速切断电源以保护电路的安全)相似,微服务架构中的熔断器能够在某个服务发生故障后,向服务调用方返回一个符合预期的、可处理的降级响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间、不必要地占用,避免故障在微服务系统中的蔓延,防止系统雪崩效应的发生。
Hystrix(服务容错与保护组件)让服务拥有自我保护的能力
1. 保护线程资源
防止单个服务的故障耗尽系统中的所有线程资源。
2. 快速失败机制
当某个服务发生了故障,不让服务调用方一直等待,而是直接返回请求失败。
3. 监控功能
提供熔断器故障监控组件HystrixDashboard(随时监控熔断器的状态)。Hystrix 会持续地记录所有通过 Hystrix 发起的请求的执行信息,并以统计报表的形式展示给用户,包括每秒执行请求的数量、成功请求的数量和失败请求的数量等。
4. 服务降级 FallBack(保证当前服务不受其他服务故障的影响,提高服务的健壮性)
(既可以在服务端,也可以在客户端)提供一个请求失败后的降级方案(通常是一个兜底方法,当请求失败后即调用该方法)。
使用场景:
1. 在服务器压力剧增时,根据实际业务情况及流量,对一些不重要、不紧急的服务进行有策略地不处理或简单处理,从而释放服务器资源以保证核心服务正常运作。
2. 当某些服务不可用时,为了避免长时间等待造成服务卡顿或雪崩效应,而主动执行备用的降级逻辑立刻返回一个友好的提示,以保障主体业务不受影响。
3. 程序运行异常、服务超时、熔断器处于打开状态、线程池资源耗尽。
使用步骤:
重写HystrixCommand的getFallBack()方法 或 HystrixObservableCommand的resumeWithFallback()方法来使服务支持降级。
5. 服务熔断(防止故障扩散到其他服务)
为了应对雪崩效应而出现的一种微服务链路保护机制。
Hystrix会监控微服务间调用的状况,当某个微服务不可用 或 响应时间太长 或 失败调用到一定比例时,为了保护系统的整体可用性,就会启动熔断机制(暂时切断请求对该服务的调用,并快速返回一个友好的错误响应)。这种熔断状态不是永久的,在经历了一定的时间后,熔断器会再次检测该微服务是否恢复正常,若服务恢复正常则恢复其调用链路。
熔断状态(3种):
1. 熔断关闭状态(Closed)
当服务访问正常时,熔断器处于关闭状态,服务调用方可以正常地对服务进行调用。
2. 熔断开启状态(Open)
默认情况下,在固定时间内接口调用出错比率达到一个阈值(例如 50%),熔断器会进入熔断开启状态。进入熔断状态后,后续对该服务的调用都会被切断,熔断器会执行本地的降级(FallBack)方法。
3. 半熔断状态(Half-Open)
在熔断开启一段时间之后,熔断器会进入半熔断状态。在半熔断状态下,熔断器会尝试恢复服务调用方对服务的调用,允许部分请求调用该服务,并监控其调用成功率。如果成功率达到预期,则说明服务已恢复正常,熔断器进入关闭状态;如果成功率仍旧很低,则重新进入熔断开启状态。
熔断流程:
1. 当服务的调用出错率达到或超过Hystix规定的比率(默认为50%)后,熔断器进入熔断开启状态。
2. 熔断器进入熔断开启状态后,Hystrix会启动一个休眠时间窗,在这个时间窗内,该服务的降级逻辑会临时充当业务主逻辑,而原来的业务主逻辑不可用。
3. 当有请求再次调用该服务时,会直接调用降级逻辑快速地返回失败响应,以避免系统雪崩。
4. 当休眠时间窗到期后,Hystrix 会进入半熔断转态,允许部分请求对服务原来的主业务逻辑进行调用,并监控其调用成功率。
5. 如果调用成功率达到预期,则说明服务已恢复正常,Hystrix 进入熔断关闭状态,服务原来的主业务逻辑恢复;否则 Hystrix 重新进入熔断开启状态,休眠时间窗口重新计时,继续重复第 2 到第 5 步。
SpringCloudHystrix
对Netflix公司的Hystrix开源组件进行二次封装,提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。
具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
@HystrixCommand注解的commandProperties属性的参数
1. metrics.rollingStats.timeInMilliseconds(统计时间窗)
监控统计时间窗内的服务调用出错率。
2. circuitBreaker.sleepWindowInMilliseconds(休眠时间窗)
熔断开启状态持续一段时间后,熔断器会自动进入半熔断状态,这段时间就被称为休眠窗口期。
3. circuitBreaker.requestVolumeThreshold(请求总数阀值)
在统计时间窗内,请求总数必须到达一定的数量级,Hystrix 才可能会将熔断器打开进入熔断开启转态,而这个请求数量级就是 请求总数阀值。Hystrix 请求总数阈值默认为 20,这就意味着在统计时间窗内,如果服务调用次数不足 20 次,即使所有的请求都调用出错,熔断器也不会打开。
4. circuitBreaker.errorThresholdPercentage(错误百分比阈值)
当请求总数在统计时间窗内超过了请求总数阀值,且请求调用出错率超过一定的比例,熔断器才会打开进入熔断开启转态,而这个比例就是错误百分比阈值。错误百分比阈值设置为 50,就表示错误百分比为 50%,如果服务发生了 30 次调用,其中有 15 次发生了错误,即超过了 50% 的错误百分比,这时候将熔断器就会打开。
示例(服务端降级)
以之前的例子为基础
1. 创建spring-cloud-provider-user-hystrix-8004服务提供者,修改pom.xml:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.2.10.RELEASE
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml(类路径resources下)
server:
port: 8004 #服务端口号
spring:
application:
name: springCloudProviderUserHystrix #对外暴露的微服务名称
jackson:
serialization:
FAIL_ON_EMPTY_BEANS: false
######### SpringCloud自定义服务名称和ip地址 #############
eureka:
client: #将客户端注册到eureka服务列表内
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/ #该地址为 7001注册中心在application.yml中暴露出来的注册地址 (单机版)
instance:
instance-id: spring-cloud-provider-8004 #自定义服务名称信息
prefer-ip-address: true #显示访问路径的ip地址
########### SpringCloud使用 SpringBootActuator 监控完善信息 ########
# SpringBoot2.50对 actuator监控屏蔽了大多数的节点,只暴露了heath节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
info:
app.name: spring-cloud-provider-user-hystrix
company.name: com.sst.cx
build.aetifactId: @project.artifactId@
build.version: @project.version@
3. 创建UserService.java(com.sst.cx.service)
package com.sst.cx.service;
public interface UserService {
// hystrix 熔断器ok
public String userInfo_Ok(Integer id);
// hystrix 熔断器超时
public String userInfo_Timeout(Integer id);
}
4. 创建UserServiceImpl.java(com.sst.cx.service.impl)
package com.sst.cx.service.impl;
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;
import com.sst.cx.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public String userInfo_Ok(Integer id) {
return "线程池:" + Thread.currentThread().getName() + " userInfo_Ok,id: " + id;
}
// 一旦该方法失败并抛出了异常信息后,会自动调用@HystrixCommand注解标注的fallbackMethod指定的方法(即指定降级方法)
@HystrixCommand(fallbackMethod = "user_TimeoutHandler",
commandProperties =
// 规定5秒钟以内就不报错,正常运行,超过5秒就报错,调用指定的方法
{@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")})
@Override
public String userInfo_Timeout(Integer id) {
int outTime = 6;
try {
TimeUnit.SECONDS.sleep(outTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:" + Thread.currentThread().getName() + " userInfo_Timeout,id: " + id + " 耗时: " + outTime;
}
// 当服务出现故障后,调用该方法给出友好提示
public String user_TimeoutHandler(Integer id) {
return "系统繁忙请稍后再试!"+"线程池:" + Thread.currentThread().getName() + " userInfo_Timeout,id: " + id;
}
}
5. 创建UserController.java(com.sst.cx.controller)
package com.sst.cx.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import com.sst.cx.service.UserService;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/user/hystrix/ok/{id}")
public String userInfo_Ok(@PathVariable("id") Integer id) {
String result = userService.userInfo_Ok(id);
log.info("端口号:" + serverPort + " result:" + result);
return result + ", 端口号:" + serverPort;
}
// Hystrix服务超时降级
@RequestMapping(value = "/user/hystrix/timeout/{id}")
public String userInfo_Timeout(@PathVariable("id") Integer id) {
String result = userService.userInfo_Timeout(id);
log.info("端口号:" + serverPort + " result:" + result);
return result + ", 端口号:" + serverPort;
}
}
6. 创建主启动类
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient // 开启Eureka客户端功能
@EnableHystrix // 激活熔断器功能
public class ServiceCloudProviderUser8004Application {
public static void main(String[] args) {
SpringApplication.run(ServiceCloudProviderUser8004Application.class, args);
}
}
7. 依次启动服务注册中心集群、spring-cloud-provider-user-hystrix-8004,
在浏览器中访问http://www.eureka7001.com:8004/user/hystrix/ok/1(正常访问)、http://www.eureka7001.com:8004/user/hystrix/timeout/1(服务降级)
示例(客户端降级)
对spring-cloud-feign-user-80项目添加Hystrix功能
1. 在pom.xml文件中添加Hystrix依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.2.10.RELEASE
2. 在application.yml文件中添加
####### 配置所有请求超时时间(单位为毫秒) ##########
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 7000
####### 配置具体请求超时时间 ########
UserHystrixService#userInfo_Timeout(Integer):
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
###### 开启hystrix
feign:
hystrix:
enabled: true #开启客户端hystrix
3. 创建UserHystrixService.java(com.sst.cx.service)
package com.sst.cx.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(value = "springCloudProviderUserHystrix")
public interface UserHystrixService {
@RequestMapping(value = "/user/hystrix/ok/{id}")
public String userInfo_Ok(@PathVariable("id") Integer id);
@RequestMapping(value = "/user/hystrix/timeout/{id}")
public String userInfo_Timeout(@PathVariable("id") Integer id);
}
4. 创建HystrixController_Consumer.java(com.sst.cx.controller)
package com.sst.cx.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import com.sst.cx.service.UserHystrixService;
@Slf4j
@RestController
public class HystrixController_Consumer {
@Resource
private UserHystrixService userHystrixService;
@RequestMapping(value = "/consumer/user/hystrix/ok/{id}")
public String userInfo_Ok(@PathVariable("id") Integer id) {
return userHystrixService.userInfo_Ok(id);
}
// 在客户端进行降级
@RequestMapping(value = "/consumer/user/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "user_TimeoutHandler") // 为该请求指定专属的回退方法
public String userInfo_Timeout(@PathVariable("id") Integer id) {
String s = userHystrixService.userInfo_Timeout(id);
log.info(s);
return s;
}
// userInfo_Timeout方法的 专用 fallback 方法
public String user_TimeoutHandler(@PathVariable("id") Integer id) {
log.info("userInfo_Timeout 出错,服务已被降级!");
return "服务端系统繁忙,请稍后再试!(客户端 userInfo_Timeout 专属的回退方法触发)";
}
}
5. 在主启动类上添加@EnableHystrix注解启用Hystrix。
6. 修改spring-cloud-provider-user-hystrix-8004的UserServiceImpl.java的userInfo_Timeout()方法的outTime改为4(让客户端来处理降级)。
7. 重启spring-cloud-provider-user-hystrix-8004、spring-cloud-feign-user-80
在浏览器中访问http://www.eureka7001.com/consumer/user/hystrix/timeout/1
/*
1. hystrix.command.xxx#yyy(zzz).execution.isolation.thread.timeoutInMilliseconds=mmm
2. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=mmm
说明
xxx:为包含该服务方法的类的名称(通常为服务绑定接口的名称),例如 UserHystrixService 接口。
yyy:服务方法名,例如 userInfo_Timeout() 方法。
zzz:方法内的参数类型,例如 Integer、String 等等
mmm:要设置的超时时间,单位为毫秒(1 秒 =1000 毫秒)
*/
【存疑:配置具体请求超时时间无效,正常请求未降级】
示例(全局降级方法)
通过上面的方式实现服务降级时,需要针对所有业务方法都配置降级方法,这极有可能会造成代码的急剧膨胀。为了解决该问题,可以为所有业务方法指定一个全局的回退方法。
全局降级方法的优先级较低,只有业务方法没有指定其降级方法时,服务降级时才会触发全局回退方法。若业务方法指定它自己的回退方法,那么在服务降级时,就只会直接触发它自己的回退方法,而非全局回退方法。
HystrixController_Consumer.java中
1. 给该类添加@DefaultProperties注解,通过其defaultFallback属性指定一个全局的降级方法。
@DefaultProperties(defaultFallback = "user_Global_FallbackMethod") // 全局的服务降级方法
2. 创建该方法。
public String user_Global_FallbackMethod() {
return "运行出错或服务端系统繁忙,请稍后再试!(客户端全局回退方法触发,)";
}
3. 在所有的业务方法上都标注@HystrixCommand注解。
重启spring-cloud-feign-user-80,
在浏览器中访问http://www.eureka7001.com/consumer/user/hystrix/timeout/1
【存疑:配置具体请求超时时间无效,正常请求未降级】
示例(解耦降级逻辑)
不管是业务方法指定的降级方法还是全局降级方法,它们都必须和业务方法在同一个类中才能生效,业务逻辑与降级逻辑耦合度极高。
1. 创建UserHystrixFallBackService.java,实现UserHystrixService(com.sst.cx.service)
package com.sst.cx.service;
import org.springframework.stereotype.Component;
@Component
public class UserHystrixFallBackService implements UserHystrixService {
@Override
public String userInfo_Ok(Integer id) {
return "--------------------系统繁忙,请稍后重试!(解耦回退方法触发)-----------------------";
}
@Override
public String userInfo_Timeout(Integer id) {
return "--------------------系统繁忙,请稍后重试!(解耦回退方法触发)-----------------------";
}
}
2. 修改UserHystrixService.java的@FeignClient注解,添加fallback属性为UserHystrixFallBackService.class
@FeignClient(value = "springCloudProviderUserHystrix",fallback = UserHystrixFallBackService.class)
3. 去除HystrixController_Consumer.java的@DefaultProperties注解、降级方法。
4. 重启spring-cloud-feign-user-80,
在浏览器中访问http://www.eureka7001.com/consumer/user/hystrix/timeout/1
【存疑:配置具体请求超时时间无效,正常请求未降级】
示例(验证熔断机制)
1. 在spring-cloud-provider-user-hystrix-8004的UserService接口中,添加一个userCircuitBreaker()方法:
// hystrix 熔断机制
public String userCircuitBreaker(Integer id);
2. 在UserServiceImpl.java中,添加userCircuitBreaker()方法实现:
//
@Override
@HystrixCommand(fallbackMethod = "userCircuitBreaker_fallback", commandProperties = {
// 以下参数在 HystrixCommandProperties 类中有默认配置
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启熔断器
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1000"), // 统计时间窗
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 统计时间窗内请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 休眠时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 在统计时间窗口期以内,请求失败率达到 60%时进入熔断状态
})
public String userCircuitBreaker(Integer id) {
if (id < 0) {
// 当传入的 id 为负数时,抛出异常,调用降级方法
throw new RuntimeException("id 不能是负数!");
}
return Thread.currentThread().getName() + "\t" + "调用成功";
}
public String userCircuitBreaker_fallback(Integer id) {
return "id 不能是负数,请稍后重试!\t id:" + id;
}
3. 在UserController.java中,添加userCircuitBreaker()方法(对外提供服务):
// Hystrix服务熔断
@RequestMapping(value = "/user/hystrix/circuit/{id}")
public String deptCircuitBreaker(@PathVariable("id") Integer id){
String result = userService.userCircuitBreaker(id);
log.info("result:"+result);
return result;
}
4. 重启spring-cloud-provider-user-hystrix-8004
在浏览器中访问http://www.eureka7001.com:8004/user/hystrix/circuit/1;
多次请求http://www.eureka7001.com:8004/user/hystrix/circuit/-1,使调用出错率大于错误百分比阀值;
多次请求http://www.eureka7001.com:8004/user/hystrix/circuit/1,当服务调用正确率上升到一定的利率后,Hystrix 进入熔断关闭状态。
【存疑:一直没进入熔断状态】
示例(故障监控)
创建spring-cloud-consumer-user-hystrix-dashboard-9002子项目 来监控spring-cloud-provider-user-hystrix-8004的运行情况。
1. 修改pom.xml文件
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
2.2.10.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
org.projectlombok
lombok
org.springframework.boot
spring-boot-maven-plugin
2. 创建application.yml文件(类路径resources目录下)
server:
port: 9002 #端口号
#http://www.eureka7001.com:9002/hystrix 熔断器监控页面
# localhost:8004//actuator/hystrix.stream 监控地址
hystrix:
dashboard:
proxy-stream-allow-list:
- "localhost"
3. 在主启动类上添加@EnableHystrixDashboard注解开启Hystrix 监控功能
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class ServiceCloudConsumerUserHystrixDashboard9002Application{
public static void main(String[] args) {
SpringApplication.run(ServiceCloudConsumerUserHystrixDashboard9002Application.class, args);
}
}
4. 创建HystrixDashboardConfig.java配置类
package com.sst.cx.config;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class HystrixDashboardConfig {
// Hystrix dashboard 监控界面必须配置
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");// 访问路径
registrationBean.setName("hystrix.stream");
return registrationBean;
}
}
5. 启动spring-cloud-consumer-user-hystrix-dashboard-9002、spring-cloud-provider-user-hystrix-8004
在浏览器中访问http://www.eureka7001.com:9002/hystrix,填入localhost:8004//actuator/hystrix.stream、2000、Hystrix Circuit,点击下MonitorStream按钮跳转到监控页面,
在浏览器中多次访问http://www.eureka7001.com:8004/user/hystrix/circuit/1、http://www.eureka7001.com:8004/user/hystrix/circuit/-1,查看Hystrix监控页面;
2. SpringCloud Gateway(网关组件)
在微服务架构中,一个系统往往由多个微服务组成,而这些服务可能部署在不同机房、不同地区、不同域名下。这种情况下,客户端(如:浏览器、手机、Postman软件工具等)想要直接请求这些服务,就需要知道它们具体的地址信息,例如IP地址、端口号等。
这种客户端直接请求服务的方式存在以下问题:
1. 当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的。
2. 在某些场景下可能会存在跨域请求的问题。
3. 身份认证的难度大,每个微服务需要独立认证。
可以通过API网关来解决这些问题。
API网关(一个搭建在客户端和微服务之间的服务)
可以在API网关中处理一些非业务功能的逻辑(如:权限验证、监控、缓存、请求路由等)。
API网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。
对于服务数量众多、复杂度较高、规模比较大的系统来说,使用 API 网关具有以下好处:
1. 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
2. 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
3. 客户端与后端的服务耦合度降低。
4. 节省流量,提高性能,提升用户体验。
5. API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。
常见的API网关实现方案:
1. Spring Cloud Gateway
2. Spring Cloud Netflix Zuul
3. Kong
4. Nginx+Lua
5. Traefik
SpringCloud Gateway
基于Spring5.0、SpringBoot2.0、Project Reactor等技术开发的高性能API网关组件。Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
旨在提供一种简单而有效的途径来发送API,并为它们提供横切关注点(如:安全性、监控/指标、弹性)。
核心概念(定义路由转发规则)
1. Route(路由) Route和Predicate必须同时声明
网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。
2. Predicate(断言,实现Route路由的匹配规则)
路由转发的判断条件(对HTTP请求进行匹配,如:请求方式、请求路径、请求头、参数等),如果请求与断言匹配成功(满足了Predicate的条件),则将请求转发到指定的服务。
需要注意以下3点:
1. Route 路由与Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
2. 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
3. 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
3. Filter(过滤器)对请求进行拦截和修改、对上文的响应进行再处理
通常情况下,出于安全方面的考虑,服务端提供的服务往往都会有一定的校验逻辑(如:用户登陆状态校验、签名校验等)。在微服务架构中,系统由多个微服务组成,所有这些服务都需要这些校验逻辑,可以将这些校验逻辑写到SpringCloudGateway的Filter过滤器中。
Filter的分类
1. Pre 类型
在请求被转发到微服务之前可以对请求进行拦截和修改(如:参数校验、权限校验、流量监控、日志输出以及协议转换等操作)。
2. Post 类型
在微服务对请求做出响应后可以对响应进行拦截和再处理(如:修改响应内容或响应头、日志输出、流量监控等)。
Filter的分类(按作用范围)
1. GatewayFilter网关过滤器
作用于单个路由或者一组路由上的过滤器(对单个路由或者一组路由上传入的请求和传出响应进行拦截,并实现一些与业务无关的功能,比如登陆状态校验、签名校验、权限校验、日志输出、流量监控等)。
GatewayFilter在配置文件中的写法与Predicate类似,格式如下:
/*
spring:
cloud:
gateway:
routes:
- id: xxxx
uri: xxxx
predicates:
- Path=xxxx
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为 X-Request-Id 值为 1024
- PrefixPath=/user #在请求路径前面加上 /user
……
*/
2. GlobalFilter
作用于所有的路由上的全局过滤器。
可以实现一些统一化的业务功能(如:权限认证、IP 访问限制等)。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链。
提供了多种默认的GlobalFilter(如:与转发、路由、负载均衡等相关的全局过滤器)。但在实际的项目开发中,通常都会自定义GlobalFilter全局过滤器以满足自身业务需求,很少直接使用默认GlobalFilter。
动态路由
默认情况下,SpringCloudGateway会根据服务注册中心(如:EurekaServer)中维护的服务列表,以服务名(spring.application.name)作为路径创建动态路由进行转发,从而实现动态路由功能。
可以在配置文件中,将Route的uri地址修改为以下形式:
lb://service-name
说明:
1. lb:uri的协议(表示开启SpringCloudGateway的负载均衡功能)。
2. service-name:服务名(SpringCloudGateway会根据它获取到具体的微服务地址)。
特性:
1. 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0 构建。
2. 能够在任意请求属性上匹配路由。
3. predicates(断言) 和 filters(过滤器)是特定于路由的。
4. 集成了 Hystrix 熔断器。
5. 集成了 Spring Cloud DiscoveryClient(服务发现客户端)。
6. 易于编写断言和过滤器。
7. 能够限制请求频率。
8. 能够重写请求路径。
工作流程:
1. 客户端将请求发送到SpringCloudGateway上。
2. SpringCloudGateway通过 GatewayHandlerMapping 找到与请求相匹配的路由,将其发送给 GatewayWebHandler。
3. GatewayWebHandler通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
4. 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
5. 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改(如:参数校验、权限校验、流量监控、日志输出以及协议转换等)。
6. 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理(如:修改响应内容或响应头、日志输出、流量监控等)。
7. 响应原路返回给客户端。
断言 | 示例 | 说明 |
---|---|---|
Path | - Path=/user/list/** | 请求路径必须与 /user/list/** 匹配。 |
Before | - Before=2001-10-20T11:47:34.255+08:00[Asia/Shanghai] | 必须是 2001 年 10 月 20 日 11 时 47 分 34.255 秒之前的请求。 |
After | - After=2001-10-20T11:47:34.255+08:00[Asia/Shanghai] | 必须是 2001 年 10 月 20 日 11 时 47 分 34.255 秒之后的请求。 |
Between | - Between=2001-10-20T15:18:33.226+08:00[Asia/Shanghai],2001-10-20T15:23:33.226+08:00[Asia/Shanghai] | 必须是 2001 年 10 月 20 日 15 时 18 分 33.226 秒 到 2001 年 10 月 20 日 15 时 23 分 33.226 秒之间的请求。 |
Cookie | - Cookie=name,com.sst.cx | 必须携带 Cookie 且 Cookie 的内容为 name=com.sst.cx 的请求。 |
Header | - Header=X-Request-Id,\d+ | 请求头上必须携带属性 X-Request-Id 且属性值为整数的请求。 |
Method | - Method=GET | 必须是GET请求。 |
路由过滤器 | 描述 | 参数 | 使用示例 |
---|---|---|---|
AddRequestHeader | 拦截请求 并添加一个指定的请求头参数。 | name:需要添加的请求头参数的 key;value:需要添加的请求头参数的value。 | - AddRequestHeader=my-request-header,1024 |
AddRequestParameter | 拦截请求 并添加一个指定的请求参数。 | name:需要添加的请求参数的 key;value:需要添加的请求参数的 value。 | - AddRequestParameter=my-request-param,c.biancheng.net |
AddResponseHeader | 拦截响应 并添加一个指定的响应头参数。 | name:需要添加的响应头的 key;value:需要添加的响应头的 value。 | - AddResponseHeader=my-response-header,c.biancheng.net |
PrefixPath | 拦截请求 并在请求路径前增加一个指定的前缀。 | prefix:需要增加的路径前缀。 | - PrefixPath=/consumer |
PreserveHostHeader | 转发请求时,保持客户端的Host信息不变,然后将它传递到提供具体服务的微服务中。 | 无 | - PreserveHostHeader |
RemoveRequestHeader | 移除请求头中指定的参数。 | name:需要移除的请求头的 key。 | - RemoveRequestHeader=my-request-header |
RemoveResponseHeader | 移除响应头中指定的参数。 | name:需要移除的响应头。 | - RemoveResponseHeader=my-response-header |
RemoveRequestParameter | 移除指定的请求参数。 | name:需要移除的请求参数。 | - RemoveRequestParameter=my-request-param |
RequestSize | 配置请求体的大小,当请求体过大时,将会返回 413 Payload Too Large。 | maxSize:请求体的大小。 | - name: RequestSize args: maxSize: 5000000 |
示例(Predicate的使用)
创建spring-cloud-gateway-9527子项目
1. 修改pom.xml文件:
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-devtools
org.projectlombok
lombok
org.springframework.boot
spring-boot-maven-plugin
2. 创建application.yml(类路径Resources目录下)
server:
port: 9527 #端口号
spring:
application:
name: microServiceCloudGateway
cloud:
gateway: #网关路由配置
routes:
#将spring-cloud-provider-user-8001提供的服务隐藏起来,不暴露给客户端,只给客户端暴露API网关的地址9527
- id: provider_user_list_routh #路由id,没有固定规则,但必须唯一,建议与服务名对应
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
#断言条件(必选全部符合)
- Path=/user/list/** #路径匹配(注意:Path 中 P 为大写)
- Method=GET #仅GET请求
eureka:
instance:
instance-id: micro-service-cloud-gateway-9527
hostname: micro-service-cloud-gateway
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
3. 依次启动Eureka服务注册中心集群、spring-cloud-provider-user-8001、spring-cloud-gateway-9527,
在浏览器访问http://localhost:9527/user/list
示例(动态路由)
1. 修改pom.xml文件:
server:
port: 9527 #端口号
spring:
application:
name: microServiceCloudGateway #服务注册中心注册的服务名
cloud:
gateway: #网关路由配置
discovery:
locator:
enabled: true #默认值为 true,即默认开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
#将spring-cloud-provider-user-8001提供的服务隐藏起来,不暴露给客户端,只给客户端暴露API网关的地址9527
- id: provider_user_list_routh #路由id,没有固定规则,但唯一,建议与服务名对应
uri: lb://springCloudProviderUser #动态路由,使用服务名代替上面的具体带端口 http://www.eureka7001.com:9527/user/list
predicates:
#断言条件(必选全部符合)
- Path=/user/list/** #路径匹配 注意:Path 中 P 为大写
- Method=GET #只能时 GET 请求时,才能访问
eureka:
instance:
instance-id: micro-service-cloud-gateway-9527
hostname: micro-service-cloud-gateway
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
2. 重新启动spring-cloud-gateway-9527,
在浏览器中访问http://localhost:9527/user/list
示例(GatewayFilter网关过滤器)
1. 修改pom.xml文件
predicates:
- Path=/get/**
filters:
- PrefixPath=/user #在请求路径上增加一个前缀 /user
2. 重新启动spring-cloud-gateway-9527,
在浏览器中访问http://localhost:9527/get/1
示例(GlobalFilter全局过滤器)
1. 创建MyGlobalFilter.java(自定义全局网关过滤器类)
package com.sst.cx.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;
// 自定义全局网关过滤器(GlobalFilter)
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("进入自定义的全局过滤器 MyGlobalFilter" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("参数 uname 不能为 null!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 过滤器的顺序,0 表示第一个
return 0;
}
}
2. 重新启动spring-cloud-gateway-9527,
在浏览器访问http://localhost:9527/user/list?uname=123456,不添加uname参数会被过滤掉。
3. SpringCloud Config(分布式配置组件)
在分布式微服务系统中,几乎所有服务的运行都离不开配置文件的支持,这些配置文件通常由各个服务自行管理,以properties或yml 格式(如:application.properties、application.yml)保存在各个微服务的类路径下。
这种将配置文件散落在各个服务中的管理方式,存在以下问题:
1. 管理难度大:配置文件散落在各个微服务中,难以管理。
2. 安全性低:配置跟随源代码保存在代码库中,容易造成配置泄漏。
3. 时效性差:微服务中的配置修改后,必须重启服务,否则无法生效。
4. 局限性明显:无法支持动态调整(如:日志开关、功能开关)。
使用配置中心(如:百度的Disconf、淘宝的diamond、360的QConf、携程的Apollo、SpringCloud的Config)对配置进行统一管理可解决这些问题。
SpringCloudConfig
为微服务架构中各个微服务提供集中化的外部配置支持。即:将各个微服务的配置文件集中存储在一个外部的存储仓库或系统(如:Git、SVN等)中,对配置统一管理。
分为2部分:
1. ConfigServer(分布式配置中心),它是一个独立运行的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密信息和解密信息的访问接口。
2. ConfigClient:微服务架构中的各个微服务,它们通过ConfigServer对配置进行管理,并从ConfigSever中获取和加载配置信息。
Spring Cloud Config 默认使用 Git 存储配置信息,因此使用 Spirng Cloud Config 构建的配置服务器天然就支持对微服务配置的版本管理。我们可以使用 Git 客户端工具方便地对配置内容进行管理和访问。除了 Git 外,Spring Cloud Config 还提供了对其他存储方式的支持,例如 SVN、本地化文件系统等。
工作流程:
1. 开发或运维人员提交配置文件到远程的 Git 仓库。
2. Config 服务端(分布式配置中心)负责连接配置仓库 Git,并对 Config 客户端暴露获取配置的接口。
3. Config 客户端通过 Config 服务端暴露出来的接口,拉取配置仓库中的配置。
4. Config 客户端获取到配置信息,以支持服务的运行。
特点:
1. 与Spring的生态体系无缝集成。
2. 将所有微服务的配置文件集中存储在一个外部的存储仓库或系统中统一管理。
3. Spring Cloud Config 配置中心将配置以 REST 接口的形式暴露给各个微服务,以方便各个微服务获取。
4. 微服务可以通过 Spring Cloud Config 向配置中心统一拉取属于它们自己的配置信息。
5. 当配置发生变化时,微服务不需要重启即可感知到配置的变化,并自动获取和应用最新配置。
6. 一个应用可能有多个环境,例如开发(dev)环境、测试(test)环境、生产(prod)环境等等,开发人员可以通过 Spring Cloud Config 对不同环境的各配置进行管理,且能够确保应用在环境迁移后仍然有完整的配置支持其正常运行。
配置文件的访问规则(在浏览器中可直接对配置文件进行访问)
格式1. /{application}/{profile}[/{label}]
例:/config/dev/master
格式2. /{application}-{profile}.{suffix}
例:/config-dev.yml
格式3. /{label}/{application}-{profile}.{suffix}
例:/master/config-dev.yml
说明:
1. {application}:应用名(即:配置文件的名称),如:config-dev。
2. {profile}:环境名,一个项目通常都有开发(dev)版本、测试(test)版本、生产(prod)版本,配置文件则以application-{profile}.yml 的形式进行区分,如:application-dev.yml、application-test.yml、application-prod.yml等。
3. {label}:Git分支名,默认:master分支,当访问默认分支下的配置文件时,该参数可以省略,即第二种访问方式。
4. {suffix}:配置文件的后缀,如:config-dev.yml的后缀为yml。
示例(搭建Config服务端)
1. 在Github或Gitee上创建一个名为 springcloud-config 的仓库(Repository),并获取该仓库的地址。
2. 创建spring-cloud-config-center-3344子项目,修改pom.xml文件
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-config-server
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-maven-plugin
3. 创建application.yml(类路径Resources目录下)
server:
port: 3344 #端口号
spring:
application:
name: spring-cloud-config-center #服务名
cloud:
config:
server:
git:
uri: https://gitee.com/xxx/springcloud-config.git
#仓库名
search-paths:
- springcloud-config
force-pull: true
# 如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写
username: ********
password: ********
#分支名
label: master
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/ #将服务注册到 Eureka 集群
4. 给主启动类添加@EnableConfigServer注解开启SpringCloudConfig配置中心功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class MicroServiceCloudConfigCenter3344Application {
public static void main(String[] args) {
SpringApplication.run(MicroServiceCloudConfigCenter3344Application.class, args);
}
}
5. 新建一个名为config-dev.yml文件,并将其上传到 springcloud-config 仓库 master 分支下。
config:
info: com.sst.cx
version: 1.0
6. 依次启动服务注册中心集群和 spring-cloud-config-center-3344,在浏览器中访问http://localhost:3344/master/config-dev.yml 或 http://localhost:3344/config-dev.yml 或 http://localhost:3344/config/dev/master
示例(搭建Config客户端)
创建spring-cloud-config-client-3355子项目
1. 修改pom.xml文件:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-bootstrap
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建bootstrap.yml(类路径Resources目录下)
#bootstrap.yml 是系统级别的,加载优先级高于 application.yml ,负责从外部加载配置并解析
server:
port: 3355 #端口号
spring:
application:
name: spring-cloud-config-client #服务名
cloud:
config:
label: master #分支名称
name: config #配置文件名称,config-dev.yml 中的 config
profile: dev #环境名 config-dev.yml 中的 dev
#这里不要忘记添加 http:// 否则无法读取
uri: http://localhost:3344 #Spring Cloud Config 服务端(配置中心)地址
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/ #将服务注册到 Eureka 集群
3. 创建ConfigClientController.java(通过该类获取配置文件中的配置)
package com.sst.cx.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("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@Value("${config.version}")
private String configVersion;
@GetMapping(value = "/getConfig")
public String getConfig() {
return "info:" + configInfo + "
version:" + configVersion + "
port:" + serverPort;
}
}
4. 给主启动类添加@EnableEurekaClient注解开启 Eureka客户端功能。
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class MicroServiceCloudConfigClient3355Application {
public static void main(String[] args) {
SpringApplication.run(MicroServiceCloudConfigClient3355Application.class, args);
}
}
5. 启动服务注册中心集群、spring-cloud-config-center-3344、spring-cloud-config-client-3355,在浏览器中访问http://localhost:3355/getConfig
6. 将配置文件 config-dev.yml 中 config.version 的值修改为 2.0
7. 在浏览器中访问http://localhost:3355/getConfig,可以看到version还是1.0;访问http://localhost:3344/master/config-dev.yml,是2.0;
9. 重启spring-cloud-config-client-3355,在浏览器中访问http://localhost:3355/getConfig,可以看到是2.0了。
示例(不重启Config客户端的情况下,手动刷新配置 来获取最新配置)
从上例中可知,配置文件更新后
1. SpringCloudConfig服务端可以直接从Git仓库中获取最新的配置。
2. SpringCloudConfig客户端则需要重启,否则无法通过SpringCloudConfig服务端获取最新的配置。
通过在Config客户端中引入SpringBootActuator监控组件来监控配置的变化,可以在不重启Config客户端的情况下获取最新配置。
使用步骤:
1. 在spring-cloud-config-client-3355的pom.xml文件中添加
org.springframework.boot
spring-boot-starter-actuator
2. 在bootstrap.yml配置文件中添加(对外暴露SpringBootActuator的监控节点):
# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 health 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: * # * 在yaml 文件属于关键字,所以需要加引号
3. 在ConfigClientController类上添加@RefreshScope注解开启配置刷新
4. 重启spring-cloud-config-client-3355
修改config-dev.yml中的config.version为3.0
在浏览器中访问http://localhost:3355/getConfig,还是2.0。
在终端执行curl -X POST "http://localhost:3355/actuator/refresh"命令
在浏览器中访问http://localhost:3355/getConfig,可以看到是3.0了。
这种方式带来一个问题:只要配置仓库中的配置发生改变,就需要挨个向Config客户端手动发送POST请求,通知它们重新拉取配置。
在微服务架构中,一个系统往往包含十几甚至几十个服务,如果因为某一个配置文件的修改而向几十个微服务发送POST请求,这显然是不合理的。
SpringCloudBus(消息总线)
通过轻量级的消息代理(如:RabbitMQ、Kafka)(构建一个公共的消息主题Topic,默认为“springCloudBus”,这个Topic中的消息会被所有服务实例监听和消费。当其中的一个服务刷新数据时,SpringCloudBus会把信息保存到Topic中,这样监听这个Topic的服务就收到消息并自动消费)将微服务架构中的各个服务连接起来,实现广播状态更改、事件推送等功能,还可以实现微服务之间的通信功能。
Config+Bus实现配置的动态刷新(一次通知,处处生效)
利用SpringCloudBus的特殊机制可以实现很多功能,如:配合SpringCloudConfig可以实现配置的动态刷新。当Git仓库中的配置发生了改变,只需要向某一个服务(既可以是Config服务端,也可以是Config客户端)发送一个 POST 请求,SpringCloudBus 就可以通过消息代理通知其他服务重新拉取最新配置,以实现配置的动态刷新。
工作流程:
1. 当 Git 仓库中的配置发生改变后,运维人员向 Config 服务端发送一个 POST 请求,请求路径为“/actuator/refresh”。
2. Config 服务端接收到请求后,会将该请求转发给服务总线 Spring Cloud Bus。
3. Spring Cloud Bus 接到消息后,会通知给所有 Config 客户端。
4. Config 客户端接收到通知,请求 Config 服务端拉取最新配置。
5. 所有 Config 客户端都获取到最新的配置。
示例(SpringCloudBus动态刷新配置)全局广播
1. 在spring-cloud-config-center-3344的pom.xml文件中,添加
org.springframework.cloud
spring-cloud-starter-bus-amqp
org.springframework.boot
spring-boot-starter-actuator
2. 在spring-cloud-config-center-3344的application.xml文件中,spring下添加
##### RabbitMQ 相关配置,15672 是web 管理界面的端口,5672 是 MQ 的访问端口###########
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 heath 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
3. 在spring-cloud-config-client-3355的pom.xml文件中,添加
org.springframework.cloud
spring-cloud-starter-bus-amqp
4. 在spring-cloud-config-client-3355的bootstrap.xml文件中,spring下添加
##### RabbitMQ 相关配置,15672 是web 管理界面的端口,5672 是 MQ 的访问端口###########
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
5. 参照spring-cloud-config-client-3355,创建spring-cloud-config-client-3366,修改bootstrap.yml(
bootstrap.yml是系统级别的,加载优先级高于application.yml ,负责从外部加载配置并解析)
server:
port: 3366 #端口号为 3366
spring:
application:
name: spring-cloud-config-client-bus
cloud:
config:
label: master #分支名称
name: config #配置文件名称,config-dev.yml 中的 config
profile: dev #配置文件的后缀名 config-dev.yml 中的 dev
#这里不要忘记添加 http:// 否则无法读取
uri: http://localhost:3344 #spring cloud 配置中心地址
##### RabbitMQ 相关配置,15672 是web 管理界面的端口,5672 是 MQ 的访问端口###########
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
###################### eureka 配置 ####################
eureka:
client: #将客户端注册到 eureka 服务列表内
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/ #将服务注册到 Eureka 集群
# Spring Boot 2.50对 actuator 监控屏蔽了大多数的节点,只暴露了 heath 节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
6. 启动spring-cloud-config-center-3344、spring-cloud-config-client-3355、spring-cloud-config-client-3366,
在浏览器中访问http://localhost:3355/getConfig、http://localhost:3366/getConfig
7. 将配置文件 config-dev.yml 中 config.version 的值修改为 4.0
8. 在终端执行curl -X POST "http://localhost:3344/actuator/bus-refresh"
在浏览器中访问http://localhost:3355/getConfig、http://localhost:3366/getConfig
可能遇到的错误:
1. 执行curl命令405(Method Not Allowed)
原因:springcloud版本过高,降低版本,或者bus-refresh改为busrefresh
2. 执行curl命令500(Internal Server Error)
【存疑:改为busrefresh后报500】
示例(SpringCloudBus动态刷新配置)定点通知
定点通知:不再通知所有的Config客户端,而是根据需求只通知指定Config客户端。
只要我们在发送POST请求时使用以下格式即可:
http://{hostname}:{port}/actuator/bus-refresh/{destination}
说明:
1. {hostname}:表示Config服务端的主机地址(域名 或 IP地址)。
2. {port}:表示Config服务端的端口号。
3. {destination}:表示需要定点通知的Config客户端(微服务)。由Config客户端的服务名(spring.application.name)+冒号+端口号(server.port)组成。
例:curl -X POST "http://localhost:3344/actuator/bus-refresh/spring-cloud-config-client:3355"