统一检查maven
maven依赖出错的解决
注意代码格式化。因代码格式混乱,导致代码出错,pom.xml出现重复的parent标签
学习方法,听得懂为什么要这么做,要远远比 怎么做 重要的多
一、远程调用Feign
- 能够使用Feign进行远程调用
- 能够给Feign配置日志输出
1. Feign简介
Feign是Netflix公司提供服务调用组件,单独使用Feign非常麻烦。
SpringCloud对Feign做了集成封装,提供了声明式服务调用组件Open-Feign。
Open-Feign支持SpringMVC注解。是SpringCloud提供的一个声明式的伪Http客户端,
它使得调用远程服务就像调用本地服务一样简单,只需要创建一个接口并添加一个注解即可。
Feign默认集成了Ribbon,所以使用Feign默认就实现了负载均衡的效果。
2. Feign入门【重点】
说明
Feign要在调用者一方配置
基础服务代码:
用户服务:提供findById功能,不需要集群
订单服务:提供findById功能,不需要集群;
步骤
导入openfeign的起步依赖
创建Feign的Client接口
在引导类上添加注解@EnableFeignClients("Client接口所在的包名")
使用Client进行远程调用
实现
添加依赖
在order-service中添加open-feign的依赖坐标 order为订单:在调用者方配置
order 称消费者 订单服务 来调用提供者 用户服务
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建
Feign客户端
启用
Feign支持
修改引导类,添加注解@EnableFeignClients("Client接口所在的包名")
使用Feign后,不需要再使用RestTemplate了,
要把order-service中所有RestTemplate相关的代码全部删除
修改
调用代码
修改OrderService类,使用UserClient调用用户服务
测试
报错.........????? 解析:
依次启动user-service,order-service
打开浏览器输入 http://localhost:7070/order/101 ,可以正常访问
3. Feign配置Ribbon
1.说明
Feign默认已经使用Ribbon做了负载均衡,不需要做额外的配置,还使用Ribbon原本的配置即可
2 配置
配置示例如下:
user-service:
ribbon:
NFLoadBalancerRuleClassName: 负载均衡策略权限类名
4. Feign配置日志
1 说明
在之前的开发中,我们通过修改
logging.level
来控制日志输出的级别。然后这项配置不会对Feign生效因为@FeignClient注解的客户端都是接口,我们实际上是通过这个接口的代理对象来进行远程调用的。而每个代理对象都会生成一个新的Feign.Logger实例对象,我们需要额外指定这个日志级别才可以。
步骤:
修改配置文件,设置整体的日志级别
创建Feign配置类,注册Logger.Level用于设置Feign的日志级别
2 步骤
1) 设置整体的日志级别
修改order-service的配置文件,设置日志级别为debug
logging: level: com.cludbase.order: debug
2) 配置Feign的日志级别
以下两种方式,用哪种都行
方式一:@Bean方式
单独为Feign配置一个日志类,设置日志级别
import feign.Feign; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfig { /** * Feign的日志级别,是通过Logger.Level枚举来指定的 * 它支持4种日志级别: * 1. NONE:不输出任何日志,是默认值 * 2. BASIC:仅输出请求的方式、URL以及响应状态码、执行时间 * 3. HEADERS:在BASIC基础上,额外输出请求头和响应头信息 * 4. FULL:输出所有请求和响应的明细,包括头信息、请求体、元数据 */ @Bean public Logger.Level feignLog(){ return Logger.Level.FULL; } }
方式二:配置文件方式
修改配置文件application.yaml,添加如下配置:
feign: client: config: default: loggerLevel: FULL
3) 测试效果
依次启动user-service、order-service
打开浏览器访问 http://localhost:7070/order/101
查看控制台日志
调用过程说明:
5. Feign使用优化
1 说明
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
这里我们用Apache的HttpClient来演示,步骤如下:
添加httpclient的依赖坐标
配置httpclient连接池
2 步骤
1) 引入依赖
在order-service的pom文件中引入Apache的HttpClient依赖:
<!--httpClient的依赖 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
2) 配置连接池
在order-service的application.yml中添加配置:
feign: httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数
3) 测试效果
接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:
Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient:
6. 小结
.............
二、网关Gateway
- 了解网关的作用
- 能够使用Gateway搭建网关
- 能够使用GatewayFilter
- 能够自定义GlobalFilter
1. 网关简介
1 网关简介
在微服务架构中,一个系统会被拆分为很多个微服务。那么作为消费者要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在消费者一端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
客户端多次请求不同的微服务,需要有微服务的地址,增加客户端代码或配置编写的复杂性
认证复杂,每个服务都需要独立认证。
存在跨域请求,在一定场景下处理相对复杂
上面的这些问题可以借助API网关来解决。所谓的API网关,就是指系统的统一入口。它封装了应用程序的内部结构,为客户端提供统一服务。
一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
2 Gateway简介
Spring Cloud Gateway是Spring基于Spring5.0、SpringBoot2.0、Project Reactor等技术开发的网关技术
旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
它不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控和限流。
它是用于代替NetFlix Zuul的一套解决方案:webflux
Spring Cloud Gateway组件的核心是一系列的过滤器,通过过滤器可以将客户端的请求转发到应用的微服务(这个过程叫路由)。Spring Cloud Gateway是站在整个微服务最前沿的防火墙和代理器,隐藏微服务节点的ip信息、从而加强安全保护。
Spring Cloud Gateway本身也是一个微服务,需要注册到注册中心
Spring Cloud Gatewa的核心功能是:路由和过滤
2. Gateway入门【重点】
1 核心概念
路由Route
一个路由的配置信息,由一个id、一个目的地url、一组断言工厂、一组过滤器组成。
断言Predicate
断言是一种判断规则;
如果客户端的请求符合要求的规则,则这次请求将会被路由到目的地
Spring Cloud Gateway的断言函数输入类型是Spring5.0框架中ServerWebExchange,
它允许开发人员自定义匹配来自HTTP请求中任何信息
过滤器Filter
Spring Cloud Gateway中的Filter可以对请求和响应进行过滤修改。是一个标准的Spring WebFilter。它分为两类:
Gateway Filter:局部过滤器(路由过滤器),应用于单个路由或者一组路由,通常由SpringCloudGateway内置好
Global Filter:全局过滤器,应用于所有路由
2 入门示例
说明
浏览器通过api网关,将以/user开头的请求转发到用户微服务
步骤
创建一个模块:网关模块,导入gateway的依赖
创建引导类
创建配置文件:
网关的端口
设置服务名称
配置网关的路由:给每个微服务设置路由信息
实现
1) 创建模块导入依赖
<dependencies> <!-- gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
2) 创建引导类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3) 编写配置文件
server: port: 10000 spring: application: name: api-gateway cloud: gateway: routes: #用户服务的路由:什么样的请求,让网关吧它分发到用户服务 - id: user # 路由的唯一标识,不设置也行 uri: http://localhost:8081/ # 路由的目的地,目标微服务的访问路径,这里是用户服务的地址 predicates: #断言,什么样的请求可以到达目标地 - Path=/user/** #如果请求路由符合/user-service/**表达式,就把请求路由到uri对应的目的地 - id: order uri: http://localhost:7071 predicates: #断言,什么样的请求可以到达目标地 - Path=/order/** #如果请求路由符合/user/**表达式,就把请求路由到uri对应的目的地
测试
依次启动user-service、api-gateway
打开浏览器输入 http://localhost:10000/user/1,发现可以查询到id为1的用户
3. 面向服务路由
1.说明
刚刚的基础版配置中,把路由目的地的地址写死了。一旦地址变化,或者是集群环境要实现负载均衡,上边的配置就难以处理。
接下来,我们要从注册中心中获取目的地的地址,并且实现负载均衡
添加nacos-discovery依赖坐标
修改引导类,添加@EnableDiscoveryClient
修改配置文件
配置注册中心地址
把路由配置的uri修改成
lb://服务名称
。lb
是loadBalanced,负载均衡的意思2.步骤
1) 添加nacos-discovery依赖
在pom.xml里添加nacos-discovery的依赖坐标
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
2) 启用服务发现
在引导类上上添加注解@EnableDiscoveryClient
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3) 修改配置文件
server: port: 10000 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: localhost:8848 #注册中心地址 gateway: routes: - id: user-service uri: lb://user-service #路由目的地的地址: lb 表示从注册中心拉取服务列表,并启用负载均衡 predicates: - Path=/user/**
配置路由uri地址时,如果使用lb协议,以
lb://user-service
为例,表示gateway将使用LoadBanalcerClient,从注册中心拉取服务实例列表,并使用ribbon进行负载均衡。3.测试
依次启动user-service、api-gateway
打开浏览器通过网关查询用户,输入 http://localhost:10000/user/1,发现可以查询到id为1的用户
........
4. Gateway断言【了解】
我们在配置文件中写的
predicates
断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个,而我们需要掌握的只有Path
所有断言工厂的使用方式都是 在网关的路由配置中,使用
predicates
配置的:spring: cloud: gateway: routes: - id: 路由唯一标识 uri: lb://user-service #路由目的地的地址 predicates: #断言,可以配置多个 - Path=/user/** # - 断言名称=配置值
其它断言工厂参考:Spring Cloud Gateway
名称 说明 示例 After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver] Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p Header 请求必须包含某些header - Header=X-Request-Id, \d+ Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org Method 请求方式必须是指定方式 - Method=GET,POST Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/** Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24 Weight 权重处理
5. Gateway过滤器【重点】
1 说明
Gateway的过滤器会对请求或响应进行拦截,完成一些通用操作。在Gateway中, Filter的生命周期(执行时机)只有两个:
PRE: 这种过滤器在请求被路由之前调用,可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等
POST:这种过滤器在路由到微服务以后执行,可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
2 局部过滤器GatewayFilter
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器,
这些过滤器如果配置到单个路由下,就只针对这个路由进行过滤
如果配置到
default-filters
下,就针对所有路由进行过滤网关过滤器列表如下:
过滤器工厂 作用 参数 AddRequestHeader 为原始请求添加Header Header的名称及值 AddResponseHeader 为原始响应添加Header Header的名称及值 RemoveRequestHeader 为原始请求删除某个Header Header名称 RemoveResponseHeader 为原始响应删除某个Header Header名称 RequestRateLimiter 用于对请求限流,限流算法为令牌桶 keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus --------------------------------------- ------------------------------------------------------- --------------------------------------- AddRequestParameter 为原始请求添加请求参数 参数名称及值 DedupeResponseHeader 剔除响应头中重复的值 需要去重的Header名称及去重策略 Hystrix 为路由引入Hystrix的断路器保护 HystrixCommand
的名称FallbackHeaders 为fallbackUri的请求头中添加具体的异常信息 Header的名称 StripPrefix 用于截断原始请求的路径 使用数字表示要截断的路径的数量 PrefixPath 为原始请求路径添加前缀 前缀路径 PreserveHostHeader 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host 无 RedirectTo 将原始请求重定向到指定的URL http状态码及重定向的url RemoveHopByHopHeadersFilter 为原始请求删除IETF组织规定的一系列Header 默认就会启用,可以通过配置指定仅删除哪些Header RewritePath 重写原始的请求路径 原始路径正则表达式以及重写后路径的正则表达式 RewriteResponseHeader 重写原始响应中的某个Header Header名称,值的正则表达式,重写后的值 SaveSession 在转发请求之前,强制执行 WebSession::save
操作无 secureHeaders 为原始响应添加一系列起安全作用的响应头 无,支持修改这些安全响应头的值 SetPath 修改原始的请求路径 修改后的路径 SetResponseHeader 修改原始响应中某个Header的值 Header名称,修改后的值 SetStatus 修改原始响应的状态码 HTTP 状态码,可以是数字,也可以是字符串 Retry 针对不同的响应进行重试 retries、statuses、methods、series RequestSize 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large
请求包大小,单位为字节,默认值为5M ModifyRequestBody 在转发请求之前修改原始请求体内容 修改后的请求体内容 ModifyResponseBody 修改原始响应体的内容 修改后的响应体内容 局部过滤器使用示例
在网关中给用户服务和订单服务做了路由配置,要求:
当浏览器访问用户服务时,添加一个响应头:abc=user service
当浏览器访问订单服务时,添加一个响应头:aaa=order service is strong
当浏览器访问任意服务时,添加一个响应头:company=itcast
配置过滤器
spring: application: name: api-gateway cloud: gateway: routes: - id: user-service #用户服务的路由配置 uri: lb://user-service predicates: - Path=/user/** filters: - AddResponseHeader=abc, user service is strong - id: order-service #订单服务的路由配置 uri: lb://order-service predicates: - Path=/order/** filters: - AddResponseHeader=aaa, order service works great default-filters: #添加到这里的过滤器,对所有路由都生效 - AddResponseHeader=company, itcast
测试效果
重启网关服务
打开浏览器F12的Network进行抓包
访问网址 http://localhost:10000/order/101,可以看到有两个响应头
访问网址 http://localhost:10000/user/1,可以看到有两个响应头
3 全局过滤器GlobalFilter
内置全局过滤器
全局过滤器作用于所有路由,而且无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理。如下:
自定义全局过滤器【重点】
说明
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的。
示例
全局过滤器类必须实现GlobalFilter接口,重写filter方法,在filter方法里实现过滤逻辑
全局过滤器类可以实现Ordered接口,重写getOrder方法,如果需要设置过滤器执行顺序的话
类上要加注解
@Component
@Component public class DemoGlobalFilter implements Ordered, GlobalFilter { /** * 执行过滤的方法 */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //如果要处理请求,就从exchange里获取request对象 ServerHttpRequest request = exchange.getRequest(); // 获取请求路径 System.out.println("本次请求路径:" + request.getURI()); // 获取请求头 System.out.println("请求头Host:" + request.getHeaders().getFirst("Host")); //如果要处理响应,就从exchange里获取response对象 ServerHttpResponse response = exchange.getResponse(); // 设置响应状态码 response.setStatusCode(HttpStatus.UNAUTHORIZED); // 设置响应cookie response.addCookie(ResponseCookie.from("cookieName", "cookieValue").build()); // 结束本次请求,并返回响应 // return response.setComplete(); //放行 return chain.filter(exchange); } /** * 设置过滤器的执行顺序,值越小,执行的越早 */ @Override public int getOrder() { return 0; } }
练习:校验token
下面,我们一起通过代码的形式自定义一个过滤器,实现用户鉴权
如果本次请求携带了请求头Authorization(token值),则放行;
否则不放行,并且返回状态码401
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.List; @Component public class AuthGlobalFilter implements GlobalFilter , Ordered{ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取请求头Authorization List<String> authorization = exchange.getRequest().getHeaders().get("Authorization"); //如果获取不到Authorization if (authorization == null || authorization.size() == 0) { System.out.println("鉴权失败"); //设置响应状态码 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); //结束本次调用 return exchange.getResponse().setComplete(); } //放行到下一个过滤器 return chain.filter(exchange); } @Override public int getOrder() { //数值越小,执行的优先级越高 return 0; } }
4 过滤器执行顺序【了解】
请求进入网关会碰到三类过滤器:defaultFilter、当前路由的过滤器、GlobalFilter
当一次请求进入网关后,网关会:
找到所有能拦截本次请求的 defaultFilter、局部过滤器GatewayFilter、全局过滤器GlobalFilter
根据所有过滤器的Order排序值进行排序,值越小,优先级越高,执行的越早
GlobalFilter全局过滤器,通过实现Ordered接口或者添加@Order注解来指定Order值
GatewayFilter局部过滤器和defaultFilter由Spring指定order值,默认按照声明的顺序从1开始递增
如果过滤器的Order值相同,就会按照:defaultFilter > GatewayFilter局部过滤器 > GlobalFilter全局过滤器
6. 跨域问题
1 浏览器的同源策略
什么是同源策略
1995年,同源策略由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个同源策略。它是指:A页面的一些资源,只允许同源的资源进行访问。
所谓的同源包含:同协议、同域名、同端口。假如有一个资源是
http://www.itcast.cn/a.html
,那么:
https://www.itcast.cn/b.html
:不同源,因为协议不同
http://itcast.cn/b.html
:不同源,因为域名不同
http://www.itcast.cn:81/b.html
:不同源,因为端口不同
http://www.itcast.cn/b.html
:同源,因为同协议、同域名、同端口被同源限制的资源有:
Cookie、LocalStorage 和 IndexDB:只能同源的页面进行访问
DOM和js对象 :只能同源的页面才能获得
AJAX :只能向同源的资源发Ajax请求
什么是跨域问题
如果
http://localhost:80/index.html
页面上要发起一个Ajax请求,请求的目的地是:http://localhost:8080/user/1
,这就是一个跨域Ajax请求了。受限于浏览器的同源策略,这次请求是必定发送不成功的
但是目前流行的开发方式是前后端分离,即前端资源使用nginx部署到单独的服务器上,服务端项目部署到其它服务器上,这样的情况下,跨域请求就不可避免了。我们该如何规避浏览器的同源策略,允许浏览器跨域发送Ajax请求呢?
2 模拟跨域问题
1 准备前端界面
把资料里的
index.html
放到Nginx的html目录里,nginx端口使用80启动Nginx,打开浏览器访问http://localhost/index.html
点击页面上的按钮,发送一次Ajax请求。使用抓包工具可以看到报错
CORS就是跨域问题
3 解决跨域问题
只需要在网关里添加如下配置即可:
spring: gateway: globalcors: # 全局的跨域处理 add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题 corsConfigurations: '[/**]': allowedOrigins: "*" # 允许哪些网站的跨域请求。 *表示任意网站 allowedMethods: # 允许的跨域ajax的请求方式 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允许在请求中携带的头信息 allowCredentials: true # 是否允许携带cookie maxAge: 360000 # 这次跨域检测的有效期
7. 小结
1. 网关Gateway
网关在整个微服务系统的最前沿,是一次请求到微服务,必须经过的第一关
网关的作用:
路由:把客户端的请求,分发到对应的微服务上
过滤:判断请求是否符合条件,如果符合就放行,否则就不放行(拦截)
2. 使用步骤:
1. 创建网关服务模块,添加依赖坐标
2. 创建配置文件,配置路由
有几个服务,就要配置多少个路由
3. 创建引导类
4. 客户端访问时,只能访问网关。由网关再把请求分发到目标微服务上
3. 网关的过滤器
GatewayFilter:网关过滤器,或者局部过滤器。是springcloud gateway内置的,我们直接配置就能使用
用法:添加到配置文件内,某一个路由上,只针对这个路由生效
用法:还可以添加到配置文件内,作为default-filters默认过滤器,针对所有路由都生效
GlobalFilter:全局过滤器。是用于让开发人员自定义过滤的
创建一个Java类,实现GlobalFilter、Ordered接口
重写接口的方法:
filter方法:在这个方法里写我们的过滤逻辑
getOrder方法:在这个方法里返回一个整数值,值越小,执行的越早
4. 跨域问题:
如果在某个资源上,访问其它资源,要求必须是同源,否则不能访问。
同源的要求有 当前资源 和 目标资源必须:
同协议:都是http或者都是https
同域名:域名要完全相同
同端口:端口也要相同
以上三个条件任意一个不同,就属于跨域请求,默认是不允许的。
如果想要我们的服务端允许跨域访问:
修改网关的配置文件,增加跨域的配置globalcors
三、配置中心Nacos
1. 配置中心简介
1 微服务的配置问题
首先我们来看一下,微服务架构下关于配置文件的一些问题:
配置文件相对分散。
在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。
配置文件无法区分环境。
微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护,这比较困难。
配置文件无法实时更新。
我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一个正在运行的项目来说是非常不友好的。
基于上面这些问题,我们就需要配置中心的加入来解决这些问题。
可以统一管理配置文件
配置文件可以区分环境。给每个微服务设置多个配置文件,在启动微服务时,可以设置拉取指定的配置文件
比如:一个微服务,在配置中心提供多个配置文件。一个开发环境的配置文件,一个测试环境的配置文件,一个生产环境的配置文件
然后:在启动微服务时,可以指定要拉取哪个环境的配置文件
配置文件可以实时更新,不需要重启微服务
2 配置问题的解决方案
配置中心的思路是:
首先把项目中各种配置全部都放到一个集中的地方进行统一管理。
当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。
当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。
SpringCloudAlibaba Nacos本身就可以管理配置文件。
我们只要把配置文件放到nacos上,微服务就可以从nacos里拉取配置、实现配置的动态刷新了
2. 配置中心Nacos入门【重点】
1 把配置文件托管到nacos
1) 新增配置
2) 设置配置信息
注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。
基本不会变更的一些配置还是保存在微服务本地比较好。
2 从nacos上拉取配置
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。
但如果尚未读取application.yml,又如何得知nacos地址呢?
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
1) 添加依赖坐标
在用户服务的pom.xml中,添加nacos配置中心的坐标
注意:一旦引入了nacos配置中心的坐标,就必须有
bootstrap.yaml
配置文件<!--nacos配置--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
2) 添加bootstrap.yaml
先把用户服务的application.yaml删除掉(配置文件的所有内容,都已经托管到nacos上了)
再给用户服务创建
bootstrap.yaml
。注意:名称必须是bootstrap
spring: cloud: nacos: config: server-addr: localhost:8848 #配置中心nacos的地址 prefix: user-service # 要加载的配置文件,{application}部分 file-extension: yaml # 要加载的配置文件,{extension}后缀名部分 profiles: active: dev # 激活的环境名称,配置文件中{profile}部分
3) 测试
打开浏览器,所有功能都可以正常使用,说明已经成功拉取到了配置参数
打开浏览器,访问 http://localhost:8080/user/company,可以看到公司名称,也说明成功拉取到了配置参数
3. 配置的动态刷新
Nacos支持动态刷新配置,也叫热更新:只要在nacos里修改了配置,微服务不需要重启,就会自动拉取最新的配置参数。
有两种实现方案:
方案1:使用@Value获取参数值,并在bean对象上添加注解@RefreshScope
方案2【推荐】:使用@ConfigurationProperties封装配置参数,会自动拉取最新配置
1) 方案1: @Value和@RefreshScope
在UserController里读取参数
在UserController里使用@Value读取了company.name配置参数
在UserController上添加注解
@RefreshScope
@RestController @RefreshScope @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Value("${company.name}") private String companyName; @GetMapping("/{id}") public User findById(@PathVariable("id") Long id) { return userService.findById(id); } @GetMapping("/company") public String company(){ return companyName; } }
打开浏览器访问 http://localhost:7070/user/company,先查看一下company.name原始值
在nacos里修改
company.name
的值把
company.name
的值修改为“”打开浏览器刷新 http://localhost:7070/user/company,可以看到已经得到最新的值了
2) 方案2: @ConfigurationProperties
1.创建Company类,用于封装配置参数
@Data @ConfigurationProperties(prefix = "company") public class Company { private String name; }
2.在引导类上添加
@EnableConfigurationProperties
,启动Company类@EnableConfigurationProperties(Company.class) @EnableDiscoveryClient @SpringBootApplication @MapperScan("com.itheima.user.mapper") public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }
3.修改UserController
去掉
@RefreshScope
注解注入
Company
对象添加方法,浏览器访问时,把Company对象返回给客户端 显示到页面上
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Value("${company.name}") private String companyName; @Autowired private Company company; @GetMapping("/{id}") public User findById(@PathVariable("id") Long id) { return userService.findById(id); } @GetMapping("/company") public String companyStr(){ return companyName; } @GetMapping("/companyObj") public Company companyObj(){ return company; } }
4.功能测试
先打开浏览器访问 http://localhost:8080/user/companyObj,查看原始参数值
在Nacos里修改company.name的值
再打开浏览器,直接刷新页面,可以看到参数值已经变成最新的了
4. 配置共享【拓展】
在实际开发中,一个服务通常有多个配置文件。在不同环境中,只要激活对应的配置文件即可。例如:
user-service-dev.yaml:作为用户服务的开发环境配置
user-service-test.yaml:作为用户服务的测试环境配置
user-service-prod.yaml:作为用户服务的生产环境配置
但是这多个环境的配置文件中,可能有大部分配置参数都是完全相同的,如果在每个文件里都复制一份的话,修改维护起来会比较麻烦,这时候可以使用共享的配置文件的:
再创建一个user-service.yaml:不带环境标识的配置文件,会被所有环境共享
0 准备多环境配置
1 准备dev和test两个配置文件
在Nacos配置中心里已经有了
user-service-dev.yaml
。我们再增加一个配置文件
user-service-test.yaml
,内容如下:server: port: 8081 #测试环境的端口为8081 spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql:///cloud_user?useSSL=false username: root password: 1234 logging: level: com.cludbase.user: debug pattern: dateformat: HH:mm:ss.SSS company: name: 程序员-test
2 准备多环境的启动链接
在idea里创建dev和test的启动链接,创建方式如下:
dev环境,激活
dev
test环境,激活
test
1 多环境配置共享
1 创建共享配置文件
在Nacos的配置中心里,创建一个用户服务的共享配置文件
2 读取配置参数值
修改UserController,读取共享配置项
aaa
的值@RestController @RequestMapping("/user") public class UserController { ...略...; @Value("${aaa}") private String shareValue; @GetMapping("/share") public String share(){ return shareValue; } }
3 启动测试
同时启动两个环境的用户服务,
打开浏览器访问:
开发环境dev:http://localhost:8080/user/share,页面上可以看到共享的配置项“共享配置值”
测试环境test:http://localhost:8081/user/share,页面上可以看到共享的配置项“共享配置值”
2 配置共享的优先级
当nacos、服务本地同时出现相同属性时,优先级高的生效。优先级从高到低顺序如下:
Nacos里的
服务名-{profile}.yaml
:Nacos里激活的某一环境的配置文件Nacos里的
服务名.yaml
:Nacos里的共享配置文件
本地配置文件.yaml
:服务本地的配置文件
bootstrap.yam
5. 小结
配置中心Nacos的使用步骤:
1. 先把配置文件内容,托管到配置中心Nacos
在Nacos的配置列表界面里,新增一个配置
配置DataID:{应用服务名}-{环境标识}.{后缀名}
例如: user-service-dev.yaml
把配置文件的内容,粘贴到配置界面里
2. 修改微服务:
添加依赖坐标:nacos-config
删除application.yaml
创建bootstrap.yaml,需要配置的内容:
spring.cloud.nacos.config.server-addr: 配置中心的地址
spring.cloud.nacos.config.prefix:配置文件名称里的{应用服务名}部分
spring.cloud.nacos.config.file-extension:配置文件名称里的{后缀名}部分
spring.profiles.active:配置文件里的{环境标识}部分
3. 重启服务,浏览器访问测试
配置参数的热更新:
方案一:
代码里使用@Value("${参数名}")来读取参数值
在类上加注解@RefreshScope
方案二:
创建一个实体类,类上加
@ConfigurationProperties(prefix="参数前缀")
@Component
类里加:
成员变量,并添加get和set方法
要求:
参数前缀 + 属性名 要和 配置参数名相同
无论哪种方案,最终实现的效果是:
你在配置中心里修改了配置参数值
不需要重启服务,代码就立即得到了最新的参数值
配置共享:
如果在配置中心里有一个配置文件,名称是{应用服务名}.{后缀名}
没有指定环境标识,那么这个文件就是 这个应用服务的共享配置文件,所有环境都可以共享其中的参数
如果一个应用服务有多个配置文件,优先级顺序是:
Nacos里的{应用服务名}-{环境标识}.{后缀名}
Nacos里的{应用服务名}.{后缀名}
服务本地的配置文件application.yaml/bootstrap.yaml
四、微服务架构小结
到目前为止,我们已经有了一套相对比较完整的微服务技术体系了:
注册中心:Eureka, Nacos
配置中心:Nacos
网关:Gateway
远程调用:Feign
负载均衡:Ribbon
五、Nacos搭建集群