赞
踩
zuul1 官网:Home · Netflix/zuul Wiki · GitHub
GateWay官网:Spring Cloud Gateway
Cloud 全家桶中有个很重要的组件就是网关,在 1.x版本中都是采用 Zuul 网关;但在2.x版本中,Zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是 SpringCloud Gateway。一句话:Gateway是原Zuul1.x版的替代。
Gateway 是在Spring生态系统之上构建的 API 网关服务,基于 Spring5,SpringBoot和Project Reactor 等技术。Gateway旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway 作为 SpringCloud 生态系统中的网关,目标是替代Zuul,在SpringCloud 2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,仍然是使用 Zuul1.x 非 Reactor 模式的老版本程序。而为了提高网关的性能,SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高效能的 Reactor 模式通讯框架 Netty。
SpringCloud Gateway 的目标是提供统一的方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控/指标和限流。
我们为什么选择 Gateway?
一方面因为 Zuul1.0 已经进入了维护阶段,而且 Gateway 是 SpringCloud 团队研发的,是亲儿子产品,值得信赖。
Gateway 是基于异步非阻塞模式上进行开发的,性能方面不需要担心。虽然 Netflix 早就发布了最新的 Zuul 2.x,但是 SpringCloud 貌似没有整合计划。而且 Netflix 相关组件都宣布进入维护期,不知前景如何。
多方面综合考虑,Gateway 是很理想的网关选择。
SpringCloud Gateway 具有如下特性:
在 SpringCloud Finchley 正式版之前,SpringCloud推荐的网关是 Netflix 提供的 Zuul;
SpringCloud Gateway 和 Zuul的区别:
Zuul1.x模型
SpringCloud中所集成的Zuul版本,采用的是Tomcat容器,使用传统的 Servlet IO 模型。
Servlet 由 Servlet Container 进行声明周期管理
上述模式的缺点:Servlet是一个简单的网络IO 模型,当请求 进入Servlet Container 时,Servlet Container 就会为其绑定一个线程,在并发不高的场景下这种模型是使用的。但是一旦高并发(比如jmeter压力测试),线程数量就会上涨,而线程资源代价是昂贵的(上下文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个 request 分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下 Servlet 模型没有优势。
所以 Zuul1.x 是基于 Servlet 之上的一个阻塞式处理模型,即Spring实现了处理所有请求的一个Servlet(DispatcherServlet),并由该 Servlet 阻塞式处理。
Gateway模型
传统的 Web 框架,比如说:struts2、SpringMVC 等都是基于 Servlet API 与 Servlet 容器基础之上运行的。
但是在 Servlet 3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。相对于传统的 Web 框架来说,它可以运行在诸如 Netty、Undertow 及支持 Servlet 3.1 的容器上。
Spring WebFlux 是 Spring5.0 引入的新的响应式框架,区别与 SpringMVC,它不需要依赖 ServletAPI ,它是完全异步非阻塞的,并基于 Reactor 来实现响应式流规范。
Route(路由):路由是构建网关的基本模块,它由ID、目标URL、一些列断言和过滤器组成,如果断言为true则匹配该路由。
Predicate(断言):参考的是 Java8 的 java.util.Predicate,开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
Filter(过滤):指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由前或之后对请求进行修改。
总体:Web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。Predicate就是我们的匹配条件;filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
客户端向 Spring Cloud Gateway 发出请求,然后 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。
Filter在 pre 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。在 post 类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发+执行过滤器链。
新建模块 cloud-gateway-gateway9527
注意:因为SpringCloudGateway的内部是通过netty + webflux实现的,webflux是和springmvc有冲突的,需要移除掉 spring-boot-starter-web 依赖
- <dependencies>
- <!-- gateway -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <!-- eureka-client -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <!-- 引入自己的 cloud-api-commons 模块-->
- <dependency>
- <groupId>com.cloud.study</groupId>
- <artifactId>cloud-api-commons</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- test -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- server:
- port: 9527
-
- spring:
- application:
- name: cloud-gateway
-
- eureka:
- client:
- register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
- fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
- service-url:
- #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
- defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
- instance:
- instance-id: ${spring.application.name}
- prefer-ip-address: true #显示IP
- @SpringBootApplication
- @EnableEurekaClient
- public class GatewayMain9527 {
- public static void main(String[] args) {
- SpringApplication.run(GatewayMain9527.class, args);
- }
- }
cloud-provider-payment8001 看看 controller的访问地址(get、lb)
我们目前不想暴露8001端口,希望在 8001 外侧嵌套一层9527
- spring:
- application:
- name: cloud-gateway
- cloud:
- gateway:
- routes:
- - id: payment_routh #路由的ID,没有固定规则,但要求唯一,建议配合服务名
- uri: http://localhost:8001 #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/get/** #断言,路径相匹配的进行路由
-
- - id: payment_routh2
- uri: http://localhost:8001
- predicates:
- - Path=/payment/lb/**
注意 Path 要大写
访问说明:
Gateway 网关路由有两种配置方式:
代码中注入 RoutLocatoe 的 Bean
我们用一个案例,通过9527的网关访问到外网百度国内新闻网址 百度新闻——海量中文资讯平台
在项目中添加如下配置类
- @Configuration
- public class GatewayConfig {
- /**
- * 配置了一个id为 path_route_study、path_route_study2 的路由规则
- * 当访问地址为 http://localhost:9527/guonei 时会自动转发到地址 https://news.baidu.com/guonei
- */
- @Bean
- public RouteLocator routes(RouteLocatorBuilder routeLocatorBuilder){
- RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
- routes.route("path_route_study", r -> r.path("/guonei").uri("https://news.baidu.com/guonei"));
- routes.route("path_route_study2", r -> r.path("/guoji").uri("https://news.baidu.com/guoji"));
- return routes.build();
- }
- }
访问 http://localhost:9527/guonei 和 http://localhost:9527/guoji 时能正常调整到页面,访问别的页面时就会报错。
默认情况下 Gateway 会根据注册中心注册的服务列表,已注册中心上微服务名为路径,创建动态路由进行转发,从而实现动态路由的功能。
修改 cloud-gateway-gateway9527 的 yaml 配置文件
- spring:
- application:
- name: cloud-gateway
- cloud:
- gateway:
- discovery:
- locator:
- enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
- routes:
- - id: payment_routh #路由的ID,没有固定规则,但要求唯一,建议配合服务名
- #uri: http://localhost:8001 #匹配后提供服务的路由地址
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/get/** #断言,路径相匹配的进行路由
-
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
需要注意的是 uri 的协议是为 lb,表示启用 Gateway 的负载均衡功能
lb://serviceName 是SpringCloud Gateway 在微服务中自动为我们创建的负载均衡 uri
Gateway 项目运行的时候会输出下面的log
SpringCloud Gateway 将路由匹配作为 Spring WebFlux HandlerMapping 基础架构的一部分。SpringCloud Gateway 包含许多内置的 Route Predicate 工厂。所有的这些 Predicate 都与HTTP请求的不同属性匹配。多个 Route Predicate 工厂可以进行组合。
SpringCloud Gateway 创建 Route 对象时,使用 RoutePredicateFactories 创建 Predicate 对象,Predicate 对象可以赋值给 Route。
Route Predicate 是在 yaml 文件下的 predicates 配置
常用的 Route Predicate 有:
After Route Predicate
在xx时间之后才能访问的路由
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - After=2021-09-16T15:19:39.381+08:00[Asia/Shanghai]
这个时间如何得出,可以运行以下测试程序:
- public class T1 {
- public static void main(String[] args) {
- ZonedDateTime zbj = ZonedDateTime.now(); //默认时区
- System.out.println(zbj); //输出 2021-09-16T15:19:39.381+08:00[Asia/Shanghai]
- }
- }
Before Route Predicate
在xx时间之前才能访问的路由
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Before=2021-09-16T15:19:39.381+08:00[Asia/Shanghai]
Between Route Predicate
在xx时间与xx时间之间才能访问的路由,两个时间应英文逗号隔开
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Between=2021-09-16T15:19:39.381+08:00[Asia/Shanghai],2021-10-16T15:19:39.381+08:00[Asia/Shanghai]
Cookie Route Predicate
Cookie Route Predicate 需要两个参数,一个是 Cookie name,一个是正则表达式。路由规则会通过获取对应的 Cookie 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Cookie=username,chenj
测试 使用 crul 工具进行测试
Header Route Predicate
两个参数:一个是属性名,一个是正则表达式,这个属性值和正则表达式匹配则执行。
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Header=X-Request-Id, \d+ #请求头要有 X-Request-Id 属性且值为整数
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
Host Route Predicate
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.
号作为分隔符。它通过参数中的主机地址作为匹配规则。
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Host=**.com
curl http://localhost:9527/payment/lb -H "Host:www.aa.com"
Method Route Predicate
可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Method=GET
Path Route Predicate
Path Route Predicate 接收一个匹配路径的参数来判断是否走路由
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
curl http://localhost:9527/payment/lb
Query Route Predicate
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- - Query=smile
路由过滤器可用于修改进入的 HTTP 请求和返回的 HTTP 响应,路由过滤器只能指定路由进行使用。
SpringCloud Gateway 内置了多种路由过滤器,他们都由 GatewayFilter 的工厂类来产生。
生命周期:pre、post
GatewayFilter
有30多种,自己可以照着官网,敲一两个例子即可。
我们现在测试 AddRequestHeader、AddResponseHeader
- spring:
- cloud:
- gateway:
- routes:
- - id: payment_routh2
- #uri: http://localhost:8001
- uri: lb://cloud-payment-service #匹配后提供服务的路由地址
- predicates:
- - Path=/payment/lb/**
- filters:
- - AddRequestHeader=X-Request-red, blue #请求头添加 X-Request-red:red
- - AddResponseHeader=X-Response-Red, Blue #响应头添加 X-Response-Red:Blue
- - AddRequestParameter=color, blue #请求url自动添加参数
此时修改8001 的PaymentController,输出 Headers 和 参数
- @GetMapping("/payment/lb")
- public String paymentLb(HttpServletRequest request) {
- Enumeration<String> headers = request.getHeaderNames();
- System.out.println("-----------header----------------");
- while (headers.hasMoreElements()) {
- String header = headers.nextElement();
- System.out.println(header+ ":" +request.getHeader(header));
- }
- System.out.println("-----------param----------------");
- Enumeration<String> params = request.getParameterNames();
- while (params.hasMoreElements()) {
- String param = params.nextElement();
- System.out.println("param: " +param+ "=" +request.getParameter(param));
- }
- return serverPort;
- }
可以发现8001的请求头中有 x-request-red:blue,参数中有 color=blue
响应头由我们甚至的值
GlobalFilter
将在下一章节降到自定义过滤器,介绍 GlobalFilter 的使用。
主要由两个接口 GlobalFilter、Ordered。
自定义过滤器能干嘛:
测试代码
- @Component
- @Slf4j
- public class MyLogGatewayFilter implements GlobalFilter, Ordered {
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- log.info("********** come in MyLogGatewayFilter: "+ new Date());
- String uname = exchange.getRequest().getQueryParams().getFirst("uname");
- if (uname == null) {
- log.info("******* 用户名为null,非法用户********");
- exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
- return exchange.getResponse().setComplete();
- }
- return chain.filter(exchange);
- }
-
- @Override
- public int getOrder() {
- return 0; //数字越小优先级越高
- }
- }
测试
官网地址:Spring Cloud Config
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud 提供了 ConfigServer 来解决这个问题,我们每一个微服务自己带着一个 application.yaml,上百个配置文件的管理...
SpringCloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config 分为 服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并未客户端提供获取配置信息,加密/解密信息等访问接口。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的访问配置内容。
集中管理配置文件。
不同环境不同配置,动态化的配置更新,分环境部署比如 dev/test/prod/beta/release。
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取自己的信息。
当配置发生改变时,服务不需要重启即可感知到配置的变化并应用新的配置。
将配置信息以 REST 接口的形式暴露。
由于 SpringCloud Config默认使用 Git来存储配置文件(也有其他方式,比如支持 SVN 和本地文件),但最推荐的还是 Git,而且使用的是 http/https 访问形式。
用你自己的账号在 码云(gitee)上新建一个名为 springcloud-config的新 Repository。
由上一步获得刚新建的 gitee 地址 https://gitee.com/canima_895/springcloud-config.git 。
本地硬盘目录上新建git仓库并 clone :
此时在本地 F 盘下 F:\config\SpringCloud2021\springcloud-config
- config:
- info: "master branch,springcloud-config/config-dev.yaml version=1"
新建module模块 cloud-config-center3344,它即为Cloud的配置中心模块 cloudConfig Center。
POM文件
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>cloud-study</artifactId>
- <groupId>com.cloud.study</groupId>
- <version>1.0</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>cloud-config-center-3344</artifactId>
-
- <dependencies>
- <!-- config -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-config-server</artifactId>
- </dependency>
- <!-- Eureka client -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <!-- 引入自己的 cloud-api-commons 模块-->
- <dependency>
- <groupId>com.cloud.study</groupId>
- <artifactId>cloud-api-commons</artifactId>
- <version>${project.version}</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
-
- <!--开启热部署-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <scope>runtime</scope>
- <optional>true</optional>
- </dependency>
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- test -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
yaml文件。
- server:
- port: 3344
-
- spring:
- application:
- name: cloud-config-server #注册进 Eureka服务器的微服务名
- cloud:
- config:
- server:
- git:
- #username: #GIT用户名
- #password: #GIT密码
- uri: https://gitee.com/canima_895/springcloud-config.git #gitee上面的 git仓库名字
- #搜索目录 (配置文件远程仓库名字)
- search-paths:
- - springcloud-config
- #读取分支
- label: master
-
- eureka:
- client:
- register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
- fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
- service-url:
- #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
- defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
主启动类。
- @SpringBootApplication
- @EnableEurekaClient
- @EnableConfigServer
- public class ConfigMain3344 {
- public static void main(String[] args) {
- SpringApplication.run(ConfigMain3344.class, args);
- }
- }
windows 下修改 hosts 文件,增加映射。
127.0.0.1 config-3344.com
测试通过 Config 微服务是否可以从 GitHub 上获取配置内容。
配置读取规则有以下几种,个人习惯还是使用第一种
/{label}/{application}-{profile}.yaml
- master 分支 :http://config-3344.com:3344/master/config-test.yaml
- dev 分支:http://config-3344.com:3344/dev/config-test.yaml
/{application}-{profile}.yaml :默认会从master分支查找
/{application}/{profile}/{label}.yaml
- http://config-3344.com:3344/config/test/master ,读出内容是个json串
{ "name":"config", "profiles":[ "test" ], "label":"master", "version":"82729ce5eef7b9c135649d0bf669cdd5d6335809", "state":null, "propertySources":[ { "name":"https://gitee.com/canima_895/springcloud-config.git/config-test.yaml", "source":{ "config.info":"master branch,springcloud-config/config-test.yaml version=1" } } ] }
新建 cloud-config-client-3355
pom文件
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>cloud-study</artifactId>
- <groupId>com.cloud.study</groupId>
- <version>1.0</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>cloud-config-client-3355</artifactId>
-
- <dependencies>
- <!-- config -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-config</artifactId>
- </dependency>
- <!-- Eureka client -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <!-- 引入自己的 cloud-api-commons 模块-->
- <dependency>
- <groupId>com.cloud.study</groupId>
- <artifactId>cloud-api-commons</artifactId>
- <version>${project.version}</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
-
- <!--开启热部署-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <scope>runtime</scope>
- <optional>true</optional>
- </dependency>
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!-- test -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
yaml文件 - bootstrap.yaml
application.yaml 是用户级的资源配置项,bootstrap.yaml 是系统的,优先级更高。
SpringCloud会创建一个“Bootstrap Context”,作为Spring应用的“Application Context”的父上下文。初始化的时候,“Bootstrap Context”负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的“Environment”。
“Bootstrap ”属性有高优先级,默认情况下,他们不会被本地配置覆盖。“Bootstrap Context”和“Application Context”有着不同的约定,所以新增了一个“bootstrap.yaml”文件,保证“Bootstrap Context”和“Application Context”配置的分离。
要将Client模块下的 application.yaml文件改为“Bootstrap Context”和“Application Context”,这是很关键的,因为
- server:
- port: 3355
- spring:
- application:
- name: config-client
- cloud:
- # config 客户端信息
- config:
- label: master #分支名称
- name: config #配置文件名称
- profile: dev #读取后缀名
- uri: http://config-3344.com:3344 #配置中心地址
-
- eureka:
- client:
- register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
- fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
- service-url:
- #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
- defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
主启动
- @SpringBootApplication
- @EnableEurekaClient
- public class ConfigClientMain3355 {
- public static void main(String[] args) {
- SpringApplication.run(ConfigClientMain3355.class, args);
- }
- }
业务类
- @RestController
- @Slf4j
- public class ConfigClientController {
- @Value("${config.info}")
- private String configInfo;
-
- @GetMapping("/configInfo")
- public String getConfigInfo() {
- return configInfo;
- }
- }
测试
问题
目的:避免每次更新配置都要重启客户端微服务 3355。
pom 引入Actuator 监控,我们在之前已经引入了
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-actuator</artifactId>
- </dependency>
修改 yaml,暴露监控端口
- #暴露监控端点
- management:
- endpoints:
- web:
- exposure:
- include: "*"
@RefreshScope 业务类修改
- @RestController
- @Slf4j
- @RefreshScope
- public class ConfigClientController {
- @Value("${config.info}")
- private String configInfo;
-
- @GetMapping("/configInfo")
- public String getConfigInfo() {
- return configInfo;
- }
- }
自动刷新只能刷新 @RefreshScope 注解下的配置,一些特殊配置,如数据库等,需要同样先设置数据库链接ConfigServer类,然后通过加 @RefreshScope 注解方式。
测试:
运维人员,使用 crul 发送POS请求:
curl -X POST "http://localhost:3355/actuator/refresh"
总结:运维人员修改完配置文件后,需要发送一个 POST 请求,这样客户端才会刷新最新的配置文件。
再次访问 http://localhost:3355/configInfo ,发现值已经刷新。
由4.1节可知,修改了配置文件后,每个客户端都要发一个 POST 请求,如果有100个微服务那么就要发送100次POST请求。所以在下一章节消息总线将会解决此问题。
SpringCloud Bus 配合 SpringCloud Config 使用可以实现配置的动态刷新。
SpringCloud Bus 是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了 Java 的事件处理机制和消息中间件的功能。
Bug 支持两种消息代理:RabbitMQ 和 Kafka。
SpringCloud Bus 能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当做微服务间的通信通道。
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个公用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线的各个实例上,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理:ConfigClient 实例都监听 MQ 中同一个 topic(默认是 SpringCloudBus)。当一个服务刷新数据的时候,它会把这个消息放入 Topic 中,这样其他监听同一个 Topic 的服务就能得到通知,然后去更新自生的配置。
Windows下RabbitMQ安装及注意事项_Harry的博客-CSDN博客
安装 Erlang,下载地址:Erlang Programming Language
安装 RabbitMQ,下载地址:Downloading and Installing RabbitMQ — RabbitMQ
进入 RabbitMQ 安装目录下的 sbin 目录:D:\Program Files\RabbitMQ Server\rabbitmq_server-3.9.5\sbin
输入以下命令启动管理功能:rabbitmq-plugins enable rabbitmq_management
安装完成后,启动我们的服务(点击 RabbitMQ Service start 图标)
访问地址查看是否安装成功:http://127.0.0.1:15672
输入账号密码并登录 guest-guest
必须先具备良好的 RabbitMQ 环境。
为演示广播效果,再以3355为模板再制作一个 3366模块,在3355、3366 的controller增加server.port的输出
- @RestController
- @Slf4j
- @RefreshScope
- public class ConfigClientController {
- @Value("${config.info}")
- private String configInfo;
- @Value("${server.port}")
- private String serverPort;
-
- @GetMapping("/configInfo")
- public String getConfigInfo() {
- return "serverPort: " + serverPort + "\tconfigInfo: " + configInfo;
- }
- }
a.利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置。
b.利用消息总线触发一个服务端ConfigServer的/bus/refresh 端点,而刷新所有客户端的配置。
显然 b 的架构更加适合,a 不适合的原因如下:
pom文件添加 RabbitMQ 支持
- <!-- 添加消息总线RabbitMQ支持 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-bus-amqp</artifactId>
- </dependency>
application.yaml 文件添加 rabbitmq 配置和 暴露监控端点配置
- #暴露监控端点
- management:
- endpoints:
- web:
- exposure:
- include: "bus-refresh"
-
- #rabbitmq相关配置
- rabbitmq:
- host: localhost
- port: 5672
- username: guest
- password: guest
pom文件添加 RabbitMQ 支持
- <!-- 添加消息总线RabbitMQ支持 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-bus-amqp</artifactId>
- </dependency>
bootstrap.yaml 添加rabbitmq配置
- #rabbitmq相关配置
- rabbitmq:
- host: localhost
- port: 5672
- username: guest
- password: guest
启动7001/7002
启动3344
启动3355、3366
去Git上增加 config-dev.yaml 中的版本号
发送 POST 请求 ,(切记,往配置中心3344 发送)
curl -X POST "http://localhost:3344/actuator/bus-refresh"
刷新页面 http://localhost:3355/configInfo 、http://localhost:3366/configInfo 发现配置均已更新
不想全部通知,只想定点通知。
公式: http://localhost:3344/actuator/bus-refresh/{destination} 。
/bus/refresh 请求不再发送到具体的服务实例上,而是发给 config server 并通过 destination 参数类指定需要更新的服务或实例。destination=微服务名+:+端口号。
我们已刷新运行在 3355 端口上的 config-client 为例
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。