Nacos配置管理
统一配置管理
- 在Nacos中新增配置信息
- 配置相关信息
获取统一配置的管理中的内容 - 引入Nacos的配置管理客户端依赖:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 运行环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀
- 删除application.yml中重复的配置代码
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root
mybatis:
configuration:
map-underscore-to-camel-case: true # true开启驼峰命名法
logging:
level:
com.zfc: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
- 我们在user-service中将pattern.dateformat这个属性注入到UserController中做测试:看有没有使用到统一配置管理中的配置
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//从nacos中获取配置属性
@Value("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器来格式化现在的时间并返回
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat, Locale.CHINA));
}
如果输出的格式和 统一配置管理 中的一样,则代表使用成功,可以更改几次观察。。。
总结,将配置交给Nacos管理的步骤:
- 在Nacos中添加配置文件
- 在微服务中引入nacos的config依赖
3.在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件 - 清除application.yml 中nacos 相关的代码
配置热更新
如果你有测试上面的获取统一配置管理会发现一个问题,那就是更改了统一配置管理中的内容后需要在idea中重启服务后才能及时同步统一配置管理中的内容,因此就需要配置热更新了。
方式一: 在@Value注入的变量所在类上添加注解@RefreshScope
方式二: 使用@ConfigurationProperties注解
新增配置类
package com.zfc.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* ConfigurationProperties
* 约定大于配置,只要你的前缀名(prefix = "pattern")和属性名(dateformat) 拼接跟配置文件一致,就能完成属性的自动注入
*/
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
使用
@Autowired
private PatternProperties patternProperties;
//编写controller,通过日期格式化器来格式化现在的时间并返回
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat(), Locale.CHINA));
}
完成后即可进行测试。
总结: Nacos配置更改后,微服务可以实现热更新,方式:
- 通过@Value注解注入,结合@RefreshScope来刷新
- 通过@ConfigurationProperties注入,自动刷新
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
配置共享
? 当有很多个环境时,如果环境中有些配置是相同的,那是不是就只需要配置一次呢?这个时候就用到了配置共享了。
微服务启动时会从nacos读取多个配置文件
- [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
- [spring.application.name].yaml,例如:userservice.yaml
- 新增配置
- 在配置文件中新增相对于的属性
- 测试
如果想看到明显的区别,可以复制一个服务,然后更改它的项目环境,比如test
多种配置的优先级: [服务名]-[环境].yaml >[服务名].yaml > 本地配置
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
搭建Nacos集群
略。黑马资料中有相对于的md笔记,这里就不过多记录
Feign远程调用
Feign 介绍 Feign是一个声明式的HTTP客户端,它的目的就是让HTTP调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
Feign替代RestTemplate
- 导入依赖
<!--feign客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 启动类上添加注解 @EnableFeignClients
- 编写feign客户端
package cn.itcast.order.clients;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("userservice") //服务名称
public interface UserClient {
@GetMapping("/user/{id}")// 请求方式 请求路径
User findById(@PathVariable("id") Long id);// 返回值类型 请求参数
}
- 使用Feign替换restTemplate
package cn.itcast.order.service;
import cn.itcast.order.clients.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired(required = false)
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.使用feign客户端替代resettemplate
User user = userClient.findById(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
}
Feign自定义配置,如下:
feign支持自定义配置:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下默认值就能满足我的需求,如果要自定义时,只需要创建自定义的@Bean覆盖默认的Bean几个。
使用 修改日志级别 案例
- 使用配置yml的方式
(1)、修改ymal配置文件# 全局生效 feign: client: config: default: # defaul 是默认使用全局配置,如果写服务名称,就只争对某个微服务的配置 logger-level: FULL # 日志级别 #局部生效 feign: client: config: userservice: # 写服务名称,就只争对某个微服务的配置 logger-level: FULL # 日志级别
- 使用java代码配置的方式
(1)、声明一个@Bean
(2)、如果是使用全局配置,就放在@EnableFeignClients 这个注解中package cn.itcast.order.configuration; import feign.Logger; import org.springframework.context.annotation.Bean; public class FeignClientConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; } }
(2)、如果是想局部配置,就放在 @FeignClient 这个注解中@EnableFeignClients(defaultConfiguration = cn.itcast.order.configuration.FeignClientConfiguration.class)
@FeignClient(value = "userservice",configuration = cn.itcast.order.configuration.FeignClientConfiguration.class)
日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign使用优化
- 日志级别尽量用basic
- 使用HttpClient或OkHttp代替URLConnection
(1)、引入对应依赖
(2)、yml配置文件中配置, 开启httpclient功能,设置连接参数<!--feign客户端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
feign: client: config: default: # defaul 是默认使用全局配置,如果写服务名称,就只争对某个微服务的配置 logger-level: NONE # 日志级别 httpclient: enabled: true #使用httpclient的开关 max-connections: 200 # 最大连接数 max-connections-per-route: 50 # 每个路径的最大连接数
最佳实践
?简单的理解就睡将重复使用的代码,抽取出来,提高代码的复用性…
将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。简单的说就是把重复的代码,公共化
-
在cloud-demo 项目的基础上 再新增一个模块
feign-api
-
引入feign客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
把order-service 中的
client
… 相关的(需要提取出来作为公关部分的),复制到feign-api
中
-
在order-service 中引入
feign-api
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
- 修改order-service中与公共部分相关的import部分,导入
feign-api
中的 - 重启测试
注意: 如果你的两个包结构不一样,会出现启动报错的情况,大概意思就是自动装配失败
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方法一:在启动类声明的@EnableFeignClients()注解中,指定FeignClient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方法二:在启动类声明的@EnableFeignClients()注解中,指定FeignClient字节码(就是用哪个类就声明哪个类,{},表示可以方多个,用, 隔开)
@EnableFeignClients(clients = {UserClient.class})
Gateway服务网关
为什么需要网关
- 不是谁都可以访问服务的(不安全),这个时候就需要网关来做
身份认证、权限校验
- 网关不能处理业务查询的业务,这个时候网关就需要根据你的请求去使用哪个服务,这个就是
服务路由
,同时一个服务可能有多个实例,这个时候就通过负载均衡
实现 - 如果我的微服务允许的用户请求数量是520个,那么超过了这个额度就只能等待520中请求结束后才能请求,否则只能等待或者结束请求
网关功能:
- 身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
网关的技术实现
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。
SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
gateway快速入门
- 在cloud-demo的基础上,新建
gateway
module - 引入所需要的依赖坐标
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 在
application.yml
中,编写nacos地址、路由配置
server:
port: 10010 # 网管端口
spring:
application:
name: geteway # 服务名称
cloud:
nacos:
server-addr: localhost:80 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id:路由标识,自己定义,必须唯一
uri: lb://userservice # 路由目标地址 ,lb代表负载均衡(LoadBalanced)://服务名称
predicates: # 路由断言,判断请求是否符合路由规则的条件( 如果为真就执行,为假就不执行)
- Path=/user/** # 按照路匹配,只要是/user/开头的请求就符合格式
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
-
编写启动类启动
-
访问测试
http://localhost:10010/user/1
http://localhost:10010/order/101
网关路由可以配置的内容包括:
- 路由id:路由唯一标示
- uri:路由目的地,支持lb和http两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
断言工厂
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:
名称 | 说明 | 示例 |
---|---|---|
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 | 权重处理 |
根据需要使用
过滤器工厂
Spring提供了31种不同的路由过滤器工厂。例如:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
请求头过滤器
- 只需要修改gateway服务的application.yml文件,添加路由过滤即可:
server:
port: 10010 # 网管端口
spring:
application:
name: geteway # 服务名称
cloud:
nacos:
server-addr: localhost:80 # nacos地址
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤
- AddRequestHeader=hello,hello zfc # 添加请求头 hello:key,hello zfc:value
- 在对应服务的请求方法中获取请求头
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "hello",required = false)String hlo) {
System.out.println("hlo = " + hlo);
return userService.queryById(id);
}
默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=hello,hello zfc # 添加请求头 hello:key,hello
- 在对应服务的请求方法中获取请求头
…
总结:
过滤器的作用是什么?
-
对路由的请求或响应做加工处理,比如添加请求头
-
配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
- 对所有路由都生效的过滤器
全局过滤器
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
-
参数中是否有authorization,
-
authorization参数值是否为admin
如果同时满足则放行,否则拦截
实现代码:
package cn.itcast.gateway.filter;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 全局过滤器
*/
//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter , Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
//2.获取authorization参数
MultiValueMap<String, String> params = request.getQueryParams();
String authorization = params.getFirst("authorization");
//3.判断请求参数
if ("admin".equals(authorization)){
//4 是 放行
return chain.filter(exchange);
}
//5.否 拦截
//5.1 禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2 结束处理
return exchange.getResponse().setComplete();
}
/**
* 执行顺序,越小优先级越高,
* 跟上面注解@Order(-1)一个效果
* @return
*/
@Override
public int getOrder() {
return -1;
}
}
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接口
- 编写处理逻辑
过滤器执行顺序
路由过滤器、defaultFilter、全局过滤器的执行顺序?
- order值越小,优先级越高
- 当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
跨域问题
- 在gateway服务的application.yml文件中,添加下面的配置:然后重启项目
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
- 编写html测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<pre>
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
</pre>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios.get("http://localhost:10010/user/1?authorization=admin")
.then(resp => console.log(resp.data))
.catch(err => console.log(err))
</script>
</html>
- 打开。注意 这里不要直接使用浏览器打开
- 打开cmd 输入指令:
npm install live-server -g
- 再输入
live-server --port=8090
- 就在浏览器中启动成功了
- 把地址栏地址
127.0.0.1:8090
修改为localhost:8090
- 打开 开发者工具 查看