SpringCloud之网关、服务保护和分布式事务详解
作者:l_tian_tian_
一、网关
网络的关口,负责请求的路由、转发、身份验证
server: port: 8080 spring: cloud: nacos: discovery: server-addr: 192.168.96.129:8848 gateway: routes: - id: item-service uri: lb://item-service predicates: - Path=/items/**,/search/** - id: user-service uri: lb://user-service predicates: - Path=/addresses/**,/users/** - id: cart-service uri: lb://cart-service predicates: - Path=/carts/** - id: trade-service uri: lb://trade-service predicates: - Path=/orders/** application: name: hm-gateway
二、网关登录校验
自定义过滤器:
package com.hmall.gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class MyGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); System.out.println("GlobalFilter pre阶段 执行了"); return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
微服务项目网关:
package com.hmall.gateway.filters; import com.hmall.common.exception.UnauthorizedException; import com.hmall.gateway.config.AuthProperties; import com.hmall.gateway.utils.JwtTool; import lombok.RequiredArgsConstructor; 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.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.List; @Component @RequiredArgsConstructor public class AuthGlobalFilter implements GlobalFilter, Ordered { //不需要处理的请求路径 public final AuthProperties authProperties; public final JwtTool jwtTool; private final AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获得请求头 ServerHttpRequest request = exchange.getRequest(); //放行不需要拦截的请求 //路径合法,需要放行 if (isUnique(request.getPath().toString())){ //合法,放行 return chain.filter(exchange); } //判断令牌是否合法 String token=null; Long userId=null; List<String> authorization = request.getHeaders().get("authorization"); if (authorization != null && authorization.size() > 0) { token = authorization.get(0); } try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { //401 未登录、未授权 ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } //TODO 保存用户id到请求头,实现多个微服务间用户id的共享 String userInfo = userId.toString(); ServerWebExchange swe=exchange.mutate().request(builder -> builder.header("user-info", userInfo)).build(); //System.out.println(userId); //放行 return chain.filter(swe); } @Override public int getOrder() { return 0; } private boolean isUnique(String path) { for (String excludePath : authProperties.getExcludePaths()) { if (antPathMatcher.match(excludePath, path)) { return true; } } return false; } }
server: port: 8080 spring: application: name: hm-gateway cloud: nacos: discovery: server-addr: 192.168.96.129:8848 gateway: routes: - id: item-service uri: lb://item-service predicates: - Path=/items/**,/search/** - id: user-service uri: lb://user-service predicates: - Path=/addresses/**,/users/** - id: cart-service uri: lb://cart-service predicates: - Path=/carts/** - id: trade-service uri: lb://trade-service predicates: - Path=/orders/** - id: pay-service uri: lb://pay-service predicates: - Path=/pay-orders/** hm: jwt: location: classpath:hmall.jks alias: hmall password: hmall123 tokenTTL: 30m auth: excludePaths: - /search/** - /users/login - /items/** - /hi
网关传递用户:将用户的id保存在请求头当中,通过统一拦截处理,获取用户的id,放入ThreadLocal当中;请求完成,清理ThreadLocal,实现用户id从网关到各个项目模块的传递
OpenFeign传递用户:OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求,在拦截处理过程中,我们将ThreadLocal中的用户id放入OpenFeign的请求头当中,其他微服务拦截处理的过程中获得用户id并放入线程当中
三、配置管理
1.拉取共享配置
2.加入相关依赖
<!--nacos配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
3.配置热更新
(1)nacos中要有一个与微服务名有关的配置文件
(2)微服务中要以特定方式读取需要热更新的配置属性
package com.hmall.cart.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @Data @ConfigurationProperties(prefix = "hm.cart") public class MaxCommodityConfig { private Integer maxCommodity; }
4.动态路由
package com.hmall.gateway.routes; import cn.hutool.json.JSONUtil; import com.alibaba.cloud.nacos.NacosConfigManager; import com.alibaba.nacos.api.config.listener.Listener; import com.alibaba.nacos.api.exception.NacosException; import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import javax.annotation.PostConstruct; import java.util.HashSet; import java.util.List; import java.util.concurrent.Executor; @Component @RequiredArgsConstructor public class DynamicRounterLoader { private final NacosConfigManager nacosConfigManager; private final RouteDefinitionWriter writer; private final String dataId="gateway-routes.json"; private final String group="DEFAULT_GROUP"; //记录路由的id private HashSet<String> set=new HashSet<String>(); //在Bean初始化之后执行 @PostConstruct public void initRoutesConfigListener() throws NacosException { //拉取配置并更新配置 String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { //路由表更新,更新监听器 System.out.println(configInfo+"监听器更新执行了"); updateRouters(configInfo); } }); System.out.println(configInfo+"监听器更新了"); //第一次启动,更新监听器 updateRouters(configInfo); } private void updateRouters(String configInfo) { //将json数据转换为实体类 List<RouteDefinition> routeDefinitionList = JSONUtil.toList(configInfo, RouteDefinition.class); //删除原来的路由表 for (String id : set) { writer.delete(Mono.just(id)).subscribe(); } set.clear(); //添加新的路由表并记录id for (RouteDefinition routeDefinition : routeDefinitionList) { writer.save(Mono.just(routeDefinition)).subscribe(); set.add(routeDefinition.getId()); } } }
将yaml配置转换为json配置:
[ { "id": "item", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"} }], "filters": [], "uri": "lb://item-service" }, { "id": "cart", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/carts/**"} }], "filters": [], "uri": "lb://cart-service" }, { "id": "user", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"} }], "filters": [], "uri": "lb://user-service" }, { "id": "trade", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/orders/**"} }], "filters": [], "uri": "lb://trade-service" }, { "id": "pay", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/pay-orders/**"} }], "filters": [], "uri": "lb://pay-service" } ]
三、服务保护和分布式事务
1.雪崩问题
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用
解决方案:保证代码的健壮性、保证网络的畅通、能应对高并发请求
2.服务保护
请求限流:限制访问服务器的并发量,避免服务因流量激增出现故障
线程隔离:模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散
服务熔断:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断业务,则拦截该接口请求
3.分布式事务
事务协调者(TC):维护全局和分支事务的状态,协调全局事务提交和回滚
事务管理器(TM):定义全局事务范围、开始全局事务、提交或回滚全局事务
资源管理器(RM):管理分支事务,与TC交谈以注册分支事务和报告分支事务状态
- XA模式:
优点:事务的强一致性,满足ACID原则,常用数据库都支持,实现简单,并且没有代码侵入
缺点:因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差,依赖关系型数据库实现事务
- AT模式:
优点:满足ACID原则,常用数据库都支持,实现简单,并且没有代码侵入,单个RM完成之后进行事务的提交,不占用资源,提高了性能
缺点:难以实现复的事务控制,如特定隔离级别;当事务的隔离级别过低时会出现脏读、不可重复读、幻读问题
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。