日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

01 Redis源码起航

發(fā)布時間:2023/12/14 数据库 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 01 Redis源码起航 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

源碼解讀

Redis 源碼文件分布大致如下:

redisDb結(jié)構(gòu)介紹

首先看下redisDb的結(jié)構(gòu),在server.h文件中:

typedef struct redisDb {dict *dict; /* 保存數(shù)據(jù)庫中所有的鍵值對 */dict *expires; /* 保存是所有有過期時間的鍵值對 */dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) 客戶端等待數(shù)據(jù)的鍵-客戶端和鍵值的映射*/dict *ready_keys; /* Blocked keys that received a PUSH */dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */int id; /* Database ID 數(shù)據(jù)庫ID*/long long avg_ttl; /* Average TTL, just for stats 平均TTL時間,僅用于統(tǒng)計*/unsigned long expires_cursor; /* Cursor of the active expire cycle. */list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ } redisDb;
  • dict:保存db中所有的鍵值對
  • expires:保存所有有過期時間的鍵值對
  • expires_cursor:周期性刪除過期鍵的游標(biāo),應(yīng)該是6.0版本以后才有的,因為書里和網(wǎng)上的一些介紹都沒有這個字段

typedef struct dict {dictType *type; /* 指定類型 */void *privdata;dictht ht[2];// ht[0] , ht[1] =null 兩個 dictht 用于數(shù)據(jù)的擴容,漸進式重新散列l(wèi)ong rehashidx; /* rehashing not in progress if rehashidx == -1 */unsigned long iterators; /* number of iterators currently running */ } dict;
  • dictType:指定的數(shù)據(jù)類型,中間可以指定hash函數(shù)
  • dictht:hashtable的縮寫,【2】在單線程的情況下擴容會影響響應(yīng)應(yīng)能造成卡頓,通過漸進式的方式對舊 hasht 槽位中的dictEntry進行數(shù)據(jù)遷移,沒有請求的時候也會進行數(shù)據(jù)遷移,通過事件輪巡遷移數(shù)據(jù),直到舊的數(shù)據(jù)遷移完。兩個hashtable中的數(shù)據(jù)相互倒換。

typedef struct dictType {uint64_t (*hashFunction)(const void *key);void *(*keyDup)(void *privdata, const void *key);void *(*valDup)(void *privdata, const void *obj);int (*keyCompare)(void *privdata, const void *key1, const void *key2);void (*keyDestructor)(void *privdata, void *key);void (*valDestructor)(void *privdata, void *obj); } dictType;
  • hashFunction:指定hash函數(shù)
  • keyCompare:類似于equals比較方法

/* This is our hash table structure. Every dictionary has two of this as we* implement incremental rehashing, for the old to the new table. */ typedef struct dictht {dictEntry **table; // 鍵值對具體封裝的對象unsigned long size; // hashtable 容量 永遠都是2的n次方unsigned long sizemask; // 恒等于 size -1 對于任何 任意數(shù)%2^n 都 可以優(yōu)化為位運算 任意數(shù) & (2^n - 1) 原因是CUP對于任意數(shù)%2^n不友好unsigned long used; // hashtable 元素個數(shù) used / size =1 used :size = 1:1 擴容 } dictht;

數(shù)據(jù)擴容邏輯

在元數(shù)個數(shù)與容量是1:1的時候回觸發(fā)擴容,擴容邏輯是,請求訪問key值的時候,如果dict對象中的dictht[0]存在key,那數(shù)據(jù)會遷移到dictht[1]中,如果dictht[0]中不存在,則訪問dictht[1]中的數(shù)據(jù),等到dictht[0]中數(shù)據(jù)遷移完成后,就把dictht[0]的指針指向dictht[1],原有dictht[0]的指向null。redis數(shù)據(jù)擴容是成倍擴容的,擴容也是相互之前互相進行數(shù)據(jù)倒換。

typedef struct dictEntry {void *key; // sds 對象,所有的key都是String類型union {void *val; //指針指向具體的數(shù)據(jù),就是redis支持的幾種數(shù)據(jù)類型。這里的數(shù)據(jù)也不是直接存儲的,而是通過 redisObject 這個對象封裝,甚至可以指定自定義的數(shù)據(jù)類型uint64_t u64;int64_t s64;double d;} v; // C語言的數(shù)據(jù)類型,用于存放value值struct dictEntry *next; // 鏈表,解決hash沖突,把產(chǎn)生hash沖突的節(jié)點關(guān)聯(lián)起來 } dictEntry;// redisObject對象,對value的封裝 : string , list ,set ,hash ,zset ... typedef struct redisObject {unsigned type:4; // 4 bit, sting , hash 約束客戶端的操作命令,基于客戶端的操作命令,把數(shù)據(jù)封裝為相應(yīng)的數(shù)據(jù)結(jié)構(gòu),如 set->String, lpush->Listunsigned encoding:4; // 4 bit 編碼類型,用于描述value值在數(shù)據(jù)庫中的編碼形式unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or 最近最少使用* LFU data (least significant 8 bits frequency 最近不經(jīng)常使用* and most significant 16 bits access time). * 24 bit * */int refcount; // 4 byte 引用計數(shù)法管理內(nèi)存,沒人使用時釋放內(nèi)存 void *ptr; // 8 byte 總空間: 4 bit + 4 bit + 24 bit + 4 byte + 8 byte = 16 byte 真實存儲數(shù)據(jù)的地方,指向數(shù)據(jù)在內(nèi)存中的位置。 } robj;

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[];// buf[0]: z: 0101001 }; 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[]; // 兼容C語言的函數(shù)庫,會占用一個字節(jié) }; 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[]; // 兼容C語言的函數(shù)庫,會占用一個字節(jié) }; 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[]; // 兼容C語言的函數(shù)庫,會占用一個字節(jié) }; 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[]; };

緩存值對象占用16個字節(jié)的總空間,內(nèi)存中的緩存也是64個字節(jié),一次讀取64字節(jié),還差48字節(jié),那怎樣讓這48個字節(jié)進行補齊,讓內(nèi)存頁地址是連續(xù)的呢?這里通過encoding這個字段確定具體的數(shù)據(jù)類型,那我們用那種類型的數(shù)據(jù)結(jié)構(gòu)剛好是48個字節(jié)呢?我們從上面的String類型數(shù)據(jù)結(jié)構(gòu)中可知,48個字節(jié)應(yīng)該落在sdshdr8這個區(qū)間類,sds這個數(shù)據(jù)結(jié)構(gòu)自身需要消耗4個字節(jié),那還剩余44個字節(jié),那我們存放一個44字節(jié)的數(shù)據(jù),那正好可以占用64個字節(jié),正好是一個緩存行的容量,這個時候數(shù)據(jù)類型是embstr,那我們讀取內(nèi)存數(shù)據(jù)時,可以一次讀取出來。

Redis 里面小于等于44個字節(jié)的字符串是embstr編碼、大于44個字節(jié)是raw編碼

當(dāng)字符串大于 44 字節(jié)時

SDS 的數(shù)據(jù)量就開始變多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是會給 SDS 分配獨立的空間,并用指針指向 SDS 結(jié)構(gòu)。 這種布局方式被稱為 raw 編碼模式。

?

RedisDb數(shù)據(jù)結(jié)構(gòu)

redis 數(shù)據(jù)結(jié)構(gòu)層次

第一個層面,是從使用者的角度。比如:

  • string
  • list
  • hash
  • set
  • sorted set

這一層面也是Redis暴露給外部的調(diào)用接口。

第二個層面,是從內(nèi)部實現(xiàn)的角度,屬于更底層的實現(xiàn)。比如:

  • dict
  • sds
  • ziplist
  • quicklist
  • skiplist

第一個層面的“數(shù)據(jù)結(jié)構(gòu)”,Redis的官方文檔(An introduction to Redis data types and abstractions – Redis)有詳細的介紹。本文的重點在于討論第二個層面,Redis數(shù)據(jù)結(jié)構(gòu)的內(nèi)部實現(xiàn),以及這兩個層面的數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系:Redis如何通過組合第二個層面的各種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)第一個層面的更高層的數(shù)據(jù)結(jié)構(gòu)。

思考:

1 redis的擴容機制是如何實現(xiàn)的?

redis在hashtable容量與其中的容量是1:1的時候回觸發(fā)redis的擴容機制,擴容機制觸發(fā)后,每當(dāng)有請求過來的時候都會去原有的hashtable中查找是否有當(dāng)前查詢的key存在,如果存在則把當(dāng)前的數(shù)據(jù)遷移到因擴容的hashtable中,然后返回數(shù)據(jù);如果原有的hashtable不存在當(dāng)前key的值,則直接去新的hashtable中查詢數(shù)據(jù)并返回。

2 redis key 的hash沖突是怎么解決的?

redis在存放key=AAA的時候,如果hashtable沒有hase(AAA)的下標(biāo),則值直接存放在槽位下面,如果有沖突則把值存放在槽位下面,并通過鏈表的方式把產(chǎn)生沖突的值用鏈表串起來。

3 redis是如何替換原有key值的?

在上一個問題的基礎(chǔ)上,通過遍歷鏈表,比較key值。

3 redis 的數(shù)據(jù)類型為什么

參考:

1?第十章:Redis中bitmap的妙用 - 唯伊 - 博客園

總結(jié)

以上是生活随笔為你收集整理的01 Redis源码起航的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。