• spring-cloud-gateway负载普通web项目


    spring-cloud-gateway负载普通web项目

    对于普通的web项目,也是可以通过spring-cloud-gateway进行负载的,只是无法通过服务发现。

    背景

    不知道各位道友有没有使用过帆软,帆软是国内一款报表工具,这里不做过多介绍。

    它是通过war包部署到tomcat,默认是单台服务。如果想做集群,需要配置cluster.xml,帆软会将当前节点的请求转发给主节点(一段时间内)。

    在实际工作中,部署四个节点时,每个节点启动需要10分钟以上(单台的情况下,则需要一两分钟)。而且一段时间内其他节点会将请求转发给主节点,存在单点压力。

    于是,通过spring-cloud-gateway来负载帆软节点。

    帆软集群介绍

    在帆软9.0,如果部署A、B两个节点,当查询A节点后,正确返回结果;如果被负载到B,那么查询是无法拿到结果的。可以认为是session(此session非web中的session)不共享的,帆软是B通过将请求转发给A执行来解决共享问题的。

    gateway负载思路

    • 对于非登录的用户(此时我们是用不了帆软的),直接采用随机请求转发到某个节点即可
    • 对于登录的用户,根据sessionId去hash,在本次会话内一直访问帆软的同一个节点

    这样,我们能保证用户在本次会话内访问的是同一个节点,就不需要帆软9.0的集群机制了。

    实现

    基于spring cloud 2.x

    依赖

    我们需要使用spring-cloud-starter-gatewayspring-cloud-starter-netflix-ribbon

    其中:

    • spring-cloud-starter-gateway用来做gateway
    • spring-cloud-starter-netflix-ribbon做客户端的LoadBalancer
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>xxx</groupId>
        <artifactId>yyy</artifactId>
        <version>1.0.0</version>
    
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
            <spring.boot.version>2.1.2.RELEASE</spring.boot.version>
            <spring.cloud.version>2.1.0.RELEASE</spring.cloud.version>
            <slf4j.version>1.7.25</slf4j.version>
        </properties>
    
        <repositories>
            <repository>
                <id>aliyun</id>
                <name>aliyun maven</name>
                <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            </repository>
        </repositories>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
                <version>${spring.cloud.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                <version>${spring.cloud.version}</version>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>${slf4j.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                    <version>4.5.5</version>
                </dependency>
                
                <dependency>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                    <version>2.9.8</version>
                </dependency>
                <dependency>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-core</artifactId>
                    <version>2.9.8</version>
                </dependency>
                <dependency>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-databind</artifactId>
                    <version>2.9.8</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
    
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring.boot.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>
    

    核心配置

    主要是通过lb指定服务名,ribbon指定多个服务实例(微服务是从注册中心中获取的)来进行负载。

    spring:
      cloud:
        gateway:
          routes:
          # http
          - id: host_route
            # lb代表服务名,后面从ribbon的服务列表中获取(其实微服务是从注册中心中获取的)
            # 这里负载所有的http请求
            uri: lb://xx-http
            predicates:
            - Path=/**
            filters:
            # 请求限制5MB
            - name: RequestSize
              args:
                maxSize: 5000000
          # ws
          - id: websocket_route
            # lb代表服务名,后面从ribbon的服务列表中获取(其实微服务是从注册中心中获取的)
            # 这里负载所有的websocket
            uri: lb:ws://xx-ws
            predicates:
            - Path=/websocket/**
    
    xx-http:
      ribbon:
        # 服务列表
        listOfServers: http://172.16.242.156:15020, http://172.16.242.192:15020
        # 10s
        ConnectTimeout: 10000
        # 10min
        ReadTimeout: 600000
        # 最大的连接
        MaxTotalHttpConnections: 500
        # 每个实例的最大连接
        MaxConnectionsPerHost: 300
    
    xx-ws:
      ribbon:
        # 服务列表
        listOfServers: ws://172.16.242.156:15020, ws://172.16.242.192:15020
        # 10s
        ConnectTimeout: 10000
        # 10min
        ReadTimeout: 600000
        # 最大的连接
        MaxTotalHttpConnections: 500
        # 每个实例的最大连接
        MaxConnectionsPerHost: 300
    

    之后,我们需要自定义负载均衡过滤器、以及规则。

    自定义负载均衡过滤器

    主要是通过判断请求是否携带session,如果携带说明登录过,则后面根据sessionId去hash,在本次会话内一直访问帆软的同一个节点;否则默认随机负载即可。

    import org.springframework.cloud.client.ServiceInstance;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.cloud.gateway.config.LoadBalancerProperties;
    import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
    import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
    import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
    import org.springframework.http.HttpCookie;
    import org.springframework.util.StringUtils;
    import org.springframework.web.server.ServerWebExchange;
    
    import java.net.URI;
    import java.util.Objects;
    
    /**
     * 自定义负载均衡过滤器
     *
     * @author 奔波儿灞
     * @since 1.0
     */
    public class CustomLoadBalancerClientFilter extends LoadBalancerClientFilter {
    
        private static final String COOKIE = "SESSIONID";
    
        public CustomLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
            super(loadBalancer, properties);
        }
    
        @Override
        protected ServiceInstance choose(ServerWebExchange exchange) {
            // 获取请求中的cookie
            HttpCookie cookie = exchange.getRequest().getCookies().getFirst(COOKIE);
            if (cookie == null) {
                return super.choose(exchange);
            }
            String value = cookie.getValue();
            if (StringUtils.isEmpty(value)) {
                return super.choose(exchange);
            }
            if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
                RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
                Object attrValue = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
                Objects.requireNonNull(attrValue);
                String serviceId = ((URI) attrValue).getHost();
                // 这里使用session做为选择服务实例的key
                return client.choose(serviceId, value);
            }
            return super.choose(exchange);
        }
    }
    

    自定义负载均衡规则

    核心就是实现choose方法,从可用的servers列表中,选择一个server去负载。

    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.Server;
    import org.apache.commons.lang.math.RandomUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.CollectionUtils;
    
    import java.util.List;
    
    /**
     * 负载均衡规则
     *
     * @author 奔波儿灞
     * @since 1.0
     */
    public class CustomLoadBalancerRule extends AbstractLoadBalancerRule {
    
        private static final Logger LOG = LoggerFactory.getLogger(CustomLoadBalancerRule.class);
    
        private static final String DEFAULT_KEY = "default";
    
        private static final String RULE_ONE = "one";
    
        private static final String RULE_RANDOM = "random";
    
        private static final String RULE_HASH = "hash";
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        @Override
        public Server choose(Object key) {
            List<Server> servers = this.getLoadBalancer().getReachableServers();
            if (CollectionUtils.isEmpty(servers)) {
                return null;
            }
            // 只有一个服务,则默认选择
            if (servers.size() == 1) {
                return debugServer(servers.get(0), RULE_ONE);
            }
            // 多个服务时,当cookie不存在时,随机选择
            if (key == null || DEFAULT_KEY.equals(key)) {
                return debugServer(randomChoose(servers), RULE_RANDOM);
            }
            // 多个服务时,cookie存在,根据cookie hash
            return debugServer(hashKeyChoose(servers, key), RULE_HASH);
        }
    
        /**
         * 随机选择一个服务
         *
         * @param servers 可用的服务列表
         * @return 随机选择一个服务
         */
        private Server randomChoose(List<Server> servers) {
            int randomIndex = RandomUtils.nextInt(servers.size());
            return servers.get(randomIndex);
        }
    
        /**
         * 根据key hash选择一个服务
         *
         * @param servers 可用的服务列表
         * @param key     自定义key
         * @return 根据key hash选择一个服务
         */
        private Server hashKeyChoose(List<Server> servers, Object key) {
            int hashCode = Math.abs(key.hashCode());
            if (hashCode < servers.size()) {
                return servers.get(hashCode);
            }
            int index = hashCode % servers.size();
            return servers.get(index);
        }
    
        /**
         * debug选择的server
         *
         * @param server 具体的服务实例
         * @param name   策略名称
         * @return 服务实例
         */
        private Server debugServer(Server server, String name) {
            LOG.debug("choose server: {}, rule: {}", server, name);
            return server;
        }
    }
    

    Bean配置

    自定义之后,我们需要激活Bean,让过滤器以及规则生效。

    import com.netflix.loadbalancer.IRule;
    import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    import org.springframework.cloud.gateway.config.LoadBalancerProperties;
    import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 负载均衡配置
     *
     * @author 奔波儿灞
     * @since 1.0
     */
    @Configuration
    public class LoadBalancerConfiguration {
    
        /**
         * 自定义负载均衡过滤器
         *
         * @param client     LoadBalancerClient
         * @param properties LoadBalancerProperties
         * @return CustomLoadBalancerClientFilter
         */
        @Bean
        public LoadBalancerClientFilter customLoadBalancerClientFilter(LoadBalancerClient client,
                                                                       LoadBalancerProperties properties) {
            return new CustomLoadBalancerClientFilter(client, properties);
        }
    
        /**
         * 自定义负载均衡规则
         *
         * @return CustomLoadBalancerRule
         */
        @Bean
        public IRule customLoadBalancerRule() {
            return new CustomLoadBalancerRule();
        }
    
    }
    

    启动

    这里是标准的spring boot程序启动。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * 入口
     *
     * @author 奔波儿灞
     * @since 1.0
     */
    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
    

    补充

    请求头太长错误

    由于spring cloud gateway使用webflux模块,底层是netty。如果超过netty默认的请求头长度,则会报错。

    默认的最大请求头长度配置reactor.netty.http.server.HttpRequestDecoderSpec,目前我采用的是比较蠢的方式直接覆盖了这个类。哈哈。

    断路器

    由于是报表项目,一个报表查询最低几秒,就没用hystrix组件了。可以参考spring cloud gateway官方文档进行配置。

  • 相关阅读:
    flash player over linux
    chmod 命令
    A*算法
    adb找不到设备
    ubuntu14.04安装wine以及国际版QQ
    linux man
    X-window
    linux file system
    linux command
    directUI
  • 原文地址:https://www.cnblogs.com/bener/p/10638814.html
Copyright © 2020-2023  润新知