主頁 > 後端開發 > Dubbo負載均衡策略之 一致性哈希

Dubbo負載均衡策略之 一致性哈希

2023-06-15 08:09:44 後端開發

本文主要講解了一致性哈希演算法的原理以及其存在的資料傾斜的問題,然后引出解決資料傾斜問題的方法,最后分析一致性哈希演算法在Dubbo中的使用,通過這篇文章,可以了解到一致性哈希演算法的原理以及這種演算法存在的問題和解決方案,

一、負載均衡

在這里參考dubbo官網的一段話——

LoadBalance 中文意思為負載均衡,它的職責是將網路請求,或者其他形式的負載“均攤”到不同的機器上,避免集群中部分服務器壓力過大,而另一些服務器比較空閑的情況,通過負載均衡,可以讓每臺服務器獲取到適合自己處理能力的負載,在為高負載服務器分流的同時,還可以避免資源浪費,一舉兩得,負載均衡可分為軟體負載均衡和硬體負載均衡,在我們日常開發中,一般很難接觸到硬體負載均衡,但軟體負載均衡還是可以接觸到的,比如 Nginx,在 Dubbo 中,也有負載均衡的概念和相應的實作,Dubbo 需要對服務消費者的呼叫請求進行分配,避免少數服務提供者負載過大,服務提供者負載過大,會導致部分請求超時,因此將負載均衡到每個服務提供者上,是非常必要的,Dubbo 提供了4種負載均衡實作,分別是基于權重隨機演算法的 RandomLoadBalance、基于最少活躍呼叫數演算法的 LeastActiveLoadBalance、基于hash 一致性的 ConsistentHashLoadBalance,以及基于加權輪詢演算法的 RoundRobinLoadBalance,這幾個負載均衡演算法代碼不是很長,但是想看懂也不是很容易,需要對這幾個演算法的原理有一定了解才行,

二、哈希演算法

圖1 無哈希演算法請求

如上所示,假設0,1,2號服務器都存盤的有用戶資訊,那么當我們需要獲取某用戶資訊時,因為我們不知道該用戶資訊存放在哪一臺服務器中,所以需要分別查詢0,1,2號服務器,這樣獲取資料的效率是極低的,

對于這樣的場景,我們可以引入哈希演算法,

圖2 引入哈希演算法后的請求

還是上面的場景,但前提是每一臺服務器存放用戶資訊時是根據某一種哈希演算法存放的,所以取用戶資訊的時候,也按照同樣的哈希演算法取即可,

假設我們要查詢用戶號為100的用戶資訊,經過某個哈希演算法,比如這里的userId mod n,即100 mod 3結果為1,所以用戶號100的這個請求最侄訓被1號服務器接收并處理,

這樣就解決了無效查詢的問題,

但是這樣的方案會帶來什么問題呢?

擴容或者縮容時,會導致大量的資料遷移,最少也會影響50%的資料,

圖3 增加一臺服務器

為了說明問題,加入一臺服務器3,服務器的數量n就從3變成了4,還是查詢用戶號為100的用戶資訊時,100 mod 4結果為0,這時,請求就被0號服務器接收了,

  • 當服務器數量為3時,用戶號為100的請求會被1號服務器處理,
  • 當服務器數量為4時,用戶號為100的請求會被0號服務器處理,

所以,當服務器數量增加或者減少時,一定會涉及到大量資料遷移的問題,

對于上述哈希演算法其優點是簡單易用,大多數分庫分表規則就采取的這種方式,一般是提前根據資料量,預先估算好磁區數,

其缺點是由于擴容或收縮節點導致節點數量變化時,節點的映射關系需要重新計算,會導致資料進行遷移,所以擴容時通常采用翻倍擴容,避免資料映射全部被打亂,導致全量遷移的情況,這樣只會發生50%的資料遷移,

三、一致性哈希演算法

一致性 hash 演算法由麻省理工學院的 Karger 及其合作者于1997年提出的,演算法提出之初是用于大規模快取系統的負載均衡,它的作業程序是這樣的,首先根據 ip 或者其他的資訊為快取節點生成一個 hash,并將這個 hash 投射到 [0, 232 - 1] 的圓環上,當有查詢或寫入請求時,則為快取項的 key 生成一個 hash 值,然后查找第一個大于或等于該 hash 值的快取節點,并到這個節點中查詢或寫入快取項,如果當前節點掛了,則在下一次查詢或寫入快取時,為快取項查找另一個大于其 hash 值的快取節點即可,大致效果如下圖所示,每個快取節點在圓環上占據一個位置,如果快取項的 key 的 hash 值小于快取節點 hash 值,則到該快取節點中存盤或讀取快取項,比如下面綠色點對應的快取項將會被存盤到 cache-2 節點中,由于 cache-3 掛了,原本應該存到該節點中的快取項最侄訓存盤到cache-4節點中,

圖4 一致性哈希演算法

在一致性哈希演算法中,不管是增加節點,還是宕機節點,受影響的區間僅僅是增加或者宕機服務器在哈希環空間中,逆時針方向遇到的第一臺服務器之間的區間,其它區間不會受到影響,

但是一致性哈希也是存在問題的:

圖5 資料傾斜

當節點很少的時候可能會出現這樣的分布情況,A服務會承擔大部分請求,這種情況就叫做資料傾斜,

那如何解決資料傾斜的問題呢?

加入虛擬節點,

首先一個服務器根據需要可以有多個虛擬節點,假設一臺服務器有n個虛擬節點,那么哈希計算時,可以使用IP+埠+編號的形式進行哈希值計算,其中的編號就是0到n的數字,由于IP+埠是一樣的,所以這n個節點都是指向的同一臺機器,

圖6 引入虛擬節點

在沒有加入虛擬節點之前,A服務器承擔了絕大多數的請求,但是假設每個服務器有一個虛擬節點(A-1,B-1,C-1),經過哈希計算后落在了如上圖所示的位置,那么A服務器的承擔的請求就在一定程度上(圖中標注了五角星的部分)分攤給了B-1、C-1虛擬節點,實際上就是分攤給了B、C服務器,

一致性哈希演算法中,加入虛擬節點,可以解決資料傾斜問題,

四、一致性哈希在DUBBO 中的應用

圖7 Dubbo中一致性哈希環

這里相同顏色的節點均屬于同一個服務提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160,這樣做的目的是通過引入虛擬節點,讓 Invoker 在圓環上分散開來,避免資料傾斜問題,所謂資料傾斜是指,由于節點不夠分散,導致大量請求落到了同一個節點上,而其他節點只會接收到了少量請求的情況,比如:

圖8 資料傾斜問題

如上,由于 Invoker-1 和 Invoker-2 在圓環上分布不均,導致系統中75%的請求都會落到 Invoker-1 上,只有 25% 的請求會落到 Invoker-2 上,解決這個問題辦法是引入虛擬節點,通過虛擬節點均衡各個節點的請求量,

到這里背景知識就普及完了,接下來開始分析原始碼,我們先從 ConsistentHashLoadBalance 的 doSelect 方法開始看起,如下:

public class ConsistentHashLoadBalance extends AbstractLoadBalance {


    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = 
        new ConcurrentHashMap<String, ConsistentHashSelector<?>>();


    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;


        // 獲取 invokers 原始的 hashcode
        int identityHashCode = System.identityHashCode(invokers);
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        // 如果 invokers 是一個新的 List 物件,意味著服務提供者數量發生了變化,可能新增也可能減少了,
        // 此時 selector.identityHashCode != identityHashCode 條件成立
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 創建新的 ConsistentHashSelector
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }


        // 呼叫 ConsistentHashSelector 的 select 方法選擇 Invoker
        return selector.select(invocation);
    }
    
    private static final class ConsistentHashSelector<T> {...}
}

如上,doSelect 方法主要做了一些前置作業,比如檢測 invokers 串列是不是變動過,以及創建 ConsistentHashSelector,這些作業做完后,接下來開始呼叫 ConsistentHashSelector 的 select 方法執行負載均衡邏輯,在分析 select 方法之前,我們先來看一下一致性 hash 選擇器 ConsistentHashSelector 的初始化程序,如下:

private static final class ConsistentHashSelector<T> {


    // 使用 TreeMap 存盤 Invoker 虛擬節點
    private final TreeMap<Long, Invoker<T>> virtualInvokers;


    private final int replicaNumber;


    private final int identityHashCode;


    private final int[] argumentIndex;


    ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
        this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
        this.identityHashCode = identityHashCode;
        URL url = invokers.get(0).getUrl();
        // 獲取虛擬節點數,默認為160
        this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
        // 獲取參與 hash 計算的引數下標值,默認對第一個引數進行 hash 運算
        String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
        argumentIndex = new int[index.length];
        for (int i = 0; i < index.length; i++) {
            argumentIndex[i] = Integer.parseInt(index[i]);
        }
        for (Invoker<T> invoker : invokers) {
            String address = invoker.getUrl().getAddress();
            for (int i = 0; i < replicaNumber / 4; i++) {
                // 對 address + i 進行 md5 運算,得到一個長度為16的位元組陣列
                byte[] digest = md5(address + i);
                // 對 digest 部分位元組進行4次 hash 運算,得到四個不同的 long 型正整數
                for (int h = 0; h < 4; h++) {
                    // h = 0 時,取 digest 中下標為 0 ~ 3 的4個位元組進行位運算
                    // h = 1 時,取 digest 中下標為 4 ~ 7 的4個位元組進行位運算
                    // h = 2, h = 3 時程序同上
                    long m = hash(digest, h);
                    // 將 hash 到 invoker 的映射關系存盤到 virtualInvokers 中,
                    // virtualInvokers 需要提供高效的查詢操作,因此選用 TreeMap 作為存盤結構
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }
}

ConsistentHashSelector 的構造方法執行了一系列的初始化邏輯,比如從配置中獲取虛擬節點數以及參與 hash 計算的引數下標,默認情況下只使用第一個引數進行 hash,需要特別說明的是,ConsistentHashLoadBalance 的負載均衡邏輯只受引數值影響,具有相同引數值的請求將會被分配給同一個服務提供者,ConsistentHashLoadBalance 不 關系權重,因此使用時需要注意一下,

在獲取虛擬節點數和引數下標配置后,接下來要做的事情是計算虛擬節點 hash 值,并將虛擬節點存盤到 TreeMap 中,到此,ConsistentHashSelector 初始化作業就完成了,接下來,我們來看看 select 方法的邏輯,

public Invoker<T> select(Invocation invocation) {
    // 將引數轉為 key
    String key = toKey(invocation.getArguments());
    // 對引數 key 進行 md5 運算
    byte[] digest = md5(key);
    // 取 digest 陣列的前四個位元組進行 hash 運算,再將 hash 值傳給 selectForKey 方法,
    // 尋找合適的 Invoker
    return selectForKey(hash(digest, 0));
}


private Invoker<T> selectForKey(long hash) {
    // 到 TreeMap 中查找第一個節點值大于或等于當前 hash 的 Invoker
    Map.Entry<Long, Invoker<T>> entry = virtualInvokers.tailMap(hash, true).firstEntry();
    // 如果 hash 大于 Invoker 在圓環上最大的位置,此時 entry = null,
    // 需要將 TreeMap 的頭節點賦值給 entry
    if (entry == null) {
        entry = virtualInvokers.firstEntry();
    }


    // 回傳 Invoker
    return entry.getValue();
}

如上,選擇的程序相對比較簡單了,首先是對引數進行 md5 以及 hash 運算,得到一個 hash 值,然后再拿這個值到 TreeMap 中查找目標 Invoker 即可,

到此關于 ConsistentHashLoadBalance 就分析完了,

在閱讀ConsistentHashLoadBalance 原始碼之前,建議讀者先補充背景知識,不然看懂代碼邏輯會有很大難度,

五、應用場景

  • DNS負載均衡最早的負載均衡技術是通過DNS來實作的,在DNS中為多個地址配置同一個名字,因而查詢這個名字的客戶機將得到其中一個地址,從而使得不同的客戶訪問不同的服務器,達到負載均衡的目的,DNS負載均衡是一種簡單而有效的方法,但是它不能區分服務器的差異,也不能反映服務器的當前運行狀態,
  • 代理服務器負載均衡使用代理服務器,可以將請求轉發給內部的服務器,使用這種加速模式顯然可以提升靜態網頁的訪問速度,然而,也可以考慮這樣一種技術,使用代理服務器將請求均勻轉發給多臺服務器,從而達到負載均衡的目的,
  • 地址轉換網關負載均衡支持負載均衡的地址轉換網關,可以將一個外部IP地址映射為多個內部IP地址,對每次TCP連接請求動態使用其中一個內部地址,達到負載均衡的目的,
  • 協議內部支持負載均衡除了這三種負載均衡方式之外,有的協議內部支持與負載均衡相關的功能,例如HTTP協議中的重定向能力等,HTTP運行于TCP連接的最高層,
  • NAT負載均衡NAT(Network Address Translation網路地址轉換)簡單地說就是將一個IP地址轉換為另一個IP地址,一般用于未經注冊的內部地址與合法的、已獲注冊的Internet IP地址間進行轉換,適用于解決Internet IP地址緊張、不想讓網路外部知道內部網路結構等的場合下,
  • 反向代理負載均衡普通代理方式是代理內部網路用戶訪問internet上服務器的連接請求,客戶端必須指定代理服務器,并將本來要直接發送到internet上服務器的連接請求發送給代理服務器處理,反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的連接請求,然后將請求轉發給內部網路上的服務器,并將從服務器上得到的結果回傳給internet上請求連接的客戶端,此時代理服務器對外就表現為一個服務器,反向代理負載均衡技術是把將來自internet上的連接請求以反向代理的方式動態地轉發給內部網路上的多臺服務器進行處理,從而達到負載均衡的目的,
  • 混合型負載均衡在有些大型網路,由于多個服務器群內硬體設備、各自的規模、提供的服務等的差異,可以考慮給每個服務器群采用最合適的負載均衡方式,然后又在這多個服務器群間再一次負載均衡或群集起來以一個整體向外界提供服務(即把這多個服務器群當做一個新的服務器群),從而達到最佳的性能,將這種方式稱之為混合型負載均衡,此種方式有時也用于單臺均衡設備的性能不能滿足大量連接請求的情況下,

作者:京東物流 喬杰

來源:京東云開發者社區

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555182.html

標籤:其他

上一篇:Go 語言之 SQLX 高級操作 sqlx.In

下一篇:返回列表

標籤雲
其他(160986) Python(38230) JavaScript(25495) Java(18240) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7251) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4593) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1966) Web開發(1951) C++(1940) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Dubbo負載均衡策略之 一致性哈希

    本文主要講解了一致性哈希演算法的原理以及其存在的資料傾斜的問題,然后引出解決資料傾斜問題的方法,最后分析一致性哈希演算法在Dubbo中的使用。通過這篇文章,可以了解到一致性哈希演算法的原理以及這種演算法存在的問題和解決方案。 ......

    uj5u.com 2023-06-15 08:09:44 more
  • Go 語言之 SQLX 高級操作 sqlx.In

    # Go 語言之 SQLX 高級操作 sqlx.In ## sqlx.In 介紹 `sqlx` is a package for Go which provides a set of extensions on top of the excellent built-in `database/sql` ......

    uj5u.com 2023-06-15 08:09:40 more
  • Scala簡介和安裝

    # 簡介 Scala是一種多范式的編程語言(多范式:多種編程方法的意思。有面向程序、面向物件、泛型、函式式四種程式設計方法),其設計的初衷是要集成面向物件編程和函式式編程的各種特性。Scala運行于Java平臺(Java虛擬機),并兼容現有的Java程式 > 官網:https://www.scala ......

    uj5u.com 2023-06-15 08:09:16 more
  • SpringBoot中Redis的基礎使用

    基礎使用 首先引入依賴 <!-- redis依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </depende ......

    uj5u.com 2023-06-15 08:08:56 more
  • Java多執行緒開發系列之六:無限分解流----Fork/Join框架

    Fork譯為拆分,Join譯為合并Fork/Join框架的思路是把一個非常巨大的任務,拆分成若然的小任務,再由小任務繼續拆解。直至達到一個相對合理的任務粒度。然后執行獲得結果,然后將這些小任務的結果匯總,生成大任務的結果,直至匯總成最初巨大任務的結果。如下圖: 紅色箭頭代表拆分子任務。綠色箭頭代表返 ......

    uj5u.com 2023-06-15 08:08:44 more
  • ArcGis 欄位值統計腳本更新

    本文實作的功能主要是根據已知欄位值,給指定的名稱欄位賦上對應的值;整個處理的邏輯很簡單,就是通過判斷陳述句,判斷其他欄位值,然后給指定的欄位填上對應的值即可; 欄位統計界面如下圖所示: #腳本代碼如下 def updatefiles(ssssss): if(ssssss == 1): total="高 ......

    uj5u.com 2023-06-15 08:03:29 more
  • Aurelia教程_編程入門自學教程_菜鳥教程-免費教程分享

    ## 教程簡介 Aurelia 是一個用于 Web 和移動應用程式開發的現代開源 UI 框架。它允許您撰寫干凈、模塊化的 JavaScript。該框架遵循簡單的約定,并專注于 Web 標準。 [Aurelia入門教程](https://www.itbaoku.cn/tutorial/aurelia- ......

    uj5u.com 2023-06-15 07:58:01 more
  • [ARM匯編]計算機原理與數制基礎—1.1.3 二進制補碼

    在計算機中,為了表示有符號整數(即正數和負數),通常采用二進制補碼表示法。二進制補碼不僅可以表示負數,還能簡化計算機的加法和減法運算。接下來,我們將介紹二進制補碼的概念及其計算方法。 #### 原碼、反碼和補碼 在討論補碼之前,我們先了解一下原碼和反碼的概念。 1. **原碼**:直接將一個有符號整 ......

    uj5u.com 2023-06-15 07:57:54 more
  • 【技識訓累】Python中的NumPy庫【二】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-15 07:57:48 more
  • 重寫RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo Mybati

    轉載于:https://blog.csdn.net/weixin_40461281/article/details/122837923 RuoYi-Cloud-Plus 微服務通用權限管理系統 重寫 RuoYi-Cloud 全方位升級(不兼容原框架) 系統演示: 傳送門 分布式集群版本(功能一致) ......

    uj5u.com 2023-06-15 07:50:58 more