文章目錄
1 Redis數據類型
Redis包含五大基本數據類型和三個特殊類型,下面介紹數據類型的基本使用。
1.1 五大基本數據類型
1.1.1 String類型
String類型可以存數字、存字符串、存序列化的對象等
存數字時,可以執行加減操作
27.0.0.1:2222> set value 10
OK
127.0.0.1:2222> get value
"10"
127.0.0.1:2222> decr value # 減一操作
(integer) 9
127.0.0.1:2222> incrby value 5 # 執行加5
(integer) 14
存字符串
127.0.0.1:6379[2]> APPEND key1 abcd #在字符串中追加字符串
(integer) 6
127.0.0.1:6379[2]> strlen key1 #獲取字符串的長度
(integer) 6
127.0.0.1:6379[2]> GETRANGE key1 0 3 #截取範圍字符串(雙閉區間)
"v2ab"
127.0.0.1:6379[2]> mset k1 v1 k2 v2 k3 v3 #同時存入多個值
OK
存對象
存對象時,其實也是將對象序列化成字符串進行存儲。
1.1.2 List類型
list集合類型,在redis中可以頭插入/刪除、尾插入/刪除;可以尾插入尾刪除實現棧,尾插入頭刪除實現隊列,也可以單純的存可重複的集合元素。
lpush/rpush/lpop/rpop #左右插入 左右刪除
lindex list 1 #根據下標獲取值
lrange list 0 1 lrange list 0 -1 #下標範圍獲取值
llen list #獲取list的長度
lrem list 1 one #刪除list中one,若有多個只移除一個
ltrim list 1 2 #通過下標截取list,list將會被改變
lset list 0 item #將list的0號元素更新爲item
linsert list before one zone #將one的前面插入zone
1.1.3 Set類型
存儲不重複的集合元素,比如在商品秒殺中存儲某商品已經秒殺的用戶ID,防止重複秒殺。
Redis中還可以返回兩個集合的並集、交集等實現多元功能,比如兩個用戶的共同好友。
sadd myset hello # 添加值
smembers myset #查看所有值
sismember myset hello #是否有hello
scard myset #獲取set集合中的元素個數
srem myset hello #刪除元素
srandmember myset #隨機的獲取其中一個值
spop myset #隨機的刪除其中一個值
sdiff set1 set2 #返回兩個集合的差集
sinter set1 set2 #返回兩個集合的交集(共同好友)
1.1.4 Zset類型
在set的基礎上增加一個score用於排序。比如在博客文章閱讀排行榜中,可以返回閱讀量從高到低的排行。
zadd myset 1 one #1爲score,用於排序
zrangebyscore myset -inf +inf #按照score升序排列。-inf +inf代表負無窮和正無窮
zrangebyscore myset -inf 100 #按照score升序排列,並輸出score在[-inf, 100]之間的value
zrange myset 0 -1 #默認升序輸出全部
zrevrange myset 0 -1 #降序輸出全部
ZREVRANGEBYSCORE zset1 10 -10 #按score降序排列,並輸出score在[-10, 10] 之間的value
zrem salary zhangsan #刪除zhangsan
zcard salary #獲取salary有序集合中元素個數
zcount salary 100 1000 #獲取指定區間的元素個數
1.1.5 Hash類型
存入鍵值對
hset myhash k1 v1 #向myhash添加鍵值對k1-v1
hmset myhash k2 v2 k3 v3 #向myhash添加鍵值對
hget myhash k1
hmget myhash k2 k3
127.0.0.1:6379[2]> hgetall myhash #獲取所有的鍵值
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
hkeys myhash #獲取所有的key
hvals myhash #獲取所有的value
hexists myhash k1 #判斷字段是否存在
1.2 三種特殊類型
1.2.1 geospatial 地理位置
應用:朋友定位,附近的人,打車距離計算?
geoadd命令:添加地理位置
## 規則:兩極無法添加,一般下載城市數據,直接java程序導入。
## 有效經度 -180~180 有效緯度 -85~85
## geoadd key value(經度 緯度 名稱)
127.0.0.1:6379[2]> GEOADD china:city 116.40 39.90 beijing 121.44 31.21 shanghai
127.0.0.1:6379[2]> GEOADD china:city 104.07 30.67 chengdu 106.54 29.58 chongqing
127.0.0.1:6379[2]> GEOADD china:city 114.109 22.54 shenzhen 120.16 30.31 hangzhou
geopos命令:獲取地理位置
127.0.0.1:6379[2]> geopos china:city chengdu
1) 1) "104.07000213861465454"
2) "30.67000055930392222"
geodist命令:返回兩個位置之間的距離
單位: m km
127.0.0.1:6379[2]> geodist china:city chengdu beijing km 查看成都到北京的直線距離
"1516.1652"
georadius 以某個位置爲中心,找出半徑多少內的元素
附近的人功能實現?
127.0.0.1:6379[2]> GEORADIUS china:city 110 30 1000 km 距離110 30 直線距離小於1000km的城市
1) "chongqing"
2) "chengdu"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379[2]> GEORADIUS china:city 110 30 1000 km count 2 限制個數爲2,只查詢兩個
1) "chongqing"
2) "chengdu"
georadiusByMember 通過名字來確定位置,找出半徑多少內的元素
127.0.0.1:6379[2]> GEORADIUSbymember china:city chengdu 1000 km count 2
1) "chengdu"
2) "chongqing"
1.2.2 Hyperloglog基數統計
應用:統計網站訪問的人數,不可重複計算。
傳統方法:建立一個set保存用戶id,訪問一個用戶就往set存入,最後返回set大小
Hyperloglog統計:將用戶ID放入Hyperloglog即可
對比:放入Set的方法會佔用較多內存,Hyperloglog利用位圖減少內存開銷,但是有一定誤差
pfadd users a b c d e f g h i a c d a b #將userid放入
pfcount users #統計基數數量(排除重複元素)
1.2.3 bitmap位圖
位圖:按位存儲,每位只能存儲0或1,分別代表兩種狀態(多種狀態也可以擴展多個位代表某個狀態)
應用:統計用戶一年每天登錄狀態
127.0.0.1:6379[2]> setbit sign 0 1 #設置第0位爲1
127.0.0.1:6379[2]> setbit sign 1 1 #設置第1位爲1....
127.0.0.1:6379[2]> GETBIT sign 0 #獲取第0位
127.0.0.1:6379[2]> bitcount sign #獲取爲1的個數
127.0.0.1:6379[2]> bitcount sign 0 5 #獲取在0-5之間爲1的個數
2 Redis數據結構
2.1 String數據結構
String類型有三種儲存方式:int、embstr、raw類型
int儲存
當我們存入數字時,就會使用int編碼儲存,測試如下:
127.0.0.1:2222> set num 123456
OK
127.0.0.1:2222> OBJECT encoding num
"int"
那麼如果數字範圍大於Java的Integer範圍呢?
127.0.0.1:2222> set num 2147483648 # 32位int最大範圍是2147483647
OK
127.0.0.1:2222> OBJECT encoding num #輸出還是int,猜測是使用64位存儲
"int"
127.0.0.1:2222> set num 9223372036854775807
OK
127.0.0.1:2222> OBJECT encoding num
"int"
127.0.0.1:2222> set num 9223372036854775808
OK
127.0.0.1:2222> OBJECT encoding num # 大於64位int最大值後不再使用int儲存。
"embstr"
# 本人使用的是64位機器,不知道32位機中C++中int是多少?
存入浮點數呢?
127.0.0.1:2222> set num 1.9
OK
127.0.0.1:2222> OBJECT encoding num
"embstr"
可見Redis中的數值類型只有一個64位int,沒有浮點數。
embstr儲存
在int測試中,大於64位的數值會用embstr存儲。另外,在字符串或者浮點小於39字節的也會使用embstr存儲。
raw儲存
127.0.0.1:2222> set k aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
OK
127.0.0.1:2222> OBJECT encoding k
"raw" # 當儲存的字符串過長(大於39字節)就會使用raw類型
2.2 List數據結構
127.0.0.1:2222> LPUSH list 1 2 3 4 5
(integer) 5
127.0.0.1:2222> OBJECT encoding list
"quicklist"
quicklist簡單理解(寫着寫着就把詳解改成簡單理解…)
可以看到,List底層是使用一種叫quicklist的數據結構存儲。在redis的安裝目錄的src下打開quicklist.h,看看quicklist的結構定義:
typedef struct quicklist {
quicklistNode *head; // 頭結點指針。quicklistNode爲結點的數據結構
quicklistNode *tail; // 尾結點
unsigned long count; /* total count of all entries in all ziplists */
unsigned long len; /* number of quicklistNodes */
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
看到這隻能理解頭結點和尾結點。爲什麼會有count和len兩個計數?註釋的意思是count代表所有ziplist的entries數量,而len代表quicklistNodes的數量。
**問題一:**ziplist是什麼結構,爲什麼存在?不是像普通雙向鏈表一樣在quicklistNodes裏面存值麼?
繼續看quicklistNode的結構定義:
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl; // 這個是存的什麼? zl=ziplist?
unsigned int sz; /* ziplist size in bytes */
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
放眼整個quicklistNode結構,能夠存儲數據的好像只有zl這個指針了。看來繞不過ziplist了…於是查看ziplist.h,並沒有結構定義,而是在ziplist.c中定義宏,redis沒有提供一個結構體來保存壓縮列表的信息,而是提供了一組宏來定位每個成員的地址。(以下內容來源:https://blog.csdn.net/men_wen/article/details/70176753)
/* Utility macros */
// ziplist的成員宏定義
// (*((uint32_t*)(zl))) 先對char *類型的zl進行強制類型轉換成uint32_t *類型,
// 然後在用*運算符進行取內容運算,此時zl能訪問的內存大小爲4個字節。
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
//將zl定位到前4個字節的bytes成員,記錄這整個壓縮列表的內存字節數
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
//將zl定位到4字節到8字節的tail_offset成員,記錄着壓縮列表尾節點距離列表的起始地址的偏移字節量
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
//將zl定位到8字節到10字節的length成員,記錄着壓縮列表的節點數量
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
//壓縮列表表頭(以上三個屬性)的大小10個字節
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
//返回壓縮列表首節點的地址
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
//返回壓縮列表尾節點的地址
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
//返回end成員的地址,一個字節。
ziplist是由*一系列特殊編碼的連續內存塊組成的順序存儲結構*,類似於數組,ziplist在內存中是連續存儲的,但是不同於數組,爲了節省內存 ziplist的每個元素所佔的內存大小可以不同,每個節點可以用來存儲一個整數或者一個字符串。
空間中的結構組成如下圖所示:
- zlbytes:佔4個字節,記錄整個壓縮列表佔用的內存字節數。
- zltail_offset:佔4個字節,記錄壓縮列表尾節點entryN距離壓縮列表的起始地址的字節數。
- zllength:佔2個字節,記錄了壓縮列表的節點數量。
- entry[1-N]:長度不定,保存數據。
- zlend:佔1個字節,保存一個常數255(0xFF),標記壓縮列表的末端。
entry結點的定義如下:
- prev_entry_len:記錄前驅節點的長度。
- encoding:記錄當前節點的value成員的數據類型以及長度。
- value:根據encoding來保存字節數組或整數。
雲裏霧裏的理解了一下ziplist(沒辦法,C++知識不過關),回到quicklist,quicklist的quicklistNode中存儲的數據是一個ziplist,所以問題一解決。
最後放一張抄過來的圖,很清晰的表達了quicklist的結構,原文博客:https://blog.csdn.net/harleylau/article/details/80534159
2.3 Set數據結構
127.0.0.1:2222> SADD set 1 2 3 4
(integer) 4
127.0.0.1:2222> OBJECT encoding set
"intset"
127.0.0.1:2222> SADD set aa bb cc dd
(integer) 4
127.0.0.1:2222> OBJECT encoding set
"hashtable"
Set有兩種實現:intset和hashtable。顯然intset用於存儲int類型的set,如果有非intset則用hashtable。
2.3.1 intset
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
數據結構非常簡單,用數組存儲int集合。數組查找效率低,所以猜測肯定是有序數組,看一下添加元素的函數:
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 1;
/* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if (valenc > intrev32ifbe(is->encoding)) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value);
} else {
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
if (intsetSearch(is,value,&pos)) { //搜索元素是否存在
if (success) *success = 0;
return is;
}
is = intsetResize(is,intrev32ifbe(is->length)+1); //擴容,數組大小加一
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
}
_intsetSet(is,pos,value); //賦值
is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
return is;
}
顯然,使用數組來存儲,在添加或刪除元素時效率低下,中間涉及到數組擴容和元素移動。
2.3.2 Hashtable
(待續)
2.4 Zset
127.0.0.1:2222> ZADD zs 10 aaaa 22 fbbb 9 ffccc 7 abh 6 odd
(integer) 5
127.0.0.1:2222> OBJECT encoding zs
"ziplist"
可見Zset可以使用ziplist儲存。
在配置文件中,有下面兩個配置,分別代表zset使用ziplist存儲的時候,最大限制存儲entries的個數和每個節點最大存儲字節數,只有破壞一個條件就會轉成skiplist。
# Similarly to hashes and lists, sorted sets are also specially encoded in
# order to save a lot of space. This encoding is only used when the length and
# elements of a sorted set are below the following limits:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
修改zset-max-ziplist-entries爲5,重啓redis,測試一下
127.0.0.1:2222> OBJECT encoding zs
"skiplist" # 變成skiplist
2.4.1 skiplist數據結構
skiplist可以理解成多層有序鏈表。數據結構如圖
圖片來源:https://blog.csdn.net/qianshangding0708/article/details/104935313?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase)
跳錶相對於紅黑樹的優勢:
- 範圍查找簡單,紅黑樹需要找到最小值後中序遍歷
- 插入和刪除元素更簡單,而紅黑樹可能有子樹的調整
- 實現更加簡單,更少的指針。
參考鏈接:https://blog.csdn.net/men_wen/article/details/70176753
https://blog.csdn.net/harleylau/article/details/80534159