深刻學習Redis(二),基本類型【String】剖析

更多精彩文章,關注公衆號【ToBeTopJavaer】,更有數萬元精品vip資源免費等你來拿!!!

今天咱們來介紹Redis的基本數據類型


整體來講:String、Hash、Set、List、Zset、Hyperloglog、Geo、Streamjava

經常使用的數據類型:redis

最基本最經常使用的數據類型就是String。set 和 get 命令就是 String 的操做命令。spring

一、 String字符串數據庫

能夠用來存儲字符串,整數,浮點數。數組

1.一、 基本操做緩存

設置多個值(批量操做,原子性)安全

設置值,如何Key存在,設置不成功session

基於此可實現分佈式鎖。 用 del key 釋放鎖。數據結構

但若是釋放鎖的操做失敗了, 致使其餘節點永遠獲取不到鎖, 怎麼辦?分佈式

加過時時間。 單獨用 expire 加過時, 也失敗了, 沒法保證原子性, 怎麼辦? 多參數。

set key value [expiration EX seconds|PX milliseconds][NX|XX]

(整數) 值遞增

(整數) 值遞減

浮點數增量

獲取多個值

獲取值長度

字符串追加內容

獲取指定範圍的字符

1.二、 存儲實現原理

數據模型

set name javaHuang爲例,由於 Redis 是 KV 的數據庫,它是經過 hashtable 實現的(我

們把這個叫作外層的哈希)。因此每一個鍵值對都會有一個 dictEntry,裏面指向了 key 和 value 的指針。next 指向下一個 dictEntry。源碼以下:

typedef struct dictEntry {
void *key; /* key 關鍵字定義 */
union {
void *val; uint64_t u64; /* value 定義 */
int64_t s64; double d;
} v;
struct dictEntry *next; /* 指向下一個鍵值對節點 */
} dictEntry;

key 是字符串,可是 Redis 沒有直接使用 C 的字符數組,而是存儲在自定義的 SDS

中。

value 既不是直接做爲字符串存儲,也不是直接存儲在 SDS 中,而是存儲在

redisObject 中。實際上五種經常使用的數據類型的任何一種,都是經過 redisObject 來存儲

的。

redisObject

redisObject 的定義的源碼以下:

typedef struct redisObject {
unsigned type:4; /* 對象的類型, 包括: OBJ_STRING、 OBJ_LIST、 OBJ_HASH、 OBJ_SET、 OBJ_ZSET */
unsigned encoding:4; /* 具體的數據結構 */
unsigned lru:LRU_BITS; /* 24 位, 對象最後一次被命令程序訪問的時間, 與內存回收有關 */
int refcount; /* 引用計數。 當 refcount 爲 0 的時候, 表示該對象已經不被任何對象引用, 則能夠進行垃圾回收了
*/
void *ptr; /* 指向對象實際的數據結構 */
} robj;

可使用 type 命令來查看對外的類型。

下面我來設置三種Key

而後咱們獲取這三種Key的內部編碼

因而咱們可知,字符串類型的內部編碼有三種:

一、int,存儲 8 個字節的長整型(long,2^63-1)。

二、embstr, 表明 embstr 格式的 SDS(Simple Dynamic String 簡單動態字符串),

存儲小於 44 個字節的字符串。

三、raw,存儲大於 44 個字節的字符串(3.2 版本以前是 39 字節)。

/* object.c */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44

二、接下來咱們一塊兒來探討幾個問題

2.一、什麼是SDS

SDS是Redis 中字符串的實現。

在 3.2 之後的版本中,SDS 又有多種結構(sds.h):sdshdr五、sdshdr八、sdshdr1六、

sdshdr3二、sdshdr64,用於存儲不一樣的長度的字符串,分別表明 2^5=32byte,

2^8=256byte,2^16=65536byte=64KB,2^32byte=4GB。

struct __attribute__ ((__packed__)) sdshdr8 {
  uint8_t len; /* 當前字符數組的長度 */
  uint8_t alloc; /*當前字符數組總共分配的內存大小 */
  unsigned char flags; /* 當前字符數組的屬性、 用來標識究竟是 sdshdr8 仍是 sdshdr16 等 */
  char buf[]; /* 字符串真正的值 */
};

2.二、爲何 Redis 要用 SDS 實現字符串?

咱們知道,C 語言自己沒有字符串類型(只能用字符數組 char[]實現)。

一、使用字符數組必須先給目標變量分配足夠的空間,不然可能會溢出。

二、若是要獲取字符長度,必須遍歷字符數組,時間複雜度是 O(n)。

三、C 字符串長度的變動會對字符數組作內存重分配。

四、經過從字符串開始到結尾碰到的第一個'\0'來標記字符串的結束,所以不能保存圖片、音頻、視頻、壓縮文件等二進制(bytes)保存的內容,二進制不安全。

SDS 的特色:

一、不用擔憂內存溢出問題,若是須要會對 SDS 進行擴容。

二、獲取字符串長度時間複雜度爲 O(1),由於定義了 len 屬性。

三、經過「空間預分配」( sdsMakeRoomFor)和「惰性空間釋放」,防止屢次重分配內存。

四、判斷是否結束的標誌是 len 屬性(它一樣以'\0'結尾是由於這樣就可使用 C 語言中函數庫操做字符串的函數了),能夠包含'\0'。

2.三、embstr 和 raw 的區別

embstr 的使用只分配一次內存空間(由於 RedisObject 和 SDS 是連續的),而 raw須要分配兩次內存空間(分別爲 RedisObject 和 SDS 分配空間)。

所以與 raw 相比,embstr 的好處在於建立時少分配一次空間,刪除時少釋放一次空間,以及對象的全部數據連在一塊兒,尋找方便。

而 embstr 的壞處也很明顯,若是字符串的長度增長鬚要從新分配內存時,整個RedisObject 和 SDS 都須要從新分配空間,所以 Redis 中的 embstr 實現爲只讀。

2.四、int 和 embstr 何時轉化爲 raw

當 int 數 據 不 再 是 整 數 , 或 大 小 超 過 了 long 的 範 圍(2^63-1=9223372036854775807)時,自動轉化爲 embstr。

2.五、明明沒有超過閾值,爲何變成 raw 了

對於 embstr,因爲其實現是隻讀的,所以在對 embstr 對象進行修改時,都會先轉化爲 raw 再進行修改。

所以,只要是修改 embstr 對象,修改後的對象必定是 raw 的,不管是否達到了 44個字節。

2.六、當長度小於閾值時,會還原嗎

關於 Redis 內部編碼的轉換,都符合如下規律:編碼轉換在 Redis 寫入數據時完成,且轉換過程不可逆,只能從小內存編碼向大內存編碼轉換(可是不包括從新 set)。

2.七、爲何要對底層的數據結構進行一層包裝呢

經過封裝,能夠根據對象的類型動態地選擇存儲結構和可使用的命令,實現節省空間和優化查詢速度。

三、應用場景

緩存

String 類型

例如:熱點數據緩存,對象緩存,全頁緩存。能夠提高熱點數據的訪問速度。

數據共享分佈式

STRING 類型,由於 Redis 是分佈式的獨立服務,能夠在多個應用之間共享,例如:分佈式 Session

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

分佈式鎖

STRING 類型 setnx 方法,只有不存在時才能添加成功,返回 true。

簡單代碼以下:

public Boolean getLock(Object lockObject){
    jedisUtil = getJedisConnetion();
    boolean flag = jedisUtil.setNX(lockObj, 1);
    if(flag){
        expire(locakObj,10);
    }
    return flag;
}
​public void releaseLock(Object lockObject){
    del(lockObj);
}

全局 ID

INT 類型,INCRBY,利用原子性

incrby userid 1000

計數器

INT 類型,INCR 方法

例如:文章的閱讀量,微博點贊數,容許必定的延遲,先寫入 Redis 再定時同步到數據庫。

限流

INT 類型,INCR 方法

以訪問者的 IP 和其餘信息做爲 key,訪問一次增長一次計數,超過次數則返回 false。

位統計

String 類型的 BITCOUNT(1.6.6 的 bitmap 數據結構介紹)。

字符是以 8 位二進制存儲的。

set k1 a
setbit k1 6 1
setbit k1 7 0
get k1

a 對應的 ASCII 碼是 97,轉換爲二進制數據是 01100001

b 對應的 ASCII 碼是 98,轉換爲二進制數據是 01100010

由於 bit 很是節省空間(1 MB=8388608 bit),能夠用來作大數據量的統計。

例如:在線用戶統計,留存用戶統計

setbit onlineusers 0 1
setbit onlineusers 1 1
setbit onlineusers 2 0

支持按位與、按位或等等操做。

BITOP AND destkey key [key ...] , 對一個或多個 key 求邏輯並, 並將結果保存到 destkey 。
BITOP OR destkey key [key ...] , 對一個或多個 key 求邏輯或, 並將結果保存到 destkey 。
BITOP XOR destkey key [key ...] , 對一個或多個 key 求邏輯異或, 並將結果保存到 destkey 。
BITOP NOT destkey key , 對給定 key 求邏輯非, 並將結果保存到 destkey 。

例子:計算出 7 天都在線的用戶

BITOP "AND" "7_days_both_online_users" "day_1_online_users" "day_2_online_users" ... "day_7_online_users"

基本數據類型String基本算是分析透了,接下來還有幾個經常使用的基本類型的深刻探討,敬請期待。

更多精彩文章,關注公衆號【ToBeTopJavaer】,更有數萬元精品vip資源免費等你來拿!!!

qrcode_for_gh_c439eb8460a8_344.jpg

相關文章
相關標籤/搜索