Spring-Cloud-Gateway--源码分析及应用

文章目录

  • 一、简介
    • 1.1 术语
  • 1.3 特性
    • 1.4 Spring Cloud Gateway与Spring Cloud Zuul
      • Spring Cloud Zuul
      • Webflux模型
    • 1.5 如何集成Gateway
  • 二、工作原理
    • 2.2 Gateway类图
  • 三、配置路由谓词工厂和网关过滤工厂
    • 3.1 两种不同的配置路由方式
      • 通过yml文件来配置
      • 通过Java Bean来配置
    • 3.2 Route Predicate 的使用
      • 3.2.1 根据datetime 匹配
      • 3.2.2 根据Cookie匹配
    • 3.2.3 Header Route Predicate
    • 3.2.4 Host Route Predicate
    • 3.2.5 Method Route Predicate
    • 3.2.5 Path Route Predicate
    • 3.2.6 Query Route Predicate
    • 3.2.7 RemoteAddr Route Predicate
    • 3.3 Route Filter 的使用
    • 3.4 Global Filters 全局过滤器
  • 四、 结合注册中心和配置中心使用
    • 4.1 使用动态路由
      • 4.1.1 基本配置
      • 4.1.2 使用Route Predicate Factory过滤器实现通过指定path访问服务
      • 4.1.3 使用Nacos数据源动态加载和刷新路由配置
    • 4.2 基于网关+nacos配置中心实现灰度路由
      • 4.2.1 定义GatewayStrategyAutoConfiguration 网关路由自定义配置入口类
    • 4.2.2 网关路由策略发布

一、简介

Spring Cloud Gateway 是Spring Cloud 生态全新项目,基于Spring 5、Spring Boot 2.X、Project Reactor实现的API网关,旨在为微服务提供简单高效的API路由管理方法。
Spring Cloud Gateway 作为Spring Cloud 生态中的网关,目标是代替Zuul 1.X。Spring Cloud 2.X版本目前仍未对Zuul 2.X高性能版本进行集成,仍使用的是非Reactor的老版本Zuul网关。

  • 目前Spring Cloud dependencies 最新版本Hoxton.SR8 仍使用的是Zuul 1.3.1
  • Zuul 2.x 高性能Reactor版本本身与18年5月开源,目前最新版本2.1.9

为了提高网关性能,Spring Cloud Gateway基于WebFlux框架实现,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

1.1 术语

  • Route: Gateway的基本构建模块,由ID、目标URL、谓词集合和过滤器集合定义。
  • Predicate: Java8 Funciton Predicate,输入类型是 SpringFramework ServerWebExchange,可以匹配HTTP请求的所有内容,比如标头或参数。
  • Filter:使用特定工厂构造的Spring FrameworkGatewayFilter实例,可以在发送下游请求之前或之后修改请求或响应。

1.3 特性

  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter(过滤器);
  • 请求限流功能;
  • 支持路径重写

1.4 Spring Cloud Gateway与Spring Cloud Zuul

Spring Cloud Zuul

Springcloud 2.x 版本到目前为止中所集成的Zuul版本(1.x),采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
servlet由servlet container进行生命周期管理。container启动时构造servlet对象并调用servlet init()进行初始化;container关闭时调用servlet destory()销毁servlet;container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()。
弊端:servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的,但是一旦并发上升,线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单的业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。

Zuul请求处理模型
所以Springcloud Zuul 是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet),并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端。

Webflux模型

Webflux模式替换了旧的Servlet线程模型。用少量的线程处理request和response io操作,这些线程称为Loop线程,而业务交给响应式编程框架处理,响应式编程是非常灵活的,用户可以将业务中阻塞的操作提交到响应式框架的work线程中执行,而不阻塞的操作依然可以在Loop线程中进行处理,大大提高了Loop线程的利用率。官方结构图:

Webflux虽然可以兼容多个底层的通信框架,但是一般情况下,底层使用的还是Netty,毕竟,Netty是目前业界认可的最高性能的通信框架。而Webflux的Loop线程,正好就是著名的Reactor 模式IO处理模型的Reactor线程,如果使用的是高性能的通信框架Netty,这就是Netty的EventLoop线程。

1.5 如何集成Gateway

使用Gateway只需要简单引入依赖:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

二、工作原理



处理流程:

  1. Gateway接受客户端请求;
  2. 网关处理程序映射确定请求与路由匹配,匹配成功则将其发送到网关Web处理程序;
  3. Web处理程序处理程序通过特定于请求的过滤器链运行请求:
    • 请求经过 Filter 过滤器链,执行 pre 处理逻辑,如修改请求头信息等。
    • 发出代理请求,请求被转发至下游服务并返回响应。
  4. 响应经过 Filter 过滤器链,执行 post 处理逻辑。
  5. 向客户端响应应答。

注意,在没有端口的路由中定义的URI,HTTP和HTTPS URI的默认端口值分别为80和443。

  • DispatcherHandler:所有请求的调度器,负载请求分发
public class DispatcherHandler implements WebHandler, ApplicationContextAware {@Nullableprivate List<HandlerMapping> handlerMappings;@Nullableprivate List<HandlerAdapter> handlerAdapters;@Nullableprivate List<HandlerResultHandler> resultHandlers;public DispatcherHandler() {}public DispatcherHandler(ApplicationContext applicationContext) {this.initStrategies(applicationContext);}@Nullablepublic final List<HandlerMapping> getHandlerMappings() {return this.handlerMappings;}public void setApplicationContext(ApplicationContext applicationContext) {this.initStrategies(applicationContext);}# 初始、校验HandlerMapping并按order排序protected void initStrategies(ApplicationContext context) {Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);ArrayList<HandlerMapping> mappings = new ArrayList(mappingBeans.values());AnnotationAwareOrderComparator.sort(mappings);this.handlerMappings = Collections.unmodifiableList(mappings);Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);this.handlerAdapters = new ArrayList(adapterBeans.values());AnnotationAwareOrderComparator.sort(this.handlerAdapters);Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);this.resultHandlers = new ArrayList(beans.values());AnnotationAwareOrderComparator.sort(this.resultHandlers);}
//遍历handlerMappings ,根据exchange找到对应的handler
// 对于Gateway 会找到对应的RoutePredicateHandlerMappingpublic Mono<Void> handle(ServerWebExchange exchange) {return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {return mapping.getHandler(exchange);}).next().switchIfEmpty(this.createNotFoundError())如果遍历不到结果,则切换到错误处理
.flatMap((handler) -> {//通过HandlerAdapter调用handler,//gateway使用的 SimpleHandlerAdapterreturn this.invokeHandler(exchange, handler);}).flatMap((result) -> {//对响应进行处理return this.handleResult(exchange, result);});}private <R> Mono<R> createNotFoundError() {return Mono.defer(() -> {Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler");return Mono.error(ex);});}private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {if (this.handlerAdapters != null) {Iterator var3 = this.handlerAdapters.iterator();while(var3.hasNext()) {HandlerAdapter handlerAdapter = (HandlerAdapter)var3.next();if (handlerAdapter.supports(handler)) {//调用handler的handle方法处理请求return handlerAdapter.handle(exchange, handler);}}}return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));}//根据result获取对应的结果处理handler并处理结果private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {return this.getResultHandler(result).handleResult(exchange, result).checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]").onErrorResume((ex) -> {return result.applyExceptionHandler(ex).flatMap((exResult) -> {String text = "Exception handler " + exResult.getHandler() + ", error=\"" + ex.getMessage() + "\" [DispatcherHandler]";return this.getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text);});});}private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {if (this.resultHandlers != null) {Iterator var2 = this.resultHandlers.iterator();while(var2.hasNext()) {HandlerResultHandler resultHandler = (HandlerResultHandler)var2.next();if (resultHandler.supports(handlerResult)) {return resultHandler;}}}throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());}
}
  • RoutePredicateHandlerMapping:路由谓语匹配器,用于路由的查找,以及找到路由后返回对应的WebHandler,DispatcherHandler会依次遍历HandlerMapping集合进行处理
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {private final FilteringWebHandler webHandler;private final RouteLocator routeLocator;private final Integer managementPort;private final RoutePredicateHandlerMapping.ManagementPortType managementPortType;public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {this.webHandler = webHandler;this.routeLocator = routeLocator;this.managementPort = getPortProperty(environment, "management.server.");this.managementPortType = this.getManagementPortType(environment);//设置排序字段1,此处的目的是Spring Cloud Gateway 的 GatewayWebfluxEndpoint 提供 HTTP API ,不需要经过网关//它通过 RequestMappingHandlerMapping 进行请求匹配处理。RequestMappingHandlerMapping 的 order = 0 ,需要排在 RoutePredicateHandlerMapping 前面。所有,RoutePredicateHandlerMapping 设置 order = 1 。this.setOrder(1);this.setCorsConfigurations(globalCorsProperties.getCorsConfigurations());}private RoutePredicateHandlerMapping.ManagementPortType getManagementPortType(Environment environment) {Integer serverPort = getPortProperty(environment, "server.");if (this.managementPort != null && this.managementPort < 0) {return RoutePredicateHandlerMapping.ManagementPortType.DISABLED;} else {return this.managementPort != null && (serverPort != null || !this.managementPort.equals(8080)) && (this.managementPort == 0 || !this.managementPort.equals(serverPort)) ? RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT : RoutePredicateHandlerMapping.ManagementPortType.SAME;}}private static Integer getPortProperty(Environment environment, String prefix) {return (Integer)environment.getProperty(prefix + "port", Integer.class);}
//设置mapping到上下文环境protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {if (this.managementPortType == RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT && this.managementPort != null && exchange.getRequest().getURI().getPort() == this.managementPort) {return Mono.empty();} else {exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());// 查找路由return this.lookupRoute(exchange).flatMap((r) -> {
exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);if (this.logger.isDebugEnabled()) {this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);}//将查找到的路由设置到上下文环境
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);//返回mapping对应的WebHandler即FilteringWebHandlerreturn Mono.just(this.webHandler);}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {//当前未找到路由时返回空,并移除GATEWAY_PREDICATE_ROUTE_ATTR  exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);if (this.logger.isTraceEnabled()) {this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");}})));}}protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {return super.getCorsConfiguration(handler, exchange);}private String getExchangeDesc(ServerWebExchange exchange) {StringBuilder out = new StringBuilder();out.append("Exchange: ");out.append(exchange.getRequest().getMethod());out.append(" ");out.append(exchange.getRequest().getURI());return out.toString();}
//通过路由定位器获取路由信息protected Mono<Route> lookupRoute(ServerWebExchange exchange) {return this.routeLocator.getRoutes().concatMap((route) -> {return Mono.just(route).filterWhen((r) -> {exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());return (Publisher)r.getPredicate().apply(exchange);//通过谓词过滤路由}).doOnError((e) -> {this.logger.error("Error applying predicate for route: " + route.getId(), e);}).onErrorResume((e) -> {return Mono.empty();});}).next().map((route) -> {if (this.logger.isDebugEnabled()) {this.logger.debug("Route matched: " + route.getId());}this.validateRoute(route, exchange);return route;});}protected void validateRoute(Route route, ServerWebExchange exchange) {}protected String getSimpleName() {return "RoutePredicateHandlerMapping";}public static enum ManagementPortType {DISABLED,SAME,DIFFERENT;private ManagementPortType() {}}
}
  • FilteringWebHandler : 使用Filter链表处理请求的WebHandler,RoutePredicateHandlerMapping找到路由后返回对应的FilteringWebHandler对请求进行处理,FilteringWebHandler负责组装Filter链表并调用链表处理请求。
# 通过过滤器处理web请求的处理器
public class FilteringWebHandler implements WebHandler {protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);
# 全局过滤器private final List<GatewayFilter> globalFilters;public FilteringWebHandler(List<GlobalFilter> globalFilters) {this.globalFilters = loadFilters(globalFilters);}private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {return (List)filters.stream().map((filter) -> {FilteringWebHandler.GatewayFilterAdapter gatewayFilter = new FilteringWebHandler.GatewayFilterAdapter(filter);if (filter instanceof Ordered) {int order = ((Ordered)filter).getOrder();return new OrderedGatewayFilter(gatewayFilter, order);} else {return gatewayFilter;}}).collect(Collectors.toList());}public Mono<Void> handle(ServerWebExchange exchange) {
#获取请求上下文设置的路由实例Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
# 获取网关路由定义下的网关过滤器集合List<GatewayFilter> gatewayFilters = route.getFilters();
# 组合全局的过滤器与路由配置的过滤器,并将路由器定义的过滤器添加集合尾部List<GatewayFilter> combined = new ArrayList(this.globalFilters);combined.addAll(gatewayFilters);AnnotationAwareOrderComparator.sort(combined);if (logger.isDebugEnabled()) {logger.debug("Sorted gatewayFilterFactories: " + combined);}
# 创建过滤器链表对其进行链式调用return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);}private static class GatewayFilterAdapter implements GatewayFilter {private final GlobalFilter delegate;GatewayFilterAdapter(GlobalFilter delegate) {this.delegate = delegate;}public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return this.delegate.filter(exchange, chain);}public String toString() {StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");sb.append("delegate=").append(this.delegate);sb.append('}');return sb.toString();}}private static class DefaultGatewayFilterChain implements GatewayFilterChain {private final int index;private final List<GatewayFilter> filters;DefaultGatewayFilterChain(List<GatewayFilter> filters) {this.filters = filters;this.index = 0;}private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {this.filters = parent.getFilters();this.index = index;}public List<GatewayFilter> getFilters() {return this.filters;}public Mono<Void> filter(ServerWebExchange exchange) {return Mono.defer(() -> {if (this.index < this.filters.size()) {GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1);return filter.filter(exchange, chain);} else {return Mono.empty();}});}}
}

2.2 Gateway类图

根据DispatcherHandler入口整理的Gateway类图

Spring Cloud Gateway的配置由一系列RouteDefinitionLocator实例驱动。以下清单显示了RouteDefinitionLocator接口的定义:

RouteDefinitionLocator.java
public interface RouteDefinitionLocator {Flux<RouteDefinition> getRouteDefinitions();
}

默认情况下,PropertiesRouteDefinitionLocator使用Spring Boot的@ConfigurationProperties机制来加载属性。

三、配置路由谓词工厂和网关过滤工厂

3.1 两种不同的配置路由方式

Gateway 提供了两种不同的方式用于配置路由,一种是通过yml文件来配置,另一种是通过Java Bean来配置。

通过yml文件来配置

service-url:user-service: http://localhost:8201
spring:cloud:gateway:routes:- id: path_route #路由的IDuri: ${service-url.user-service}/user/{id} #匹配后路由地址predicates: # 断言,路径相匹配的进行路由- Path=/user/{id}

通过Java Bean来配置

@Configuration
public class GatewayConfig {@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes().route("path_route2", r -> r.path("/user/getByUsername").uri("http://localhost:8201/user/getByUsername")).build();}
}

3.2 Route Predicate 的使用

Spring Cloud Gateway将路由匹配作为Spring WebFluxHandlerMapping基础架构的一部分。Spring Cloud Gateway包括许多内置的路由谓词工厂。所有这些谓词都与HTTP请求的不同属性匹配。可以将多个路由谓词工厂与逻辑and语句结合使用。
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。

下图为 Spring Cloud Gateway内置的几种常见谓词路由器:

3.2.1 根据datetime 匹配

After Route Predicate
在指定时间之后的请求会匹配该路由。

spring:cloud:gateway:routes:- id: after_routeuri: ${service-url.user-service}predicates:- After=2019-09-24T16:30:00+08:00[Asia/Shanghai]

Before Route Predicate
在指定时间之前的请求会匹配该路由。

spring:cloud:gateway:routes:- id: before_routeuri: ${service-url.user-service}predicates:- Before=2019-09-24T16:30:00+08:00[Asia/Shanghai]

Between Route Predicate
在指定时间区间内的请求会匹配该路由。

spring:cloud:gateway:routes:- id: before_routeuri: ${service-url.user-service}predicates:- Between=2019-09-24T16:30:00+08:00[Asia/Shanghai], 2019-09-25T16:30:00+08:00[Asia/Shanghai]

3.2.2 根据Cookie匹配

带有指定Cookie的请求会匹配该路由。

spring:cloud:gateway:routes:- id: cookie_routeuri: ${service-url.user-service}predicates:- Cookie=username,macro

3.2.3 Header Route Predicate

带有指定请求头的请求会匹配该路由。

spring:cloud:gateway:routes:- id: header_routeuri: ${service-url.user-service}predicates:- Header=X-Request-Id, \d+

3.2.4 Host Route Predicate

带有指定Host的请求会匹配该路由。

spring:cloud:gateway:routes:- id: host_routeuri: https://example.orgpredicates:- Host=**.qt.com

3.2.5 Method Route Predicate

发送指定方法的请求会匹配该路由。

spring:cloud:gateway:routes:- id: method_routeuri: ${service-url.user-service}predicates:- Method=GET

3.2.5 Path Route Predicate

发送指定路径的请求会匹配该路由。

spring:cloud:gateway:routes:- id: path_routeuri: ${service-url.user-service}/user/{id}predicates:- Path=/user/{id}

3.2.6 Query Route Predicate

带指定查询参数的请求可以匹配该路由。

spring:cloud:gateway:routes:- id: query_routeuri: ${service-url.user-service}/user/getByUsernamepredicates:- Query=username

3.2.7 RemoteAddr Route Predicate

从指定远程地址发起的请求可以匹配该路由。

spring:cloud:gateway:routes:- id: remoteaddr_routeuri: ${service-url.user-service}predicates:- RemoteAddr=192.168.1.1/24

3.3 Route Filter 的使用

根据Gateway工作原理,我们知道Gateway实际是由路由匹配到的一系列Filter过滤链来处理请求的,Spring Cloud Gateway包括许多内置的GatewayFilter工厂。具体详情参考官网:
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

3.4 Global Filters 全局过滤器

当请求与路由匹配时,过滤Web处理程序会将的所有实例GlobalFilter和所有特定GatewayFilter于路由的实例添加到过滤器链中。该组合的过滤器链按org.springframework.core.Ordered接口排序,可以通过实现该getOrder()方法进行设置。
Spring Cloud Gateway区分了执行过滤器逻辑的“前”和“后”阶段,因此优先级最高的过滤器是“前”阶段的第一个,而“后”阶段的最后一个是优先级最低的一个。

例如,下面程序配置了一个过滤器链:

@Bean
public GlobalFilter customFilter() {return new CustomGlobalFilter();
}public class CustomGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("custom global filter");return chain.filter(exchange);}@Overridepublic int getOrder() {return -1;}
}

四、 结合注册中心和配置中心使用

Gateway会根据注册中心注册的服务列表,以服务名为路径创建动态路由。这里主要使用Nacos作为注册中心和配置中心

4.1 使用动态路由

4.1.1 基本配置

引入依赖

    <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>

启用DiscoveryClient网关集成

# spring.cloud.gateway.discovery.locator.enabled=true
spring:cloud:gateway:discovery:locator:enabled: true #开启从注册中心动态创建路由的功能lower-case-service-id: true #使用小写服务名,默认是大写

使用网关访问服务:

C:\Users\liangbodlz\.ssh>curl 192.168.132.49:1500/nacos-provider/index
Hello!

4.1.2 使用Route Predicate Factory过滤器实现通过指定path访问服务

在实际生产环境中,我们往往不会通过服务的application-name来访问服务,而是通过某个固定的url path来访问,比如xx.xxx/user/login,来访问用户服务的接口
通过Spring Cloud Gateway 内置 Path Route Predicate Factory 可以实现该目标:

spring:cloud:gateway:discovery:locator:enabled: true #开启从注册中心动态创建路由的功能lower-case-service-id: true #使用小写服务名,默认是大写routes:- id: nacos-provideruri: lb://nacos-providerpredicates:- Path=/nprovider/**filters:- StripPrefix=1

使用指定path访问服务

C:\Users\liangbodlz\.ssh>curl 192.168.132.49:1500/nprovider/index
Hello!
C:\Users\liangbodlz\.ssh>

4.1.3 使用Nacos数据源动态加载和刷新路由配置

通常我们将微服务的Route Predicate Path和Gateway应用本身的配置放在一起,但是随着微服务的扩展,Route Predicate Path会逐渐增加导致Gateway 服务配置会变得臃肿,且Route Predicate Path配置会随着服务的增减进行变更,而更新的路由配置生效需要重启Gateway,这都是实际线上环境不可忍受的。因此独立管理Route Predicate Path配置且支持动态刷新配置变得必要起来。

基于上述需求,我们可以考虑将Gateway 路由配置存储到内存或者其他介质中。
从源码分析中可以知道Gateway路由配置信息由RouteDefinitionLocator 接口完成。
RouteDefinitionLocator实现关系
RouteDefinitionLocator 是Gateway路由配置读取的顶级接口,提供从缓存、配置文件、服务注册中心、组合等不同方式读取配置,以及提供RouteDefinitionRepository 接口方式对RouteDefinition进行增、删、查操作。要自定义路由配置实现可以考虑从上述接口着手实现。
这里主要基于Nacos配置中心+RouteDefinitionRepository 自定义路由配置加载,并参考,CachingRouteLocator实现路由配置的动态刷新
核心源码清单

//自定义路由配置加载核心接口
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter {
}//查询路由
public interface RouteDefinitionLocator {//返回自定义路由配置加载Flux<RouteDefinition> getRouteDefinitions();
}//路由增、删
public interface RouteDefinitionWriter {Mono<Void> save(Mono<RouteDefinition> route);Mono<Void> delete(Mono<String> routeId);
}
//动态路由刷新实现
public class CachingRouteLocator implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {
....//省略private ApplicationEventPublisher applicationEventPublisher;
.....//省略public void onApplicationEvent(RefreshRoutesEvent event) {try {this.fetch().collect(Collectors.toList()).subscribe((list) -> {Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe((signals) -> {this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));this.cache.put("routes", signals);}, (throwable) -> {this.handleRefreshError(throwable);});});} catch (Throwable var3) {this.handleRefreshError(var3);}}private void handleRefreshError(Throwable throwable) {if (log.isErrorEnabled()) {log.error("Refresh routes error !!!", throwable);}this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this, throwable));}

代码实现

//实现RouteDefinitionRepository接口
package com.easy.mall.route;import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.exception.NacosException;
import com.easy.mall.config.GatewayConfig;
import com.easy.mall.operation.NacosConfigOperation;
import com.easy.mall.operation.NacosSubscribeCallback;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Optional;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/*** @description: 基于Nacos配置中心实现Gateway 动态路由配置* @author: liangbo* @create 2020-12-15 19:29* @Version 1.0**/
@Slf4j
@DependsOn(value= {"gatewayConfig","nacosAutoConfiguration"})
@Configuration
@ConditionalOnProperty(prefix = "global.gateway.dynamicRoute", name = "enabled", havingValue = "true")
public class NacosDynamicRouteDefinitionRepository implements RouteDefinitionRepository {@Autowiredprivate NacosConfigOperation nacosConfigOperation;@Autowiredprivate ApplicationEventPublisher publisher;@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {//从Nacos配置中心读取路由配置try {String dynamicRouteStr = nacosConfigOperation.getConfig(GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.NACOS_ROUTE_DATA_ID);log.info("init dynamicRoute success.:{}", dynamicRouteStr);List<RouteDefinition> routeDefinitions = Optional.ofNullable(dynamicRouteStr).map(str -> JSONObject.parseArray(str, RouteDefinition.class)).orElse(Lists.newArrayList());return Flux.fromIterable(routeDefinitions);} catch (NacosException e) {log.error("load gateway dynamicRoute config error:{}", e);}return Flux.fromIterable(Lists.newArrayList());}@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return null;}@Overridepublic Mono<Void> delete(Mono<String> routeId) {return null;}/*** 侦听nacos config 实时刷新路由配置*/@PostConstructpublic void subscribeConfigRefresh()  {try {nacosConfigOperation.subscribeConfig(GatewayConfig.NACOS_ROUTE_GROUP,GatewayConfig.NACOS_ROUTE_DATA_ID, null, new NacosSubscribeCallback () {@Overridepublic void callback(String config) {publisher.publishEvent(new RefreshRoutesEvent(this));}});} catch (NacosException e) {log.error("nacos-addListener-error", e);}}}

动态路由配置清单

[{"id": "easy-mall-auth","predicates": [{"name": "Path","args": {"pattern": "/emallauth/**"}}],"uri": "lb://easy-mall-auth","filters": [{"name": "StripPrefix","args": {"parts": "1"}}]} 
]

4.2 基于网关+nacos配置中心实现灰度路由

实现思路见Nacos安装及Spring Cloud 集成 3.4

4.2.1 定义GatewayStrategyAutoConfiguration 网关路由自定义配置入口类

@Configuration
@AutoConfigureBefore(RibbonClientConfiguration.class)
//通过注解@RibbonClient声明附加配置,此处声明的配置会覆盖配置文件中的配置
@RibbonClients(defaultConfiguration = { GatewayStrategyLoadBalanceConfiguration.class })
@ConditionalOnProperty(value = StrategyConstant.SPRING_APPLICATION_STRATEGY_CONTROL_ENABLED, matchIfMissing = true)
public class GatewayStrategyAutoConfiguration {
//省略......

通过入口类,加载自定义全局过滤器、Ribbon自定义负载均衡配置、元数据处理适配器等。

自定义Ribbon 负载均衡实现
自定义Ribbon 负载均衡实现分别对PredicateBasedRule和ZoneAvoidanceRule进行了扩展

//通过注解@RibbonClient声明附加配置,此处声明的配置会覆盖配置文件中的配置
@RibbonClients(defaultConfiguration = { GatewayStrategyLoadBalanceConfiguration.class })@Bean
public IRule ribbonRule(IClientConfig config) {if (this.propertiesFactory.isSet(IRule.class, serviceId)) {return this.propertiesFactory.get(IRule.class, config, serviceId);}//开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为trueboolean zoneAvoidanceRuleEnabled = environment.getProperty(StrategyConstant.SPRING_APPLICATION_STRATEGY_ZONE_AVOIDANCE_RULE_ENABLED, Boolean.class, Boolean.TRUE);if (zoneAvoidanceRuleEnabled) {DiscoveryEnabledZoneAvoidanceRule discoveryEnabledRule = new DiscoveryEnabledZoneAvoidanceRule();discoveryEnabledRule.initWithNiwsConfig(config);DiscoveryEnabledZoneAvoidancePredicate discoveryEnabledPredicate = discoveryEnabledRule.getDiscoveryEnabledPredicate();discoveryEnabledPredicate.setPluginAdapter(pluginAdapter);discoveryEnabledPredicate.setDiscoveryEnabledAdapter(discoveryEnabledAdapter);return discoveryEnabledRule;} else {DiscoveryEnabledBaseRule discoveryEnabledRule = new DiscoveryEnabledBaseRule();DiscoveryEnabledBasePredicate discoveryEnabledPredicate = discoveryEnabledRule.getDiscoveryEnabledPredicate();discoveryEnabledPredicate.setPluginAdapter(pluginAdapter);discoveryEnabledPredicate.setDiscoveryEnabledAdapter(discoveryEnabledAdapter);return discoveryEnabledRule;}
}

DiscoveryEnabledZoneAvoidanceRule:
ZoneAvoidanceRule扩展
DiscoveryEnabledBaseRule
PredicateBasedRule

自定义全局过滤器 实现将网关路由配置以及Http Header加载到请求ServerWebExchange中

@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 把ServerWebExchange放入ThreadLocal中GatewayStrategyContext.getCurrentContext().setExchange(exchange);// 通过过滤器设置路由Header头部信息,并全链路传递到服务端ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate();if (gatewayCoreHeaderTransmissionEnabled) {// 内置Header预先塞入Map<String, String> headerMap = strategyWrapper.getHeaderMap();if (MapUtils.isNotEmpty(headerMap)) {for (Map.Entry<String, String> entry : headerMap.entrySet()) {String key = entry.getKey();String value = entry.getValue();GatewayStrategyFilterResolver.setHeader(requestBuilder, key, value, gatewayHeaderPriority);}}//获取网关配置的路由规则String routeVersion = getRouteVersion();String routeVersionWeight = getRouteVersionWeight();String routeIdBlacklist = getRouteIdBlacklist();String routeAddressBlacklist = getRouteAddressBlacklist();if (StringUtils.isNotEmpty(routeVersion)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_VERSION, routeVersion, gatewayHeaderPriority);} else {GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);}if (StringUtils.isNotEmpty(routeVersionWeight)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT, routeVersionWeight, gatewayHeaderPriority);} else {GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);}if (StringUtils.isNotEmpty(routeIdBlacklist)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST, routeIdBlacklist, gatewayHeaderPriority);} else {GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);}if (StringUtils.isNotEmpty(routeAddressBlacklist)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST, routeAddressBlacklist, gatewayHeaderPriority);} else {GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST, gatewayHeaderPriority, gatewayOriginalHeaderIgnored);}} else {GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION);GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_VERSION_WEIGHT);GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ID_BLACKLIST);GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, DiscoveryConstant.N_D_ADDRESS_BLACKLIST);}// 对于服务A -> 网关 -> 服务B调用链// 域网关下(zuulHeaderPriority=true),只传递网关自身的group,不传递服务A的group,起到基于组的网关端服务调用隔离// 非域网关下(zuulHeaderPriority=false),优先传递服务A的group,基于组的网关端服务调用隔离不生效,但可以实现基于相关参数的熔断限流等功能GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_GROUP, pluginAdapter.getGroup(), gatewayHeaderPriority);// 网关只负责传递服务A的相关参数(例如:serviceId),不传递自身的参数,实现基于相关参数的熔断限流等功能GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_TYPE, pluginAdapter.getServiceType(), false);String serviceAppId = pluginAdapter.getServiceAppId();if (StringUtils.isNotEmpty(serviceAppId)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_APP_ID, serviceAppId, false);}GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_ID, pluginAdapter.getServiceId(), false);GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_VERSION, pluginAdapter.getVersion(), false);GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.N_D_SERVICE_ENVIRONMENT, pluginAdapter.getEnvironment(), false);ServerHttpRequest newRequest = requestBuilder.build();ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();ServerWebExchange extensionExchange = extendFilter(newExchange, chain);ServerWebExchange finalExchange = extensionExchange != null ? extensionExchange : newExchange;// 把新的ServerWebExchange放入ThreadLocal中GatewayStrategyContext.getCurrentContext().setExchange(newExchange);String path = finalExchange.getRequest().getPath().toString();if (path.contains(DiscoveryConstant.INSPECTOR_ENDPOINT_URL)) {GatewayStrategyFilterResolver.setHeader(requestBuilder, DiscoveryConstant.INSPECTOR_ENDPOINT_HEADER, pluginAdapter.getPluginInfo(null), true);}return chain.filter(finalExchange);}

自定义DefaultDiscoveryEnabledAdapter封装实例过滤规则
Ribbon负载均衡器执行默认过滤后会执行该规则

  protected boolean apply(Server server) {if (discoveryEnabledAdapter == null) {return true;}return discoveryEnabledAdapter.apply(server);}
//自定义过滤规则@Overridepublic boolean apply(Server server) {boolean enabled = applyEnvironment(server);if (!enabled) {return false;}enabled = applyVersion(server);if (!enabled) {return false;}enabled = applyIdBlacklist(server);if (!enabled) {return false;}enabled = applyAddressBlacklist(server);if (!enabled) {return false;}return applyStrategy(server);}

4.2.2 网关路由策略发布

基于nacos配置实现网关策略动态发布,根据网关元数据组以及serviceId创建路由策略配置:
网关路由策略配置

配置通过网关的请求都走版本xx

<?xml version="1.0" encoding="UTF-8"?>
<rule><strategy><version>1.0</version></strategy>
</rule>

step1 启动网关以及2个服务实例

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=1100 --spring.cloud.nacos.discovery.metadata.version=1.0"
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=1101 --spring.cloud.nacos.discovery.metadata.version=1.1"

nacos实例
nacos服务实例
step2 通过网关调用服务,可以验证到请求始终访问到version为1.0 的服务实例

192.168.132.49:1500/nacos-provider/index

配置网关路由权重

<?xml version="1.0" encoding="UTF-8"?>
<rule><strategy><version>1.0;1.1</version><version-weight>1.0=90;1.1=10</version-weight></strategy>
</rule>

灰度策略信息基于Nacos Client以及异步事件处理,动态更新,无需重启网关。
通过网关访问多次服务,请求基本按照9:1的比例命中服务。

配置IP地址和端口屏蔽策略,实现服务流量无损策略下线
服务下线场景中,由于Ribbon负载均衡组件存在着缓存机制,当被调用的服务实例已经下线,而调用的服务实例还暂时缓存着它,直到下个心跳周期才会把已下线的服务实例剔除,在此期间,会造成流量有损
框架提供流量的实时性的绝对无损。采用下线之前,把服务实例添加到屏蔽名单中,负载均衡不会去寻址该服务实例。
代码清单:

//省略
enabled = applyIdBlacklist(server);if (!enabled) {return false;}
//省略//过滤黑名单IP,框架会将黑名单中IP从Ribbon负载实例中移除
public boolean applyIdBlacklist(Server server) {String ids = pluginContextHolder.getContextRouteIdBlacklist();if (StringUtils.isEmpty(ids)) {return true;}String serviceUUId = pluginAdapter.getServerServiceUUId(server);List<String> idList = StringUtil.splitToList(ids, DiscoveryConstant.SEPARATE);if (idList.contains(serviceUUId)) {return false;}return true;}

配置Ip黑名单

<?xml version="1.0" encoding="UTF-8"?>
<rule>//此处省略<strategy-blacklist><!-- 单个Address形式。如果多个用“;”分隔,不允许出现空格 --><address value="192.168.132.49:1100"/></strategy-blacklist>
</rule>

发布配置后,访问服务可以发现请求屏蔽了端口为1100的服务,确保1100服务下线,请求不会命中到1100服务。
源码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/342787.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

经典的泡泡龙游戏源码免费下载

源码介绍 HTML5泡泡龙冒险小游戏是一款休闲网页游戏&#xff0c;游戏玩法是玩家从下方中央的弹珠发射台射出彩珠&#xff0c;多于3个同色珠相连则会消失。 源码下载 经典的泡泡龙游戏源码免费下载

搜索与图论:有向图的拓扑序列

搜索与图论&#xff1a;有向图的拓扑序列 题目描述参考代码 题目描述 输入样例 3 3 1 2 2 3 1 3输出样例 1 2 3 参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n, m; int h[N], e…

【Qt知识】部分QWidget属性表格

QWidget是Qt库中所有图形用户界面组件的基类&#xff0c;它提供了大量属性以供自定义和配置控件的行为和外观。下面列出了一些主要的QWidget属性及其作用。 属性 作用 accessibleName 控件的辅助技术名称&#xff0c;用于无障碍访问。 accessibleDescription 控件的辅助技…

前端开发之性能优化

本文章 对各大学习技术论坛知识点&#xff0c;进行总结、归纳自用学习&#xff0c;共勉&#x1f64f; 文章目录 1. [CDN](https://www.bootcdn.cn/)2.懒加载3.缓存4.图片压缩5.图片分割6.sprite7.Code Splitting8.gzip9.GPU加速10.Ajax11.Tree Shaking12.Resource Hints 1. CD…

AI大底座核心平台:百度百舸AI异构计算平台(AI IaaS)与AI中台(AI PaaS)

AI大底座正是整合了以上端到端全要素技术能力&#xff0c;将基础架构IaaS与应用平台PaaS能力深度融合&#xff0c;面向企业和产业AI生 产与应用的全生命周期提供完整解决方案。 百舸AI异构计算平台是AI IaaS层的核心平台&#xff0c;包括AI计算、AI存储、AI加速、AI容器四层套件…

Mysql使用中的性能优化——搭建Mysql的监测服务

大纲 环境安装配置Mysql安装设置root密码新增远程访问账户修改绑定地址重启 新增 MySQL Server Exporter 用户 安装启动mysqld_exporter安装启动新增配置启动 安装启动Prometheus创建用户下载并解压修改配置启动 安装启动grafana安装启动 测试参考资料 抛开场景和数据&#xff…

【ARM】PK51-如何添加芯片型号的方法

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 遇到打开工程提示没有该芯片设备提示如何解决。 2、 问题场景 客户发来一个工程文件&#xff0c;打开后软件提示没有发现该芯片设备提示。 图 1 3、软硬件环境 1&#xff09;、软件版本&#xff1a;keil μvision…

搜索与图论:八皇后问题

搜索与图论&#xff1a;八皇后问题 题目描述参考代码 题目描述 输入样例 4输出样例 .Q.. ...Q Q... ..Q...Q. Q... ...Q .Q..参考代码 #include <iostream>using namespace std;const int N 20;int n; char g[N][N]; bool col[N], dg[N], udg[N];void dfs(int u) {//…

【机器学习】机器学习与大模型在人工智能领域的融合应用与性能优化新探索

文章目录 引言机器学习与大模型的基本概念机器学习概述监督学习无监督学习强化学习 大模型概述GPT-3BERTResNetTransformer 机器学习与大模型的融合应用自然语言处理文本生成文本分类机器翻译 图像识别自动驾驶医学影像分析 语音识别智能助手语音转文字 大模型性能优化的新探索…

C++之类与类之间的关系

1、UML 2、继承&#xff08;泛化&#xff09; 3、关联 一个类对象与另一个类对象存在一个固定关系。他们的关系不是暂时的&#xff0c;而是固定的。 一个类对象作为另一个类对象的成员。例如订单&#xff0c;是用户的一个成员。用户关联订单。 4、聚合 聚合其实是特殊的一种…

spring boot +Scheduled 动态定时任务配置

通常情况下我们设定的定时任务都是固定的,有时候需要我们动态的配置定时任务,下面看代码 import com.mybatisflex.core.query.QueryWrapper; import com.yzsec.dsg.web.modules.exportpwd.entity.ExportPwd; import com.yzsec.dsg.web.modules.exportpwd.entity.table.Export…

PbootCms微信小程序官网模版/企业官网/社交电商官网/网络工作室/软件公司官网

在数字化时代&#xff0c;企业网站已成为吸引潜在客户、提升企业形象、和扩大品牌影响力的必备工具。因此&#xff0c;一个优秀的企业网站模板显得尤为重要。 企业官网的内容框架通常都包含企业形象、产品或服务类型、信息展示等部分&#xff0c;设计师需要借助和企业形象契合…

mysql的binlog占用大量磁盘空间的解决方法

查看当前日志保存天数&#xff1a; mysql> show variables like %expire_logs_days%; ------------------------- | Variable_name | Value | ------------------------- | expire_logs_days | 0 | ------------------------- 1 row in set (0.08 sec) 默认是0&…

【Linux 网络】高级 IO -- 详解

一、IO 的基本概念 I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在冯诺依曼体系结构当中&#xff0c;将数据从输入设备拷贝到内存就叫作输入&#xff0c;将数据从内存拷贝到输出设备就叫作输出。 对文件进行的读写操作本质就是一种 IO&#xff0c;文…

安防视频融合汇聚平台EasyCVR如何实现视频画面自定义标签?

安防视频融合汇聚平台EasyCVR兼容性强&#xff0c;可支持Windows系统、Linux系统以及国产化操作系统等&#xff0c;平台既具备传统安防视频监控的能力&#xff0c;也具备接入AI智能分析的能力&#xff0c;可拓展性强、视频能力灵活&#xff0c;能对外分发RTMP、RTSP、HTTP-FLV、…

港中文斯坦福提出SD加速模型PCM,一步即可生成图像和视频,可直接与SD1.5,SDXL,AnimateLCM结合!

又有新的SD加速模型可以用了&#xff0c;PCM解决了原来LCM模型的各种问题。并且对 AnimateLCM 也做了优化&#xff0c;用PCM直接生成动画也可以保证质量了。 PCM从这三个角度说明了LCM的设计空间是有限的并很好地解决了这些限制。 PCM主要改善了三个LCM原有的问题&#xff1a;…

Spring Cloud工程添加子模块打包后文件为war包而非jar包

Spring Cloud工程添加子模块打包后文件为war包而非jar包 Spring Cloud子模块打出的包通常是JAR包而非WAR包&#xff0c;这是因为Spring Cloud主要基于Spring Boot构建&#xff0c;而Spring Boot默认打包为可执行JAR包。然而&#xff0c;如果遇到了Spring Cloud子模块打成了WAR…

俯视角2D游戏_02 子弹对象池

[!NOTE] 对象池 应用场合:这种做法常用于子弹这种会大量产生的对象 &#xff0c;目的是减少性能的损耗 基本思路:产生的对象是有限的&#xff0c;并且加入到"对象池"的数组中不进行销毁&#xff0c;当需要使用时&#xff0c;再从对象池中提取对象循环利用&#xff0c…

碰撞问题和单调栈的结合-735. 小行星碰撞【有小坑】

题目链接及描述 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/asteroid-collision/description/?envTypestudy-plan…

Vue——子级向父级使用props传递数据(函数)

文章目录 前言原理案例效果演示 前言 看到这个标题&#xff0c;相信很多人会说我&#xff0c;你之前博客写的父级向子级中传递数据使用的是props&#xff0c;然后说的子级向父级传递数据则是用的$emit。 并且还说了对于String、数组Array&#xff0c;只能是父级使用props传递…