對一致性Hash演算法,Java代碼實現的深入研究

来源:http://www.cnblogs.com/xrq730/archive/2016/02/13/5186728.html
-Advertisement-
Play Games

一致性Hash演算法 關於一致性Hash演算法,在我之前的博文中已經有多次提到了,MemCache超詳細解讀一文中"一致性Hash演算法"部分,對於為什麼要使用一致性Hash演算法、一致性Hash演算法的演算法原理做了詳細的解讀。 演算法的具體原理這裡再次貼上: 先構造一個長度為232的整數環(這個環被稱為一致性


一致性Hash演算法

關於一致性Hash演算法,在我之前的博文中已經有多次提到了,MemCache超詳細解讀一文中"一致性Hash演算法"部分,對於為什麼要使用一致性Hash演算法、一致性Hash演算法的演算法原理做了詳細的解讀。

演算法的具體原理這裡再次貼上:

先構造一個長度為232的整數環(這個環被稱為一致性Hash環),根據節點名稱的Hash值(其分佈為[0, 232-1])將伺服器節點放置在這個Hash環上,然後根據數據的Key值計算得到其Hash值(其分佈也為[0, 232-1]),接著在Hash環上順時針查找距離這個Key值的Hash值最近的伺服器節點,完成Key到伺服器的映射查找。

這種演算法解決了普通餘數Hash演算法伸縮性差的問題,可以保證在上線、下線伺服器的情況下儘量有多的請求命中原來路由到的伺服器。

當然,萬事不可能十全十美,一致性Hash演算法比普通的餘數Hash演算法更具有伸縮性,但是同時其演算法實現也更為複雜,本文就來研究一下,如何利用Java代碼實現一致性Hash演算法。在開始之前,先對一致性Hash演算法中的幾個核心問題進行一些探究。

 

數據結構的選取

一致性Hash演算法最先要考慮的一個問題是:構造出一個長度為232的整數環,根據節點名稱的Hash值將伺服器節點放置在這個Hash環上。

那麼,整數環應該使用何種數據結構,才能使得運行時的時間複雜度最低?首先說明一點,關於時間複雜度,常見的時間複雜度與時間效率的關係有如下的經驗規則:

O(1) < O(log2N) < O(n) < O(N * log2N) < O(N2) < O(N3) < 2N < 3N < N!

一般來說,前四個效率比較高,中間兩個差強人意,後三個比較差(只要N比較大,這個演算法就動不了了)。OK,繼續前面的話題,應該如何選取數據結構,我認為有以下幾種可行的解決方案。

1、解決方案一:排序+List

我想到的第一種思路是:算出所有待加入數據結構的節點名稱的Hash值放入一個數組中,然後使用某種排序演算法將其從小到大進行排序,最後將排序後的數據放入List中,採用List而不是數組是為了結點的擴展考慮。

之後,待路由的結點,只需要在List中找到第一個Hash值比它大的伺服器節點就可以了,比如伺服器節點的Hash值是[0,2,4,6,8,10],帶路由的結點是7,只需要找到第一個比7大的整數,也就是8,就是我們最終需要路由過去的伺服器節點。

如果暫時不考慮前面的排序,那麼這種解決方案的時間複雜度:

(1)最好的情況是第一次就找到,時間複雜度為O(1)

(2)最壞的情況是最後一次才找到,時間複雜度為O(N)

平均下來時間複雜度為O(0.5N+0.5),忽略首項繫數和常數,時間複雜度為O(N)。

但是如果考慮到之前的排序,我在網上找了張圖,提供了各種排序演算法的時間複雜度:

看得出來,排序演算法要麼穩定但是時間複雜度高、要麼時間複雜度低但不穩定,看起來最好的歸併排序法的時間複雜度仍然有O(N * logN),稍微耗費性能了一些。

2、解決方案二:遍歷+List

既然排序操作比較耗性能,那麼能不能不排序?可以的,所以進一步的,有了第二種解決方案。

解決方案使用List不變,不過可以採用遍歷的方式:

(1)伺服器節點不排序,其Hash值全部直接放入一個List中

(2)帶路由的節點,算出其Hash值,由於指明瞭"順時針",因此遍歷List,比待路由的節點Hash值大的算出差值並記錄,比待路由節點Hash值小的忽略

(3)算出所有的差值之後,最小的那個,就是最終需要路由過去的節點

在這個演算法中,看一下時間複雜度:

1、最好情況是只有一個伺服器節點的Hash值大於帶路由結點的Hash值,其時間複雜度是O(N)+O(1)=O(N+1),忽略常數項,即O(N)

2、最壞情況是所有伺服器節點的Hash值都大於帶路由結點的Hash值,其時間複雜度是O(N)+O(N)=O(2N),忽略首項繫數,即O(N)

所以,總的時間複雜度就是O(N)。其實演算法還能更改進一些:給一個位置變數X,如果新的差值比原差值小,X替換為新的位置,否則X不變。這樣遍歷就減少了一輪,不過經過改進後的演算法時間複雜度仍為O(N)。

總而言之,這個解決方案和解決方案一相比,總體來看,似乎更好了一些。

3、解決方案三:二叉查找樹

拋開List這種數據結構,另一種數據結構則是使用二叉查找樹。對於樹不是很清楚的朋友可以簡單看一下這篇文章樹形結構

當然我們不能簡單地使用二叉查找樹,因為可能出現不平衡的情況。平衡二叉查找樹有AVL樹、紅黑樹等,這裡使用紅黑樹,選用紅黑樹的原因有兩點:

1、紅黑樹主要的作用是用於存儲有序的數據,這其實和第一種解決方案的思路又不謀而合了,但是它的效率非常高

2、JDK裡面提供了紅黑樹的代碼實現TreeMap和TreeSet

另外,以TreeMap為例,TreeMap本身提供了一個tailMap(K fromKey)方法,支持從紅黑樹中查找比fromKey大的值的集合,但並不需要遍歷整個數據結構。

使用紅黑樹,可以使得查找的時間複雜度降低為O(logN),比上面兩種解決方案,效率大大提升。

為了驗證這個說法,我做了一次測試,從大量數據中查找第一個大於其中間值的那個數據,比如10000數據就找第一個大於5000的數據(模擬平均的情況)。看一下O(N)時間複雜度和O(logN)時間複雜度運行效率的對比:

  50000 100000 500000 1000000 4000000
ArrayList 1ms 1ms 4ms 4ms 5ms
LinkedList 4ms 7ms 11ms 13ms 17ms
TreeMap 0ms 0ms 0ms 0ms 0ms

因為再大就記憶體溢出了,所以只測試到4000000數據。可以看到,數據查找的效率,TreeMap是完勝的,其實再增大數據測試也是一樣的,紅黑樹的數據結構決定了任何一個大於N的最小數據,它都只需要幾次至幾十次查找就可以查到。

當然,明確一點,有利必有弊,根據我另外一次測試得到的結論是,為了維護紅黑樹,數據插入效率TreeMap在三種數據結構裡面是最差的,且插入要慢上5~10倍

 

Hash值重新計算

伺服器節點我們肯定用字元串來表示,比如"192.168.1.1"、"192.168.1.2",根據字元串得到其Hash值,那麼另外一個重要的問題就是Hash值要重新計算,這個問題是我在測試String的hashCode()方法的時候發現的,不妨來看一下為什麼要重新計算Hash值:

/**
 * String的hashCode()方法運算結果查看
 * @author 五月的倉頡 http://www.cnblogs.com/xrq730/
 *
 */
public class StringHashCodeTest
{
    public static void main(String[] args)
    {
        System.out.println("192.168.0.0:111的哈希值:" + "192.168.0.0:1111".hashCode());
        System.out.println("192.168.0.1:111的哈希值:" + "192.168.0.1:1111".hashCode());
        System.out.println("192.168.0.2:111的哈希值:" + "192.168.0.2:1111".hashCode());
        System.out.println("192.168.0.3:111的哈希值:" + "192.168.0.3:1111".hashCode());
        System.out.println("192.168.0.4:111的哈希值:" + "192.168.0.4:1111".hashCode());
    }
}

我們在做集群的時候,集群點的IP以這種連續的形式存在是很正常的。看一下運行結果為:

192.168.0.0:111的哈希值:1845870087
192.168.0.1:111的哈希值:1874499238
192.168.0.2:111的哈希值:1903128389
192.168.0.3:111的哈希值:1931757540
192.168.0.4:111的哈希值:1960386691

這個就問題大了,[0,232-1]的區間之中,5個HashCode值卻只分佈在這麼小小的一個區間,什麼概念?[0,232-1]中有4294967296個數字,而我們的區間只有122516605,從概率學上講這將導致97%待路由的伺服器都被路由到"192.168.0.1"這個集群點上,簡直是糟糕透了!

另外還有一個不好的地方:規定的區間是非負數,String的hashCode()方法卻會產生負數(不信用"192.168.1.0:1111"試試看就知道了)。不過這個問題好解決,取絕對值就是一種解決的辦法。

綜上,String重寫的hashCode()方法在一致性Hash演算法中沒有任何實用價值,得找個演算法重新計算HashCode。這種重新計算Hash值的演算法有很多,比如CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,其中KETAMA_HASH是預設的MemCache推薦的一致性Hash演算法,用別的Hash演算法也可以,比如FNV1_32_HASH演算法的計算效率就會高一些。

 

一致性Hash演算法實現版本1:不帶虛擬節點

使用一致性Hash演算法,儘管增強了系統的伸縮性,但是也有可能導致負載分佈不均勻,解決辦法就是使用虛擬節點代替真實節點,第一個代碼版本,先來個簡單的,不帶虛擬節點。

下麵來看一下不帶虛擬節點的一致性Hash演算法的Java代碼實現:

 1 /**
 2  * 不帶虛擬節點的一致性Hash演算法
 3  * @author 五月的倉頡http://www.cnblogs.com/xrq730/
 4  *
 5  */
 6 public class ConsistentHashingWithoutVirtualNode
 7 {
 8     /**
 9      * 待添加入Hash環的伺服器列表
10      */
11     private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
12             "192.168.0.3:111", "192.168.0.4:111"};
13     
14     /**
15      * key表示伺服器的hash值,value表示伺服器的名稱
16      */
17     private static SortedMap<Integer, String> sortedMap = 
18             new TreeMap<Integer, String>();
19     
20     /**
21      * 程式初始化,將所有的伺服器放入sortedMap中
22      */
23     static
24     {
25         for (int i = 0; i < servers.length; i++)
26         {
27             int hash = getHash(servers[i]);
28             System.out.println("[" + servers[i] + "]加入集合中, 其Hash值為" + hash);
29             sortedMap.put(hash, servers[i]);
30         }
31         System.out.println();
32     }
33     
34     /**
35      * 使用FNV1_32_HASH演算法計算伺服器的Hash值,這裡不使用重寫hashCode的方法,最終效果沒區別 
36      */
37     private static int getHash(String str)
38     {
39         final int p = 16777619;
40         int hash = (int)2166136261L;
41         for (int i = 0; i < str.length(); i++)
42             hash = (hash ^ str.charAt(i)) * p;
43         hash += hash << 13;
44         hash ^= hash >> 7;
45         hash += hash << 3;
46         hash ^= hash >> 17;
47         hash += hash << 5;
48         
49         // 如果算出來的值為負數則取其絕對值
50         if (hash < 0)
51             hash = Math.abs(hash);
52         return hash;
53     }
54     
55     /**
56      * 得到應當路由到的結點
57      */
58     private static String getServer(String node)
59     {
60         // 得到帶路由的結點的Hash值
61         int hash = getHash(node);
62         // 得到大於該Hash值的所有Map
63         SortedMap<Integer, String> subMap = 
64                 sortedMap.tailMap(hash);
65         // 第一個Key就是順時針過去離node最近的那個結點
66         Integer i = subMap.firstKey();
67         // 返回對應的伺服器名稱
68         return subMap.get(i);
69     }
70     
71     public static void main(String[] args)
72     {
73         String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
74         for (int i = 0; i < nodes.length; i++)
75             System.out.println("[" + nodes[i] + "]的hash值為" + 
76                     getHash(nodes[i]) + ", 被路由到結點[" + getServer(nodes[i]) + "]");
77     }
78 }

可以運行一下看一下結果:

[192.168.0.0:111]加入集合中, 其Hash值為575774686
[192.168.0.1:111]加入集合中, 其Hash值為8518713
[192.168.0.2:111]加入集合中, 其Hash值為1361847097
[192.168.0.3:111]加入集合中, 其Hash值為1171828661
[192.168.0.4:111]加入集合中, 其Hash值為1764547046

[127.0.0.1:1111]的hash值為380278925, 被路由到結點[192.168.0.0:111]
[221.226.0.1:2222]的hash值為1493545632, 被路由到結點[192.168.0.4:111]
[10.211.0.1:3333]的hash值為1393836017, 被路由到結點[192.168.0.4:111]

看到經過FNV1_32_HASH演算法重新計算過後的Hash值,就比原來String的hashCode()方法好多了。從運行結果來看,也沒有問題,三個點路由到的都是順時針離他們Hash值最近的那台伺服器上。

 

使用虛擬節點來改善一致性Hash演算法

上面的一致性Hash演算法實現,可以在很大程度上解決很多分散式環境下不好的路由演算法導致系統伸縮性差的問題,但是會帶來另外一個問題:負載不均。

比如說有Hash環上有A、B、C三個伺服器節點,分別有100個請求會被路由到相應伺服器上。現在在A與B之間增加了一個節點D,這導致了原來會路由到B上的部分節點被路由到了D上,這樣A、C上被路由到的請求明顯多於B、D上的,原來三個伺服器節點上均衡的負載被打破了。某種程度上來說,這失去了負載均衡的意義,因為負載均衡的目的本身就是為了使得目標伺服器均分所有的請求

解決這個問題的辦法是引入虛擬節點,其工作原理是:將一個物理節點拆分為多個虛擬節點,並且同一個物理節點的虛擬節點儘量均勻分佈在Hash環上。採取這樣的方式,就可以有效地解決增加或減少節點時候的負載不均衡的問題。

至於一個物理節點應該拆分為多少虛擬節點,下麵可以先看一張圖:

橫軸表示需要為每台福利伺服器擴展的虛擬節點倍數,縱軸表示的是實際物理伺服器數。可以看出,物理伺服器很少,需要更大的虛擬節點;反之物理伺服器比較多,虛擬節點就可以少一些。比如有10台物理伺服器,那麼差不多需要為每台伺服器增加100~200個虛擬節點才可以達到真正的負載均衡。

 

一致性Hash演算法實現版本2:帶虛擬節點

在理解了使用虛擬節點來改善一致性Hash演算法的理論基礎之後,就可以嘗試開發代碼了。編程方面需要考慮的問題是:

1、一個真實結點如何對應成為多個虛擬節點?

2、虛擬節點找到後如何還原為真實結點?

這兩個問題其實有很多解決辦法,我這裡使用了一種簡單的辦法,給每個真實結點後面根據虛擬節點加上尾碼再取Hash值,比如"192.168.0.0:111"就把它變成"192.168.0.0:111&&VN0"到"192.168.0.0:111&&VN4",VN就是Virtual Node的縮寫,還原的時候只需要從頭截取字元串到"&&"的位置就可以了。

下麵來看一下帶虛擬節點的一致性Hash演算法的Java代碼實現:

 1 /**
 2  * 帶虛擬節點的一致性Hash演算法
 3  * @author 五月的倉頡 http://www.cnblogs.com/xrq730/
 4  */
 5 public class ConsistentHashingWithVirtualNode
 6 {
 7     /**
 8      * 待添加入Hash環的伺服器列表
 9      */
10     private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
11             "192.168.0.3:111", "192.168.0.4:111"};
12     
13     /**
14      * 真實結點列表,考慮到伺服器上線、下線的場景,即添加、刪除的場景會比較頻繁,這裡使用LinkedList會更好
15      */
16     private static List<String> realNodes = new LinkedList<String>();
17     
18     /**
19      * 虛擬節點,key表示虛擬節點的hash值,value表示虛擬節點的名稱
20      */
21     private static SortedMap<Integer, String> virtualNodes = 
22             new TreeMap<Integer, String>();
23     
24     /**
25      * 虛擬節點的數目,這裡寫死,為了演示需要,一個真實結點對應5個虛擬節點
26      */
27     private static final int VIRTUAL_NODES = 5;
28     
29     static
30     {
31         // 先把原始的伺服器添加到真實結點列表中
32         for (int i = 0; i < servers.length; i++)
33             realNodes.add(servers[i]);
34         
35         // 再添加虛擬節點,遍歷LinkedList使用foreach迴圈效率會比較高
36         for (String str : realNodes)
37         {
38             for (int i = 0; i < VIRTUAL_NODES; i++)
39             {
40                 String virtualNodeName = str + "&&VN" + String.valueOf(i);
41                 int hash = getHash(virtualNodeName);
42                 System.out.println("虛擬節點[" + virtualNodeName + "]被添加, hash值為" + hash);
43                 virtualNodes.put(hash, virtualNodeName);
44             }
45         }
46         System.out.println();
47     }
48     
49     /**
50      * 使用FNV1_32_HASH演算法計算伺服器的Hash值,這裡不使用重寫hashCode的方法,最終效果沒區別 
51      */
52     private static int getHash(String str)
53     {
54         final int p = 16777619;
55         int hash = (int)2166136261L;
56         for (int i = 0; i < str.length(); i++)
57             hash = (hash ^ str.charAt(i)) * p;
58         hash += hash << 13;
59         hash ^= hash >> 7;
60         hash += hash << 3;
61         hash ^= hash >> 17;
62         hash += hash << 5;
63         
64         // 如果算出來的值為負數則取其絕對值
65         if (hash < 0)
66             hash = Math.abs(hash);
67         return hash;
68     }
69     
70     /**
71      * 得到應當路由到的結點
72      */
73     private static String getServer(String node)
74     {
75         // 得到帶路由的結點的Hash值
76         int hash = getHash(node);
77         // 得到大於該Hash值的所有Map
78         SortedMap<Integer, String> subMap = 
79                 virtualNodes.tailMap(hash);
80         // 第一個Key就是順時針過去離node最近的那個結點
81         Integer i = subMap.firstKey();
82         // 返回對應的虛擬節點名稱,這裡字元串稍微截取一下
83         String virtualNode = subMap.get(i);
84         return virtualNode.substring(0, virtualNode.indexOf("&&"));
85     }
86     
87     public static void main(String[] args)
88     {
89         String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
90         for (int i = 0; i < nodes.length; i++)
91             System.out.println("[" + nodes[i] + "]的hash值為" + 
92                     getHash(nodes[i]) + ", 被路由到結點[" + getServer(nodes[i]) + "]");
93     }
94 }

關註一下運行結果:

虛擬節點[192.168.0.0:111&&VN0]被添加, hash值為1686427075
虛擬節點[192.168.0.0:111&&VN1]被添加, hash值為354859081
虛擬節點[192.168.0.0:111&&VN2]被添加, hash值為1306497370
虛擬節點[192.168.0.0:111&&VN3]被添加, hash值為817889914
虛擬節點[192.168.0.0:111&&VN4]被添加, hash值為396663629
虛擬節點[192.168.0.1:111&&VN0]被添加, hash值為1032739288
虛擬節點[192.168.0.1:111&&VN1]被添加, hash值為707592309
虛擬節點[192.168.0.1:111&&VN2]被添加, hash值為302114528
虛擬節點[192.168.0.1:111&&VN3]被添加, hash值為36526861
虛擬節點[192.168.0.1:111&&VN4]被添加, hash值為848442551
虛擬節點[192.168.0.2:111&&VN0]被添加, hash值為1452694222
虛擬節點[192.168.0.2:111&&VN1]被添加, hash值為2023612840
虛擬節點[192.168.0.2:111&&VN2]被添加, hash值為697907480
虛擬節點[192.168.0.2:111&&VN3]被添加, hash值為790847074
虛擬節點[192.168.0.2:111&&VN4]被添加, hash值為2010506136
虛擬節點[192.168.0.3:111&&VN0]被添加, hash值為891084251
虛擬節點[192.168.0.3:111&&VN1]被添加, hash值為1725031739
虛擬節點[192.168.0.3:111&&VN2]被添加, hash值為1127720370
虛擬節點[192.168.0.3:111&&VN3]被添加, hash值為676720500
虛擬節點[192.168.0.3:111&&VN4]被添加, hash值為2050578780
虛擬節點[192.168.0.4:111&&VN0]被添加, hash值為586921010
虛擬節點[192.168.0.4:111&&VN1]被添加, hash值為184078390
虛擬節點[192.168.0.4:111&&VN2]被添加, hash值為1331645117
虛擬節點[192.168.0.4:111&&VN3]被添加, hash值為918790803
虛擬節點[192.168.0.4:111&&VN4]被添加, hash值為1232193678

[127.0.0.1:1111]的hash值為380278925, 被路由到結點[192.168.0.0:111]
[221.226.0.1:2222]的hash值為1493545632, 被路由到結點[192.168.0.0:111]
[10.211.0.1:3333]的hash值為1393836017, 被路由到結點[192.168.0.2:111]

從代碼運行結果看,每個點路由到的伺服器都是Hash值順時針離它最近的那個伺服器節點,沒有任何問題。

通過採取虛擬節點的方法,一個真實結點不再固定在Hash換上的某個點,而是大量地分佈在整個Hash環上,這樣即使上線、下線伺服器,也不會造成整體的負載不均衡。

 

後記

在寫本文的時候,很多知識我也是邊寫邊學,難免有很多寫得不好、理解得不透徹的地方,而且代碼整體也比較糙,未有考慮到可能的各種情況。拋磚引玉,一方面,寫得不對的地方,還望網友朋友們指正;另一方面,後續我也將通過自己的工作、學習不斷完善上面的代碼。


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

-Advertisement-
Play Games
更多相關文章
  • 已知輸入整數14時,運行結果如下,試根據規律編寫程式 #include <stdio.h> int t;//標記i的重覆次數 //void divide(int i,int number) //{ //int k,n=number; //while(n>i) //{ // n=n-i; // if(
  • 寒假這些天在看《The C++ Programming Language, 3rd》。 今天看到Chapter7 Function,裡頭好一些東西是C語言里沒有的,比如overload、passing by reference。這裡不講這些,講C語言也有的 pointer to function。以
  • 在這一系列博客中,主要是記錄在實際開發中會常用的一些Java工具類,方便後續開發中使用。 以下的目錄會隨著後邊具體工具類的添加而改變。 浮點數精確計算 第二章 Java浮點數精確計算
  • 1、實際意義 在實際開發中,如果需要進行float或double的精確計算(尤其是財務計算),直接使用float或double是不行的(具體的例子看下邊的代碼的main方法的測試結果),需要使用BigDecimal。 2、代碼 package com.xxx.util; import java.ma
  • 時序約束實例詳解 本篇博客結合之前的內容,然後實打實的做一個約束實例,通過本實例讀者應該會實用timequest去分析相關的實例。本實例以VGA實驗為基礎,介紹如何去做時序約束。 首先VGA這種情況屬於供源時鐘情況,不明白供源時鐘的可以參看之前博客講解。首先查看ADV7123的數據手冊,查看其時序圖
  • 使用Timequest 筆者對Altera較熟悉,這裡以quartus ii中的timequest作為講解。 Timequest分析時序的核心,也就是在於延遲因數的計算。那麼建立約束文件,去告訴timequest,哪個地方有什麼樣的約束,該怎麼進行約束。 之所以要建立相關網表的概念,是因為我們在利用
  • 談及此部分,多多少少有一定的難度,筆者寫下這篇文章,差不多是在學習FPGA一年之後的成果,儘管當時也是看過類似的文章,但是都沒有引起筆者註意,筆者現在再對此知識進行梳理,也發現了有很多不少的收穫。筆者根據網上現有的資源,作進一步的總結,希望能夠有所幫助。 一個不錯的網站,類似於一個手冊,隨時可以去查
  • TTL,CMOS以及LVTTL,LVCMOS TTL和CMOS是數字電路中兩種常見的邏輯電平,LVTTL和LVCMOS是兩者低電平版本。TTL是流控器件,輸入電阻小,TTL電平器件速度快,驅動能力大,但功耗大。CMOS是MOS管邏輯,為壓控器件,且輸入電阻極大,CMOS電平器件速度慢,驅動能力不足T
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...