一 什麼是NoSQL?
RDBMS
- 組織化結構
- 固定SQL
- 資料和關係都存在單獨的表中(行列)
- DML(資料操作語言)、DDL(資料定義語言)等
- 嚴格的一致性(ACID): 原子性、一致性、隔離性、永續性
- 基礎的事務
NoSQL
- 不僅僅是資料
- 沒有固定查詢語言
- 鍵值對儲存(redis)、列儲存(HBase)、文件儲存(MongoDB)、圖形資料庫(不是存圖形,放的是關係)(Neo4j)
- 最終一致性(BASE):基本可用、軟狀態/柔性事務、最終一致性
二 redis是什麼?
三 redis五大基本型別
1 String(字串)
1.String型別是redis的最基礎的資料結構,也是最經常使用到的型別。
而且其他的四種類型多多少少都是在字串型別的基礎上構建的,所以String型別是redis的基礎。
2.String 型別的值最大能儲存 512MB,這裡的String型別可以是簡單字串、
複雜的xml/json的字串、二進位制影像或者音訊的字串、以及可以是數字的字串
應用場景
2 List(列表)
list型別是用來儲存多個有序的字串的,列表當中的每一個字元看做一個元素
2.一個列表當中可以儲存有一個或者多個元素,redis的list支援儲存2^32次方-1個元素。
3.redis可以從列表的兩端進行插入(pubsh)和彈出(pop)元素,支援讀取指定範圍的元素集,
或者讀取指定下標的元素等操作。redis列表是一種比較靈活的連結串列資料結構,它可以充當佇列或者棧的角色。
4.redis列表是連結串列型的資料結構,所以它的元素是有序的,而且列表內的元素是可以重複的。
意味著它可以根據連結串列的下標獲取指定的元素和某個範圍內的元素集。
應用場景
3 Set(集合)
1.redis集合(set)型別和list列表型別類似,都可以用來儲存多個字串元素的集合。
2.但是和list不同的是set集合當中不允許重複的元素。而且set集合當中元素是沒有順序的,不存在元素下標。
3.redis的set型別是使用雜湊表構造的,因此複雜度是O(1),它支援集合內的增刪改查,
並且支援多個集合間的交集、並集、差集操作。可以利用這些集合操作,解決程式開發過程當中很多資料集合間的問題。
4 sorted set(有序集合)
redis有序集合也是集合型別的一部分,所以它保留了集合中元素不能重複的特性,但是不同的是,
有序集合給每個元素多設定了一個分數,利用該分數作為排序的依據。
應用場景
5 hash(雜湊)
Redis hash資料結構 是一個鍵值對(key-value)集合,它是一個 string 型別的 field 和 value 的對映表,
redis本身就是一個key-value型資料庫,因此hash資料結構相當於在value中又套了一層key-value型資料。
所以redis中hash資料結構特別適合儲存關係型物件
應用場景
四 五大基本型別底層資料儲存結構

/*
* 字典
*/
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
// 指向具體redisObject
void *val;
//
uint64_t u64;
int64_t s64;
} v;
// 指向下個雜湊表節點,形成連結串列
struct dictEntry *next;
} dictEntry;
1 redisObject
/*
* Redis 物件
*/
typedef struct redisObject {
// 型別 4bits
unsigned type:4;
// 編碼方式 4bits
unsigned encoding:4;
// LRU 時間(相對於 server.lruclock) 24bits
unsigned lru:22;
// 引用計數 Redis裡面的資料可以透過引用計數進行共享 32bits
int refcount;
// 指向物件的值 64-bit
void *ptr;
} robj;
REDIS_ENCODING_INT(long 型別的整數)
REDIS_ENCODING_EMBSTR embstr (編碼的簡單動態字串)
REDIS_ENCODING_RAW (簡單動態字串)
REDIS_ENCODING_HT (字典)
REDIS_ENCODING_LINKEDLIST (雙端連結串列)
REDIS_ENCODING_ZIPLIST (壓縮列表)
REDIS_ENCODING_INTSET (整數集合)
REDIS_ENCODING_SKIPLIST (跳躍表和字典)

2 String資料結構
-
當儲存的值為整數且值的大小不超過long的範圍,使用整數儲存
-
當字串長度不超過44位元組時,使用EMBSTR 編碼
-
大於44字元時,使用raw編碼
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[];
};
typedef struct redisObject {
// 型別 4bits
unsigned type:4;
// 編碼方式 4bits
unsigned encoding:4;
// LRU 時間(相對於 server.lruclock) 24bits
unsigned lru:22;
// 引用計數 Redis裡面的資料可以透過引用計數進行共享 32bits
int refcount;
// 指向物件的值 64-bit
void *ptr;
} robj;
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[];
};
3 List儲存結構
-
Redis3.2之前的底層實現方式:壓縮列表ziplist 或者 雙向迴圈連結串列linkedlist
-
list中儲存的每個元素的長度小於 64 位元組; -
列表中資料個數少於512個
ziplist

快速列表(quickList)
typedef struct quicklist {
// 指向quicklist的頭部
quicklistNode *head;
// 指向quicklist的尾部
quicklistNode *tail;
unsigned long count;
unsigned int len;
// ziplist大小限定,由list-max-ziplist-size給定
int fill : 16;
// 節點壓縮深度設定,由list-compress-depth給定
unsigned int compress : 16;
} quicklist;
typedef struct quicklistNode {
// 指向上一個ziplist節點
struct quicklistNode *prev;
// 指向下一個ziplist節點
struct quicklistNode *next;
// 資料指標,如果沒有被壓縮,就指向ziplist結構,反之指向quicklistLZF結構
unsigned char *zl;
// 表示指向ziplist結構的總長度(記憶體佔用長度)
unsigned int sz;
// ziplist數量
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
// 預留欄位,存放資料的方式,1--NONE,2--ziplist
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
// 解壓標記,當檢視一個被壓縮的資料時,需要暫時解壓,標記此引數為1,之後再重新進行壓縮
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;
typedef struct quicklistLZF {
// LZF壓縮後佔用的位元組數
unsigned int sz; /* LZF size in bytes*/
// 柔性陣列,存放壓縮後的ziplist位元組陣列
char compressed[];
} quicklistLZF;

4 Hash型別
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
typedef struct dictht {
//指標陣列,這個hash的桶
dictEntry **table;
//元素個數
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictEntry大家應該熟悉,在上面有講,使用來真正儲存key->value的地方
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
// 指向具體redisObject
void *val;
//
uint64_t u64;
int64_t s64;
} v;
// 指向下個雜湊表節點,形成連結串列
struct dictEntry *next;
} dictEntry;

5 漸進式rehash

6 set資料結構
-
儲存的資料都是整數 -
儲存的資料元素個數小於512個
inset結構體
typedef struct intset {
uint32_t encoding;
// length就是陣列的實際長度
uint32_t length;
// contents 陣列是實際儲存元素的地方,陣列中的元素有以下兩個特性:
// 1.沒有重複元素
// 2.元素在陣列中從小到大排列
int8_t contents[];
} intset;
// encoding 的值可以是以下三個常量的其中一個
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
7 Zset資料結構
ziplist做排序

skiplist跳錶
/*
* 跳躍表
*/
typedef struct zskiplist {
// 頭節點,尾節點
struct zskiplistNode *header, *tail;
// 節點數量
unsigned long length;
// 目前表內節點的最大層數
int level;
} zskiplist;
/*
* 跳躍表節點
*/
typedef struct zskiplistNode {
// member 物件
robj *obj;
// 分值
double score;
// 後退指標
struct zskiplistNode *backward;
// 層
struct zskiplistLevel {
// 前進指標
struct zskiplistNode *forward;
// 這個層跨越的節點數量
unsigned int span;
} level[];
} zskiplistNode;


redis跳躍表

-
header:指向跳躍表的表頭節點,透過這個指標程式定位表頭節點的時間複雜度就為O(1);
-
tail:指向跳躍表的表尾節點,透過這個指標程式定位表尾節點的時間複雜度就為O(1);
-
level:記錄目前跳躍表內,層數最大的那個節點的層數(表頭節點的層數不計算在內),透過這個屬性可以再O(1)的時間複雜度內獲取層高最好的節點的層數;
-
length:記錄跳躍表的長度,也即是,跳躍表目前包含節點的數量(表頭節點不計算在內),透過這個屬性,程式可以再O(1)的時間複雜度內返回跳躍表的長度。
-
層(level):
-
後退(backward)指標:
-
分值(score):
-
成員物件(oj):
五 三大特殊資料型別
1 geospatial(地理位置)
1.geospatial將指定的地理空間位置(緯度、經度、名稱)新增到指定的key中。
這些資料將會儲存到sorted set這樣的目的是為了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令對資料進行半徑查詢等操作。
2.sorted set使用一種稱為Geohash的技術進行填充。經度和緯度的位是交錯的,以形成一個獨特的52位整數。
sorted set的double score可以代表一個52位的整數,而不會失去精度。(有興趣的同學可以學習一下Geohash技術,使用二分法構建唯一的二進位制串)
3.有效的經度是-180度到180度
有效的緯度是-85.05112878度到85.05112878度
應用場景
-
檢視附近的人 -
微信位置共享 -
地圖上直線距離的展示
2 Hyperloglog(基數)
hyperloglog 是用來做基數統計的,其優點是:輸入的提及無論多麼大,hyperloglog使用的空間總是固定的12KB ,利用12KB,它可以計算2^64個不同元素的基數!非常節省空間!但缺點是估算的值,可能存在誤差
應用場景
-
網頁統計UV (瀏覽使用者數量,同一天同一個ip多次訪問算一次訪問,目的是計數,而不是儲存使用者)
3 Bitmaps(位儲存)
Redis提供的Bitmaps這個“資料結構”可以實現對位的操作。Bitmaps本身不是一種資料結構,實際上就是字串,但是它可以對字串的位進行操作。
可以把Bitmaps想象成一個以位為單位陣列,陣列中的每個單元只能存0或者1,陣列的下標在bitmaps中叫做偏移量。單個bitmaps的最大長度是512MB,即2^32個位元位。
應用場景
六 Redis事務
1 資料庫事務與redis事務
資料庫的事務
redis事務
-
Redis 命令只會因為錯誤的語法而失敗(並且這些問題不能在入隊時發現),或是命令用在了錯誤型別的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由程式設計錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生產環境中。
-
因為不需要對回滾進行支援,所以 Redis 的內部可以保持簡單且快速。
事務監控

七 Redis持久化
-
RDB持久化方式會在一個特定的間隔儲存那個時間點的資料快照。
-
AOF持久化方式則會記錄每一個伺服器收到的寫操作。在服務啟動時,這些記錄的操作會逐條執行從而重建出原來的資料。寫操作命令記錄的格式跟Redis協議一致,以追加的方式進行儲存。
-
Redis的持久化是可以停用的,就是說你可以讓資料的生命週期只存在於伺服器的執行時間裡。
-
兩種方式的持久化是可以同時存在的,但是當Redis重啟時,AOF檔案會被優先用於重建資料。
1 RDB持久化
工作原理
-
Redis 呼叫forks。同時擁有父程序和子程序。 -
子程序將資料集寫入到一個臨時 RDB 檔案中。 -
當子程序完成對新 RDB 檔案的寫入時,Redis 用新 RDB 檔案替換原來的 RDB 檔案,並刪除舊的 RDB 檔案。
觸發機制
-
save命令是同步的命令,會佔用主程序,會造成阻塞,阻塞所有客戶端的請求
-
bgsave
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
save自動觸發配置,見下面配置,滿足m秒內修改n次key,觸發rdb
# 時間策略 save m n m秒內修改n次key,觸發rdb
save 900 1
save 300 10
save 60 10000
# 檔名稱
dbfilename dump.rdb
# 檔案儲存路徑
dir /home/work/app/redis/data/
# 如果持久化出錯,主程序是否停止寫入
stop-writes-on-bgsave-error yes
# 是否壓縮
rdbcompression yes
# 匯入時是否檢查
rdbchecksum yes
-
從節點全量複製時,主節點發送rdb檔案給從節點完成複製操作,主節點會觸發bgsave命令; -
執行flushall命令,會觸發rdb -
退出redis,且沒有開啟aof時
-
RDB 的內容為二進位制的資料,佔用記憶體更小,更緊湊,更適合做為備份檔案; -
RDB 對災難恢復非常有用,它是一個緊湊的檔案,可以更快的傳輸到遠端伺服器進行 Redis 服務恢復; -
RDB 可以更大程度的提高 Redis 的執行速度,因為每次持久化時 Redis 主程序都會 fork() 一個子程序,進行資料持久化到磁碟,Redis 主程序並不會執行磁碟 I/O 等操作; -
與 AOF 格式的檔案相比,RDB 檔案可以更快的重啟。
-
因為 RDB 只能儲存某個時間間隔的資料,如果中途 Redis 服務被意外終止了,則會丟失一段時間內的 Redis 資料。 -
RDB 需要經常 fork() 才能使用子程序將其持久化在磁碟上。如果資料集很大,fork() 可能很耗時,並且如果資料集很大且 CPU 效能不佳,則可能導致 Redis 停止為客戶端服務幾毫秒甚至一秒鐘。
2 AOF(Append Only File)
AOF配置項
# 預設不開啟aof 而是使用rdb的方式
appendonly no
# 預設檔名
appendfilename "appendonly.aof"
# 每次修改都會sync 消耗效能
# appendfsync always
# 每秒執行一次 sync 可能會丟失這一秒的資料
appendfsync everysec
# 不執行 sync ,這時候作業系統自己同步資料,速度最快
# appendfsync no
AOF 重寫機制
觸發方式
-
手動觸發:bgrewriteaof -
自動觸發 就是根據配置規則來觸發,當然自動觸發的整體時間還跟Redis的定時任務頻率有關係。
3 rdb與aof對比
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
八 釋出與訂閱
1 頻道(channel)
訂閱

釋出

完整流程
127.0.0.1:6379> publish channel:1 hi
(integer) 1
127.0.0.1:6379> subscribe channel:1
Reading messages... (press Ctrl-C to quit)
1) "subscribe" // 訊息型別
2) "channel:1" // 頻道
3) "hi" // 訊息內容
-
subscribe。表示訂閱成功的反饋資訊。第二個值是訂閱成功的頻道名稱,第三個是當前客戶端訂閱的頻道數量。
-
message。表示接收到的訊息,第二個值表示產生訊息的頻道名稱,第三個值是訊息的內容。
-
unsubscribe。表示成功取消訂閱某個頻道。第二個值是對應的頻道名稱,第三個值是當前客戶端訂閱的頻道數量,當此值為0時客戶端會退出訂閱狀態,之後就可以執行其他非"釋出/訂閱"模式的命令了。
資料結構
struct redisServer {
// ...
dict *pubsub_channels;
// ...
};


2 模式(pattern)

訂閱釋出完整流程
127.0.0.1:6379> publish b m1
(integer) 1
127.0.0.1:6379> publish b1 m1
(integer) 1
127.0.0.1:6379> publish b11 m1
(integer) 1
127.0.0.1:6379> psubscribe b*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "b*"
3) (integer) 3
1) "pmessage"
2) "b*"
3) "b"
4) "m1"
1) "pmessage"
2) "b*"
3) "b1"
4) "m1"
1) "pmessage"
2) "b*"
3) "b11"
4) "m1"
資料結構
pattern屬性是一個連結串列,連結串列中儲存著所有和模式相關的資訊。
struct redisServer {
// ...
list *pubsub_patterns;
// ...
};
// 連結串列中的每一個節點結構如下,儲存著客戶端與模式資訊
typedef struct pubsubPattern {
redisClient *client;
robj *pattern;
} pubsubPattern;


def PUBLISH(channel, message):
# 遍歷所有訂閱頻道 channel 的客戶端
for client in server.pubsub_channels[channel]:
# 將資訊傳送給它們
send_message(client, message)
# 取出所有模式,以及訂閱模式的客戶端
for pattern, client in server.pubsub_patterns:
# 如果 channel 和模式匹配
if match(channel, pattern):
# 那麼也將資訊發給訂閱這個模式的客戶端
send_message(client, message)
九 主從複製
主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。
2.前者稱為主節點(master),後者稱為從節點(slave);資料的複製是單向的,只能由主節點到從節點
預設情況下,每臺redis伺服器都是主節點;且一個主節點可以有多個從節點(或者沒有),但一個從節點只有一個主
-
資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘方式。
-
故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗餘。
-
負載均衡:在主從複製的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連線主節點,讀Redis資料時應用連線從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,透過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量。
-
高可用基石:除了上述作用以外,主從複製還是哨兵和叢集能夠實施的基礎,因此說主從複製是Redis高可用的基礎。

1 原理
2 全量複製的三個階段
十 哨兵機制

-
對於節點的故障判斷是由多個Sentinel節點共同完成,這樣可以有效地防止誤判 -
即使個別Sentinel節點不可用,整個Sentinel叢集依然是可用的。
-
監控:每個Sentinel節點會對資料節點(Redis master/slave 節點)和其餘Sentinel節點進行監控 -
通知:Sentinel節點會將故障轉移的結果通知給應用方 -
故障轉移:實現slave晉升為master,並維護後續正確的主從關係 -
配置中心:在Redis Sentinel模式中,客戶端在初始化的時候連線的是Sentinel節點集合,從中獲取主節點資訊
1 原理
監控
-
監控主從拓撲資訊:每隔10秒,每個Sentinel節點,會向master和slave傳送INFO命令獲取最新的拓撲結構 -
Sentinel節點資訊交換:每隔2秒,每個Sentinel節點,會向Redis資料節點的__sentinel__:hello頻道上,傳送自身的資訊,以及對主節點的判斷資訊。這樣,Sentinel節點之間就可以交換資訊 -
節點狀態監控:每隔1秒,每個Sentinel節點,會向master、slave、其餘Sentinel節點發送PING命令做心跳檢測,來確認這些節點當前是否可達
主觀/客觀下線
哨兵選舉
-
每個Sentinel節點都有資格成為領導者,當它主觀認為某個資料節點宕機後,會向其他Sentinel節點發送sentinel is-master-down-by-addr命令,要求自己成為領導者; -
收到命令的Sentinel節點,如果沒有同意過其他Sentinel節點的sentinelis-master-down-by-addr命令,將同意該請求,否則拒絕(每個Sentinel節點只有1票); -
如果該Sentinel節點發現自己的票數已經大於等於MAX(quorum, num(sentinels)/2+1),那麼它將成為領導者; -
如果此過程沒有選舉出領導者,將進入下一次選舉。
故障轉移
-
跟master斷開連線的時長:如果一個slave跟master的斷開連線時長已經超過了down-after-milliseconds的10倍,外加master宕機的時長,那麼該slave就被認為不適合選舉為master; -
slave的優先順序配置:slave priority引數值越小,優先順序就越高; -
複製offset:當優先順序相同時,哪個slave複製了越多的資料(offset越靠後),優先順序越高; -
run id:如果offset和優先順序都相同,則哪個slave的run id越小,優先順序越高。
十一 快取穿透、擊穿、雪崩
1 快取穿透
-
問題來源
-
解決方案
-
介面層增加校驗,如使用者鑑權校驗,id做基礎校驗,id<=0的直接攔截; -
從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,快取有效時間可以設定短點,如30秒(設定太長會導致正常情況也沒法使用)。這樣可以防止攻擊使用者反覆用同一個id暴力攻擊。 -
布隆過濾器。類似於一個hash set,用於快速判某個元素是否存在於集合中,其典型的應用場景就是快速判斷一個key是否存在於某容器,不存在就直接返回。布隆過濾器的關鍵就在於hash演算法和容器大小。
2 快取擊穿
-
問題來源
-
解決方案
3 快取雪崩
-
問題來源
-
解決方案
-
快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生。 -
如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同的快取資料庫中。 -
設定熱點資料永遠不過期
文章最後
阿里雲新版雲計算架構師ACE認證全面重構
為建立雲計算生態領域,含金量第一的專家級人才標準和認證體系,影響泛雲生態高層次技術人才,阿里雲認證對阿里云云計算架構師ACE認證進行了里程碑式重構!2022年3月28日 14:00 來觀看阿里雲新版雲計算架構師ACE認證重構釋出會。改革重點:1、依據阿里雲P8架構師能力模型進行設計,含金量大幅度提升。2、新增實驗、面試環節,全部由阿里雲P9技術專家組成考官團隊。3、新增阿里雲技術專家全球唯一編號-ACE00001開始排序。4、新增配套證書&紀念套裝,包含定製化水晶杯,獎牌等。點選閱讀原文檢視詳情!
