1. 远程调用
微服务就是拆分, 当拆分后某些数据在不同的服务里,此时用RestTemplate发送Http请求,实现远程调用。
- tips:
2.服务治理,当我想开启多个服务去处理请求时,会遇到一些问题,比如说:a、b、c三个请求,端口号不一样,那我怎么去选择请求地址?类似的问题,此时就需要服务治理。
2.1 注册中心原理
2.2Nacos注册中心
调用服务的人需要做以下的事情,服务注册只需要做前两步,心跳等功能都包含在依赖里,直接就封装注册好了。
配合远程调用使用,解决远程调用时把服务地址写死的问题。
以下是加上服务发现改造的代码。
2.3OpenFeign
2.3.1 是什么?
2.3.2使用
这里是创建了一个接口类,给类上加了@FeignClient注解, value名称就是要调用的服务名称,接口类的方法编写如图所示, 类似Controller。 且在调用的时候类似Mapper,不需要写实现的方法,直接注入后调用方法就可以了。 里面封装了获取服务地址、参数、负载均衡等功能,所以之前调用服务的方法里RestTemplate和DiscoveryClient的代码都用不到了。 直接一行代码调用方法,搞定。
上面在使用时,底层是反射代理,每次都要去连接,这里再次优化。(这我没太听懂说实话,像是线程 jdbc吗?)
这时候又出现了别的问题, 难道每一个需要调用商品的服务都要去写对应的DTO和ItemClient接口吗?给出解决方案:
2.3.3 配置OpenFeign的日志级别, 建议平常不要开启(因为会输出很多信息,降低性能), 需要调试的时候再开启。
上面四个功能总结
3.网关
3.1什么是网关
网关就是网络的关口,负责请求的路由、转发、身份校验。
有了网关以后,微服务的地址也不需要再暴露给前端,前端只需要知道网关的地址就可以了。
3.2 快速入门
步骤: 1. 创建新模块(网关模块gateway)2.引入网关依赖 3.编写启动类 4.配置路由规则
在 网关gateway微服务里 的 .yaml文件中配置路由
spring:
cloud:
gateway:
routes:
- id: item-service;
# 路由目标 lb的意思的是 负载均衡
uri: lb://item-service
# 路径 只要符合这个路径就会路由到 item-service这个微服务里
predicates:
# 当有很多的controller时,用,分隔配置多个
- Path=/item/**, /search/**
3.3 路由属性
常见的路由过滤器,拿StripPrefix举例,在前端有时候发请求会以/api/items/list这种,那么就会去除/api这段前缀
举例
spring:
cloud:
gateway:
routes:
- id: item-service;
# 路由目标 lb的意思的是 负载均衡
uri: lb://item-service
# 路径 只要符合这个路径就会路由到 item-service这个微服务里
predicates:
# 当有很多的controller时,用,分隔配置多个
- Path=/item/**, /search/**
filters:
# 添加了一段请求头 truth = Nothing is impossible
- AddRequestHeader=truth, Nothing is impossible
如果不是只想在某一个微服务里配置过滤器, 可以这样写
spring:
cloud:
gateway:
routes:
xxxx
default-filters:
- AddRequestHeader=truth, xxxx
3.4 网关登录校验
三个问题:1. 如何实现登录校验? 2. 如何通过网关传递用户信息 3.微服务间的调用如何传递用户信息?
3.4.1 自定义过滤器
实现登录校验 的过滤器
@Component
@RequiredArgsConstructor //构造注入bean
public class AuthGlobalFilter implements GlobalFilter, Ordered {
//引入配置信息bean
private final AuthProperties authProperties;
//jwt工具
private final JwtTool jwtTool;
//路径匹配器
private final AntPathMatcher antPathMathcer = new AntPathMatcher()
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
//获取Servlet
ServerHttpRequest request = exchange.getRequest();
//判断是否需要做登录拦截
if(isExclude(request.getPath().toString())){
//放行
return chain.filter(exchange)
}
//获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
//判断
if(headers != null && !headers.isEmpty()){
token = headers.get(0);
}
//解析token
Long userId = null;
try{
userId = jwtTool.parseToken(token);
}catch(UnauthorizedException e){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//TODO 传递登录信息
System.out.println("userId:" + userId)
//放行
return chain.filter(exchange)
}
//判断路径是否为 不需要登陆权限的方法
private boolean isExclude(String path){
for (String pathPattern : authProperties.getExcludePaths()){
if(antPathMathcer.match(pathPatter, path)){
return true;
}
}
retrun false;
}
}
实现网关传递信息给微服务
完成第一件事:保存用户信息到请求头
完善上面最后两段代码
@Component
@RequiredArgsConstructor //构造注入bean
public class AuthGlobalFilter implements GlobalFilter, Ordered {
//引入配置信息bean
private final AuthProperties authProperties;
//jwt工具
private final JwtTool jwtTool;
//路径匹配器
private final AntPathMatcher antPathMathcer = new AntPathMatcher()
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
//获取Servlet
ServerHttpRequest request = exchange.getRequest();
//判断是否需要做登录拦截
if(isExclude(request.getPath().toString())){
//放行
return chain.filter(exchange)
}
//获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
//判断
if(headers != null && !headers.isEmpty()){
token = headers.get(0);
}
//解析token
Long userId = null;
try{
userId = jwtTool.parseToken(token);
}catch(UnauthorizedException e){
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//传递用户信息
String userInfo = userId.toString();
//通过封装好的方法,把userInfo写入请求头,注意这里要和接受方统一请求头名称
ServerWebExchange swe = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build();
//放行
return chain.filter(swe)
}
//判断路径是否为 不需要登陆权限的方法
private boolean isExclude(String path){
for (String pathPattern : authProperties.getExcludePaths()){
if(antPathMathcer.match(pathPatter, path)){
return true;
}
}
retrun false;
}
}
完成第二件事:在微服务里添加拦截器, 获取用户信息保存到ThreadLocal中。
思考:要在每一个微服务中都添加拦截器,那么代码重复性过高。 所以把拦截器写在common模块中,微服务直接调用。
实现 : 1. 写完拦截器代码后,注册拦截器信息。但是包不同,如何让这个配置类生效?
答: 利用自动装配的原理, 写入 spring.factories中。
2. 一开始所有微服务都生效,但是报错。 因为网关里是无法扫描SpringMVC的包的。
解决:
第三件事: 微服务之间互相调用,如何传递用户信息?
总结:登录功能解决
3.5配置管理
问题: 不同微服务的配置文件重复配置过多,维护成本高
解决: 配置管理服务, 让微服务去读取配置。
实现:
在Nacos中配置列表新建配置文件,不同的模块创建不同的配置文件。
注意:
步骤:
这样的话微服务里的 .yaml文件只需要去定义端口号、变量等很少的配置信息就可以了。
比如
3.5.1配置热更新
即修改文件中的配置信息时,微服务无需重启就可以生效。
4. 雪崩问题
4.1.服务保护方案 : 请求限流、线程隔离、服务熔断,统称服务降级。
请求限流:限制流量在服务可以处理的范围内
线程隔离: 控制业务可用的线程数量,将故障隔离到一定范围(类似船的各个船舱,A进水了,就隔离在A。)
4.2服务保护技术
Hystrix只支持2020之前版本的SpringCloud,Sentinel支持新版本的
4.2.1认识 Sentinel
(1)参考文档搭建图形化控制台
(2)系统整合 引入依赖
调用请求,查看控制台
最后一行:
请求限流设置
线程隔离
Fallback
作用:出现异常时走Fallback的逻辑,不要去抛异常,这样对客户端的用户提示更加友好
设置熔断规则
扩展: 熔断规则的持久化,通过Nacos拉取配置文件完成。(了解一下)
5.分布式事务
5.1 概念
5.2 认识 Seata事务管理
5.3 部署TC服务
5.4XA模式 强一致性,因为都会锁住
5.5AT模式 最终一致 如果微服务1、2没问题都提交了,就可以进行别的操作了。但是如果3出问题了,那么会出现数据短暂不一致的情况。
1.需要在每一个微服务的数据库生成自己的快照表。
2.修改配置文件