1.動態(tài)字符串
redis中的字符串,是對c語言中的字符串(以空字符串結尾的字符數(shù)組)進行了一層包裝,自己定義了一個結構塊,名為動態(tài)字符串(simple dynamic string ,SDS)的抽象類型,并且將SDS作為redis默認字符串類型(可以表示字符串盟戏、整數(shù)、位圖)
參考:redis的設計與實現(xiàn)
1.1.動態(tài)字符串結構
struct SDS{
//記錄buf數(shù)組中已經(jīng)使用的字節(jié)數(shù)量
//等于sds所保存的字符串長度
int8 len; // 1byte
// 記錄buf中未使用的字節(jié)數(shù)量
int8 free; // 1byte
//類型標記 int(long 類型整數(shù)), embstr 嵌入式字符串(編碼后長度小于 44 字節(jié)的字符串) , raw(sds 字符串)
int8 flags; // 1byte
//字符數(shù)組,用于保存字符串
byte[] buf[]; // 內(nèi)聯(lián)數(shù)組,長度為 len
}
1.2.SDS字符串優(yōu)點
為什么redis性能高,為什么redis要封裝字符串.其原因主要在以下幾點:
在常數(shù)的獲取復雜度上:
SDS可以直接返回字符串長度,C語言的需要遍歷
緩沖區(qū)溢出問題:
在擴容或者修改時,redis每次會檢查free的值,從而直接指導buf是否夠用.不夠會進行擴容處理.而C語言中的字符串則可能出現(xiàn)由于內(nèi)存已經(jīng)分配,修改其中的字符串導致內(nèi)存溢出問題.
內(nèi)存分配問題
每次C語言處理字符串時,需要重新分配內(nèi)存,但是redis由于是自定義的結構,相當于可以預支一定的內(nèi)存.所以可以減少分配次數(shù).在SDS中,如果len小于1MB,則free和len相同,否則redis會每次預支1MB的free
惰性釋放
redis的惰性釋放由于優(yōu)化redis中字符串的縮短操作.當字符串縮短時,redis不會立馬就釋放空間,而是使用free記錄可利用的空間,便于以后使用.
1.3.字符串常用關命令
命令 | 描述 |
---|---|
set key value | 存放一個key-value鍵值對 |
get key | 根據(jù)key獲取對應的值 |
strlen key | 獲取字符串長度 |
getrange key index1 index2 | 獲取字符串指定索引范圍字符 |
getset key newValue | 獲取key的值,并為其設置新的值 |
mset key1 value1 key2 value2 | 批量設置key value值 |
mget key1 key2 | 批量獲取key的值 |
setnx key value | 不存在key 就插入 key value ,返回值 1 成功 0 失敗 |
setrange key index value | 找到指定的key,使用value值,從index索引處開始替換 |
incr key | 遞增,只對值為數(shù)字生效 |
incrby key 值 | 指定自增的值 |
decr key | 遞減 |
decrby key 值 | 指定遞減的值 |
incrbyfloat key 值 | 指定遞增的小數(shù),不推薦 可能精度丟失 |
append key 值 | 為key的值追加內(nèi)容 |
127.0.0.1:6379> set num 123456789
OK
127.0.0.1:6379> get num
"123456789"
127.0.0.1:6379> strlen num
(integer) 9
127.0.0.1:6379> getrange num 0 1
"12"
127.0.0.1:6379> getset num 1234567890
"123456789"
127.0.0.1:6379> get num
"1234567890"
127.0.0.1:6379> mset num1 1 num2 2
OK
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> mget num1 num2
1) "1"
2) "2"
127.0.0.1:6379> setnx num2 2
(integer) 0
127.0.0.1:6379> setnx num3 3
(integer) 1
127.0.0.1:6379> setrange num 1 000000000
(integer) 10
127.0.0.1:6379> get num
"1000000000"
127.0.0.1:6379> incr num1
(integer) 2
127.0.0.1:6379> incrby num2 10
(integer) 12
127.0.0.1:6379> decr num1
(integer) 1
127.0.0.1:6379> get num1
"1"
127.0.0.1:6379> decrby num2 10
(integer) 2
127.0.0.1:6379> incrbyfloat num1 0.222
"1.222"
127.0.0.1:6379> append num 1
(integer) 11
127.0.0.1:6379> get num
"10000000001"
1.4.字符串類型問題
使用命令 object encoding key可以獲取redis中存儲的數(shù)據(jù)的類型.每個數(shù)據(jù)在redis中都是一個對象.
這個命令的返回值有:
- int long整形
- embstr 嵌入式字符串(redis 5.x 新增的)
- raw redis中的動態(tài)字符串
在使用該命令時,會發(fā)現(xiàn)一個有意思的問題.
127.0.0.1:6379> set num '1234567890 1234567890 1234567890 1234567890'
OK
127.0.0.1:6379> strlen num
(integer) 43
127.0.0.1:6379> object encoding num
"embstr"
127.0.0.1:6379>set num '1234567890 1234567890 1234567890 1234567890 1'
OK
127.0.0.1:6379> object encoding num
"raw"
注意:當字符串長度為不小于44時,該類型為raw類型
在redis中,每個數(shù)據(jù)都會當做一個對象處理,而每個對象都會有個頭信息.每個對象的頭信息一般是==16==個字節(jié)
struct RedisObject {
int4 type; // 4bits 類型
int4 encoding; // 4bits
int24 lru; // 24bits 3字節(jié) LRU 信息
int32 refcount; // 4bytes 4字節(jié)
void *ptr; // 8bytes,64-bit system 8字節(jié)
};
其中:
-
refcount
引用計數(shù),當引用計數(shù)為0時,對象就會被銷毀,內(nèi)存會回收.4字節(jié) -
ptr
指針指向?qū)ο髢?nèi)容的具體存儲位置.8字節(jié)
SDS結構體的大小
struct SDS {
int8 capacity; // 1byte
int8 len; // 1byte
int8 flags; // 1byte
byte[] content; // 內(nèi)聯(lián)數(shù)組,長度為 capacity
}
SDS的大小是: 1+1+1+?,所以一個SDS的大小最小是3個字節(jié).所以存在redis中一個字符串數(shù)據(jù)大小,最小16+3個字節(jié),19個字節(jié).
而內(nèi)存分配器等分內(nèi)存的大小的單位是2的冪次:2/4/8/16/32/64.為了能容納一個完成的字符串,那么最少分配32個字節(jié)空間.如果字符串稍微大一點就是64個字節(jié)空間.如果總體超出了 64 字節(jié)花鹅,Redis 認為它是一個大字符串,不再使用 emdstr 形式存儲,而該用 raw 形式点骑。
為什么redis會在超過64個字節(jié)時當做raw處理呢.或者說為什么字符串長度為44時,就變?yōu)榱藃aw呢?
首先,raw是指redis動態(tài)字符串,是radis對c語言原生字符串的一種包裝.而原生c語言的字符串,最后一個始終使用\0的字符串結尾,是為了方便使用glibc的字符串函數(shù)處理,及便于打印輸出.而64-19(所有頭占用的)=45
個字符串.字符串又是以\0結尾,所以embstr 最大能容納的字符串長度就是 44.