memcached的總結(jié)和分布式一致性hash
創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都做網(wǎng)站、網(wǎng)站建設(shè)、雁江網(wǎng)絡(luò)推廣、成都微信小程序、雁江網(wǎng)絡(luò)營銷、雁江企業(yè)策劃、雁江品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供雁江建站搭建服務(wù),24小時服務(wù)熱線:18980820575,官方網(wǎng)址:www.cdcxhl.com
當(dāng)前很多大型的web系統(tǒng)為了減輕數(shù)據(jù)庫服務(wù)器負(fù)載,會采用memchached作為緩存系統(tǒng)以提高響應(yīng)速度。
目錄: ()
memchached簡介
hash
取模
一致性hash
虛擬節(jié)點
源碼解析
參考資料
1. memchached簡介
memcached是一個開源的高性能分布式內(nèi)存對象緩存系統(tǒng)。
其實思想還是比較簡單的,實現(xiàn)包括server端(memcached開源項目一般只單指server端)和client端兩部分:
server端本質(zhì)是一個in-memory key-value store,通過在內(nèi)存中維護(hù)一個大的hashmap用來存儲小塊的任意數(shù)據(jù),對外通過統(tǒng)一的簡單接口(memcached protocol)來提供操作。
client端是一個library,負(fù)責(zé)處理memcached protocol的網(wǎng)絡(luò)通信細(xì)節(jié),與memcached server通信,針對各種語言的不同實現(xiàn)分裝了易用的API實現(xiàn)了與不同語言平臺的集成。
web系統(tǒng)則通過client庫來使用memcached進(jìn)行對象緩存。
2. hash
memcached的分布式主要體現(xiàn)在client端,對于server端,僅僅是部署多個memcached server組成集群,每個server獨自維護(hù)自己的數(shù)據(jù)(互相之間沒有任何通信),通過daemon監(jiān)聽端口等待client端的請求。
而在client端,通過一致的hash算法,將要存儲的數(shù)據(jù)分布到某個特定的server上進(jìn)行存儲,后續(xù)讀取查詢使用同樣的hash算法即可定位。
client端可以采用各種hash算法來定位server:
取模
最簡單的hash算法
targetServer = serverList[hash(key) % serverList.size]
直接用key的hash值(計算key的hash值的方法可以自由選擇,比如算法CRC32、MD5,甚至本地hash系統(tǒng),如java的hashcode)模上server總數(shù)來定位目標(biāo)server。這種算法不僅簡單,而且具有不錯的隨機(jī)分布特性。
但是問題也很明顯,server總數(shù)不能輕易變化。因為如果增加/減少memcached server的數(shù)量,對原先存儲的所有key的后續(xù)查詢都將定位到別的server上,導(dǎo)致所有的cache都不能被命中而失效。
一致性hash
為了解決這個問題,需要采用一致性hash算法(consistent hash)
相對于取模的算法,一致性hash算法除了計算key的hash值外,還會計算每個server對應(yīng)的hash值,然后將這些hash值映射到一個有限的值域上(比如0~2^32)。通過尋找hash值大于hash(key)的最小server作為存儲該key數(shù)據(jù)的目標(biāo)server。如果找不到,則直接把具有最小hash值的server作為目標(biāo)server。
為了方便理解,可以把這個有限值域理解成一個環(huán),值順時針遞增。
如上圖所示,集群中一共有5個memcached server,已通過server的hash值分布到環(huán)中。
如果現(xiàn)在有一個寫入cache的請求,首先計算x=hash(key),映射到環(huán)中,然后從x順時針查找,把找到的第一個server作為目標(biāo)server來存儲cache,如果超過了2^32仍然找不到,則命中第一個server。比如x的值介于A~B之間,那么命中的server節(jié)點應(yīng)該是B節(jié)點
可以看到,通過這種算法,對于同一個key,存儲和后續(xù)的查詢都會定位到同一個memcached server上。
那么它是怎么解決增/刪server導(dǎo)致的cache不能命中的問題呢?
假設(shè),現(xiàn)在增加一個server F,如下圖
此時,cache不能命中的問題仍然存在,但是只存在于B~F之間的位置(由C變成了F),其他位置(包括F~C)的cache的命中不受影響(刪除server的情況類似)。盡管仍然有cache不能命中的存在,但是相對于取模的方式已經(jīng)大幅減少了不能命中的cache數(shù)量。
虛擬節(jié)點
但是,這種算法相對于取模方式也有一個缺陷:當(dāng)server數(shù)量很少時,很可能他們在環(huán)中的分布不是特別均勻,進(jìn)而導(dǎo)致cache不能均勻分布到所有的server上。
如圖,一共有3臺server – 1,2,4。命中4的幾率遠(yuǎn)遠(yuǎn)高于1和2。
為解決這個問題,需要使用虛擬節(jié)點的思想:為每個物理節(jié)點(server)在環(huán)上分配100~200個點,這樣環(huán)上的節(jié)點較多,就能抑制分布不均勻。
當(dāng)為cache定位目標(biāo)server時,如果定位到虛擬節(jié)點上,就表示cache真正的存儲位置是在該虛擬節(jié)點代表的實際物理server上。
另外,如果每個實際server的負(fù)載能力不同,可以賦予不同的權(quán)重,根據(jù)權(quán)重分配不同數(shù)量的虛擬節(jié)點。
// 采用有序map來模擬環(huán)
this.consistentBuckets = new TreeMap();
MessageDigest md5 = MD5.get();//用MD5來計算key和server的hash值
// 計算總權(quán)重
if ( this.totalWeight for ( int i = 0; i this.weights.length; i++ )
this.totalWeight += ( this.weights[i] == null ) ? 1 : this.weights[i];
} else if ( this.weights == null ) {
this.totalWeight = this.servers.length;
}
// 為每個server分配虛擬節(jié)點
for ( int i = 0; i servers.length; i++ ) {
// 計算當(dāng)前server的權(quán)重
int thisWeight = 1;
if ( this.weights != null this.weights[i] != null )
thisWeight = this.weights[i];
// factor用來控制每個server分配的虛擬節(jié)點數(shù)量
// 權(quán)重都相同時,factor=40
// 權(quán)重不同時,factor=40*server總數(shù)*該server權(quán)重所占的百分比
// 總的來說,權(quán)重越大,factor越大,可以分配越多的虛擬節(jié)點
double factor = Math.floor( ((double)(40 * this.servers.length * thisWeight)) / (double)this.totalWeight );
for ( long j = 0; j factor; j++ ) {
// 每個server有factor個hash值
// 使用server的域名或IP加上編號來計算hash值
// 比如server - "172.45.155.25:11111"就有factor個數(shù)據(jù)用來生成hash值:
// 172.45.155.25:11111-1, 172.45.155.25:11111-2, ..., 172.45.155.25:11111-factor
byte[] d = md5.digest( ( servers[i] + "-" + j ).getBytes() );
// 每個hash值生成4個虛擬節(jié)點
for ( int h = 0 ; h 4; h++ ) {
Long k =
((long)(d[3+h*4]0xFF) 24)
| ((long)(d[2+h*4]0xFF) 16)
| ((long)(d[1+h*4]0xFF) 8 )
| ((long)(d[0+h*4]0xFF));
// 在環(huán)上保存節(jié)點
consistentBuckets.put( k, servers[i] );
}
}
// 每個server一共分配4*factor個虛擬節(jié)點
}
// 采用有序map來模擬環(huán)
this.consistentBuckets = new TreeMap();
MessageDigest md5 = MD5.get();//用MD5來計算key和server的hash值
// 計算總權(quán)重
if ( this.totalWeight for ( int i = 0; i this.weights.length; i++ )
this.totalWeight += ( this.weights[i] == null ) ? 1 : this.weights[i];
} else if ( this.weights == null ) {
this.totalWeight = this.servers.length;
}
// 為每個server分配虛擬節(jié)點
for ( int i = 0; i servers.length; i++ ) {
// 計算當(dāng)前server的權(quán)重
int thisWeight = 1;
if ( this.weights != null this.weights[i] != null )
thisWeight = this.weights[i];
// factor用來控制每個server分配的虛擬節(jié)點數(shù)量
// 權(quán)重都相同時,factor=40
// 權(quán)重不同時,factor=40*server總數(shù)*該server權(quán)重所占的百分比
// 總的來說,權(quán)重越大,factor越大,可以分配越多的虛擬節(jié)點
double factor = Math.floor( ((double)(40 * this.servers.length * thisWeight)) / (double)this.totalWeight );
for ( long j = 0; j factor; j++ ) {
// 每個server有factor個hash值
// 使用server的域名或IP加上編號來計算hash值
// 比如server - "172.45.155.25:11111"就有factor個數(shù)據(jù)用來生成hash值:
// 172.45.155.25:11111-1, 172.45.155.25:11111-2, ..., 172.45.155.25:11111-factor
byte[] d = md5.digest( ( servers[i] + "-" + j ).getBytes() );
// 每個hash值生成4個虛擬節(jié)點
for ( int h = 0 ; h 4; h++ ) {
Long k =
((long)(d[3+h*4]0xFF) 24)
| ((long)(d[2+h*4]0xFF) 16)
| ((long)(d[1+h*4]0xFF) 8 )
| ((long)(d[0+h*4]0xFF));
// 在環(huán)上保存節(jié)點
consistentBuckets.put( k, servers[i] );
}
}
// 每個server一共分配4*factor個虛擬節(jié)點
}
// 用MD5來計算key的hash值
MessageDigest md5 = MD5.get();
md5.reset();
md5.update( key.getBytes() );
byte[] bKey = md5.digest();
// 取MD5值的低32位作為key的hash值
long hv = ((long)(bKey[3]0xFF) 24) | ((long)(bKey[2]0xFF) 16) | ((long)(bKey[1]0xFF) 8 ) | (long)(bKey[0]0xFF);
// hv的tailMap的第一個虛擬節(jié)點對應(yīng)的即是目標(biāo)server
SortedMap tmap = this.consistentBuckets.tailMap( hv );
return ( tmap.isEmpty() ) ? this.consistentBuckets.firstKey() : tmap.firstKey();
更多問題到問題求助專區(qū)()
我只列出SQL查詢語句,具體在VB怎么用就你自己搞定了,因為你提供的信息有限,沒法給你提供完整的VB代碼:
select max([序號]) as 最大序號, avg([平均值]) as 總平均值 from 表名
要得到轉(zhuǎn)動慣量 列中所有數(shù)據(jù)的平均值,你可以自己舉一反三啊,一定要嚼爛了你才會吃啊:
select max([序號]) as 最大序號, avg([平均值]) as 總平均值, avg([轉(zhuǎn)動慣量]) as 轉(zhuǎn)動慣量平均值 from 表名
或者分別求三個值也行:
select max([序號]) as 最大序號 from 表名
select avg([平均值]) as 總平均值 from 表名
select avg([轉(zhuǎn)動慣量]) as 轉(zhuǎn)動慣量平均值 from 表名
Set rs1 = db.OpenRecordset("select avg(轉(zhuǎn)動慣量) from 項目")
然后rs1(0)的值就是“項目”這個表中所有“轉(zhuǎn)動慣量”的平均值了
function getDivideNumber($number, $total, $index = 2) {
// 取平均數(shù)
$divide_number = floor($number / $total * pow(10, $index)) / pow(10, $index);
$divide_number = number_format($divide_number, $index, '.', '');
// 獲取最后一個數(shù)字
$last_number = $number - $divide_number * ($total - 1);
$last_number = number_format($last_number, $index, '.', '');
// 拼裝平分后的數(shù)據(jù)返回
$number_str = str_repeat($divide_number . ',', $total - 1) . $last_number;
return explode(',', $number_str);
}
$array = getDivideNumber(120, 3, $index = 0);
得到平均分配的數(shù)字?jǐn)?shù)組,用遍歷后入庫