1. 统一检查maven

  2. maven依赖出错的解决

  3. 注意代码格式化。因代码格式混乱,导致代码出错,pom.xml出现重复的parent标签

  4. 学习方法,听得懂为什么要这么做,要远远比 怎么做 重要的多

一、远程调用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功能,不需要集群;

步骤

  1. 导入openfeign的起步依赖

  2. 创建FeignClient接口

  3. 在引导类上添加注解@EnableFeignClients(“Client接口所在的包名“)

  4. 使用Client进行远程调用

实现

添加依赖

在order-service中添加open-feign的依赖坐标order为订单:在调用者方配置

order 称消费者 订单服务 来调用提供者用户服务


org.springframework.cloud
spring-cloud-starter-openfeign

创建

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实例对象,我们需要额外指定这个日志级别才可以。

步骤:

  1. 修改配置文件,设置整体的日志级别

  2. 创建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;@Configurationpublic class FeignConfig {/** * Feign的日志级别,是通过Logger.Level枚举来指定的 * 它支持4种日志级别: *1. NONE:不输出任何日志,是默认值 *2. BASIC:仅输出请求的方式、URL以及响应状态码、执行时间 *3. HEADERS:在BASIC基础上,额外输出请求头和响应头信息 *4. FULL:输出所有请求和响应的明细,包括头信息、请求体、元数据 */@Beanpublic 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来演示,步骤如下:

  1. 添加httpclient的依赖坐标

  2. 配置httpclient连接池

2 步骤

1) 引入依赖

在order-service的pom文件中引入Apache的HttpClient依赖:

io.github.openfeignfeign-httpclient

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开头的请求转发到用户微服务

步骤

  1. 创建一个模块:网关模块,导入gateway的依赖

  2. 创建引导类

  3. 创建配置文件:

    • 网关的端口

    • 设置服务名称

    • 配置网关的路由:给每个微服务设置路由信息

实现

1) 创建模块导入依赖

org.springframework.cloudspring-cloud-starter-gateway

2) 创建引导类

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

3) 编写配置文件

server:port: 10000spring:application:name: api-gatewaycloud:gateway:routes: #用户服务的路由:什么样的请求,让网关吧它分发到用户服务- id: user # 路由的唯一标识,不设置也行uri: http://localhost:8081/ # 路由的目的地,目标微服务的访问路径,这里是用户服务的地址predicates: #断言,什么样的请求可以到达目标地- Path=/user/** #如果请求路由符合/user-service/**表达式,就把请求路由到uri对应的目的地- id: orderuri: http://localhost:7071predicates: #断言,什么样的请求可以到达目标地- Path=/order/** #如果请求路由符合/user/**表达式,就把请求路由到uri对应的目的地


测试

  • 依次启动user-service、api-gateway

  • 打开浏览器输入 http://localhost:10000/user/1,发现可以查询到id为1的用户

3. 面向服务路由

1.说明

刚刚的基础版配置中,把路由目的地的地址写死了。一旦地址变化,或者是集群环境要实现负载均衡,上边的配置就难以处理。

接下来,我们要从注册中心中获取目的地的地址,并且实现负载均衡

  1. 添加nacos-discovery依赖坐标

  2. 修改引导类,添加@EnableDiscoveryClient

  3. 修改配置文件

    配置注册中心地址

    把路由配置的uri修改成 lb://服务名称lb是loadBalanced,负载均衡的意思

2.步骤

1) 添加nacos-discovery依赖

在pom.xml里添加nacos-discovery的依赖坐标

com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discovery

2) 启用服务发现

在引导类上上添加注解@EnableDiscoveryClient

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient@SpringBootApplicationpublic class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

3) 修改配置文件

server:port: 10000spring:application:name: api-gatewaycloud:nacos:discovery:server-addr: localhost:8848 #注册中心地址gateway:routes:- id: user-serviceuri: 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为原始请求添加HeaderHeader的名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
———————————————————————————————-—————————————
AddRequestParameter为原始请求添加请求参数参数名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
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-gatewaycloud:gateway:routes:- id: user-service #用户服务的路由配置uri: lb://user-servicepredicates:- Path=/user/**filters:- AddResponseHeader=abc, user service is strong- id: order-service #订单服务的路由配置uri: lb://order-servicepredicates:- Path=/order/**filters:- AddResponseHeader=aaa, order service works greatdefault-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

@Componentpublic class DemoGlobalFilter implements Ordered, GlobalFilter {/** * 执行过滤的方法 */@Overridepublic Mono 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);//设置响应cookieresponse.addCookie(ResponseCookie.from("cookieName", "cookieValue").build());//结束本次请求,并返回响应// return response.setComplete();//放行return chain.filter(exchange);}/** * 设置过滤器的执行顺序,值越小,执行的越早 */@Overridepublic 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;@Componentpublic class AuthGlobalFilter implements GlobalFilter , Ordered{@Overridepublic Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {//获取请求头AuthorizationList authorization = exchange.getRequest().getHeaders().get("Authorization");//如果获取不到Authorizationif (authorization == null || authorization.size() == 0) {System.out.println("鉴权失败");//设置响应状态码exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//结束本次调用return exchange.getResponse().setComplete();}//放行到下一个过滤器return chain.filter(exchange);}@Overridepublic int getOrder() {//数值越小,执行的优先级越高return 0;}}

4 过滤器执行顺序【了解】

请求进入网关会碰到三类过滤器:defaultFilter、当前路由的过滤器、GlobalFilter

当一次请求进入网关后,网关会:

  1. 找到所有能拦截本次请求的 defaultFilter、局部过滤器GatewayFilter、全局过滤器GlobalFilter

  2. 根据所有过滤器的Order排序值进行排序,值越小,优先级越高,执行的越早

    GlobalFilter全局过滤器,通过实现Ordered接口或者添加@Order注解来指定Order值

    GatewayFilter局部过滤器和defaultFilter由Spring指定order值,默认按照声明的顺序从1开始递增

  3. 如果过滤器的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 # 是否允许携带cookiemaxAge: 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 微服务的配置问题

首先我们来看一下,微服务架构下关于配置文件的一些问题:

  1. 配置文件相对分散。

    在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。

  2. 配置文件无法区分环境。

    微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护,这比较困难。

  3. 配置文件无法实时更新。

    我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一个正在运行的项目来说是非常不友好的。

基于上面这些问题,我们就需要配置中心的加入来解决这些问题。

  1. 可以统一管理配置文件

  2. 配置文件可以区分环境。给每个微服务设置多个配置文件,在启动微服务时,可以设置拉取指定的配置文件

    比如:一个微服务,在配置中心提供多个配置文件。一个开发环境的配置文件,一个测试环境的配置文件,一个生产环境的配置文件

    然后:在启动微服务时,可以指定要拉取哪个环境的配置文件

  3. 配置文件可以实时更新,不需要重启微服务

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配置文件

com.alibaba.cloudspring-cloud-starter-alibaba-nacos-config

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 {@Autowiredprivate 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;}}
  1. 打开浏览器访问 http://localhost:7070/user/company,先查看一下company.name原始值

  2. 在nacos里修改company.name的值

    company.name的值修改为“”

  3. 打开浏览器刷新 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 {@Autowiredprivate UserService userService;@Value("${company.name}")private String companyName;@Autowiredprivate 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 #测试环境的端口为8081spring:application:name: user-servicecloud:nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql:///cloud_user" />

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、服务本地同时出现相同属性时,优先级高的生效。优先级从高到低顺序如下:

  1. Nacos里的服务名-{profile}.yaml:Nacos里激活的某一环境的配置文件

  2. Nacos里的服务名.yaml:Nacos里的共享配置文件

  3. 本地配置文件.yaml:服务本地的配置文件

  4. 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搭建集群