SpringCloud Alibaba集成 Gateway(自定义负载均衡器)、Nacos(配置中心、注册中心)、Loadbalancer
文章目录
- 常见Gateway架构
- Gateway核心点
- 工作机制
- POM依赖
- 环境准备
- 配置
- 配置文件
- 配置类
- 案例展示
常见Gateway架构
Gateway核心点
-
路由(route):路由是网关最基础的部分,路由信息由一个ID,一个目的URL、一组断言工厂和一 组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
-
断言(Predicate):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配 来自http Request中的任何信息,比如请求头和参数等。
-
过滤器(Filter):一个标准的Spring WebFilter,Spring Cloud Gateway中的Filter分为两种类型: Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。
工作机制
客户端向Spring Cloud网关发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序运行通过特定于请求的筛选器链发送请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前或之后执行逻辑。执行所有“前置”过滤器逻辑,然后发出代理请求。发出代理请求后,将执行“后”过滤器逻辑。
POM依赖
org.springframework.boot spring-boot-starter-parent 2.7.10 8 8 3.1.6 2021.0.4.0 UTF-8 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ${springcloudalibaba.version} com.alibaba.nacos nacos-client com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config ${springcloudalibaba.version} com.alibaba.nacos nacos-client org.springframework.cloud spring-cloud-starter-bootstrap ${springcloud.version} com.alibaba.nacos nacos-client 2.1.1 org.springframework.cloud spring-cloud-starter-openfeign ${springcloud.version} org.springframework.cloud spring-cloud-starter-loadbalancer ${springcloud.version} org.springframework.cloud spring-cloud-starter-netflix-hystrix 2.2.9.RELEASE org.springframework.cloud spring-cloud-starter-gateway 3.1.5 org.springframework.boot spring-boot-starter-web org.springframework spring-webmvc spring-boot-starter-tomcat org.springframework.boot
环境准备
未安装Nacos可先进行简单的单机部署
nacos搭建Nacos standalone单机搭建部署
配置
配置文件
application.yml
server: port: 8082 spring: profiles: active: dev main: web-application-type: reactive application: name: api-gateway cloud: gateway: routes: - id: Mes uri: lb://Mes predicates: - Path=/mes/** - id: Test uri: lb://Test predicates: - Path=/test/**
跨域配置
对于所有GET请求的路径,来自docs.spring.io的请求都将允许CORS请求。
要为未被某些网关路由谓词处理的请求提供相同的CORS配置,请将属性spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping设置为true。当尝试支持CORS预检请求并且您的路由谓词未评估为true时,这很有用,因为http方法为options。
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "https://docs.spring.io" allowedMethods: - GET
bootstrap.properties
spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.nacos.discovery.group=dev spring.cloud.nacos.discovery.namespace=1e6d33e2-5f43-45ec-8c1b-9c883c2c71d9 spring.cloud.nacos.config.group=dev spring.cloud.nacos.config.prefix=gateway spring.cloud.nacos.config.server-addr=localhost:8848 spring.cloud.nacos.config.file-extension=properties spring.cloud.nacos.config.namespace=1e6d33e2-5f43-45ec-8c1b-9c883c2c71d9 spring.profiles.active=dev spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedOriginPatterns=* spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedHeaders=* spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedMethods=* spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowCredentials=true
bootstrap-dev.properties
# 用于配置中心测试 message.name=lisi
配置类
自定义Gateway负载均衡器,采用nacos所配置的权重进行负载均衡调用,随机权重算法
import com.alibaba.cloud.nacos.balancer.NacosBalancer; import com.alibaba.nacos.api.naming.pojo.Instance; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.DefaultResponse; import org.springframework.cloud.client.loadbalancer.EmptyResponse; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.loadbalancer.core.*; import reactor.core.publisher.Mono; import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer { private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class); final AtomicInteger position; final String serviceId; ObjectProvider serviceInstanceListSupplierProvider; /** * @param serviceInstanceListSupplierProvider a provider of * {@link ServiceInstanceListSupplier} that will be used to get available instances * @param serviceId id of the service for which to choose an instance */ public CustomRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId) { this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000)); } /** * @param serviceInstanceListSupplierProvider a provider of * {@link ServiceInstanceListSupplier} that will be used to get available instances * @param serviceId id of the service for which to choose an instance * @param seedPosition Round Robin element position marker */ public CustomRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider, String serviceId, int seedPosition) { this.serviceId = serviceId; this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; this.position = new AtomicInteger(seedPosition); } @SuppressWarnings("rawtypes") @Override public Mono choose(Request request) { ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new); return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); } private Response processInstanceResponse(ServiceInstanceListSupplier supplier, List serviceInstances) { Response serviceInstanceResponse = getInstanceResponse(serviceInstances); if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } /** * 按nacos权重 * * @return */ private Response getInstanceResponse(List serviceInstances) { Map collect = serviceInstances.stream().collect(Collectors.groupingBy(g -> { //nacos在2.0版本之后移除了对实例id查询 // return g.getMetadata().get("nacos.instanceId"); return g.getHost() +":"+ g.getPort(); })); if (serviceInstances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + serviceId); } return new EmptyResponse(); } List instances = serviceInstances.stream().map(i -> { Instance instance = new Instance(); instance.setInstanceId(i.getInstanceId()); Map metadata = i.getMetadata(); instance.setInstanceId(metadata.get("nacos.instanceId")); instance.setWeight(new BigDecimal(metadata.get("nacos.weight")).doubleValue()); instance.setClusterName(metadata.get("nacos.cluster")); instance.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral"))); instance.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy"))); instance.setPort(i.getPort()); instance.setIp(i.getHost()); instance.setServiceName(i.getServiceId()); instance.setMetadata(metadata); return instance; }).collect(Collectors.toList()); //采用nacos所配置的权重进行负载均衡调用,随机权重算法 Instance instance = NacosBalancer.getHostByRandomWeight2(instances); // // TODO: enforce order? // int pos = Math.abs(this.position.incrementAndGet()); // ServiceInstance instance = instances.get(pos % instances.size()); return new DefaultResponse(collect.get(instance.getIp()+":"+ instance.getPort()).stream().findFirst().get()); } }
负载均衡配置类,指定使用哪一个负载均衡器
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 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 public class RoundRobinLoadBalancerConfig { @Bean ReactorLoadBalancer randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new CustomRoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }
LoadBalancerClient,负载均衡调用客户端,指定负载均衡器配置类,LoadBalancerClient注解中value要对用配置文件中路由的id
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @LoadBalancerClients({@LoadBalancerClient(value = "Mes", configuration = RoundRobinLoadBalancerConfig.class), @LoadBalancerClient(value = "Test", configuration = RoundRobinLoadBalancerConfig.class)}) @Configuration public class RestTemplateConfig { @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
全局过滤器
import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; 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; @Slf4j @Component public class RouteRecordGlobalFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI proxyRequestUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); long start = System.currentTimeMillis(); return chain.filter(exchange).then(Mono.fromRunnable(() -> { long end = System.currentTimeMillis(); log.info("实际调用地址为:{},调用耗时为:{}ms", proxyRequestUri, (end - start)); })); } @Override public int getOrder() { // 优先级设为最低,先让RouteToRequestUrlFilter先调用 return Ordered.LOWEST_PRECEDENCE; } }
案例展示
Nacos服务列表
服务权重配置
服务测试代码
负载效果
配置中心测试代码
本地配置项
配置中心配置
效果
配置中心注意:
这三个配置的拼接等于配置中心的配置 Data ID一定要正确
spring.cloud.nacos.config.prefix=gateway spring.cloud.nacos.config.file-extension=properties spring.profiles.active=dev
-