当前位置: 首页 > article >正文

SpringCloud通过服务逻辑分组实现灰度功能

声明内容来自AI未经验证仅供参考!1、原理服务注册到注册中心时我们可以添加额外的信息对服务进行打标/染色从而实现逻辑分组。当有调用时我们根据HTTP头信息约定好头来决定转发到那个分组从而能实现灰度功能当然这样只实现了接口的灰度其他中间件像MQ、Redis需要根据情况来决定要不要灰度还是和非灰度共用。假设用户发起一个访问服务的调用路径为用户-- ZUUL --app-consumer--app-provider那么我们在ZUUL和SERVICE里都需要实现自定义的访问路由。2、网关实现服务分组2.1 ZUUL网关首先假设服务实例在注册到Eureka时带有元数据如版本信息eureka: instance: metadata-map: version: version1然后在Spring Cloud Gateway中可以编写一个自定义的GlobalFilter来实现基于元数据的路由规则import com.netflix.appinfo.InstanceInfo; import com.netflix.config.DynamicPropertyFactory; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.shared.Application; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.List; Component public class DynamicRoutingFilter extends ZuulFilter { Autowired private EurekaClient discoveryClient; Override public String filterType() { return FilterConstants.ROUTE_TYPE; } Override public int filterOrder() { // 在RibbonRoutingFilter之前执行 return FilterConstants.RIBBON_ROUTING_FILTER_ORDER - 1; } Override public boolean shouldFilter() { // 根据需要决定是否执行过滤器的逻辑 return true; } Override public Object run() { RequestContext ctx RequestContext.getCurrentContext(); HttpServletRequest request ctx.getRequest(); // 从请求URI中提取服务名称 String serviceName extractServiceName(request); String targetVersion request.getHeader(Target-Version); // 从注册中心获取服务实例 Application application discoveryClient.getApplication(serviceName.toUpperCase()); if (application ! null) { ListInstanceInfo instances application.getInstances(); // 根据目标版本筛选服务实例 InstanceInfo instanceInfo instances.stream() .filter(instance - targetVersion.equals(instance.getMetadata().get(version))) .findFirst() .orElse(null); if (instanceInfo ! null) { // 构建目标URL String targetUrl instanceInfo.getHomePageUrl(); // 更新请求的URI使之指向特定的服务实例 String newRequestUri buildNewRequestUri(request, targetUrl); ctx.set(FilterConstants.REQUEST_URI_KEY, newRequestUri); // 确保不设置serviceId这会导致Zuul使用SimpleHostRoutingFilter而不是RibbonRoutingFilter ctx.set(FilterConstants.SERVICE_ID_KEY, null); } else { // 如果没有找到合适的实例可以设置错误状态或者直接抛出异常 // ctx.setResponseStatusCode(404); // throw new ZuulException(No matching instances found, 404, No matching version); } } return null; } private String extractServiceName(HttpServletRequest request) { // 假设服务名称是路径的第一个部分即/之后的部分 String path request.getRequestURI(); if (path.startsWith(/)) { path path.substring(1); } String[] parts path.split(/); return (parts.length 0) ? parts[0] : null; } private String buildNewRequestUri(HttpServletRequest request, String targetUrl) { String serviceName extractServiceName(request); // 使用正则表达式替换掉原始URI中的服务名称部分 String regex http[s]?://[^/]/ serviceName; String requestUri request.getRequestURI(); // 如果原始URI包含查询参数则一并携带 String query request.getQueryString() ! null ? ? request.getQueryString() : ; // 确保目标URL以斜杠结尾以便正确拼接 targetUrl targetUrl.endsWith(/) ? targetUrl : targetUrl /; return requestUri.replaceFirst(regex, targetUrl) query; } }在上面的代码中DynamicRoutingFilter首先从请求中提取服务名称和目标版本信息。然后它查询Eureka注册中心找到符合版本元数据条件的服务实例。如果存在匹配的实例就构建一个新的请求URI并通过set()方法将其设置到请求上下文中。此外为了防止Zuul使用RibbonRoutingFilter进行负载均衡和服务发现我们将服务IDserviceId设置为null。这样Zuul将使用SimpleHostRoutingFilter直接将请求转发到我们指定的URLrequestURI而不会试图再次查找服务实例或应用负载均衡。在上述示例过滤器中如果groupInstances为空意味着没有找到任何匹配特定分组元数据的服务实例。在这种情况下过滤器不会修改请求的路由路径请求将继续通过Zuul的正常路由机制进行。返回null意味着告诉Zuul“我的自定义过滤逻辑已经执行完毕你可以继续执行后续的过滤器链”。这里返回null是Zuul过滤器的标准做法它表示过滤器已成功执行没有新的动作需要执行。2.2 ZUUL路由补充在Zuul中filterType()的返回值决定了过滤器的类型它可以是以下之一pre这些过滤器在请求路由到目标服务之前执行。route在这个阶段的过滤器可以控制请求的实际路由。例如它可以修改请求要路由到的地址或者完全重写路由逻辑。post这些过滤器在请求已经被路由到目标服务并且响应即将返回给客户端时执行。error当在其他阶段发生错误时执行这些过滤器。2.2 SpringGateWay实现服务分组确保所有服务实例都注册到Eureka时带有所需的元数据以便你的服务实例选择逻辑可以根据这些元数据正确识别逻辑分组。例如你可以在application.yaml中为每个服务实例配置元数据eureka: instance: metadata-map: logic-group: group-a # 根据实际情况设置逻辑分组的名称import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.URI; import java.util.List; import java.util.Random; Component public class HeaderMetadataGlobalFilter implements GlobalFilter, Ordered { private final DiscoveryClient discoveryClient; private final String HEADER_NAME X-Logic-Group; // 请求头用于传递逻辑分组信息 public HeaderMetadataGlobalFilter(DiscoveryClient discoveryClient) { this.discoveryClient discoveryClient; } Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 从请求头中获取逻辑分组信息 String logicGroup exchange.getRequest().getHeaders().getFirst(HEADER_NAME); if (logicGroup ! null !logicGroup.isEmpty()) { // 假设服务名称是路径的一部分比如 /serviceA/... String serviceName getServiceNameFromRequest(exchange); ListServiceInstance instances discoveryClient.getInstances(serviceName); // 筛选属于指定逻辑分组的服务实例 ServiceInstance instance instances.stream() .filter(inst - logicGroup.equals(inst.getMetadata().get(HEADER_NAME))) // 这里需要改成一个负载均衡算法 .findAny() .orElse(null); if (instance ! null) { // 如果找到匹配的服务实例构建新的URI URI newUri buildNewUri(instance, exchange.getRequest().getURI()); // 用新的URI更新请求并继续过滤链 exchange exchange.mutate() .request(exchange.getRequest().mutate().uri(newUri).build()) .build(); } } return chain.filter(exchange); } // 从请求中提取服务名称的方法 private String getServiceNameFromRequest(ServerWebExchange exchange) { String path exchange.getRequest().getURI().getPath(); if (path.startsWith(/)) { path path.substring(1); } String[] segments path.split(/); return segments.length 0 ? segments[0] : null; } // 根据选中的服务实例构建新URI的方法 private URI buildNewUri(ServiceInstance instance, URI originalUri) { // 获取服务实例的基本URI String instanceUriStr instance.getUri().toString(); // 获取原始URI的路径部分 String path originalUri.getRawPath(); // 使用getRawPath保留编码状态 // 获取原始URI的查询字符串如果存在的话 String query originalUri.getRawQuery(); // 使用getRawQuery以保留编码状态 // 构建新的URI将路径和查询字符串拼接到服务实例的URI后 String newUriStr instanceUriStr path (query ! null ? ? query : ); return URI.create(newUriStr); } Override public int getOrder() { // 将此过滤器的顺序设置为在LoadBalancerClientFilter之前后者默认顺序为10150 return 10100; } }当Spring Cloud Gateway接收到请求时它遵循以下一般过程来处理该请求请求匹配请求首先被检查是否匹配任何已配置的路由。每个路由定义了一系列的断言谓词这些断言可以基于HTTP请求的各个方面例如路径、头部、方法等进行评估。过滤器链执行一旦找到匹配的路由请求将通过一系列已配置的过滤器。这些过滤器可以在发送下游请求之前“pre”过滤器和下游响应之后“post”过滤器修改请求和响应。路由到下游服务通过路由配置的URI将请求发送到下游服务。如果URI以lb://开头它将使用Spring Cloud的负载均衡机制来选择服务实例。返回响应下游服务的响应通过过滤器链发送回客户端过滤器可以对响应进行最终修改。在上述例子中提到的自定义过滤器中通过创建新的ServerWebExchange并修改请求的URI来直接指向选择的服务实例。理论上这种方法确实可以绕过负载均衡因为你已经明确指定了目标服务实例的地址。然而细节方面需要注意当你通过修改ServerWebExchange来改变URI时确实可以绕过默认的负载均衡机制因为你是直接指定了目标服务的URI。重要的是确保新的URI是完整的正确地指向了目标服务实例的地址。你需要包括协议如http或https、主机名和端口号以及可能的路径。如果URI以lb://开头Spring Cloud Gateway会尝试对其进行负载均衡。因此确保你的新URI不以lb://开头而是直接使用http或https等。3、服务间的实现这里有两个工作需要做一是实现类似网关的工作也就是基于自定义头来路由请求到Eureka服务的特定逻辑分组实例二是要实现头的传递因为调用链很长这个头需要一直传递下去。3.1 RestTemplate传递头1、创建带有LoadBalanced注解的RestTemplatebeanConfiguration public class RestClientConfig { Bean LoadBalanced public RestTemplate restTemplate() { RestTemplate restTemplate new RestTemplate(); restTemplate.getInterceptors().add(new HeaderPropagationInterceptor()); return restTemplate; } }2、定义一个ClientHttpRequestInterceptor以传递头信息也可以把所有头都传递下去public class HeaderPropagationInterceptor implements ClientHttpRequestInterceptor { private static final String LOGIC_GROUP_HEADER X-Logic-Group; Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpServletRequest currentRequest ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String logicGroup currentRequest.getHeader(LOGIC_GROUP_HEADER); if (logicGroup ! null) { request.getHeaders().add(LOGIC_GROUP_HEADER, logicGroup); } return execution.execute(request, body); } }3.2 FeignClient传递头拦截器实现。Configuration public class GlobalFeignConfig { Bean public RequestInterceptor headerForwardingInterceptor() { return template - { HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); EnumerationString headerNames request.getHeaderNames(); if (headerNames ! null) { while (headerNames.hasMoreElements()) { String name headerNames.nextElement(); String values request.getHeader(name); template.header(name, values); } } }; } }它传递了所有的头。3.3 自定义Ribbon路由规则由于FeignClient和RestTemplte(带LoadBalanced注解)底层都使用了Ribbon自然通过这两种方式调用接口都会有这个路由效果。因此我们只需要自定义一个Bibbon路由规则就可以了。public class CustomLoadBalancerRule extends AbstractLoadBalancerRule { private static final String LOGIC_GROUP_HEADER X-Logic-Group; Override public Server choose(Object key) { ILoadBalancer lb getLoadBalancer(); ListServer servers lb.getReachableServers(); HttpServletRequest request RequestContext.getCurrentContext().getRequest(); String logicGroup request.getHeader(LOGIC_GROUP_HEADER); // 实现基于logicGroup的自定义逻辑来选择服务实例 // ... return chooseAnyServer(servers); } Override public void initWithNiwsConfig(IClientConfig clientConfig) { // 初始化配置如果需要 } private Server chooseAnyServer(ListServer servers) { // 这里可以实现备选的选择逻辑例如随机选择 if (servers.isEmpty()) return null; return servers.get(ThreadLocalRandom.current().nextInt(servers.size())); } }创建一个RibbonClientConfiguration类Configuration public class RibbonConfiguration { Bean public IRule ribbonRule() { return new CustomLoadBalancerRule(); // 使用你的自定义规则 } }然后在你的应用主类或任何配置类上使用RibbonClients注解来应用这个配置貌似不是必须的需要验证SpringBootApplication RibbonClients(defaultConfiguration RibbonConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }对于大多数使用场景如果你只是想全局应用一个统一的Ribbon配置那么Configuration类中定义的Bean方法提供的配置就已经足够。只有在需要更细粒度控制或解决特定问题时才考虑使用RibbonClients。3.4 SpringCloud LoadBalancerReactiveLoadBalancer与ServiceInstanceListSupplier的选择问题!在Spring Cloud LoadBalancer中ReactiveLoadBalancer的实现通常是用来选择服务实例而不是用来过滤服务实例列表。过滤服务实例列表通常是通过实现ServiceInstanceListSupplier接口来完成的。ReactiveLoadBalancer是一个更高层次的接口其choose方法被用来从服务实例列表中按照某种策略选择一个实例。当你使用ReactiveLoadBalancer时通常是在已经有一个服务实例列表的基础上根据负载均衡策略如轮询、随机等选择一个实例。而ServiceInstanceListSupplier则是负责提供这个初始的服务实例列表可以在这个阶段根据请求头信息对列表进行过滤。如果你想在ReactiveLoadBalancer层面实现基于请求头的路由决策你需要在调用choose方法时传递一些上下文信息。Spring Cloud LoadBalancer中的Request接口可以携带这些信息。然而Request对象需要在调用choose之前构建并且Spring Cloud LoadBalancer并没有提供一个内置的方式来根据传入的HTTP请求构建这个Request对象。这就是为什么在之前的解决方案中我们使用ServiceInstanceListSupplier来实现基于请求头的路由决策的原因。ServiceInstanceListSupplier 方式首先你需要确保在请求进入Spring Cloud Gateway时能够捕获并存储当前的ServerWebExchange。Spring Cloud Gateway提供了一个方便的方式来进行这种操作你可以定义一个全局过滤器在WebFlux环境中使用ThreadLocal来存储ServerWebExchange是不合适的正确的做法是使用Reactor的上下文Context来存储信息import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; Component public class ServerWebExchangeStoreFilter implements GlobalFilter, Ordered { public static final String EXCHANGE_ATTRIBUTE_NAME serverWebExchange; Override public int getOrder() { // Ensure this filter has the highest precedence return Ordered.HIGHEST_PRECEDENCE; } Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // Store ServerWebExchange in the Reactor Context return chain.filter(exchange) .contextWrite(ctx - ctx.put(EXCHANGE_ATTRIBUTE_NAME, exchange)); } }在这个实现中get()方法利用了Flux.deferContextual来延迟对Reactor上下文的访问。我们试图从上下文中获取ServerWebExchange然后从中读取请求头Custom-Header。根据这个头信息我们过滤服务实例列表以匹配头信息。然后创建一个全局的ServiceInstanceListSupplierimport org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import java.util.List; import java.util.stream.Collectors; public class HeaderBasedServiceInstanceListSupplier implements ServiceInstanceListSupplier { private final DiscoveryClient discoveryClient; private final String serviceId; public HeaderBasedServiceInstanceListSupplier(DiscoveryClient discoveryClient, String serviceId) { this.discoveryClient discoveryClient; this.serviceId serviceId; } Override public FluxListServiceInstance get() { return Flux.deferContextual(ctx - { if (ctx.hasKey(ServerWebExchangeStoreFilter.EXCHANGE_CONTEXT_ATTRIBUTE)) { ServerWebExchange exchange ctx.get(ServerWebExchangeStoreFilter.EXCHANGE_CONTEXT_ATTRIBUTE); String headerValue exchange.getRequest().getHeaders().getFirst(Custom-Header); ListServiceInstance instances discoveryClient.getInstances(serviceId); // 过滤服务实例仅保留头信息匹配的实例 ListServiceInstance filteredInstances instances.stream() .filter(instance - instance.getMetadata().get(Custom-Metadata-Key).equals(headerValue)) .collect(Collectors.toList()); return Flux.just(filteredInstances); } else { return Flux.error(new IllegalStateException(ServerWebExchange is not available in the context.)); } }); } Override public String getServiceId() { return serviceId; } }现在创建一个全局的自定义ReactorLoadBalancer工厂import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; Configuration LoadBalancerClients(defaultConfiguration DefaultCustomLoadBalancerConfiguration.class) public class GlobalLoadBalancerConfiguration { } Configuration class DefaultCustomLoadBalancerConfiguration { Bean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( DiscoveryClient discoveryClient, Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { return new HeaderBasedServiceInstanceListSupplier(discoveryClient, loadBalancerClientFactory.getName(environment)); } }在这个配置中使用LoadBalancerClients注解来指定默认的负载均衡配置。DefaultCustomLoadBalancerConfiguration类中定义了一个Bean工厂方法该方法会根据当前的服务创建一个HeaderBasedServiceInstanceListSupplier实例。ReactiveLoadBalancer方式保存ServerWebExchange同上import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; Component public class ServerWebExchangeStoreFilter implements GlobalFilter, Ordered { public static final String EXCHANGE_CONTEXT_ATTRIBUTE serverWebExchange; Override public int getOrder() { // Ensure this filter runs before other filters return Ordered.HIGHEST_PRECEDENCE; } Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // Add ServerWebExchange to Reactor context return chain.filter(exchange) .subscriberContext(ctx - ctx.put(EXCHANGE_CONTEXT_ATTRIBUTE, exchange)); } }实现自定义的ReactiveLoadBalancer自定义的负载均衡器将访问Reactor上下文来获取ServerWebExchange并提取请求头信息基于该信息做出服务实例选择决策import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.reactive.ReactorServiceInstanceLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import reactor.core.publisher.Mono; import java.util.List; public class CustomHeaderBasedReactiveLoadBalancer implements ReactorServiceInstanceLoadBalancer { private final ServiceInstanceListSupplier serviceInstanceListSupplier; private final String serviceId; public CustomHeaderBasedReactiveLoadBalancer(ServiceInstanceListSupplier serviceInstanceListSupplier, String serviceId) { this.serviceInstanceListSupplier serviceInstanceListSupplier; this.serviceId serviceId; } Override public MonoResponseServiceInstance choose(Request request) { return Mono.deferContextual(ctx - { if (ctx.hasKey(ServerWebExchangeStoreFilter.EXCHANGE_CONTEXT_ATTRIBUTE)) { ServerWebExchange exchange ctx.get(ServerWebExchangeStoreFilter.EXCHANGE_CONTEXT_ATTRIBUTE); String headerValue exchange.getRequest().getHeaders().getFirst(Custom-Header); return serviceInstanceListSupplier.get(request) .next() .map(serviceInstances - { // 过滤服务实例列表基于请求头信息 ListServiceInstance filteredInstances serviceInstances.stream() .filter(si - headerValue.equals(si.getMetadata().get(Custom-Metadata-Key))) .collect(Collectors.toList()); // 这里只是示例实际上你可能需要一个更复杂的选择策略 return new DefaultResponse(filteredInstances.get(0)); }); } else { return Mono.error(new IllegalStateException(ServerWebExchange is not available in the context.)); } }); } }注册自定义的ReactiveLoadBalancer最后在你的Spring Cloud Gateway配置类中注册自定义的ReactiveLoadBalancerimport org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration LoadBalancerClient(name default, configuration DefaultLoadBalancerConfiguration.class) public class CustomLoadBalancerConfiguration { Bean public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer( ServiceInstanceListSupplier serviceInstanceListSupplier) { return new CustomHeaderBasedReactiveLoadBalancer(serviceInstanceListSupplier); } // CustomHeaderBasedReactiveLoadBalancer definition here // ... }在上面的代码中LoadBalancerClient注解的name属性被设置为default这意味着它将为所有没有特定负载均衡配置的服务提供负载均衡策略。确保你的自定义ReactorServiceInstanceLoadBalancer实现和全局过滤器用于存储ServerWebExchange已经正确定义如之前的回答中所示。不过需要注意的是不同版本的Spring Cloud可能在实现和配置上有所差异。LoadBalancerClient的默认行为和如何对待default这个名称可能会有所不同因此建议查阅你正在使用的Spring Cloud版本的官方文档来获取确切的信息。总之你选择哪种方式来注册你的自定义负载均衡器取决于你的具体需求和你所使用的Spring Cloud版本。在某些场景下重写LoadBalancerClientFactory可能更合适而在其他场景下使用LoadBalancerClient注解可能更简单直观。import org.springframework.cloud.client.loadbalancer.LoadBalancerClientFactory; import org.springframework.cloud.gateway.config.LoadBalancerProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; Configuration Import(LoadBalancerClientConfiguration.class) public class GlobalLoadBalancerConfiguration { Bean public LoadBalancerClientFactory loadBalancerClientFactory() { return new CustomLoadBalancerClientFactory(); } static class CustomLoadBalancerClientFactory extends LoadBalancerClientFactory { Override public ReactorServiceInstanceLoadBalancer getInstance(String serviceId) { ServiceInstanceListSupplier supplier getLazyProvider(serviceId, ServiceInstanceListSupplier.class).getIfAvailable(); return new CustomHeaderBasedReactiveLoadBalancer(supplier, serviceId); } } // Your CustomHeaderBasedReactiveLoadBalancer definition here // ... }这个配置类使用了Import注解来确保LoadBalancerClientConfiguration中的默认配置被加载。然后它提供了一个自定义LoadBalancerClientFactory的Bean该Bean重写了getInstance方法来返回自定义的负载均衡器实例。这样在应用程序中就设置了一个全局有效的自定义负载均衡器它将用于所有通过Spring Cloud Gateway的服务调用。请注意具体的配置可能依赖于您使用的Spring Cloud版本并且Spring Cloud组件可能会有所不同。以上示例代码提供了一种可能的实现方式但根据您的实际环境和需求您可能需要调整配置。如果您在注册过程中遇到任何问题建议查看Spring Cloud Gateway和Spring Cloud LoadBalancer的官方文档以获取最新的配置指导。

相关文章:

SpringCloud通过服务逻辑分组实现灰度功能

声明:内容来自AI,未经验证,仅供参考!1、原理服务注册到注册中心时,我们可以添加额外的信息对服务进行打标/染色,从而实现逻辑分组。当有调用时,我们根据HTTP头信息(约定好头)来决定转…...

如何在Windows上测试ip和端口

我们在开发中常常需要测试服务器之间的连通性,下面我给大家分享一下我的经验,方法有三 方法1 使用ping命令,但是这个命令只能简单的测试服务器之间是否具备通信能力,使用方法如下 ping 192.168.0.1回车之后,两个ip所在…...

如何使用SoccerOnTable:将足球视频转换为3D AR/VR体验的完整指南

如何使用SoccerOnTable:将足球视频转换为3D AR/VR体验的完整指南 【免费下载链接】soccerontable Upconverting YouTube soccer videos in 3D for viewing in AR/VR devices.Soccer On Your Tabletop 项目地址: https://gitcode.com/gh_mirrors/so/soccerontable …...

探索Schema Inspector:数据验证与文档生成的新星!

探索Schema Inspector:数据验证与文档生成的新星! 【免费下载链接】schema-inspector Schema-Inspector is a simple JavaScript object sanitization and validation module. 项目地址: https://gitcode.com/gh_mirrors/sc/schema-inspector Sch…...

如何快速提升网站交互体验:SlipHover 悬停动画库完全指南

如何快速提升网站交互体验:SlipHover 悬停动画库完全指南 【免费下载链接】SlipHover apply direction aware animation to images caption 项目地址: https://gitcode.com/gh_mirrors/sl/SlipHover SlipHover 是一款轻量级的 jQuery 悬停动画库,…...

如何用《百万英雄助手》轻松通关知识问答?智能答题神器全攻略

如何用《百万英雄助手》轻松通关知识问答?智能答题神器全攻略 【免费下载链接】MillionHeroAssistant 百万 / 冲顶 / 芝士 / UC / 万能 答题助手(知识图谱更加专业,自动推荐答案, Android手机自动屏幕适配,模拟器支持&…...

如何使用CSS Ratiocinator:轻松优化混乱CSS的终极指南

如何使用CSS Ratiocinator:轻松优化混乱CSS的终极指南 【免费下载链接】css-ratiocinator because your CSS is garbage 项目地址: https://gitcode.com/gh_mirrors/cs/css-ratiocinator CSS Ratiocinator是一款强大的CSS重构工具,能够自动分析网…...

如何使用SlipHover:为图片添加方向感知动画的完整指南

如何使用SlipHover:为图片添加方向感知动画的完整指南 【免费下载链接】SlipHover apply direction aware animation to images caption 项目地址: https://gitcode.com/gh_mirrors/sl/SlipHover SlipHover是一个轻量级的jQuery插件,能够为图片添…...

如何使用 Laravel Purity:简化 Laravel 数据筛选与排序的终极指南

如何使用 Laravel Purity:简化 Laravel 数据筛选与排序的终极指南 【免费下载链接】laravel-purity An elegant way to filter and sort queries in Laravel 项目地址: https://gitcode.com/gh_mirrors/la/laravel-purity Laravel Purity 是一款为 Laravel 框…...

如何快速安装与使用ESSE:保护数据安全的终极加密即时通讯工具

如何快速安装与使用ESSE:保护数据安全的终极加密即时通讯工具 【免费下载链接】ESSE Encrypted peer-to-peer IM for data security. Own data, own privacy. (RustFlutter) 项目地址: https://gitcode.com/gh_mirrors/es/ESSE ESSE是一款专注于数据安全的加…...

如何高效管理多GitHub仓库?Turbolift开源工具的终极使用指南

如何高效管理多GitHub仓库?Turbolift开源工具的终极使用指南 【免费下载链接】turbolift A simple tool to help apply changes across many GitHub repositories simultaneously 项目地址: https://gitcode.com/gh_mirrors/tu/turbolift Turbolift是一款强大…...

如何使用ESSE:打造你的终极加密点对点通信系统

如何使用ESSE:打造你的终极加密点对点通信系统 【免费下载链接】ESSE Encrypted peer-to-peer IM for data security. Own data, own privacy. (RustFlutter) 项目地址: https://gitcode.com/gh_mirrors/es/ESSE ESSE(Encrypted Symmetrical Sess…...

Tessera性能优化:提升大规模仪表盘加载速度的6个技巧

Tessera性能优化:提升大规模仪表盘加载速度的6个技巧 【免费下载链接】tessera A dashboard front-end for graphite. 项目地址: https://gitcode.com/gh_mirrors/te/tessera Tessera作为Graphite的仪表盘前端工具,在处理大规模数据可视化时&…...

解决UnityDebugSheet常见问题的终极指南:从入门到精通

解决UnityDebugSheet常见问题的终极指南:从入门到精通 【免费下载链接】UnityDebugSheet Hierarchical debug menu system for Unity that makes it easy to create intuitive and organized debug menus. 项目地址: https://gitcode.com/gh_mirrors/un/UnityDebu…...

终极Grafana Dash Gen问题解决方案:从入门到精通的完整指南

终极Grafana Dash Gen问题解决方案:从入门到精通的完整指南 【免费下载链接】grafana-dash-gen grafana dash dash dash gen 项目地址: https://gitcode.com/gh_mirrors/gr/grafana-dash-gen Grafana Dash Gen是一款强大的Grafana仪表盘生成工具,…...

Kymatio项目常见问题解决方案

Kymatio项目常见问题解决方案 【免费下载链接】kymatio Wavelet scattering transforms in Python with GPU acceleration 项目地址: https://gitcode.com/gh_mirrors/ky/kymatio 1. 项目基础介绍和主要编程语言 Kymatio 是一个在 Python 编程语言中实现的波let 散射变…...

终极指南:解决ShuffleNet-V2 PyTorch Caffe项目的常见问题

终极指南:解决ShuffleNet-V2 PyTorch & Caffe项目的常见问题 【免费下载链接】ShuffleNet_V2_pytorch_caffe ShuffleNet-V2 for both PyTorch and Caffe. 项目地址: https://gitcode.com/gh_mirrors/sh/ShuffleNet_V2_pytorch_caffe ShuffleNet-V2是一款…...

如何快速上手hecs:5分钟创建你的第一个ECS世界

如何快速上手hecs:5分钟创建你的第一个ECS世界 【免费下载链接】hecs A handy ECS 项目地址: https://gitcode.com/gh_mirrors/he/hecs hecs是一个高性能、极简主义的实体组件系统(ECS)库,专为游戏开发和实时模拟设计。本文…...

贡献指南:如何为Nanocoder开源项目提交代码和新功能

贡献指南:如何为Nanocoder开源项目提交代码和新功能 【免费下载链接】nanocoder A beautiful local-first coding agent running in your terminal - built by the community for the community ⚒ 项目地址: https://gitcode.com/gh_mirrors/na/nanocoder N…...

基于强化学习的目标跟踪 研究初探

强化学习 目标跟踪Visual tracking by means of deep reinforcement learning and an expert demonstratorYOLO 检测下基于 ETC-DDPG 算法的无人机视觉跟踪基于特征与深度强化学习方法的机器人视觉伺服技术研究高性能可拓展视频目标跟踪算法研究基于目标运动与外观特征的多目标…...

机器学习逻辑回归实战

解决分类的一种模型逻辑回归预测考试通过 基于examdata.csv数据,建立逻辑回归模型 预测Exam175,Exam260时 该同学在Exam3时passed or failed import pandas as pd import numpy as npdata pd.read_csv(examdata.csv) data.head()#可视化 %matplotlib in…...

【线性代数】目录

📚 线性代数目录 基础部分 📝 【线性代数】线性方程组与矩阵——(1)线性方程组与矩阵初步📊 【线性代数】线性方程组与矩阵——行列式🔍 【线性代数】线性方程组与矩阵——(2)矩阵与…...

usbrip存储模块深度解析:创建加密USB事件备份与自动更新策略

usbrip存储模块深度解析:创建加密USB事件备份与自动更新策略 【免费下载链接】usbrip Tracking history of USB events on GNU/Linux 项目地址: https://gitcode.com/gh_mirrors/us/usbrip USB设备的使用记录对于系统安全审计和事件追溯至关重要。usbrip作为…...

如何实现Ivy分布式训练容错:5大关键机制确保训练稳定性

如何实现Ivy分布式训练容错:5大关键机制确保训练稳定性 【免费下载链接】ivy unifyai/ivy: 是一个基于 Python 的人工智能库,支持多种人工智能算法和工具。该项目提供了一个简单易用的人工智能库,可以方便地实现各种人工智能算法的训练和推理…...

网络原理(9):HTTPS 协议初识 对称加密与非对称加密

网络原理(9):HTTPS协议初识 文章目录网络原理(9):HTTPS协议初识观前提醒:1. HTTPS1.1 HTTPS 是什么 & 组成1.2 引入 HTTPS 的原因2. 加密2.1 密钥2.1 对称加密 & 非对称加密2.2 对称加密…...

如何使用Skynet框架打造高自由度游戏装备系统:材料合成与属性随机生成完整指南

如何使用Skynet框架打造高自由度游戏装备系统:材料合成与属性随机生成完整指南 【免费下载链接】skynet 一个轻量级的在线游戏框架。 项目地址: https://gitcode.com/GitHub_Trending/sk/skynet Skynet作为一款轻量级在线游戏框架,为开发者提供了…...

终极指南:如何实现 nvim-treesitter 多窗口语法状态同步

终极指南:如何实现 nvim-treesitter 多窗口语法状态同步 【免费下载链接】nvim-treesitter Nvim Treesitter configurations and abstraction layer 项目地址: https://gitcode.com/GitHub_Trending/nv/nvim-treesitter nvim-treesitter 作为 Neovim 生态中最…...

如何快速查看与恢复Magpie窗口放大历史设置?完整指南

如何快速查看与恢复Magpie窗口放大历史设置?完整指南 【免费下载链接】Magpie An all-purpose window upscaler for Windows 10/11. 项目地址: https://gitcode.com/gh_mirrors/mag/Magpie Magpie作为一款功能强大的Windows窗口放大工具,让用户能…...

如何用React Hooks与Context模式构建Conductor前端状态管理系统

如何用React Hooks与Context模式构建Conductor前端状态管理系统 【免费下载链接】conductor Conductor is a microservices orchestration engine. 项目地址: https://gitcode.com/gh_mirrors/condu/conductor Conductor是Netflix开源的微服务编排引擎,其前端…...

DIY-Thermocam实战案例:用自制热成像仪检测电器故障的完整步骤

DIY-Thermocam实战案例:用自制热成像仪检测电器故障的完整步骤 【免费下载链接】diy-thermocam A do-it-yourself thermal imager, compatible with the FLIR Lepton 2.5, 3.1R and 3.5 sensor with Arduino firmware 项目地址: https://gitcode.com/gh_mirrors/d…...