我們都知道Redis很快,它QPS可達10萬(每秒請求數)。 Redis為什麼這麼快呢,這篇文章將跟大家一起學習。 【相關推薦:Redis影片教學】
我們都知道記憶體讀寫是比磁碟讀寫快很多的。 Redis是基於記憶體儲存實作的資料庫,相對於資料存在磁碟的資料庫,就省去磁碟磁碟I/O的消耗。 MySQL等磁碟資料庫,需要建立索引來加快查詢效率,而Redis資料存放在內存,直接操作內存,所以就很快。
我們知道,MySQL索引為了提高效率,選擇了B 樹的資料結構。其實合理的資料結構,就是可以讓你的應用程式/程式更快。先看下Redis的資料結構&內部編碼圖:
##SDS簡單動態字串struct sdshdr { //SDS简单动态字符串 int len; //记录buf中已使用的空间 int free; // buf中空闲空间长度 char buf[]; //存储的实际内容 }字串長度處理在C語言中,要取得
撿田螺的小男孩這個字串的長度,需要從頭開始遍歷,複雜度為O(n);
在Redis中, 已經有一個
len欄位記錄目前字串的長度啦,直接取得即可,時間複雜度為O(1)。
消耗性能的。而在Redis中,SDS提供了兩種最佳化策略:空間預先分配和惰性空間釋放。
空間預先分配
當SDS簡單動態字串修改和空間擴充時,除了分配必要的記憶體空間,還會額外分配未使用的空間。分配規則是醬紫的:SDS修改後,len的長度小於1M,那麼將額外分配與len相同長度的未使用空間。例如len=100,重新分配後,buf 的實際長度會變成100(已使用空間) 100(額外空間) 1(空字元)=201。
- SDS修改後, len長度大於1M,那麼程式將分配1M的未使用空間。
惰性空間釋放
當SDS縮短時,不是回收多餘的記憶體空間,而是用free記錄下多餘的空間。後續再有修改操作,直接使用free中的空間,減少記憶體分配。 哈希Redis 作為一個K-V的記憶體資料庫,它使用用一張全域的哈希來保存所有的鍵值對。這張哈希表,有多個哈希桶組成,哈希桶中的entry元素保存了*key和
*value指針,其中
*key指向了實際的鍵,
*value指向了實際的值。
HashMap,它讓我們在O(1) 的時間複雜度快速找到鍵值對。首先透過key計算哈希值,找到對應的哈希桶位置,然後定位到entry,在entry找到對應的資料。
有些小夥伴可能會有疑問:你往雜湊表中寫入大量資料時,不是會遇到雜湊衝突問題嘛,那效率就會降下來啦。
Redis為了解決哈希衝突,採用了哈希衝突: 透過不同的key,計算相同的雜湊值,導致落在同一個雜湊桶中。
鍊式哈希。鍊式雜湊是指同一個雜湊桶中,多個元素用一個鍊錶來保存,它們之間依序用指標連接。
有些小夥伴可能還會有疑問:雜湊衝突鏈上的元素只能透過指標逐一尋找再操作。當往哈希表插入資料很多,衝突也會越多,衝突鍊錶就會越長,那麼查詢效率就會降低了。 為了保持高效,Redis 會對雜湊表做rehash操作,也就是增加雜湊桶,減少衝突。為了rehash更有效率,Redis還預設使用了兩個全域雜湊表,一個用於目前使用,稱為主雜湊表,一個用於擴容,稱為備用雜湊表。
跳跃表是Redis特有的数据结构,它其实就是在链表的基础上,增加多级索引,以提高查找效率。跳跃表的简单原理图如下:
压缩列表ziplist是列表键和字典键的的底层实现之一。它是由一系列特殊编码的内存块构成的列表, 一个ziplist可以包含多个entry, 每个entry可以保存一个长度受限的字符数组或者整数,如下:
由于内存是连续分配的,所以遍历速度很快。。
Redis支持多种数据基本类型,每种基本类型对应不同的数据结构,每种数据结构对应不一样的编码。为了提高性能,Redis设计者总结出,数据结构最适合的编码搭配。
Redis是使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。
//关注公众号:捡田螺的小男孩 typedef struct redisObject{ //类型 unsigned type:4; //编码 unsigned encoding:4; //指向底层数据结构的指针 void *ptr; //... }robj;
redisObject中,type 对应的是对象类型,包含String对象、List对象、Hash对象、Set对象、zset对象。encoding 对应的是编码。
Redis是单线程的,其实是指Redis的网络IO和键值对读写是由一个线程来完成的。但Redis的其他功能,比如持久化、异步删除、集群数据同步等等,实际是由额外的线程执行的。
Redis的单线程模型,避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向快速执行场景的内存数据库,所以要慎用如lrange和smembers、hgetall等命令。
什么是上下文切换?举个粟子:
- 比如你在看一本英文小说,你看到某一页,发现有个单词不会读,你加了个书签,然后去查字典。查完字典后,你回来从书签那里继续开始读,这个流程就很舒畅。
- 如果你一个人读这本书,肯定没啥问题。但是如果你去查字典的时候,别的小伙伴翻了一下你的书,然后溜了。你再回来看的时候,发现书不是你看的那一页了,你得花时间找到你的那一页。
- 一本书,你一个人怎么看怎么打标签都没事,但是人多了翻来翻去,这本书各种标记就很乱了。可能这个解释很粗糙,但是道理应该是一样的。
什么是I/O多路复用?
多路I/O重複使用技術可以讓單一執行緒高效的處理多個連線請求,而Redis則使用epoll作為I/O多路復用技術的實現。而Redis本身的事件處理模型將epoll中的連接、讀寫、關閉都轉換為事件,不在網路I/O上浪費過多的時間。
Redis直接自己建構了VM機制 ,不會像一般的系統會呼叫系統函數處理,會浪費一定的時間去移動和請求。
Redis的虛擬記憶體機制是啥呢?
虛擬記憶體機制就是暫時把不經常存取的資料(冷資料)從記憶體交換到磁碟中,從而騰出寶貴的記憶體空間用於其它需要存取的資料(熱數據)。透過VM功能可以實現冷熱資料分離,使熱資料仍在記憶體中、冷資料儲存到磁碟。這樣就可以避免因為記憶體不足而造成存取速度下降的問題。
更多程式相關知識,請造訪:程式設計影片! !
以上是深入解析一下Redis為什麼這麼快?的詳細內容。更多資訊請關注PHP中文網其他相關文章!