当前位置: 移动技术网 > IT编程>开发语言>Java > spring-cloud-gateway负载普通web项目

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

2019年04月02日  | 移动技术网IT编程  | 我要评论

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官方文档进行配置。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网