redis:remote dictionary server
字面意思:遠程“字典“服務器(哈哈哈哈)
由Salvatore Sanfilippo寫的key-value存儲系統,屬於nosql的一種,Redis是一個開源的使用ANSI c語言編寫,遵守BSD協議,支持網絡, 基於內存但是可以實現持久的一款數據庫。
redis相對於數據庫家族的優勢:
- redis有着更爲複雜的數據類型,支持set、list、hash、string等,它的一個鍵最大可爲512M,value最大可達1G。
- redis有着更高的運行速度和持久化的選擇, redis是一款運行在內存的單進程單線程的異步數據庫,單進程單線程並代表就low ,能讀的速度是110000次/s,寫的速度是81000次/s 。對於持持久化來說,它提供快照持久化和AOF持久化的方式選擇設置,以及master-slave方式的數據備份,極大的方便了程序媛用戶。
- redis極高的支持原子化操作,redis的操作基本上都是原子化的,此外,多個操作合併後的原子性執行它也是支持的。
redis的安裝:
網上教程多,它支持原碼安裝,看一下它的src目錄下的關鍵文件
redis中的對象類型:
命名規則:除了\n
和空格不能作爲名字的組成外,其他都可以出現,且長度不作要求。
redis基本數據對象類型模型圖:
string對象類型:
string類型的基本操作:
string對象類型是redis的基本數據類型, 但是它跟c語言裏面的字符串常量或則字符數組不是同一個概念;在redis裏面實現string對象有三種基本的方式:
- 如果string對象保存的是整數值, 並且這個值可以用long來表示, 那麼redisobject裏面的ptr指針從void*轉變爲long型, 字符串編碼設置爲int。這個編碼方式並不是一成不變的,
當操作改變了存儲對象,編碼也許會變爲raw。- 如果string對象保存的是字符串值, 且這個字符串長度大於32字節, string對象將採用一個SDS來保存這個字符串,編碼方式爲raw, 調用兩次內存分配,
一次是redisobject對象結構一次是sdshdr對象結構。- 如果string對象保存的是字符串值, 且這個字符串長度小於32字節, string對象將採用一個embstr編碼方式來保存,分配的內容跟raw編碼一樣,只不過它調用一次內存分配一塊連續的空間,
這塊空間包含兩個結構體。嚴格意義上, embstr編碼方式的字符串只是可讀的,若操作改變, 則可能變爲raw編碼方式。
int
embstr
sdshdr – SDS
看一下SDS(簡單動態字符串)的結構組成:
SDS相對應c語言字符串的優勢:
1. 將獲取字符串長度的工作從0(n)降到0(1)的複雜度。
2. SDS API能夠降SDS的空間自動擴展,解決溢出的問題。如c的strcat不檢查溢出,以及SDS的sdscat的檢查並擴展容量。這也是一種節約空間的策略;
3. 空間優化策略: 起作用的前提是SDS對象的數組長度內容頻繁的改變 ;利用的是SDS對象的free屬性
·······a、空間預分配:優化在字符數組增長;擴展數組時,額外分配空間(小於1M翻倍分配,大於1M多加1M);利用SDS的free屬性來記錄這個位置。
·······b、惰性空間釋放:優化字符數組縮短;縮短數組時,並不通過內存重新分配回縮,而是調整free的值,記錄,以待將來對這段空間的使用。
4. 字節數組,類型多;在c的數組裏面他以\0
的方式標誌結束一個字符串,內容中間不可包含空格;但是SDS中它是可以保存的,它採用二進制處理buf裏面的內容,不會對其有任何的限制,存取不會造成差別,所以各種的文件類型都可以被它完好的引用。
list對象類型:
lis對象t類型的基本操作:
編碼方式:
1. ziplist 列表保存的所有字符串元素長度都小於64個字節;保存的元素個數小於512個。
2. 其餘情況使用linklist。
linklist
linklist是一個對雙向不循環鏈表進行過封裝的對象,兩邊雙向一共四個方向操作,可以練兩結合,因此list既可以作爲棧出現也可以作爲隊列出現。
鏈表結點結構體:
typedef struct listNode{
//前驅節點
struct listNode* prev;
//後驅節點
struct listNode* next;
//節點值
void *value;
}listNode;
鏈表結構:
typedef struct list{
//表頭結點
listNode* head;
//表尾結點
listNode* tail;
//鏈表長度
unsigned long len;
//結點值複製函數
void *(*dup)(void * ptr);
//結點值釋放函數
void (*free)(void * ptr);
//結點值對比函數
void (*match)(void *ptr, void* key);
}list;
實現模型:
ziplist
ziplist 壓縮列表, 爲了節約內存而出現, 特點:是由一系列特殊比阿媽的連續內存塊組成的序列, 可以包含任意多個結點。
zlbytes 記錄整個壓縮列表佔用的內存字節數
zltail 記錄壓縮列表 表尾結點 距離列表起始位置的字節數
zllen 記錄壓縮列表包含的字節數
entry 列表結點
zlend 標記壓縮列表的結束
壓縮列表的結點:
結點包含三個屬性:
其中previous_entry_length屬性記錄的是上一個結點的長度,如果上一個結點長度小於254個字節, 該屬性用一個字節來保存它的長度, 否則用5個字節來保存。(利用這個屬性, 可以實現向前的結點獲取)
enccoding 屬性標記結點保存的數據類型以及長度
content 保存結點的值
連鎖更新:
當有結點的添加或着擴大修改或着刪除中間小結點的時候, 可能後續結點的prvious_entry_length屬性的大小由1變爲5, 因此,該結點的大小如果大於254個字節。後繼結點的prvious_entry_length屬性應該變爲5個字節,因此又引發一次空間重新分配。以此類推。
set類型:
可以實現數學裏面的交,並,差集的運算,集合中的元素不重複。
set類型的基本操作:
編碼方式:
1. intset 集合對象保存的所有元素都是整數值,元素數量不超過512個。
2.其他情況使用hashtable
hashtable
intset
intset 整數集合 :各個數據項在數組中按值的大小從小到大有序的排列,並且數組中不包含任何重複項。
整數集合的結構體:
typedef struct inset{
uint32_t encoding; //編碼方式
uint32_t length; //集合包含的元素數量
int8_t contents[]; //保存元素的數組
}inset;
整數集合的升級: 新添加的元素類型比整數集合現有的元素的類型要長,就要進行升級的活動。
升級三部曲:
1. 擴展底層數組的大小
2. 將底層數組現有的元素都換成與新元素相同的類型,並將轉換後的元素放置到正確的位置上,這個過程保持元素的有序性。
3. 將新元素添加到底層數組裏面。
注意:不存在回縮的降級。
sorted set有序對象類型:
每個元素都是一個值-權的組合,通過權值可以有序的獲取集合中的元素。
sorted set的基本操作:
有序對象zset的編碼方式:
1. ziplist 保存元素小於128個, 所有雅俗怒的長度都小於64個字節。
2. 其餘情況採用skiplist編碼。
ziplist
ziplist 方式:每個集合元素使用兩個緊挨着的儀器的壓縮列表結點來保存,第一個結點保存元素的成員,第二個元素爆粗元素的分值。元素在壓縮列表內按分值的從小到達進行排序。
skiplist編碼方式採用zset結構體的方式實現,zset結構體包含一個跳越表skiplist和一個字典。
typedef struct set{
zskiplist *zsl;
dict *dict;
}set;
skiplist – zset
使用skiplist編碼的zset對象模型:
跳越表:
跳越表:
typedef struct zskiplist{
struct skiplistNode *header, *tail;
//表頭結點和表尾結點
unsigned long length; //表中結點數量
int level; // 表中層數最大的結點的層數
}zskiplist;
跳越表結點:
typedef struct zskiplistNode{
struct zskiplistLevel{ //層
struct zskiplistNode *forward; //前進指針
unsigned int span; //跨度
}level[];
struct zskiplistNode *backward; //後退指針
double score; //分值
rob *obj; //對象
}zskiplistNode;
跳越表的結構模型:
hash對象類型:
鍵-值對集合,每個hash可以存儲40多億個鍵-值對。
hansh對象的編碼方式:
- ziplist 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小於64字節,哈希對象保存的鍵值對數量小於512個,滿足以上條件使用ziplist。
- 不滿足以上條件使用hashtable編碼。
ziplist
採用ziplist的hash對象,首先將保存了鍵的壓縮列表結點壓入壓縮列表表尾, 然後再將保存了值的壓縮列表結點壓入隊尾, 這樣鍵值對的位置是相鄰的。
hashtable
hashtable 編碼採用字典作爲底層的實現。
字典:又稱符號表、關聯數組或映射。
字典:
typedef struct dict{
dictType *type; //類型特定函數
void *privdata; //私有數據
ditch ht[2]; //哈希表, 包含兩個數組項, 通常只用第一個, 第二個在進行rehash的時候使用。
int trehashind; //rehash索引
}dict;
哈希表:
typedef struct dictch{
dictEntry **table; //哈希表數組
unsigned long size; //哈希表大小
unsigned long sizemask; //哈希表大小掩碼, 用於計算索引值, 數值爲size-1
unsigned long used; //哈希表已有的結點數量
}dictcht;
哈希表結點:
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}
struct dictEntry *next; //後面連接的是哈希表結點, 將多個哈希值相同的鍵值對連接在一起, 以此來解決鍵衝突的問題。
}dictEntry;
對象模型:
解決鍵衝突:當兩個或以上的鍵被分配到了哈希數組的同一個索引上的時候, 就稱發生了衝突。
鏈地址法:將這些發生衝突的結點, 使用next指針構成一個單向的鏈表。以此來解決鍵衝突的問題。(通常將新的結點放在第一個位置, 因此添加的複雜度爲 0(1))
rehash重整散列:這個動作不是一下完成的,具有漸進式的特點。
重新計算鍵的哈希值和索引值,然後將鍵值對放置到ht[1]哈希表的制定位置上。當所有的動作完成, ht[0]變成一個空表,將ht[1]設置爲ht[0],並再創建一個空的哈希表, 爲下一次的rehash做準備。
rehash的步驟;
1. 爲ht[1]分配空間, 讓字典同時持有ht[1] ht[0]兩個哈希表。
2. 在字典維護一個索引計數器變量rehashidx,並將它設爲0, 表示rehash正式開始。
3. 在reshah期間, 每次對字典的操作, 程序會順帶將ht[0]哈希表在rehashidx索引上的所有鍵值對rehash到ht[1],當工作完成rehashidx屬性的值增加一。
4. 當rehash操作完全的完成, 程序將rehashidx屬性的值設置爲-1;
負載因子=哈希表已保存結點數量/哈希表大小
哈希表自動進行擴展的條件:
1. 服務器目前沒有進行BGSAVE 或BGREWRITEAOF,並且哈希表的負載因子大於1。
2. 服務器目前進行BGSAVE 或BGREWRITEAOF,並且哈希表的負載因子大於5。
持久化:
爲了數據安全, redis會把本身的數據以文件的形式保存到硬盤中,在服務器重啓之後自動吧硬盤數據恢復到內存裏面。將數據保存到硬盤的過程就叫做’持久化’。
快照持久化RDB:
一次性把redis中的全部數據文件(鍵值對)保存一份存儲在硬盤中。(適合數據量不大的情況,如10G以下)
設置快照頻率的配置:
手動發起快照持久化的方式有兩種save
和 bgsave
:
save
是直接的阻塞服務器,直到工作完成, bgsave
會啓動子進程。
RDB的載入是在服務器啓動的時候如果檢測到它的存在就會自動載入。(如果開啓了AOF持久化,則載入AOF文件不載入RDB文件)
AOF(APPEND ONLY FILE)持久化:
把用戶執行的指令(添加、修改、刪除)都保存到備份到文件中,還原數據的時候及時執行具體寫指令。
注意:當在配置文件中開啓AOF持久化的時候會清空redis數據庫的全部內容,所以慎重!!
相關配置:
優化操作:
持久化的相關操作:
主從服務器:
目的:降低一個redis服務器的負載,將多個redis服務器設置爲一個主服務器的slave服務器,master主服務器根slave服務器之間會進行自動的數據同步。
將一個服務器設置爲其他服務器的slave服務器的方法:
all