Memcached客戶端可以設多個memcached服務器,它是如何把數據分發到各個服務器上,而使各個服務器負載平衡的呢?
可以看看.net版中的客戶端中的源碼,就可以知道 先看代碼:
獲取Socket連接代碼
1 /// <summary>
2 /// Returns appropriate SockIO object given
3 /// string cache key and optional hashcode.
4 ///
5 /// Trys to get SockIO from pool. Fails over
6 /// to additional pools in event of server failure.
7 /// </summary>
8 /// <param name="key">hashcode for cache key</param>
9 /// <param name="hashCode">if not null, then the int hashcode to use</param>
10 /// <returns>SockIO obj connected to server</returns>
11 public SockIO GetSock(string key, object hashCode)
12 {
13 string hashCodeString = "<null>";
14 if(hashCode != null)
15 hashCodeString = hashCode.ToString();
16
17 if(Log.IsDebugEnabled)
18 {
19 Log.Debug(GetLocalizedString("cache socket pick").Replace("$$Key$$", key).Replace("$$HashCode$$", hashCodeString));
20 }
21
22 if (key == null || key.Length == 0)
23 {
24 if(Log.IsDebugEnabled)
25 {
26 Log.Debug(GetLocalizedString("null key"));
27 }
28 return null;
29 }
30
31 if(!_initialized)
32 {
33 if(Log.IsErrorEnabled)
34 {
35 Log.Error(GetLocalizedString("get socket from uninitialized pool"));
36 }
37 return null;
38 }
39
40 // if no servers return null
41 if(_buckets.Count == 0)
42 return null;
43
44 // if only one server, return it
45 if(_buckets.Count == 1)
46 return GetConnection((string)_buckets[0]);
47
48 int tries = 0;
49
50 // generate hashcode
51 int hv;
52 if(hashCode != null)
53 {
54 hv = (int)hashCode;
55 }
56 else
57 {
58
59 // NATIVE_HASH = 0
60 // OLD_COMPAT_HASH = 1
61 // NEW_COMPAT_HASH = 2
62 switch(_hashingAlgorithm)
63 {
64 case HashingAlgorithm.Native:
65 hv = key.GetHashCode();
66 break;
67
68 case HashingAlgorithm.OldCompatibleHash:
69 hv = OriginalHashingAlgorithm(key);
70 break;
71
72 case HashingAlgorithm.NewCompatibleHash:
73 hv = NewHashingAlgorithm(key);
74 break;
75
76 default:
77 // use the native hash as a default
78 hv = key.GetHashCode();
79 _hashingAlgorithm = HashingAlgorithm.Native;
80 break;
81 }
82 }
83
84 // keep trying different servers until we find one
85 while(tries++ <= _buckets.Count)
86 {
87 // get bucket using hashcode
88 // get one from factory
89 int bucket = hv % _buckets.Count;
90 if(bucket < 0)
91 bucket += _buckets.Count;
92
93 SockIO sock = GetConnection((string)_buckets[bucket]);
94
95 if(Log.IsDebugEnabled)
96 {
97 Log.Debug(GetLocalizedString("cache choose").Replace("$$Bucket$$", _buckets[bucket].ToString()).Replace("$$Key$$", key));
98 }
99
100 if(sock != null)
101 return sock;
102
103 // if we do not want to failover, then bail here
104 if(!_failover)
105 return null;
106
107 // if we failed to get a socket from this server
108 // then we try again by adding an incrementer to the
109 // current key and then rehashing
110 switch(_hashingAlgorithm)
111 {
112 case HashingAlgorithm.Native:
113 hv += ((string)("" + tries + key)).GetHashCode();
114 break;
115
116 case HashingAlgorithm.OldCompatibleHash:
117 hv += OriginalHashingAlgorithm("" + tries + key);
118 break;
119
120 case HashingAlgorithm.NewCompatibleHash:
121 hv += NewHashingAlgorithm("" + tries + key);
122 break;
123
124 default:
125 // use the native hash as a default
126 hv += ((string)("" + tries + key)).GetHashCode();
127 _hashingAlgorithm = HashingAlgorithm.Native;
128 break;
129 }
130 }
131
132 return null;
133 }
134
上面代碼是代碼文件SockIOPool.cs中的一個方法,從方法簽名上可以看出,獲取一個socket連接是根據需要緩存數據的唯一鍵和它的哈希值,因為緩存的數據的鍵值是唯一的,所以它的哈希代碼也是唯一的;
再看看上面方法中的以下代碼:
int bucket = hv % _buckets.Count;
if(bucket < 0)
bucket += _buckets.Count;
SockIO sock = GetConnection((string)_buckets[bucket]);
具體的選擇服務器的算法是:唯一鍵值的哈希值與存放服務器列表中服務器(服務器地址記錄不是唯一的)的數量進行模數運算來選擇服務器的地址的。所以數據緩存在那台服務器取決於緩存數據的唯一鍵值所產生的哈希值和存放服務器列表中服務器的數量值,所以訪問memcached服務的所有客戶端操作數據時都必須使用同一種哈希算法和相同的服務器列表配置,否則就會或取不到數據或者重復存取數據。由於不同數據的唯一鍵所對應的哈希值不同,所以不同的數據就有可能分散到不同的服務器上,達到多個服務器負載平衡的目的。
如果幾台服務器當中,負載能力各不同,想根據具體情況來配置各個服務器負載作用,也是可以做到的。看上面代碼,可以知道程序是從_buckets中獲取得服務器地址的,_buckets存放著服務器的地址信息,服務器地址在_bucket列表中並不是唯一的,它是可以有重復記錄的。相同的服務器地址在_bucket重復記錄越多,它被選中的機率就越大,相應負載作用也就越大。
怎麼設置服務器讓它發揮更大的負載作用,如下面代碼:
String[] serverlist = {"192.168.1.2:11211", "192.168.1.3:11211"};
int[] weights = new int[]{5, 2};
SockIOPool pool = SockIOPool.GetInstance();
pool.SetServers(serverlist);
pool.SetWeights(weights);
pool.Initialize();
pool.SetWeights(weights)方法就是設配各個服務器負載作用的系數,系數值越大,其負載作用也就越大。如上面的例子,就設服務器192.168.1.2的負載系數為5,服務器192.168.1.3的負載系數為2,也就說服務器192.168.1.2 比192.168.1.3的負載作用大。
程序中根據緩存數據中的唯一鍵標識的哈希值跟服務器列表中服務器記錄數量求模運算來確定數據的緩存的位置的方法,算法的優點:能夠把數據勻均的分散到各個服務器上數據服務器負載平衡,當然也可以通過配置使不同服務器有不同的負載作用。但也有缺點:使同類的數據過於分散,同個模塊的數據都分散到不同的數據,不好統一管理和唯護;比如:現在有A、B、C、D四台服務器一起來做緩存服務器,數月後C台服務器突然死掉不可用啦,那麼按算法緩存在C台服務器的數據都不可用啦,但客戶端還是按原來的四台服務器的算法來取操作數據,所以分布在C服務上的數據在C服務器恢復可用之前都不可用,都必須從數據庫中讀取數據,並且不能添加到緩存中,因為只要緩存數據的Key不變,它還是會被計算分配到C服務器上。如果想把分配到C服務器就必須全部初始化A、B、D三台服務器上的所有數據,並把C服務器從服務器列表中移除。
如果我們能夠把數據分類分布到各個服務器中,同類型的數據分布到相同的服務器;比如說,A服務器存放用戶日志模塊信息,B服務器存放用戶相冊模塊信息,C服務器存放音樂模塊信息,D服務器存放用戶基礎信息。如果C服務器不可用後,就可以更改下配置使它存放在其它服務器當中,而且並不影響其它服務器的緩存信息。
解決方法1:不同的模塊使用不同memcached客戶端實例,這樣不同模塊就可以配置不同的服務器列表,這樣不同模塊的數據就緩存到了不同的服務器中。這樣,當某台服務器不可用後,只會影響到相應memcached客戶端實例的數據,而不會影響到其它客戶端實例的數據。
解決方法2:修改或添加新的算法,並在數據唯一鍵中添加命名空間,算法根據配置和數據唯一鍵中命名空間來選擇不同的Socket連接,也就是服務器啦。
數據項唯一鍵(key)的定義:命名空間.數據項ID,就跟編程中的” 命名空間”一樣,經如說用戶有一篇日志的ID是”999999”, 那麼這條篇日志的唯一鍵就是:Sns.UserLogs.Log.999999,當然我們存貯的時候考慮性能問題,可以用一個短的數值來代替命名空間。這樣在選擇Socket的時候就可以根據數據項中的唯一鍵來選擇啦。