Nacos除了可以做注册中心,同样可以做配置管理来使用。
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。
Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。 热更新---开关,逻辑语句
> 注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。
基本不会变更的一些配置还是保存在微服务本地比较好。
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。
但如果尚未读取application.yml,又如何得知nacos地址呢?
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
首先,在user-service服务中,引入nacos-config的客户端依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
然后,在user-service中添加一个bootstrap.yaml文件,内容如下:
并且把applicationproperties中的重复部分删除
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
3)读取nacos配置
@value有两个选项,需要选择spring那个
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@org.springframework.beans.factory.annotation.Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
在user-service服务中,添加一个类,读取patterrn.dateformat属性:
@Component //处理为bean对象
@Data
@ConfigurationProperties(prefix = "pattern") //prefix中文件头+声明的string变量=要查找的文件名即可
public class PatternProperties {
private String dateformat;
}
实微服务启动时,会去nacos读取多个配置文件,例如:
- `[spring.application.name]-[spring.profiles.active].yaml`,例如:userservice-dev.yaml
- `[spring.application.name].yaml`,例如:userservice.yaml
无论profile怎么改变,`[spring.application.name].yaml`这个文件一定会被加载,因此多环境共享配置可以写入这个文件
修改user(1)中的配置,修改后就读不到dev文件
user(1)读到的属性
当nacos、服务本地同时出现相同属性时,优先级有高低之分:
配置本地环境
配置nacos中的属性,以userservice为准。
再配置nacos中dev,以dev为准
搭建集群
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
•代码可读性差,编程体验不统一
•参数复杂URL难以维护
Feign是一个声明式的http客户端
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
在order的pom中
org.springframework.cloud
spring-cloud-starter-openfeign
在order的启动项中
在order-service中新建一个接口,内容如下:
@FeignClient ("user-service") //服务名称
public interface UserClient {
@GetMapping("/user/{id}") //请求的方式+请求的路径
User findById(@PathVariable("id") Long id);
//User是返回值; Long id传递值 ;@PathVariable("id")路径占用符
}
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用feign远程调空
User user = userClient.findById(order.getUserId());
//3. 封装user和order
order.setUser(user);
// 4.返回
return order;
}
/*@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.利用RestTemplatefa发起http请求,查询用户
//2.1 url路径
String url = "http://user-server/user/"+order.getUserId();
//2.2 发送http请求,实现远程调控
User user = restTemplate.getForObject(url,User.class);
//3. 封装user和order
order.setUser(user);
// 4.返回
return order;
}*/
}
使用Feign的步骤:
① 引入依赖
② 添加@EnableFeignClients注解
③ 编写FeignClient接口
④ 使用FeignClient中定义的方法代替RestTemplate
两种方式来配置日志
基于配置文件修改feign的日志级别可以针对单个服务:
```yaml
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
```
也可以针对所有服务:
```yaml
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
```
而日志的级别分为四种:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
全局有效
局部有效
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用**连接池**代替默认的URLConnection。
还有就是日志不要开成full,basic就行了
io.github.openfeign
feign-httpclient
在order-service的application.yml中添加配置:
```yaml
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
```
1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。
2)Feign客户端和Controller都集成改接口
优点:
- 简单
- 实现了代码共享
缺点:
- 服务提供方、服务消费方紧耦合
- 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。
在cloud-demo下创建一个子module--feign-api
在feign-api中然后引入feign的starter依赖
org.springframework.cloud
spring-cloud-starter-openfeign
把这些剪切到feign-api 的module中
在order-service的pom文件中中引入feign-api的依赖:
```xml
cn.itcast.demo
feign-api
1.0
```
这是因为UserClient现在在cn.itcast.feign.clients包下,
而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。
在启动项中order中加入
方式一:
指定Feign应该扫描的包:全取
```java
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
```
方式二:
指定需要加载的Client接口:精准(推荐),可以写数组,一个就不需要{}
```java
@EnableFeignClients(clients = {UserClient.class})
```
**权限控制**:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
**路由和负载均衡**:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
**限流**:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而gateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
创建cloud-demo中的子类----gateway
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
创建application.yml文件
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: userservice # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://user-service # 路由的目标地址 lb(loadbalance)就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
- id: orderservice
uri: lb://order-service
predicates:
- Path=/order/**
总结:
网关搭建步骤:
1. 创建项目,引入nacos服务发现和gateway依赖
2. 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
1. 路由id:路由的唯一标示
2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
3. 路由断言(predicates):判断路由的规则,
4. 路由过滤器(filters):对请求或响应做处理
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由
`org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory`类来
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
以AddRequestHeader 为例
> **需求**:给所有进入userservice的请求添加一个请求头:Truth=Lakers is freaking awesome!
只需要修改gateway服务的application.yml文件,添加路由过滤即可:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Laker is freaking awesome! # 添加请求头
默认过滤器
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
总结
过滤器的作用是什么?
① 对路由的请求或响应做加工处理,比如添加请求头
② 配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
① 对所有路由都生效的过滤器
网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
在filter中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization,
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
@Order(-1) //优先级,越小越优先
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
- 每一个过滤器都必须指定一个int类型的order值,**order值越小,优先级越高,执行顺序越靠前**。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域:域名不一致就是跨域,主要包括:
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
spring: #删除多余的语句
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]': #拦截一切请求
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期