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

spring-cloud-gateway负载普通web项目的更多相关文章

  1. 创建网关项目(Spring Cloud Gateway)

    创建网关项目 加入网关后微服务的架构图 创建项目 POM文件 <properties> <java.version>1.8</java.version> <s ...

  2. API网关spring cloud gateway和负载均衡框架ribbon实战

    通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...

  3. 搭建一套ASP.NET Core+Nacos+Spring Cloud Gateway项目

    前言     伴随着随着微服务概念的不断盛行,与之对应的各种解决方案也层出不穷.这毕竟是一个信息大爆发的时代,各种编程语言大行其道,各有各的优势.但是有一点未曾改变,那就是他们服务的方式,工作的时候各 ...

  4. Spring Cloud Gateway Ribbon 自定义负载均衡

    在微服务开发中,使用Spring Cloud Gateway做为服务的网关,网关后面启动N个业务服务.但是有这样一个需求,同一个用户的操作,有时候需要保证顺序性,如果使用默认负载均衡策略,同一个用户的 ...

  5. Spring Cloud Gateway 不小心换了个 Web 容器就不能用了,我 TM 人傻了

    个人创作公约:本人声明创作的所有文章皆为自己原创,如果有参考任何文章的地方,会标注出来,如果有疏漏,欢迎大家批判.如果大家发现网上有抄袭本文章的,欢迎举报,并且积极向这个 github 仓库 提交 i ...

  6. Spring cloud gateway自定义filter以及负载均衡

    自定义全局filter package com.example.demo; import java.nio.charset.StandardCharsets; import org.apache.co ...

  7. Spring cloud gateway 如何在路由时进行负载均衡

    本文为博主原创,转载请注明出处: 1.spring cloud gateway 配置路由 在网关模块的配置文件中配置路由: spring: cloud: gateway: routes: - id: ...

  8. API网关性能比较:NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd API 网关出现的原因

    API网关性能比较:NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd http://www.infoq.com/cn/articles/compa ...

  9. 微服务网关实战——Spring Cloud Gateway

    导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...

随机推荐

  1. C# - 学习总目录

    C# - 基础 C# - 操作符 C# - 值类型和引用类型 C# - 表达式与语句 C# - 数组 C# - 引用类型 C# - 常用类 C# - 常用接口 C# - LINQ 语言集成查询 C# ...

  2. mac 删除文件夹里所有的.svn文件

    先用命令行,进入你要删除的文件夹中(./ 为这个文件夹的当前路径,也可以填写绝对路径) 命令行下输入: sudo find ./ -name ".svn" -exec rm -r ...

  3. dubbo注册中心

    官方推荐的是zookeeper注册中心. 1.Multicast 注册中心 Multicast 注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现. 提供方启动时广播自己的地址消费方启动 ...

  4. 适用于typecho0.9的评论表情插件

    依旧是寻找插件,实在是太累人,很多插件现在更新后不支持typecho0.9了,今天想给评论框加一个表情拓展,发现新版本的插件完全不兼容typecho0.9,无奈用回旧版本····· 实际上,旧版本的插 ...

  5. JUC--闭锁 CountDownLatch

    CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,允许一个或者多个线程一直等待. 闭锁可以延迟线程的进度直到其到达终止状态,可以确保某些活动知道其他活动都完成才继续 ...

  6. 【转载 | 笔记】IIS无法删除应该程序池 因为它包含X个应用程序

    IIS无法删除应该程序池 因为它包含X个应用程序 今天代码主分支在vs2015创建了虚拟目录http://localhost/webapp指向的物理路径是E:\webapp 之后新开了一个分支把代码放 ...

  7. 高可用Redis(九):Redis Sentinel

    1.主从复制高可用的问题 主从复制高可用的作用 1.为master提供备份,当master宕机时,slave有完整的备份数据 2.对master实现分流,实现读写分离 但是主从架构有一个问题 1.如果 ...

  8. mysql 字符串去掉指定字符

    如:在每一列meeting_persons的现有内容之上,去掉15112319字符串 ','')

  9. shell脚本之不同系统上ftp交互使用

    场景:当公司将有文件要自动将ubuntu系统的文件要上传到windows上面,或者windows上的文件要下载到ubuntu上面,尤其是像什么日志啊,编译结果啊,测试结果啊等等,做个备份或者做分析处理 ...

  10. css设置多列等高布局

    初始时,多个列内容大小不同,高度不同.现在需要设置不同的背景来显示,而且各个列的高度需要保持一致.那么这就需要利用到多列等高布局. 最终需要的效果: 1. 真实等高布局 flex 技术点:弹性盒子布局 ...