redis之String字符串類型以及存儲原理

文章目錄

1.redis String類型的數據類型,是二進制安全的,那麼我們如何理解這個二進制安全呢?

0.概述

0.1Redis數據類型 中文官網 (不推薦,更新不及時)

http://www.redis.cn/topics/data-types-intro

0.2.Redis數據類型 英文官網 (推薦)

https://redis.io/topics/data-types-intro

1.String類型:是最簡單的一種數據類型,key-value的形式存儲,key都是String類型

0.3.key的最大大小:512M

0.4.value的最大大小:512M

0.5.可以存儲的數據類型:字符串,整數,浮點型

下面設置如下信息內容:
gaoxinfu年齡:18歲
gaoxinfu性別:男
gaoxinfu收入:10000.10
127.0.0.1:6379> mset gaoxinfu_age 18 gaoxinfu_sex man gaoxinfu_salary 10000.10
OK
127.0.0.1:6379> 

1.String 字符串類型的相關命令

1.2.簡單的賦值:set key1 value1

1.2.1.格式

set key value [expiration EX seconds|PX milliseconds] [NX|XX]
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> 

1.2.2.批量操作

1.2.2.1.批量賦值:mset key2 value2 key3 value3

127.0.0.1:6379> mset key2 value2 key3 value3
OK
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> get key3
"value3"
127.0.0.1:6379> 

1.2.2.3.批量取值:mget name2 name3

127.0.0.1:6379> keys *
 1) "name2"
 2) "salary"
 3) "name3"
 4) "rename1"
 5) "gaoxinfu_sex"
 6) "gaoxinfu_age"
 7) "key1"
 8) "gaoxinfu_salary"
 9) "frank_age"
10) "key3"
11) "key2"
127.0.0.1:6379> mget name2 name3 
1) "gaoxinfu2"
2) "gaoxinfu3"
127.0.0.1:6379> 

1.2.3.如果key不存在,則設置key的值爲value:setnx key value

1.2.3.2.設置失敗返回:0

由於key:gaoxinfu_age前面已經存在,所以設置的時候沒成功,返回了0

127.0.0.1:6379> setnx gaoxinfu_age 19
(integer) 0
127.0.0.1:6379> 

1.2.3.2.設置成功返回:1

新創建的key:frank_age 設置成功

127.0.0.1:6379> setnx frank_age 19
(integer) 1
127.0.0.1:6379> 

1.2.3.3.總結:setnx可以用來作爲分佈式鎖,進行資源的競爭,設置成功即爲獲得鎖;

setnx可以用來作爲分佈式鎖,進行資源的競爭,設置成功即爲獲得鎖;
當然,獲取完鎖之後,如果要釋放需要用del命令

1.2.4.刪除key(這個可以作爲釋放鎖的命令):del frank_age

127.0.0.1:6379> del frank_age
(integer) 1
127.0.0.1:6379> 

1.2.4.1.設置10s後過期:set key value ex 10

設置10s後過期

127.0.0.1:6379> set name10 gaoxinfu ex 10
OK
127.0.0.1:6379> 

10s後去查詢,已經不存在

127.0.0.1:6379> get name10
(nil)
127.0.0.1:6379> 

1.2.4.2.單獨設置過期時間10s:EXPIRE name11 10

127.0.0.1:6379> set name11 gaoxnfu11
OK
127.0.0.1:6379> EXPIRE name11 10
(integer) 1
127.0.0.1:6379> 

1.2.4.3.如果不存在的時候,設置key的值並且過期時間爲10s:set name12 gaoxinfu12 ex 10 nx

127.0.0.1:6379> set name12 gaoxinfu12 ex 10 nx
OK
127.0.0.1:6379> 

10s後再次查詢,已經不存在

127.0.0.1:6379> get name12
(nil)
127.0.0.1:6379> 

在這裏插入圖片描述

1.2.4.4.只有key存在的時候,設置key的值並且過期時間爲10s:set name13 gaoxinfu13_new ex 10 xx

127.0.0.1:6379> set name13 gaoxinfu13
OK
127.0.0.1:6379> set name13 gaoxinfu13_new  ex 10 xx
OK
127.0.0.1:6379> 

在這裏插入圖片描述

1.2.5.value爲數值類型的操作

1.2.5.1.設置值爲數值:set frank_age 18

127.0.0.1:6379> set frank_age 18
OK
127.0.0.1:6379> type frank_age
string

1.2.5.2.減少1:dec frank_age

127.0.0.1:6379> dec frank_age
(error) ERR unknown command `dec`, with args beginning with: `frank_age`, 
127.0.0.1:6379> decr frank_age
(integer) 17

1.2.5.3.增加1:incr frank_age

127.0.0.1:6379> incr frank_age
(integer) 18
127.0.0.1:6379> 

1.2.5.4.增加n:incrby frank_age 10

127.0.0.1:6379> INCRBY frank_age 10
(integer) 29
127.0.0.1:6379> 

1.2.5.5.減少n:DECRBY frank_age 2

127.0.0.1:6379> DECRBY frank_age 2
(integer) 27
127.0.0.1:6379> 

1.2.6.value爲浮點類型的操作

1.2.6.1.增加n:incrbyfloat salary 8.4

127.0.0.1:6379> set salary 1002.6
OK
127.0.0.1:6379> incrbyfloat salary 8.4
"1011"
127.0.0.1:6379> 

1.2.6.2.減少n:incrbyfloat salary 8.4

1.2.7.追加內容:APPEND name2 frank

127.0.0.1:6379> get name2
"gaoxinfu2"
127.0.0.1:6379> APPEND name2 frank
(integer) 14
127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> 

1.2.8.獲取字符串指定位置內容:getrange name2 9 14

127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> getrange name2 9 14
"frank"
127.0.0.1:6379> 
其中 9是開始位置,14是結束位置

1.2.9.其他命令參考:http://redisdoc.com/string/index.html

http://redisdoc.com/string/index.html

2.源碼

1.首先,redis的數據存儲通過dictEntry去存儲;

2.1.dictEntry數據結構-源碼

typedef struct dictEntry {
    void *key; # 這個就是我們的key
    union {
        void *val; # 這個就是我們的value,但是這裏的value是通過redisOject對象(見下面的分析)去存儲,
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;# 這個dictEntry是指向下一個dictEntry的引用地址
} dictEntry;

在這裏插入圖片描述

2.2.redisObject數據結構-源碼

在這裏插入圖片描述

#define OBJ_SHARED_REFCOUNT INT_MAX
typedef struct redisObject {
    unsigned type:4; 
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

2.2.1.type :指的是我們客戶端調用存儲的數據類型

127.0.0.1:6379> get name1
(nil)
127.0.0.1:6379> get name2
"gaoxinfu2frank"
127.0.0.1:6379> type name2
string
127.0.0.1:6379> 

2.2.2.encoding:字符串在redis中的字符編碼類型(主要是根據存儲值的大小去劃分)

1.關於字節的長度問題,我們這裏不做過多介紹,根據不同的變成語言(C語言,Java語言)以及不同字符編碼有關係
  比如在C語言中:
  UTF-8編碼:一個英文字符等於一個字節,一箇中文(含繁體)等於三個字節。中文標點佔三個字節,英文標點佔一個字節

2.2.2.1.int類型:存儲 8 個字節的長整型(long,2^63-1)

127.0.0.1:6379> set gaoxinfu_age 10
OK
127.0.0.1:6379> type gaoxinfu_age
string
127.0.0.1:6379> object encoding gaoxinfu_age
"int"
127.0.0.1:6379> 

2.2.2.2.embstr類型:代表embstr 格式的SDS(Simple Dynamic String 簡單動態字符串), 存儲小於 44 個字節的字符串

127.0.0.1:6379> set name4 gaoxinfu4
OK
127.0.0.1:6379> type name4
string
127.0.0.1:6379> type name4
string
127.0.0.1:6379> object encoding name4
"embstr"
127.0.0.1:6379> 

2.2.2.3.raw類型:代表raw 格式的SDS,存儲大於 44 個字節的字符串(3.2 版本之前是 39 字節)

127.0.0.1:6379> set name3 我叫高新富我來自山東臨沂非常高興認識大家,歡迎大家來我的博客學習,謝謝大家
OK
127.0.0.1:6379> type name3
string
127.0.0.1:6379> object encoding name3
"raw"
127.0.0.1:6379>

在這裏插入圖片描述

2.2.3.lru :垃圾回收的策略:

2.2.3.1.lru:Least Recently Used 最近最久未使用算法

1.如果一個數據在最近一段時間沒有被訪問到,那麼可以認爲在將來它被訪問的可能性也很小。
因此,當空間滿時,最久沒有訪問的數據最先被置換(淘汰)。

2.2.3.2.lfu Frequently Used :最小頻率未使用算法

1.如果一個數據在最近一段時間很少被訪問到,那麼可以認爲在將來它被訪問的可能性也很小。
因此,當空間滿時,最小頻率訪問的數據最先被淘汰

2.2.4.refcount:被引用次數(如果爲0,標示沒有對象在引用,可以被回收)

2.2.5.*ptr:真正的value數據存儲結構的地址

2.3.SDS數據結構-源碼:(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)

在這裏插入圖片描述

1.sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64爲不同的SDS存儲類型
  其中:sdshdr5 已經不用
  sdshdr5標示:0-2^5=32byte 
  sdshdr8表示:2^8=64byte 
  sdshdr16表示:2^16=64K        <---- 2^16=2
  sdshdr32表示:2^32=4G
  sdshdr64表示:2^64=16G

typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

2.3.1.len:字符串長度

2.3.2.alloc:分配的內存空間

2.3.3.flags:sds類型(sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64)

2.3.4.buf:數據內容(C語言中使用字符數組)

比如我們的value是“gaoxinfu”,那麼buf[] 存儲的就是如下的字符:'g','a','o','x','i','n','f','u'

3.爲什麼我們說redis String類型是二進制安全的?

4.爲什麼Redis用SDS實現字符串的存儲?爲啥不直接用C語言中的Char[]數組呢?

4.1.首先,C語言本身是沒有字符串這種數據類型,而是通過Char[]數組存儲,但是Char[]存儲存在下面問題?

4.1.1.C語言Char[]類型必須去提前分配數組的大小,意味着必須存儲足夠大的空間,否則會溢出;

4.1.2.如果獲取數組長度,必須遍歷字符數據,時間複雜度O(n)

4.1.3.C語言 對於字符串的變更會對字符數組進行內存的重新分配

4.1.4.通過從字符串開始到結尾碰到的第一個’\0’來標記字符串的結束,因此不能保存圖片、音頻、視頻、壓縮文件等二進制(bytes)保存的內容,二進制不安全

4.2.使用SDS存儲的優勢

4.2.1.不用擔心內存溢出問題,如果需要會對SDS 進行擴容

4.2.2.獲取字符串長度時間複雜度爲O(1),因爲定義了len 屬性

4.2.3.通過“空間預分配”( sdsMakeRoomFor)和“惰性空間釋放”,防止多次重分配內存

4.2.4.判斷是否結束的標誌是len 屬性(它同樣以’\0’結尾是因爲這樣就可以使用C語言函數庫操作字符串的函數)

5.String字符串類型應用場景

5.1.熱點數據的存儲

1.直接存儲的redis中,後面訪問的時候會更快
  比如像一些前端界面的緩存數據等等
  比如:對象緩存,全頁數據緩存

5.2.數據共享緩存<----分佈式

<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>
1.因爲redis是分佈式部署的,所以不同的應用服務是可以調用同一個redis請求的,
  所以不同的應用服務器可以進行數據共享;

5.3.分佈式鎖<----setnx方法:大家可以設置失效時間

http://redisdoc.com/string/set.html

redis> EXISTS job                # job 不存在
(integer) 0

redis> SETNX job "programmer"    # job 設置成功
(integer) 1

redis> SETNX job "code-farmer"   # 嘗試覆蓋 job ,失敗
(integer) 0

redis> GET job                   # 沒有被覆蓋
"programmer"

5.4.全局唯一的Id(原子性):分庫分表主鍵

incrby orderid 20

5.5.計數器:如微博點贊數量,或者抽獎數量,文章的閱讀數量

1.微博點贊數量,或者抽獎數量,文章的閱讀數量,都是可以先寫寫入redis,再寫入數據庫的

5.6.限流:客戶訪問次數限制等 incr

1.可以使用客戶訪問的ip或者其他信息作爲key,存儲訪問的次數,一旦超了次數,則直接返回不允許訪問

6.關於SDS的概念的學習,可以參考下面的地址

https://www.cnblogs.com/break-python/p/5506606.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章