hash_hmac — 使用 HMAC 方法生成帶有密鑰的哈希值
創(chuàng)新互聯(lián)專注于云龍網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供云龍營銷型網(wǎng)站建設(shè),云龍網(wǎng)站制作、云龍網(wǎng)頁設(shè)計(jì)、云龍網(wǎng)站官網(wǎng)定制、微信平臺小程序開發(fā)服務(wù),打造云龍網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供云龍網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
string?hash_hmac(string?$algo,?string?$data,?string?$key[,?bool?$raw_output?=?false])
參數(shù):
algo:要使用的哈希算法名稱,例如:"md5","sha256","haval160,4" 等。
data:要進(jìn)行哈希運(yùn)算的消息。
key:使用 HMAC 生成信息摘要時所使用的密鑰。
raw_output:設(shè)置為 TRUE 輸出原始二進(jìn)制數(shù)據(jù), 設(shè)置為 FALSE 輸出小寫 16 進(jìn)制字符串。
返回值:
如果 raw_output 設(shè)置為 TRUE, 則返回原始二進(jìn)制數(shù)據(jù)表示的信息摘要,否則返回 16 進(jìn)制小寫字符串格式表示的信息摘要。
如果 algo 參數(shù)指定的不是受支持的算法,返回 FALSE。
將給定的明文密碼通過加"鹽"(干擾碼)后,再經(jīng)過哈希算法的sha512算法結(jié)果與哈希算法whirlpool算法的兩個值進(jìn)行與運(yùn)算,將結(jié)果返回。
舉例:(示例一下,例子未必形象)
假如你輸入一個密碼:123456
通過運(yùn)算(自定義一個干擾碼?abcd@!#$)
1、hash("abcd@!#$","123456")
2、用hash算法的sha512算法對(abcd@!#$123456)進(jìn)行加密取得值a
3、用hash算法的whirlpool算法對(abcd@!#$123456)進(jìn)行加密取得值b
4、將a和b進(jìn)行二進(jìn)制位與運(yùn)算得到c
5、將c轉(zhuǎn)化為十六進(jìn)制數(shù)返回
通過該方法可以將用戶的輸入的明文進(jìn)行加密,多用于用戶密碼的存儲和比較。說白了就是只有輸入的用戶知道密碼的明文,程序設(shè)計(jì)者、數(shù)據(jù)庫管理員、黑客就算拿到加密的密文也不會知道(短時間內(nèi))密碼的明文。
例外:如果黑客知道了使用的鹽(干擾碼)和算法,當(dāng)然可以自己創(chuàng)建一個新的彩虹表,通過高性能計(jì)算是有可能將明文碰撞出來的,當(dāng)然你可以個直接找到用戶強(qiáng)迫他說出來~嘿嘿嘿~
當(dāng)分片索引不是純整型的字符串時,只接受整型的內(nèi)置 hash 算法是無法使用的。為此,stringhash 按照用戶定義的起點(diǎn)和終點(diǎn)去截取分片索引字段中的部分字符,根據(jù)當(dāng)中每個字符的二進(jìn)制 unicode 值換算出一個長整型數(shù)值,然后就直接調(diào)用內(nèi)置 hash 算法求解分片路由:先求模得到邏輯分片號,再根據(jù)邏輯分片號直接映射到物理分片。
用戶需要在 rule.xml 中定義 partitionLength[] 和 partitionCount[] 兩個數(shù)組和 hashSlice 二元組。
在 DBLE 的啟動階段,點(diǎn)乘兩個數(shù)組得到模數(shù),也是邏輯分片的數(shù)量
并且根據(jù)兩個數(shù)組的叉乘,得到各個邏輯分片到物理分片的映射表(物理分片數(shù)量由 partitionCount[] 數(shù)組的元素值之和)
此外根據(jù) hashSlice 二元組,約定把分片索引值中的第 4 字符到第 5 字符(字符串以 0 開始編號,編號 3 到編號 4 等于第 4 字符到第 5 字符)字符串用于 “字符串-整型”的轉(zhuǎn)換
在 DBLE 的運(yùn)行過程中,用戶訪問使用這個算法的表時,WHERE 子句中的分片索引值會被提取出來,取當(dāng)中的第 4 個字符到第 5 字符,送入下一步
設(shè)置一個初始值為 0 的累計(jì)值,逐個取字符,把累計(jì)值乘以 31,再把這個字符的 unicode 值當(dāng)成長整型加入到累計(jì)值中,如此類推直至處理完截取出來的所有字符,此時的累計(jì)值就能夠代表用戶的分片索引值,完成了 “字符串-整型” 的轉(zhuǎn)換
對上一步的累計(jì)值進(jìn)行求模,得到邏輯分片號
再根據(jù)邏輯分片號,查映射表,直接得到物理分片號
與MyCat的類似分片算法對比
請點(diǎn)擊輸入圖片描述
兩種算法在string轉(zhuǎn)化為int之后,和 hash 分區(qū)算法相同,區(qū)別也繼承了 hash 算法的區(qū)別。
開發(fā)注意點(diǎn)
【分片索引】1. 必須是字符串
【分片索引】2. 最大物理分片配置方法是,讓 partitionCount[] 數(shù)組和等于 2880
例如:
property name="partitionLength"1/propertyproperty name="partitionCount"2880/property
或
property name="partitionLength"1,1/propertyproperty name="partitionCount"1440,1440/property
【分片索引】3. 最小物理分片配置方法是,讓 partitionCount[] 數(shù)組和等于 1
例如
property name="partitionLength"2880/propertyproperty name="partitionCount"1/property
【分片索引】4. partitionLength 和 partitionCount 被當(dāng)做兩個逗號分隔的一維數(shù)組,它們之間的點(diǎn)乘必須在 [1, 2880] 范圍內(nèi)
【分片索引】5. partitionLength 和 partitionCount 的配置對順序敏感
property name="partitionLength"512,256/propertyproperty name="partitionCount"1,2/property
和
property name="partitionLength"256,512/propertyproperty name="partitionCount"2,1/property
是不同的分片結(jié)果
【分片索引】6. 分片索引字段長度小于用戶指定的截取長度時,截取長度會安全減少到符合分片索引字段長度
【數(shù)據(jù)分布】1. 分片索引字段截取越長則越有利于數(shù)據(jù)均勻分布
【數(shù)據(jù)分布】2. 分片索引字段的內(nèi)容重復(fù)率越低則越有利于數(shù)據(jù)均勻分布
運(yùn)維注意點(diǎn)
【擴(kuò)容】1. 預(yù)先過量分片,并且不改變 partitionCount 和 partitionLength 點(diǎn)乘結(jié)果,也不改變截取設(shè)置 hashSlice 時,可以避免數(shù)據(jù)再平衡,只需進(jìn)行涉及數(shù)據(jù)的遷移
【擴(kuò)容】2. 若需要改變 partitionCount 和 partitionLength 點(diǎn)乘結(jié)果或改變截取設(shè)置 hashSlice 時,需要數(shù)據(jù)再平衡
【縮容】1. 預(yù)先過量分片,并且不改變 partitionCount 和 partitionLength 點(diǎn)乘結(jié)果,也不改變截取設(shè)置 hashSlice 時,可以避免數(shù)據(jù)再平衡,只需進(jìn)行涉及數(shù)據(jù)的遷移
【縮容】2. 若需要改變 partitionCount 和 partitionLength 點(diǎn)乘結(jié)果或改變截取設(shè)置 hashSlice 時,需要數(shù)據(jù)再平衡
配置注意點(diǎn)
【配置項(xiàng)】1. 在 rule.xml 中,可配置項(xiàng)為?property name="partitionLength"? 、property name="partitionCount" 和 property name="hashSlice"
【配置項(xiàng)】2.在 rule.xml 中配置 property name="partitionLength"?標(biāo)簽
內(nèi)容形式為:物理分片持有的虛擬分片數(shù)[,物理分片持有的虛擬分片數(shù),...物理分片持有的虛擬分片數(shù)]
物理分片持有的虛擬分片數(shù)必須是整型,物理分片持有的虛擬分片數(shù)從左到右與同順序的物理分片數(shù)對應(yīng),partitionLength 和partitionCount 的點(diǎn)乘結(jié)果必須在 [1, 2880] 范圍內(nèi)
【配置項(xiàng)】3. 在 rule.xml 中配置 property name="partitionCount"?標(biāo)簽
內(nèi)容形式為:物理分片數(shù)[,物理分片數(shù),...物理分片數(shù)]
其中物理分片數(shù)必須是整型,物理分片數(shù)按從左到右的順序與同順序的物理分片持有的虛擬分片數(shù)對應(yīng),物理分片的編號從左到右連續(xù)遞進(jìn),partitionLength 和 partitionCount 的點(diǎn)乘結(jié)果必須在 [1, 2880] 范圍內(nèi)
【配置項(xiàng)】4. partitionLength 和 partitionCount 的語義是:持有partitionLength[i] 個虛擬分片的物理分片有 partitionCount[i] 個
例如
property name="partitionLength"512,256/propertyproperty name="partitionCount"1,2/property
語義是持有 512 個邏輯分片的物理分片有 1 個,緊隨其后,持有 256 個邏輯分片的物理分片有 2 個
【配置項(xiàng)】5.partitionLength 和 partitionCount 都對書寫順序敏感,
例如
property name="partitionLength"512,256/propertyproperty name="partitionCount"1,2/property
分片結(jié)果是第一個物理分片持有頭512個邏輯分片,第二個物理分片持有緊接著的256個邏輯分片,第三個物理分片持有最后256個邏輯分片,相對的
property name="partitionLength"256,512/propertyproperty name="partitionCount"2,1/property
分片結(jié)果則是第一個物理分片持有頭 256 個邏輯分片,第二個物理分片持有緊接著的 256 個邏輯分片,第三個物理分片持有最后 512 個邏輯分片
【配置項(xiàng)】6.partitionLength[] 的元素全部為 1 時,這時候partitionCount 數(shù)組和等于 partitionLength 和 partitionCount 的點(diǎn)乘,物理分片和邏輯分片就會一一對應(yīng),該分片算法等效于直接取余
【配置項(xiàng)】7.在 rule.xml 中配置標(biāo)簽,從分片索引字段的第幾個字符開始截取到第幾個字符:
若希望從首字符開始截取 k 個字符( k 為正整數(shù)),配置的內(nèi)容形式可以為“ 0 : k ”、“ k ”或“ : k ”;
若希望從末字符開始截取 k 個字符( k 為正整數(shù)),則配置的內(nèi)容形式可以為“ -k : 0 ”、“ -k ”或“ -k : ”;
若希望從頭第 m 個字符起算截取 n 個字符( m 和 n 都是正整數(shù)),則先計(jì)算出 i = m - 1 和 j = i + n - 1,配置的內(nèi)容形式為“ i : j ”;
若希望從尾第 m 個字符起算截取從尾算起的 n 個字符( m 和 n 都是正整數(shù)),則先計(jì)算出 i = -m + n - 1,配置的內(nèi)容形式可以為“ -m : i ”;
若希望不截取,則配置的內(nèi)容形式可以為“ 0 : 0 ”、“ 0 : ”、“ : 0 ”或 “ : ”
把圖形文件(其實(shí)任何文件都這樣)讀入,然后將文件內(nèi)容字符串做哈希就行了。和md5('abc')沒區(qū)別,自己看一下手冊怎么將文件內(nèi)容讀入變量就好了。
memcached的總結(jié)和分布式一致性hash
當(dāng)前很多大型的web系統(tǒng)為了減輕數(shù)據(jù)庫服務(wù)器負(fù)載,會采用memchached作為緩存系統(tǒng)以提高響應(yīng)速度。
目錄: ()
memchached簡介
hash
取模
一致性hash
虛擬節(jié)點(diǎn)
源碼解析
參考資料
1. memchached簡介
memcached是一個開源的高性能分布式內(nèi)存對象緩存系統(tǒng)。
其實(shí)思想還是比較簡單的,實(shí)現(xiàn)包括server端(memcached開源項(xiàng)目一般只單指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通信,針對各種語言的不同實(shí)現(xiàn)分裝了易用的API實(shí)現(xiàn)了與不同語言平臺的集成。
web系統(tǒng)則通過client庫來使用memcached進(jìn)行對象緩存。
2. hash
memcached的分布式主要體現(xiàn)在client端,對于server端,僅僅是部署多個memcached server組成集群,每個server獨(dú)自維護(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值(計(jì)算key的hash值的方法可以自由選擇,比如算法CRC32、MD5,甚至本地hash系統(tǒng),如java的hashcode)模上server總數(shù)來定位目標(biāo)server。這種算法不僅簡單,而且具有不錯的隨機(jī)分布特性。
但是問題也很明顯,server總數(shù)不能輕易變化。因?yàn)槿绻黾?減少memcached server的數(shù)量,對原先存儲的所有key的后續(xù)查詢都將定位到別的server上,導(dǎo)致所有的cache都不能被命中而失效。
一致性hash
為了解決這個問題,需要采用一致性hash算法(consistent hash)
相對于取模的算法,一致性hash算法除了計(jì)算key的hash值外,還會計(jì)算每個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的請求,首先計(jì)算x=hash(key),映射到環(huán)中,然后從x順時針查找,把找到的第一個server作為目標(biāo)server來存儲cache,如果超過了2^32仍然找不到,則命中第一個server。比如x的值介于A~B之間,那么命中的server節(jié)點(diǎn)應(yīng)該是B節(jié)點(diǎn)
可以看到,通過這種算法,對于同一個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é)點(diǎn)
但是,這種算法相對于取模方式也有一個缺陷:當(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é)點(diǎn)的思想:為每個物理節(jié)點(diǎn)(server)在環(huán)上分配100~200個點(diǎn),這樣環(huán)上的節(jié)點(diǎn)較多,就能抑制分布不均勻。
當(dāng)為cache定位目標(biāo)server時,如果定位到虛擬節(jié)點(diǎn)上,就表示cache真正的存儲位置是在該虛擬節(jié)點(diǎn)代表的實(shí)際物理server上。
另外,如果每個實(shí)際server的負(fù)載能力不同,可以賦予不同的權(quán)重,根據(jù)權(quán)重分配不同數(shù)量的虛擬節(jié)點(diǎn)。
// 采用有序map來模擬環(huán)
this.consistentBuckets = new TreeMap();
MessageDigest md5 = MD5.get();//用MD5來計(jì)算key和server的hash值
// 計(jì)算總權(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é)點(diǎn)
for ( int i = 0; i servers.length; i++ ) {
// 計(jì)算當(dāng)前server的權(quán)重
int thisWeight = 1;
if ( this.weights != null this.weights[i] != null )
thisWeight = this.weights[i];
// factor用來控制每個server分配的虛擬節(jié)點(diǎn)數(shù)量
// 權(quán)重都相同時,factor=40
// 權(quán)重不同時,factor=40*server總數(shù)*該server權(quán)重所占的百分比
// 總的來說,權(quán)重越大,factor越大,可以分配越多的虛擬節(jié)點(diǎn)
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加上編號來計(jì)算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é)點(diǎn)
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é)點(diǎn)
consistentBuckets.put( k, servers[i] );
}
}
// 每個server一共分配4*factor個虛擬節(jié)點(diǎn)
}
// 采用有序map來模擬環(huán)
this.consistentBuckets = new TreeMap();
MessageDigest md5 = MD5.get();//用MD5來計(jì)算key和server的hash值
// 計(jì)算總權(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é)點(diǎn)
for ( int i = 0; i servers.length; i++ ) {
// 計(jì)算當(dāng)前server的權(quán)重
int thisWeight = 1;
if ( this.weights != null this.weights[i] != null )
thisWeight = this.weights[i];
// factor用來控制每個server分配的虛擬節(jié)點(diǎn)數(shù)量
// 權(quán)重都相同時,factor=40
// 權(quán)重不同時,factor=40*server總數(shù)*該server權(quán)重所占的百分比
// 總的來說,權(quán)重越大,factor越大,可以分配越多的虛擬節(jié)點(diǎn)
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加上編號來計(jì)算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é)點(diǎn)
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é)點(diǎn)
consistentBuckets.put( k, servers[i] );
}
}
// 每個server一共分配4*factor個虛擬節(jié)點(diǎn)
}
// 用MD5來計(jì)算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é)點(diǎn)對應(yīng)的即是目標(biāo)server
SortedMap tmap = this.consistentBuckets.tailMap( hv );
return ( tmap.isEmpty() ) ? this.consistentBuckets.firstKey() : tmap.firstKey();
更多問題到問題求助專區(qū)()
PHP中使用最多的非Array莫屬了,那Array是如何實(shí)現(xiàn)的?在PHP內(nèi)部Array通過一個hashtable來實(shí)現(xiàn),其中使用鏈接法解決hash沖突的問題,這樣最壞情況下,查找Array元素的復(fù)雜度為O(N),最好則為1.
而其計(jì)算字符串hash值的方法如下,將源碼摘出來以供查備:
復(fù)制代碼
代碼如下:
static
inline
ulong
zend_inline_hash_func(const
char
*arKey,
uint
nKeyLength)
{
register
ulong
hash
=
5381;
//此處初始值的設(shè)置有什么玄機(jī)么?
/*
variant
with
the
hash
unrolled
eight
times
*/
for
(;
nKeyLength
=
8;
nKeyLength
-=
8)
{
//這種step=8的方式是為何?
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
//比直接*33要快
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
hash
=
((hash
5)
+
hash)
+
*arKey++;
}
switch
(nKeyLength)
{
case
7:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
//此處是將剩余的字符hash
case
6:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
case
5:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
case
4:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
case
3:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
case
2:
hash
=
((hash
5)
+
hash)
+
*arKey++;
/*
fallthrough...
*/
case
1:
hash
=
((hash
5)
+
hash)
+
*arKey++;
break;
case
0:
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
return
hash;//返回hash值
}
ps:對于以下函數(shù),仍有兩點(diǎn)不明:
hash
=
5381設(shè)置的理由?
這種step=8的循環(huán)方式是為了效率么?