spring-cloud-gateway負載普通web項目

来源:https://www.cnblogs.com/bener/archive/2019/04/01/10638814.html
-Advertisement-
Play Games

spring cloud gateway負載普通web項目 對於普通的web項目,也是可以通過 進行負載的,只是無法通過服務發現。 背景 不知道各位道友有沒有使用過 "帆軟" ,帆軟是國內一款報表工具,這裡不做過多介紹。 它是通過war包部署到 ,預設是單台服務。如果想做集群,需要配置 ,帆軟會將當 ...


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官方文檔進行配置。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 死磕 java集合之LinkedHashMap源碼分析 你瞭解它的存儲結構嗎? 你知道它為什麼可以用來實現LRU緩存嗎? 它真的可以直接拿來實現LRU緩存嗎? ...
  • 通常GC採用有向圖的方式記錄和管理堆區中的所有對象 JVM將堆記憶體劃分為 Eden、Survivor 和 Tenured/Old 空間。 1. 年輕代 所有新生成的對象首先都是放在Eden區。 年輕代的目標就是儘可能快速的收集掉那些生命周期短的對象,對應的是Minor GC,每次 Minor GC ...
  • 用鏈表實現棧一開始在表頭插入,就要一直在表頭插入一開始在表尾插入,就要一直在表頭插尾表頭當棧底 也可以把表尾當棧底 實現的測試代碼筆記如下: 附: 推箱子實現,代碼筆記如下所示: 最後實現效果如下所示; 2019-04-01 21:33:15 ...
  • ●使用PHP+MySQL實現修改密碼 頁面: index.php 登陸頁面,輸入預設密碼登陸系統 check.php 核查頁面,通過查詢資料庫檢測密碼是否正確 ——> 正確,則進入系統 或 錯誤,提示“密碼錯誤”,返回登錄頁面 system.php 系統頁面,內含“修改密碼”鏈接 change.ph ...
  • Leetcode(2) 記:這幾天內心十分焦慮,研究生的日子一天天過去,感覺毫無收穫,每天刷個題來壓壓驚. 2.Add Two Number · 錯誤示例 · 思路 寫兩個函數將一個數從List轉化為int, 相加後再從int轉化為List返回. · 偽代碼: 1.構造兩個函數分別代表: 一個數字從 ...
  • ssm客戶管理系統 註意:本文是在我的上一篇文章 https://www.cnblogs.com/peter-hao/p/ssm.html的基礎上開發 1 需求 1.1 添加客戶 客戶填寫信息,提交,將信息保存到資料庫中。 1.2 刪除客戶 在每條查詢出來的客戶信息設置刪除操作,點擊即可刪除。更新數 ...
  • /** * 給定一個字元串,統計每個字元出現的次數。 如:abdaewrwqask435a1aasd */public class ReplaceString { static int length; public static void countString(String s) { while ...
  • 見過的最詳細的ArrayList的源碼分析了,分析得很透徹。比如,c.toArray()一定返回Object[]類型嗎?elementData聲明為transient,那它到底是怎麼序列化的呢?遠遠不止這些…… ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...