免費空間asp網(wǎng)站知名品牌營銷策略
一、簡單動態(tài)字符串SDS
無論是Redis中的key還是value,其基礎數(shù)據(jù)類型都是字符串。如,Hash型value的field與value的類型,List型,Set型,ZSet型value的元素的類型等都是字符串。redis沒有使用傳統(tǒng)C中的字符串而是自定義了一種字符串結構類型,這種字符串本身的結構比較簡單,但是功能強大,稱為簡單動態(tài)字符串 Simple Dynamic String ,簡稱為SDS。
SDS結構
SDS不同于C中的字符串,C中的字符串是以一個雙引號括起來,以空字符'\0'結尾的字符序列。而SDS是重新定義的一個結構體,在redis的安裝目錄src/sds.h
這個結構體有三個部分組成:
struct sdshdr{// 字節(jié)數(shù)組,用來保存字符串char buf[];// SDS的長度,在buf[]中使用的字節(jié)數(shù)量int len;// buf[]中沒有使用的字節(jié)數(shù)量int free;
}
注意:雖然redis當中定義了結構體sds,但是并不是所有的字符串都使用這種方式存儲,比如我們執(zhí)行一個set命令,返回了一個OK,這個OK就是C語言中的字符串。get命令獲取的值返回的也是C語言的字符串。C 字符串只會出現(xiàn)在字符串“字面常量”中,并且該字符串不可能發(fā)生變更!!
?我們看下面的命令我們來了解一下類型與實際內存中的存儲結構的區(qū)別:
set name zhangsan # 寫入key為name的值為zhangsan,這里寫入的是zhangsan應該是SDS結構類型的
type name # 這里返回的會是string 表示是字符串
object encoding name # 這里會返回embstr,它就表示是SDS,是內存中的存儲類型set age 23 # 寫入key為age的值為23
type age # 這里返回的是string,表示是字符串
object encoding age # 這里返回的是int,表示在內存中是以整型存儲的
SDS優(yōu)勢
C字符串底層是一個數(shù)組,字符串最后以’\0‘來結束。
優(yōu)勢一:防止字符串長度獲取性能瓶頸
C字符串的長度獲取必須要通過遍歷整個字符串(直到\0結束)才可以獲得,這樣的話對于超長的字符串遍歷,還是會產生性能瓶頸的。
SDS結構體中我們可以看到直接存放著字符串長度的數(shù)據(jù),所以不管字符串有多長,獲取字符串的長度所要消耗的系統(tǒng)性能都是一樣的,不會成為redis的性能瓶頸。
優(yōu)勢二:保障二進制安全
C字符串對于字符串中的字符是有要求的,遇到\0就表示字符串結束了,但是我們在redis中會用字符串存儲二進制數(shù)據(jù)(圖片,視頻,壓縮文件...),在這些文件中使用\0在中間作為分隔符的情況是很常見的。所以redis中的SDS不以\0作為字符串結束標志,而是通過len屬性來判斷字符串是否結束,所以對于程序處理SDS中的字符串數(shù)據(jù),不需要對數(shù)據(jù)做任何的限制、過濾等處理,直接讀取即可。寫入的是什么讀出來的就是什么。
優(yōu)勢三:減少內存再分配
SDS中采用了空間預分配策略與惰性空間釋放策略來避免內存再分配的問題。
這兩個策略就是以空間換時間的做法!
如果我們要釋放SDS的未使用空間,可以通過sdsRemoveFreeSpace()函數(shù)來釋放。
優(yōu)勢四:兼容C函數(shù)
redis中提供了很多的SDS的api,方便用戶對redis進行二次開發(fā),為了兼容C函數(shù),SDS的底層數(shù)組buf[]中的字符串仍以空字符'\0'結尾。
SDS常用操作函數(shù)
- 空間預分配策略,每次SDS進行空間擴展的時候,程序不但為其分配所需的空間,還會為其分配額外的未使用空間,從而減少內存再分配次數(shù),客戶分配的未使用空間大小取決于空間擴展后SDS的len屬性值。
- 如果len屬性值小于1M,那么會分配未使用空間保持與len屬性相當
- 如果len屬性值大于等于1M,那么會固定分配未使用空間1M
-
惰性空間釋放策略,如果SDS字符串長度如果縮短,那么多出的未使用空間會暫時不釋放,而是增加到free當中去,從而保證后期擴展SDS時減少再分配的次數(shù)
函數(shù) | 功能 |
---|---|
sdsnew() | 使用指定的C字符串創(chuàng)建一個SDS |
sdsempty() | 創(chuàng)建一個不包含任何字符串數(shù)據(jù)的SDS |
sdsdup() | 創(chuàng)建一個指定SDS的副本 |
sdsfree() | 釋放是定的SDS |
sdsclear() | 清空指定SDS的字符串內容 |
sdslen() | 獲取指定SDS的已使用空間len值 |
sdsavail() | 獲取指定SDS的未使用空間free值 |
sdsMakeRoomFor() | 使指定的SDS的free空間增加指定的大小 |
sdsRemoveFreeSpace() | 釋放指定SDS的free空間 |
sdscat() | 把指定的C字符串拼接到指定SDS的字符串末尾 |
sdscatsds() | 把指定的SDS字符串拼接到另一個指定的SDS字符串末尾 |
sdscpy() | 把指定C字符串復制到指定的SDS中,覆蓋原SDS字符串內容 |
sdsgrouzero() | 擴展SDS字符串到指定長度,這個擴展使用空字符'\0'填充 |
sdsrange() | 截取指定SDS中指定范圍內的字符串 |
sdstrim() | 在指定SDS中刪除報有指定C字符串中出現(xiàn)的所有字符 |
sdsemp() | 對比給定的兩個SDS字符串是否相同 |
sdstolow() | 把指定SDS字符串中的所有字母變?yōu)樾?/td> |
sdstoupper() | 把指定SDS字符串中所有字母變?yōu)榇髮?/td> |
?這些函數(shù)在需要對redis二次開發(fā)時可能會用到,正常使用redis時用不到!
集合底層實現(xiàn)原理
Set集合
對于set,我們看它底層的實現(xiàn)可以如下:
sadd cities bj sh gz sz # 添加一個set集合
type cities # 返回的類型是set
object encoding cities # 返回的是hashtable
從上面可以看到set底層是使用hashtable存儲的!
Hash與ZSet
這兩種它們的底層實際上有兩種:壓縮列表zipList;跳躍列表skipList
至于Redis什么時候用zipList,什么時候用skipList對于我們用戶來說是透明的,用戶寫入不同的數(shù)據(jù),系統(tǒng)會自動判斷使用不同的實現(xiàn)。
只要我們的數(shù)據(jù)量不超過我們設定的閾值則會使用zipList,一旦超過這個閾值則什么變?yōu)閟kipList。
關于這個閾值的查看,可以使用命令進行查看:
config get zset-*-ziplist-*
# 運行后得到的結果類似如下:
1) "zset-max-ziplist-entries"
2) "128"
3) "zset-max-ziplist-value"
4) "64"
上面的信息表示,zset中包含的元素不超過128,并且每一個元素的大小不超過64字節(jié)就會使用ziplist。這兩個任意一個不滿足就會變?yōu)閟kipList。
同樣hash的查詢方式也類似,其查詢命令如下:
config get hash-*-ziplist-*
# 運行這個命令后的結果如下:
1) "hash-max-ziplist-value"
2) "64"
3) "hash-max-ziplist-entries"
4) "512"
關于zipList的底層結構
首先,什么是zipList?
通常稱為壓縮列表,是一個經(jīng)過特殊編碼(體現(xiàn)了zip壓縮的)的用于存儲字符串或整數(shù)的雙向鏈表。其底層數(shù)據(jù)結構由三部分構成:head(基礎信息)、entries(元素)、end(結束標記),這三部分在內存上是連續(xù)存放的。
zipList的三部分在底層如何存儲?
說明 | |
---|---|
head | 它包含總括性信息,由三部分組成: zlbytes:4個字節(jié),用來存放zipList列表整體數(shù)據(jù)結構所占的字節(jié)數(shù),包括zlbytes本身的長度 zltail:4個字節(jié),用于存放zipList中最后一個entry在整個數(shù)據(jù)結構中的偏移量(字節(jié))這個數(shù)據(jù)可以快速定位列表的尾entry位置,便于操作。 zllen:兩個字節(jié),用于存放列表包含的entry的個數(shù),兩個字節(jié)是16位,所以zipList最多可以包含有 |
entries | entries是真正的列表,由很多的列表元素entry構成,由于不同的元素類型、數(shù)值是不同的,從而導致每個entry的長度不同。每個entry由三部分構成: prevlength:用來記錄上一個entry的長度,用以實現(xiàn)逆序遍歷,默認長度是1字節(jié),只要上一個entry的長度<254字節(jié),prevlength就會占1字節(jié),否則會擴展為3字節(jié)長度。 注意:這里分節(jié)點為什么是小于254?不是255?因為zipList中有一個部分是1字節(jié)的全為1表示結束,所以不能用255,而254也是一個保留的值用來做為prevlength的自動擴展標志的 encoding:用來標記后面的data具體類型。如果data為整數(shù)類型,encoding固定長度是1字節(jié),如果data為字符串類型,則encoding長度可能會是1字節(jié)、2字節(jié)或5字節(jié)。data字符串不同的長度,對應著不同的encoding長度 data:真正存儲的數(shù)據(jù)。數(shù)據(jù)類型只能是整數(shù)類型或字符串類型,不同的數(shù)據(jù)占用的字節(jié)長度不同 |
end | end只包含一部分,稱為zlend。其占一個字節(jié),值固定為255,也就是二進制全為1,表示一個zipList列表的結束。 |
zipList的結構圖大致如下:
?關于listPack底層結構
zipList是在Redis7之前版本中使用的,到了7版本不再使用zipList,而是使用listPack進行了替換。
為什么要把zipList替換為listPack?
在zipList中每一個entry中的prevlength當中會記錄前一個entry的長度,如果在中間某個元素前插入了一個元素或修改了元素,那這個元素entry中的prevlength還要跟著去調整(級聯(lián)更新)。在高并發(fā)的寫操作場景下會極度地降低Redis的性能,所以為了實現(xiàn)更緊湊、更快的解析,更簡單的寫操作所以重寫了zipList,并把它命名為listPack。
那listPack有是什么呢?
它也是一種經(jīng)過特殊編碼的用于存儲字符串或整數(shù)的雙向鏈表。其底層數(shù)據(jù)結構也是由三部分構成的:head、entries、end并且這三部分在內存上也是連續(xù)進行存放的。
listPack與zipList的最大區(qū)別在于head和每個entry的結構上,表示列表結束的end兩者是相同的占一個字節(jié)且全為1。
說明 | |
---|---|
head | 由兩部分組成: totalBytes:占4個字節(jié),用來存放listPack列表整體數(shù)據(jù)結構所占的字節(jié)數(shù),也包含totalBytes本身的長度 elemNum:占2個字節(jié),用于存放列表包含的entry個數(shù)其意義與zipList中的zllen是相同的 注意:在listPack中沒有記錄最后一個entry偏移量的zltail |
entries | entries也是listPack中真正的列表,由很多的列表元素entry構成。由于不同的元素類型、數(shù)值的不同,從而導致每個entry的長度不同。但是與zipList的entry結構相比,listPack的entry結構發(fā)生了較大的變化。其中變化最大的就是沒有了記錄前一個entry長度的prevlength,而增加了記錄當前entry長度的element-total-len。而這一變化仍然可以實現(xiàn)逆序遍歷(由新的算法來支撐),但去避免了由于在列表中間修改或插入entry引發(fā)的級聯(lián)更新。 每個entry由三個部分組成: encoding:標記后面data的具體類型,如果是整數(shù)類型,其長度可能是1,2,3,4,5,9字節(jié)。不同的字節(jié)長度,標識位不同。如果是字符串則長度可能是1,2,5字節(jié)。data字符串不同的長度,對應著不同的encoding長度 data:真正存儲的數(shù)據(jù)。數(shù)據(jù)類型只能是整數(shù)類型或字符串類型,不同的數(shù)據(jù)占用的字節(jié)長度不同 element-total-len:記錄當前entry的長度,用來支持逆序遍歷,由于其特殊的記錄方式,其本身占用的字節(jié)數(shù)據(jù)中能會是1,2,3,4,5字節(jié) |
end | end只包含一部分其占一個字節(jié),值固定為255,也就是二進制全為1,表示一個zipList列表的結束。 |
listPack的底層結構圖大致如下:
?注意:encoding是比較復雜的,有些數(shù)據(jù)中可能在encoding中就包含了數(shù)據(jù)本身了沒有data這部分了
關于skipList底層結構
什么是skipList呢?
skipList指的是跳躍列表,簡稱跳表,是一種隨機化的數(shù)據(jù)結構,基于并聯(lián)的鏈表,實現(xiàn)簡單,查找效率較高。簡單理解這里的跳表也是鏈表的一種,只是在鏈表的基礎上添加了跳躍的功能。因為有了這個跳躍的功能,所以在查找元素時,能夠提供較高的效率。
skipList是如何跳躍后提高查找元素效率的呢?原理是什么?
我們先看下面這個有序列表
?此時如果我們要查找某個數(shù)據(jù)比如23,則需要從頭開始一個一個進行比較,直接找到包含要找數(shù)據(jù)的節(jié)點或者找到第一個比要找數(shù)據(jù)還大的節(jié)點或者找到末尾節(jié)點(這后面的兩種都表示沒有找到),這種方式的查找沒有效率是不高的,為了提升效率可以在偶數(shù)結點上加上一個指針,讓這個指針指向下一個偶數(shù)節(jié)點,如下圖所示:
?此時所有的偶數(shù)節(jié)點連成了一個新的鏈表(稱為高層鏈表),高層鏈表的節(jié)點數(shù)只有原鏈表的一半。如果這個時候要查找某個數(shù)據(jù)時,會先沿著高層鏈表進行查找,當遇到第一個比待查數(shù)據(jù)大的節(jié)點時,立即從這個大節(jié)點的前一個節(jié)點回到原鏈表進行查找。比如我們想找23,先在高層節(jié)點進行遍歷,遍歷到31時發(fā)現(xiàn)比23大了,此時會從它的前一個節(jié)點開始也就是19按原鏈表順序進行遍歷,這時遍歷到23,找到元素。
使用跳表這種方式明顯可以減少比較的次數(shù),提高查詢的效率,如果鏈表的元素比較多還想進一步提升效率可以再加一層高層鏈表。此時就變成了先遍歷最高層的鏈表,再遍歷第二高層的鏈表,依次類推。
上面這種跳表的基礎結構可能存在的問題:
這種對鏈表的分層級的方式看上去確實提升了查找效率,但是實際操作時情況比較多,會產生問題,比如固定序號的元素有固定的層級,那么列表中的元素如果出現(xiàn)增加或刪除,這會導致列表整體的層級都需要調整,這會大大降低系統(tǒng)性能。
解決這種序號變化帶來的層級變化影響性能的算法優(yōu)化辦法:
為了避免前面的問題,skipList采用了隨機分配層級方式。在確認了總層級后,每添加一個新的元素時會自己動為其隨機分配一個層級。這種隨機性就解決了節(jié)點序號與層級之前的固定關系問題。
數(shù)據(jù)新插入進來不影響其它節(jié)點的層級數(shù)。只需要修改插入節(jié)點的前后的指針,而不需要對很多節(jié)點進行調整,這也降低了插入操作的復雜度。
skipList指的就是除了最下面第1層鏈表外,它會產生若干層稀疏的鏈表,這些鏈表里面的指針跳過了一些節(jié)點,并且越高層級的鏈表跳過的節(jié)點越多。在查找數(shù)據(jù)的時先在高層級鏈表中進行查找,然后逐層降低,最終可能會降到第 1 層鏈表來精確地確定數(shù)據(jù)位置。在這個過程中由于跳過了一些節(jié)點所以提升了效率。
關于quickList底層結構
什么是quickList呢?
它是一個快速列表,它本身是一個雙向無循環(huán)鏈表,它的每一個節(jié)點都是一個zipList。從Redis3.2版本開始對于List底層實現(xiàn),使用quickList代替了zipList和linkedList。
quickList本質上來講是zipList和linkedList的混合體,它把linkedList按段切分,每一段使用zipList來緊湊存儲若干個真正的數(shù)據(jù)元素,多個zipList之間使用雙向指針串接起來。對于每個zipList中最多可存放多大容量的數(shù)據(jù)元素,在配置文件中可以通過list-max-ziplist-size屬性來指定。
如何對它其中的元素進行檢索?
?首先我們要清楚的是:對于List元素的檢索操作,都是使用索引來操作的。
quickList由一個一個的zipList構成,每個zipList的zllen中記錄了當前zipList中包含的entry的個數(shù),根據(jù)要檢索的index,從quickList的頭節(jié)點開始,逐個對zipList的zllen做求和操作,直到找到第一個求和后的值大于要檢索的index時,那么要檢索的元素就在這個zipList當中。
如何對它進行元素的插入呢?
由于zipList是有大小限制的,所以在quickList中插入一個元素則相對就比較復雜了。它需要分幾種情況來處理。
假設要插入的元素大小為insertBytes,而查找到的插入位置所在zipList當前的大小為zlBytes,處理情況則如下:
- 情況1:當insertBytes + zlBytes <= list-max-ziplist-size時,直接插入到zipList中的相應位置即可
- 情況2:當insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于這個zipList的首部位置時,此時需要看當前zipList的前一個zipList的大小prev_zlBytes
- 如果insertBytes + prev_zlBytes <= list-max-ziplist-size時,直接把元素插入到前一個zipList的尾部位置
- 如果insertBytes + prev_zlBytes > list-max-ziplist-size時,就會直接創(chuàng)建一個新的zipList并連接入quickList中
- 情況3:當insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于這個zipList的尾部位置時,此時需要看當前zipList的后一個zipList的大小next_zlBytes
- 如果insertBytes + next_zlBytes <= list-max-ziplist-size時,直接把元素插入到后一個zipList的首部位置
- 如果insertBytes + next_zlBytes > list-max-ziplist-size時,就會直接創(chuàng)建一個新的zipList并連接入quickList中
- 情況4:當insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于這個zipList的中間位置時,則會把zipList分割為兩個zipList連接入quickList當中然后把元素插入到分隔后的前面zipList的尾部
刪除操作如何操作?
對于刪除操作,只需要注意一點,在相應的 zipList 中刪除元素后,該 zipList 中是否還有元素。如果沒有其它元素了,則將該 zipList 刪除,將其前后兩個 zipList 相連接。
關于Redis中key與value支持的數(shù)量
- Redis中最多可以處理
?個key,大約42億。每個 Redis 實例至少可以處理 2.5 億個 key
- 每個Hash,List,Set,ZSet集合都可以包含
個元素
二、BitMap操作
BitMap是什么?
BitMap是Redis2.2.0版本中引入的一種新的數(shù)據(jù)類型。這個數(shù)據(jù)類型本質上就是一個僅包含0和1二進制字符串。而它相關的命令都是對這個字符串二進制位的操作。用于描述這個字符串的屬性有三個:key,offset,bitValue
- key:BitMap是Redis的key-value中的一種Value的數(shù)據(jù)類型,所以這個value一定是會有對應key的
- offset:每個BitMap數(shù)據(jù)都是一個字符串,字符串中的每個字符都有其對應的索引,這個索引是從0開始計數(shù)的。該索引則稱為每個字符在BitMap中的偏移量offset, 它的范圍是[0,
],所以最大值是42億多。
- bitValue:每個BitMap數(shù)據(jù)中都是一個僅包含0和1的二進制字符串,每個offset位上的字符就稱這個位的值bitValue,它的值只能是0或者1
相關操作命令
命令 | 說明 | 示例 |
---|---|---|
setbit | 為指定key的BitMap數(shù)據(jù)的offset位置上設置值為value,它的返回值是offset位置的bitValue 對于原BitMap字符串中不存在的offset進行賦值,字符串會自動伸展以確保它可以把value保存到指定的offset上,當字符串值進行伸展時,空白位置上會以0來填充,對于value值只能是0或者1。 注意:對于較大的offset的setbit操作來說,內存分配過程可以造成Redis服務器被阻塞 | setbit sing 3 1 |
getbit | 對key所存儲的BitMap字符串值,獲取指定offset偏移量上的位值bitValue 當offset比字符串值的長度大,或者key不存在時,返回0 | getbit sing 3 |
bitcount | 統(tǒng)計給定字符串中被設置為1的bit位的數(shù)量。一般情況下統(tǒng)計范圍是給定的整個BitMap字符串。也可以通過指定額外的start或end參數(shù),實現(xiàn)僅對指定字節(jié)范圍內字符串進行統(tǒng)計。 注意:是包含start和end的,且start與end的單位是字節(jié),不是bit,并且從0開始計數(shù),也可以在后帶上BIT選項指定是按bit單位指定統(tǒng)計 | bitcount sing 0 1:這個命令用來統(tǒng)計前兩個字節(jié),因為0表示第一個字節(jié),1表示第二個字節(jié) bitcount sing 0 8 BIT:這里指統(tǒng)計的是0到8位,我們給定了單位選項是BIT |
bitpos | 返回key指定的BitMap中第一個值為指定值的bit的二進制位的位置,默認情況下,命令會檢測整個BitMap,也可以指定start參數(shù)與end參數(shù)來指定檢測的范圍。start 和end默認是字節(jié)為單位,也可以帶上個單位選項 | bitpos sing 1 0 0:這里0 0表示就找第一個字節(jié)中值為1的位是哪位 |
bitop | 對一個或多個BitMap字符串key進行二進制位操作,并把結果保存到目標key上,對于操作類型可以是AND,OR,NOT,XOR中的任意一種 AND:表示按位與操作 OR:表示按位或操作 NOT:表示非操作 XOR:表示異或操作 | bitop AND result rel sing:AND表示操作,result表示結果保存的BitMap,rel和sing是兩個進行操作的BitMap bitop or result rel sing:按位或 bitop not result result:NOT操作只接收一個進行按位操作BitMap,這個操作表示把result這個BitMap中各位進行取反再寫回去 bitop xor result rel sing:按位異或 |
應用場景
由于 offset 的取值范圍很大,所以其一般應用于大數(shù)據(jù)量的二值性統(tǒng)計。
例如平臺活躍用戶統(tǒng)計(二值:訪問或未訪問)、支持率統(tǒng)計(二值:支持或不支持)、員工考勤統(tǒng)計(二值:上班或未上班)...
注意:對于數(shù)據(jù)量較小的二值性統(tǒng)計并不適合 BitMap,可能使用 Set 更為合適。當然,具體多少數(shù)據(jù)量適合使用 Set,超過多少數(shù)據(jù)量適合使用 BitMap,這需要根據(jù)具體場景進行具體分析
關于一個平臺統(tǒng)計日活躍用戶數(shù)量的方案:
方案一:使用set統(tǒng)計
如果使用 Set 來統(tǒng)計,只需上線一個用戶,就將其用戶 ID 寫入 Set 集合即可,最后只需統(tǒng)計出 Set 集合中的元素個數(shù)即可完成統(tǒng)計(scard取集合長度即可)。
方案二:使用BitMap統(tǒng)計
先定義一個BitMap,其占有的bit位至少為注冊用戶數(shù)量,當上線一個用戶,則立即使用其中一個bit位置為1,最后只需要使用bitcout來統(tǒng)計整個BitCount中為1的個數(shù)即可。
這兩個方案使用哪個合適呢?
這個要看數(shù)據(jù)量,如果用戶量大活躍用戶比較大則使用BitMap更合適更能節(jié)省內存空間。
三、HyperLogLog操用
它是Redis2.8.9版本中引入的一種新的數(shù)據(jù)類型,其意義是hyperlog log,超級日志記錄。這個數(shù)據(jù)類型可以簡單理解為一個set集合,集合的元素為字符串。
實際上HyperLogLog是一種基數(shù)計數(shù)概率算法,通過這個算法可以利用極小的內存完成獨立總數(shù)的統(tǒng)計,其所有相關命令都是對這個set集合的操作。
它的命令都是pf開頭的,目的是紀念念 Philippe Flajolet 博士(法國人)對組合數(shù)學和基數(shù)計算算法分析的研究。
相關命令
命令 | 說明 | 示例 |
---|---|---|
pfadd | 把任意數(shù)量的元素添加到指定的HyperLogLog集合里,如果內部存儲被修改了返回1,否則返回0 | pfadd p1 sz ls ww? |
pfcount | 這個命令對于單個key時,給定key的HyperLogLog集合的近似基數(shù),對于多個key時,返回所有key的HyperLogLog集合的并集的近似基數(shù),如果key不存在則返回0 | pfcount p1 p2 p3 |
pfmerge | 把多個HyperLogLog集合合并為一個HyperLogLog集合,并存儲到指定的key中,合并后的HyperLogLog的基數(shù)接近于所有被合并集合的并集 | pfmerge destpf p1 p3:把p1與p3合并后的集合存在destpf |
應用場景
HyperLogLog 可對數(shù)據(jù)量超級龐大的日志數(shù)據(jù)做不精確的去重計數(shù)統(tǒng)計。
注意:這個統(tǒng)計不是是精確的!!Redis官方給的誤差在0.81%
四、Geospatial操作
Geospatial,地理空間
Redis在3.2版本中引入的數(shù)據(jù)類型。這個類型本質上仍是一個集合,只不過這個集合元素比較特殊,它是由三個部分構成的數(shù)據(jù)結構。這種數(shù)據(jù)結構稱為空間元素。
- 經(jīng)度:longitude。有效值是:[-180,180]。正數(shù)表示東經(jīng),負數(shù)表示西經(jīng)
- 緯度:latitude。有效緯度是:[-85.05112878,85.05112878]。正的表示北緯,負的表示南緯
- 位置名稱:為經(jīng)緯度所標注的位置命名和名稱,也稱為Geospatial集合的空間元素名稱
可以通過這個類型設置、查詢某地理位置的經(jīng)緯度,查詢某范圍內的空間元素,計算兩個空間元素之間的距離等,它的命令都是以geo開頭的。
相關命令
命令 | 說明 | 示例 |
---|---|---|
geoadd | 把一個或多個空間元素添加到指定的空間集合中,如果輸入一個超出范圍的經(jīng)緯度時會返回一個錯誤 | geoadd location 120.58 31.30 sz |
geopos | 從指定的地址空間中返回指定元素(可以指定返回多個元素)的位置(經(jīng)緯度),返回的值是一個數(shù)組 | geopos location sz cd |
geodist | 返回兩個給定位置之間的距離,其中unit單位必須是以下單位中的一種(默認是米) m:米 km:千米 mi:英里 ft:英尺 如果兩個位置之間其中一個不存在,那么返回空值,計算距離在極端情況下可能會產生最大0.5%的差距 | geodist location cd sz KM |
geohash | 返回一個或多個位置元素的geohash值,這個geohash是一種地址編碼方法,它可以把二維空間經(jīng)緯度數(shù)據(jù)編碼為一個字符串,主要用于底層應用或者調試,實際作用并不大 | geohash location cd |
georadius | 以給定的緯度為中心,返回指定地址空間中包含的所有位置元素中,與中心距離不超過給定半徑。返回的時候還可以攜帶上額外的信息
命令中可以排序位置,可以通過以下兩個參數(shù),用戶可以指定被返回位置元素的排序方式
注意:默認情況下,這個命令會返回所有匹配的位置元素,雖然可以使用count來指定獲取前n個元素,但是命令內部可能會需要對所有匹配的元素進行處理,對一個非常大的區(qū)域進行搜索時,使用count取少量的元素也可能非常慢。 | georadius location 111.6 29.046 100 KM |
georadiusbymember | 這個命令與georadius是相同的,都是可以找出位于指定范圍內的元素,但是這個命令的中心點是由位置元素給定的而不是單獨再輸入一個中心點 | georadiusbymember location cd 1000 KM |
其它 | 因為GEO 數(shù)據(jù)實際上被存儲在有序集合(sorted set)中,所以我們一樣可以使用Sorted set中的一些命令 | zrem location p1 p2:刪除其中指定的元素 zrange location 0 -1:遍歷元素 |
應用場景
Geospatial 的意義是地理位置,所以其主要應用地理位置相關的計算。如,微信中的“附近”功能,釘釘中的“距離打卡”...
五、發(fā)布/訂閱操作
發(fā)布/訂閱,也就是pub/sub,是一種消息通信模式。發(fā)布者也就是消息的生產者,生產和發(fā)布消息到存儲系統(tǒng)當中,訂閱者也就是消息的消費者,從存儲系統(tǒng)中接收和消費消息。這個存儲系統(tǒng)可以是文件系統(tǒng)FS,消息中間件MQ,數(shù)據(jù)庫管理系統(tǒng)DBMS,也可以是Redis。整個消息發(fā)布者、訂閱者與存儲系統(tǒng)稱為消息系統(tǒng)。
消息系統(tǒng)中的訂閱者訂閱了某類消息后,只要存儲系統(tǒng)中存在這類消息,就可以不斷地接收并消費這些信息。當沒有這類消息后,訂閱者的接收、消費會阻塞,當發(fā)布者把消息寫入到存儲系統(tǒng)后,立即會喚醒訂閱者。當存儲系統(tǒng)放滿時,不同的發(fā)布者會有不同的處理方式,比如:阻塞發(fā)布者的發(fā)布,等待可用的存儲空間或者是把多余的消息丟失。
不同的消息系統(tǒng)消息的發(fā)布/訂閱方式也是不同的,如RocketMQ、Kafka等中間件構成的消息系統(tǒng)中,發(fā)布/訂閱的消息都是以主題Topic分類的,而Redis當中則是以頻道Channel分類的。
?相關命令
命令 | 說明 | 示例 |
---|---|---|
subscribe | redis客戶端通過一個subscribe命令可以同時訂閱任意數(shù)量的頻道,在輸出了訂閱主題后,命令處理阻塞的狀態(tài)等待相關頻道的消息。 返回訂閱的結果報告 | subscribe news sports:訂閱news和sports |
publish | redis客戶端通過一條publish命令可以發(fā)布一個頻道的消息,返回值是接收到這個消息的訂閱者數(shù)量。 | publish news "this is a fun news" |
psubscribe | 訂閱一個或多個符合給定模式的頻道,這里可以指定模式,這個模式中只能使用*號,如:it* 表示的是可以匹配所有以it開頭的頻道,如:it.news,it.blog|,it.tech等都可以匹配到 | psubscribe it.* news.* |
unsubscribe | redis客戶端退訂指定的頻道,如果沒有指定具體的頻道,那么客戶端使用subscribe命令都會被退訂,這個命令會返回一個信息,告知客戶端所有被退訂的頻道。 | |
punsubscribe | 這個與unsubcribe類似只是說它退訂的是帶模式的頻道。 | |
pubsub | 它是一個查看訂閱與發(fā)布系統(tǒng)狀態(tài)的內省命令集,它由數(shù)個不同格式的子命令組成
|
六、Redis事務
Redis的事務本質上是一組命令的批處理,這組命令在執(zhí)行過程中會被順序地,一次性全部執(zhí)行完畢,只要沒有出現(xiàn)語法錯誤,這組命令在執(zhí)行期間是不會被中斷的。
Redis事務特性
Redis的事務只保證數(shù)據(jù)的一致性。
- 不具備原子性,這組命令中的某些命令執(zhí)行失敗不會影響到其它命令的執(zhí)行
- 沒有復雜的隔離級別,這組命令使用樂觀鎖機制實現(xiàn)簡單的隔離性
- 這組命令的執(zhí)行結果是被寫入到內存的,是否持久化取決于Redis的持久化策略,與事務無關
Redis事務實現(xiàn)
三個命令
Redis事務通過三個命令進行控制
muti:開始事務
exec:執(zhí)行事務
discard:取消事務
基本使用
在multi執(zhí)行后就會開啟事務,在其后每寫的一個命令都會放到隊列當中,當執(zhí)行exec的時候才會一個一個執(zhí)行隊列中的命令。
如果我們事務開啟了并且命令也放到隊列中了,但是我們此時執(zhí)行了discard則會取消所有命令
exec與discard兩者是互斥的。最后要么執(zhí)行exec要么執(zhí)行discard
以上的兩個命令set age 20 與 incr age是一組,在執(zhí)行過程中不會被中斷
如果我們不執(zhí)行exec,而是執(zhí)行discard則會取消隊列中的命令執(zhí)行
Redis事務異常處理
語法錯誤
當事務中的命令出現(xiàn)語法錯誤時,整個事務在exec執(zhí)行時會被取消。
因為在隊列中的命令存在錯誤的語法,這個時候exec會取消執(zhí)行會事務中的命令
執(zhí)行過程中異常?
如果事務中的命令沒有語法錯誤,但是在執(zhí)行過程中出現(xiàn)異常,這個異常不會影響到其它命令的執(zhí)行
這里可以看到事務中的命令都是沒有語法錯誤的,但是在執(zhí)行的時候第二個命令出現(xiàn)的執(zhí)行異常,這個時候第一個命令已經(jīng)執(zhí)行成功了,第二個不會執(zhí)行。此時執(zhí)行命令get score會返回A是因為這個命令執(zhí)行成功了!
Redis事務隔離機制
為什么要隔離呢?
在并發(fā)的場景下可能會出現(xiàn)多個客戶端對同一數(shù)據(jù)進行修改的情況。
示例如下:
假設我們有一個賬戶,里面有金額100元,A客戶端要扣減80元,B客戶端要扣減50元。首先他們會去看是否夠扣減,兩個并發(fā)執(zhí)行查看到的金額是100,這對于兩者來說都是夠減的,于似乎兩者都進行扣減金額的動作,這時候時候會出現(xiàn)賬戶“超扣”的情況。
? ? ?
?為了解決這種情況,Redis事務通過樂觀鎖機制實現(xiàn)了多線程下的執(zhí)行隔離。
隔離實現(xiàn)
Redis通過watch命令再配合事務實現(xiàn)了多線程下的執(zhí)行隔離
我們先watch 一個key,再使用multi開事務
? ?
實際上我們watch account的時候,我們不僅看到account的值是100,而且可以看到有一個版本是1,當另一個客戶端對這個值進行修改后,account中的版本號已經(jīng)變?yōu)榱?,此時在執(zhí)行命令時會發(fā)現(xiàn)watch中的account的版本是1小于實際數(shù)據(jù)的版本2表示現(xiàn)在的版本已經(jīng)過時了。執(zhí)行時就會取消掉。
版本號要大于等于原數(shù)據(jù)的版本號時才能執(zhí)行,否則不能執(zhí)行。
七、benchmark測試工具
在Redis安裝安成后會自動安裝一個redis-benchmark測試工具,它是一個壓力測試工具,用于測試Redis的性能。
Redis安裝完成后這個具體在:/usr/local/bin/
通過redis-benchmark --help命令可以查看到其用法。
常用的一些命令選項:
- -c ?表示并行連接的數(shù)量,默認是50
- -n 表示請求的總數(shù)量,默認值是100000
- -h 表示連接的Redis的ip,如查是本機可以省略
- -p 表示Redis的端口號,如果是6379則可以省略
- -d 表示數(shù)據(jù)的大小,get/set命令時其操作的value的數(shù)據(jù)長度,單位字節(jié),默認值是3,這個測試其它命令的時候沒有用
- -k 表示這些并發(fā)連接的狀態(tài),如果是1表示一直處于連接狀態(tài)(keep alive);0表示中間會出現(xiàn)斷連然后重連接的情況(reconnect),默認值是1
基本測試
命令:redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 200000
這個測試的命令表示使用100個客戶端并發(fā)連接,總共發(fā)起200000個請求
測試的報告如下(每一個命令都是一樣的報告結構,下面是set命令測試報告結果):
上在這個測試會把所有命令都測試一遍
指定命令測試
命令:?redis-benchmark -t set,lpush,sadd -c 100 -n 100000 -q
上面這個命令中-n如果是100000表示是默認的請求可以省略不寫;
其它的選項說明:
- -q 表示測試結果只給出總述性報告
- -t 指定要測試的命令,多個命令之間使用逗號進行分隔,不能有空格
通過benchmark的測試我們看到,redis的性能非常高,其中還是得益于它對五種值類型全部進行了設計,經(jīng)過設計后變成了適合于它自己的結構。