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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

从零单排学Redis【青铜】

發布時間:2025/3/21 数据库 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零单排学Redis【青铜】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

redis

最近在學Redis,我相信只要是接觸過Java開發的都會聽過Redis這么一個技術。面試也是非常高頻的一個知識點,之前一直都是處于了解階段。秋招過后這段時間是沒有什么壓力的,所以打算系統學學Redis,這也算是我從零學習Redis的筆記吧。

本文力求講清每個知識點,希望大家看完能有所收獲。

一、介紹一下Redis

首先,肯定是去官網看看官方是怎么介紹Redis的啦。https://redis.io/topics/introduction

如果像我一樣,英語可能不太好的,可能看不太懂。沒事,咱們Chrome瀏覽器可以切換成中文的,中文是我們的母語,肯定沒啥壓力了。Eumm…

讀完之后,發現中文也就那樣了。

一大堆沒見過的技術:lua(Lua腳本)、replication(復制)、Redis Sentinel(哨兵)、Redis Cluster(Redis 集群),當然我們也會有看得懂的技術:transactions(事務)、different levels of on-disk persistence(數據持久化)、LRU eviction(LRU淘汰機制)..

至少官方介紹Redis的第一句應該是可以很容易看懂:"Redis is an open source (BSD licensed),in-memory data structure store, used as a database,cache?and message broker."

Redis是一個開源的,基于內存的數據結構存儲,可用作于數據庫、緩存、消息中間件。

  • 從官方的解釋上,我們可以知道:Redis是基于內存,支持多種數據結構。

  • 從經驗的角度上,我們可以知道:Redis常用作于緩存。

就我個人認為:學習一種新技術,先把握該技術整體的知識(思想),再扣細節,這樣學習起來會比較輕松一些。所以我們先以“內存”、“數據結構”、“緩存”來對Redis入門。

1.1為什么要用Redis?

從上面可知:Redis是基于內存,常用作于緩存的一種技術,并且Redis存儲的方式是以key-value的形式。

我們可以發現這不就是Java的Map容器所擁有的特性嗎,那為什么還需要Redis呢?

  • Java實現的Map是本地緩存,如果有多臺實例(機器)的話,每個實例都需要各自保存一份緩存,緩存不具有一致性

  • Redis實現的是分布式緩存,如果有多臺實例(機器)的話,每個實例都共享一份緩存,緩存具有一致性

  • Java實現的Map不是專業做緩存的,JVM內存太大容易掛掉的。一般用做于容器來存儲臨時數據,緩存的數據隨著JVM銷毀而結束。Map所存儲的數據結構,緩存過期機制等等是需要程序員自己手寫的。

  • Redis是專業做緩存的,可以用幾十個G內存來做緩存。Redis一般用作于緩存,可以將緩存數據保存在硬盤中,Redis重啟了后可以將其恢復。原生提供豐富的數據結構、緩存過期機制等等簡單好用的功能。

參考資料:

  • 為什么要用redis而不用map做緩存?https://segmentfault.com/q/1010000009106416

1.2為什么要用緩存?

如果我們的網站出現了性能問題(訪問時間慢),按經驗來說,一般是由于數據庫撐不住了。因為一般數據庫的讀寫都是要經過磁盤的,而磁盤的速度可以說是相當慢的(相對內存來說)

  • 科普文:讓 CPU 告訴你硬盤和網絡到底有多慢https://zhuanlan.zhihu.com/p/24726196

數據庫撐不住了

如果學過Mybaits、Hibernate的同學就可以知道,它們有一級緩存、二級緩存這樣的功能(終究來說還是本地緩存)。目的就是為了:不用每次讀取的時候,都要查一次數據庫

有了緩存之后,我們的訪問就變成這樣了:

有了緩存提高了并發和性能

二、Redis的數據結構

本文不會講述命令的使用方式,具體的如何使用可查詢API。

  • Redis 命令參考:http://doc.redisfans.com/

  • try Redis(不用安裝Redis即可體驗Redis命令):http://try.redis.io/

Redis支持豐富的數據結構,常用的有string、list、hash、set、sortset這幾種。學習這些數據結構是使用Redis的基礎!

"Redis is written in ANSI C"-->Redis由C語言編寫

首先還是得聲明一下,Redis的存儲是以key-value的形式的。Redis中的key一定是字符串,value可以是string、list、hash、set、sortset這幾種常用的。

redis數據結構

但要值得注意的是:Redis并沒有直接使用這些數據結構來實現key-value數據庫,而是基于這些數據結構創建了一個對象系統

  • 簡單來說:Redis使用對象來表示數據庫中的鍵和值。每次我們在Redis數據庫中新創建一個鍵值對時,至少會創建出兩個對象。一個是鍵對象,一個是值對象。

Redis中的每個對象都由一個redisObject結構來表示:

typedef?struct?redisObject{//?對象的類型unsigned?type?4:;//?對象的編碼格式unsigned?encoding:4;//?指向底層實現數據結構的指針void?*?ptr;//.....}robj; 數據結構對應的類型與編碼

簡單來說就是Redis對key-value封裝成對象,key是一個對象,value也是一個對象。每個對象都有type(類型)、encoding(編碼)、ptr(指向底層數據結構的指針)來表示。

以值為1006的字符串對象為例

下面我就來說一下我們Redis常見的數據類型:string、list、hash、set、sortset。它們的底層數據結構究竟是怎么樣的!

2.1SDS簡單動態字符串

簡單動態字符串(Simple dynamic string,SDS)

Redis中的字符串跟C語言中的字符串,是有點差距的

Redis使用sdshdr結構來表示一個SDS值:

struct?sdshdr{//?字節數組,用于保存字符串char?buf[];//?記錄buf數組中已使用的字節數量,也是字符串的長度int?len;//?記錄buf數組未使用的字節數量int?free; }

例子:

SDS例子

2.1.1使用SDS的好處

SDS與C的字符串表示比較

  • sdshdr數據結構中用len屬性記錄了字符串的長度。那么獲取字符串的長度時,時間復雜度只需要O(1)

  • SDS不會發生溢出的問題,如果修改SDS時,空間不足。先會擴展空間,再進行修改!(內部實現了動態擴展機制)。

  • SDS可以減少內存分配的次數(空間預分配機制)。在擴展空間時,除了分配修改時所必要的空間,還會分配額外的空閑空間(free 屬性)。

  • SDS是二進制安全的,所有SDS API都會以處理二進制的方式來處理SDS存放在buf數組里的數據。

  • 2.2鏈表

    對于鏈表而言,我們不會陌生的了。在大學期間肯定開過數據結構與算法課程,鏈表肯定是講過的了。在Java中Linkedxxx容器底層數據結構也是鏈表+[xxx]的。我們來看看Redis中的鏈表是怎么實現的:

    使用listNode結構來表示每個節點:

    typedef?strcut?listNode{//前置節點strcut?listNode??*pre;//后置節點strcut?listNode??*pre;//節點的值void??*value;}listNode

    使用listNode是可以組成鏈表了,Redis中使用list結構來持有鏈表

    typedef?struct?list{//表頭結點listNode??*head;//表尾節點listNode??*tail;//鏈表長度unsigned?long?len;//節點值復制函數void?*(*dup)?(viod?*ptr);//節點值釋放函數void??(*free)?(viod?*ptr);//節點值對比函數int?(*match)?(void?*ptr,void?*key);}list

    具體的結構如圖:

    2.2.1Redis鏈表的特性

    Redis的鏈表有以下特性:

    • 無環雙向鏈表

    • 獲取表頭指針,表尾指針,鏈表節點長度的時間復雜度均為O(1)

    • 鏈表使用void *指針來保存節點值,可以保存各種不同類型的值

    2.3哈希表

    聲明:《Redis設計與實現》里邊有“字典”這么一個概念,我個人認為還是直接叫哈希表比較通俗易懂。從代碼上看:“字典”也是在哈希表基礎上再抽象了一層而已。

    在Redis中,key-value的數據結構底層就是哈希表來實現的。對于哈希表來說,我們也并不陌生。在Java中,哈希表實際上就是數組+鏈表的形式來構建的。下面我們來看看Redis的哈希表是怎么構建的吧。

    在Redis里邊,哈希表使用dictht結構來定義:

    ????typedef?struct?dictht{//哈希表數組dictEntry?**table;??//哈希表大小unsigned?long?size;????//哈希表大小掩碼,用于計算索引值//總是等于size-1unsigned?long?sizemark;?????//哈希表已有節點數量unsigned?long?used;}dictht 哈希表的數據結構

    我們下面繼續寫看看哈希表的節點是怎么實現的吧:

    ????typedef?struct?dictEntry?{//鍵void?*key;//值union?{void?*value;uint64_tu64;int64_ts64;}v;????//指向下個哈希節點,組成鏈表struct?dictEntry?*next;}dictEntry;

    從結構上看,我們可以發現:Redis實現的哈希表和Java中實現的是類似的。只不過Redis多了幾個屬性來記錄常用的值:sizemark(掩碼)、used(已有的節點數量)、size(大小)。

    同樣地,Redis為了更好的操作,對哈希表往上再封裝了一層(參考上面的Redis實現鏈表),使用dict結構來表示:

    typedef?struct?dict?{//類型特定函數dictType?*type;//私有數據void?*privdata;//哈希表dictht?ht[2];//rehash索引//當rehash不進行時,值為-1int?rehashidx;??}dict;//-----------------------------------typedef?struct?dictType{//計算哈希值的函數unsigned?int?(*hashFunction)(const?void?*?key);//復制鍵的函數void?*(*keyDup)(void?*private,?const?void?*key);//復制值得函數void?*(*valDup)(void?*private,?const?void?*obj);??//對比鍵的函數int?(*keyCompare)(void?*privdata?,?const?void?*key1,?const?void?*key2)//銷毀鍵的函數void?(*keyDestructor)(void?*private,?void?*key);//銷毀值的函數void?(*valDestructor)(void?*private,?void?*obj);??}dictType

    所以,最后我們可以發現,Redis所實現的哈希表最后的數據結構是這樣子的:

    從代碼實現和示例圖上我們可以發現,Redis中有兩個哈希表

    • ht[0]:用于存放真實的key-vlaue數據

    • ht[1]:用于擴容(rehash)

    Redis中哈希算法和哈希沖突跟Java實現的差不多,它倆差異就是:

    • Redis哈希沖突時:是將新節點添加在鏈表的表頭

    • JDK1.8后,Java在哈希沖突時:是將新的節點添加到鏈表的表尾

    2.3.1rehash的過程

    下面來具體講講Redis是怎么rehash的,因為我們從上面可以明顯地看到,Redis是專門使用一個哈希表來做rehash的。這跟Java一次性直接rehash是有區別的。

    在對哈希表進行擴展或者收縮操作時,reash過程并不是一次性地完成的,而是漸進式地完成的。

    Redis在rehash時采取漸進式的原因:數據量如果過大的話,一次性rehash會有龐大的計算量,這很可能導致服務器一段時間內停止服務

    Redis具體是rehash時這么干的:

    • (1:在字典中維持一個索引計數器變量rehashidx,并將設置為0,表示rehash開始。

    • (2:在rehash期間每次對字典進行增加、查詢、刪除和更新操作時,除了執行指定命令外;還會將ht[0]中rehashidx索引上的值rehash到ht[1],操作完成后rehashidx+1。

    • (3:字典操作不斷執行,最終在某個時間點,所有的鍵值對完成rehash,這時將rehashidx設置為-1,表示rehash完成

    • (4:在漸進式rehash過程中,字典會同時使用兩個哈希表ht[0]和ht[1],所有的更新、刪除、查找操作也會在兩個哈希表進行。例如要查找一個鍵的話,服務器會優先查找ht[0],如果不存在,再查找ht[1],諸如此類。此外當執行新增操作時,新的鍵值對一律保存到ht[1],不再對ht[0]進行任何操作,以保證ht[0]的鍵值對數量只減不增,直至變為空表。

    2.4跳躍表(shiplist)

    跳躍表(shiplist)是實現sortset(有序集合)的底層數據結構之一!

    跳躍表可能對于大部分人來說不太常見,之前我在學習的時候發現了一篇不錯的文章講跳躍表的,建議大家先去看完下文再繼續回來閱讀:

    • 漫畫算法:什么是跳躍表?http://blog.jobbole.com/111731/

    Redis的跳躍表實現由zskiplist和zskiplistNode兩個結構組成。其中zskiplist保存跳躍表的信息(表頭,表尾節點,長度),zskiplistNode則表示跳躍表的節點

    按照慣例,我們來看看zskiplistNode跳躍表節點的結構是怎么樣的:

    typeof?struct?zskiplistNode?{//?后退指針struct?zskiplistNode?*backward;//?分值double?score;//?成員對象robj?*obj;//?層struct?zskiplistLevel?{//?前進指針struct?zskiplistNode?*forward;//?跨度unsigned?int?span;}?level[]; }?zskiplistNode;

    zskiplistNode的對象示例圖(帶有不同層高的節點):

    帶有不同層高的節點

    示例圖如下:

    跳躍表節點的示例圖

    zskiplist的結構如下:

    typeof?struct?zskiplist?{//?表頭節點,表尾節點struct?skiplistNode?*header,*tail;//?表中節點數量unsigned?long?length;//?表中最大層數int?level; }?zskiplist;

    最后我們整個跳躍表的示例圖如下:

    跳躍表示例圖

    2.5整數集合(intset)

    整數集合是set(集合)的底層數據結構之一。當一個set(集合)只包含整數值元素,并且元素的數量不多時,Redis就會采用整數集合(intset)作為set(集合)的底層實現。

    整數集合(intset)保證了元素是不會出現重復的,并且是有序的(從小到大排序),intset的結構是這樣子的:

    typeof?struct?intset?{//?編碼方式unit32_t?encoding;//?集合包含的元素數量unit32_t?lenght;//?保存元素的數組int8_t?contents[]; }?intset;

    intset示例圖:

    intset示例圖

    說明:雖然intset結構將contents屬性聲明為int8_t類型的數組,但實際上contents數組并不保存任何int8_t類型的值,contents數組的真正類型取決于encoding屬性的值

    • INTSET_ENC_INT16

    • INTSET_ENC_INT32

    • INTSET_ENC_INT64

    從編碼格式的名字我們就可以知道,16,32,64編碼對應能存放的數字范圍是不一樣的。16明顯最少,64明顯最大。

    如果本來是INTSET_ENC_INT16的編碼,想要存放大于INTSET_ENC_INT16編碼能存放的整數值,此時就得編碼升級(從16升級成32或者64)。步驟如下:

    • 1)根據新元素類型拓展整數集合底層數組的空間并為新元素分配空間。

    • 2)將底層數組現有的所以元素都轉換成與新元素相同的類型,并將類型轉換后的元素放到正確的位上,需要維持底層數組的有序性質不變。

    • 3)將新元素添加到底層數組。

    另外一提:只支持升級操作,并不支持降級操作

    2.6壓縮列表(ziplist)

    壓縮列表(ziplist)是list和hash的底層實現之一。如果list的每個都是小整數值,或者是比較短的字符串,壓縮列表(ziplist)作為list的底層實現。

    壓縮列表(ziplist)是Redis為了節約內存而開發的,是由一系列的特殊編碼的連續內存塊組成的順序性數據結構。

    壓縮列表結構圖例如下:

    壓縮列表的組成部分

    下面我們看看節點的結構圖:

    ?

    壓縮列表從表尾節點倒序遍歷,首先指針通過zltail偏移量指向表尾節點,然后通過指向節點記錄的前一個節點的長度依次向前遍歷訪問整個壓縮列表

    三、Redis中數據結構的對象

    再次看回這張圖,覺不覺得就很好理解了?

    數據結構對應的類型與編碼

    3.1字符串(stirng)對象

    在上面的圖我們知道string類型有三種編碼格式

    • int:整數值,這個整數值可以使用long類型來表示

      • 如果是浮點數,那就用embstr或者raw編碼。具體用哪個就看這個數的長度了

    • embstr:字符串值,這個字符串值的長度小于32字節

    • raw:字符串值,這個字符串值的長度大于32字節

    embstr和raw的區別

    • raw分配內存和釋放內存的次數是兩次,embstr是一次

    • embstr編碼的數據保存在一塊連續的內存里面

    編碼之間的轉換

    • int類型如果存的不再是一個整數值,則會從int轉成raw

    • embstr是只讀的,在修改的時候回從embstr轉成raw

    3.2列表(list)對象

    在上面的圖我們知道list類型有兩種編碼格式

    • ziplist:字符串元素的長度都小于64個字節&&總數量少于512個

    • linkedlist:字符串元素的長度大于64個字節||總數量大于512個

    ziplist編碼的列表結構:

    ????redis?>?RPUSH?numbers?1?"three"?5(integer)?3? ziplist的列表結構

    linkedlist編碼的列表結構:

    linkedlist編碼的列表結構

    編碼之間的轉換:

    • 原本是ziplist編碼的,如果保存的數據長度太大或者元素數量過多,會轉換成linkedlist編碼的。

    3.3哈希(hash)對象

    在上面的圖我們知道hash類型有兩種編碼格式

    • ziplist:key和value的字符串長度都小于64字節&&鍵值對總數量小于512

    • hashtable:key和value的字符串長度大于64字節||鍵值對總數量大于512

    ziplist編碼的哈希結構:

    ziplist編碼的哈希結構1

    ?

    ziplist編碼的哈希結構2

    hashtable編碼的哈希結構:

    hashtable編碼的哈希結構

    編碼之間的轉換:

    • 原本是ziplist編碼的,如果保存的數據長度太大或者元素數量過多,會轉換成hashtable編碼的。

    3.4集合(set)對象

    在上面的圖我們知道set類型有兩種編碼格式

    • intset:保存的元素全都是整數&&總數量小于512

    • hashtable:保存的元素不是整數||總數量大于512

    intset編碼的集合結構:

    intset編碼的集合結構

    hashtable編碼的集合結構:

    hashtable編碼的集合結構

    編碼之間的轉換:

    • 原本是intset編碼的,如果保存的數據不是整數值或者元素數量大于512,會轉換成hashtable編碼的。

    3.5有序集合(sortset)對象

    在上面的圖我們知道set類型有兩種編碼格式

    • ziplist:元素長度小于64&&總數量小于128

    • skiplist:元素長度大于64||總數量大于128

    ziplist編碼的有序集合結構:

    ziplist編碼的有序集合結構1

    ?

    ziplist編碼的有序集合結構2

    skiplist編碼的有序集合結構:

    skiplist編碼的有序集合結構

    有序集合(sortset)對象同時采用skiplist和哈希表來實現

    • skiplist能夠達到插入的時間復雜度為O(logn),根據成員查分值的時間復雜度為O(1)

    編碼之間的轉換:

    • 原本是ziplist編碼的,如果保存的數據長度大于64或者元素數量大于128,會轉換成skiplist編碼的。

    3.6Redis對象一些細節

    • (1:服務器在執行某些命令的時候,會先檢查給定的鍵的類型能否執行指定的命令。

      • 比如我們的數據結構是sortset,但你使用了list的命令。這是不對的,服務器會檢查一下我們的數據結構是什么才會進一步執行命令

    • (2:Redis的對象系統帶有引用計數實現的內存回收機制

      • 對象不再被使用的時候,對象所占用的內存會釋放掉

    • (3:Redis會共享值為0到9999的字符串對象

    • (4:對象會記錄自己的最后一次被訪問時間,這個時間可以用于計算對象的空轉時間。

    最后

    本文主要講了一下Redis常用的數據結構,以及這些數據結構的底層設計是怎么樣的。整體來說不會太難,因為這些數據結構我們在學習的過程中多多少少都接觸過了,《Redis設計與實現》這本書寫得也足夠通俗易懂。

    至于我們在使用的時候挑選哪些數據結構作為存儲,可以簡單看看:

    • string-->簡單的key-value

    • list-->有序列表(底層是雙向鏈表)-->可做簡單隊列

    • set-->無序列表(去重)-->提供一系列的交集、并集、差集的命令

    • hash-->哈希表-->存儲結構化數據

    • sortset-->有序集合映射(member-score)-->排行榜

    如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~

    參考博客:

    • Redis簡明教程http://bridgeforyou.cn/2018/05/19/Redis-Tutorial/

    • 五旬大爺教你一窺redis之謎https://zhuanlan.zhihu.com/p/34762100

    參考資料:

    • 《Redis設計與實現》

    • 《Redis實戰》

    總結

    以上是生活随笔為你收集整理的从零单排学Redis【青铜】的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 97精品自拍| 亚洲色图2 | 天美视频在线观看 | xxx麻豆| 免费久久网站 | 啪啪福利视频 | 国产刺激对白 | 久久伊人免费视频 | 91亚洲精品久久久蜜桃 | 中文一区视频 | 2020亚洲男人天堂 | 欧美国产视频一区 | 日韩特一级 | 性色av网| 久草视频免费在线 | 成人在线观看免费网站 | 日韩无遮挡 | 欧美少妇一区二区三区 | 13日本xxxxxⅹxxx20 | 在线视频免费观看你懂的 | 日韩福利在线播放 | 一区二区在线免费看 | 欧美性插动态图 | 极品在线观看 | 逼特逼在线视频 | 亚洲无码精品国产 | 少妇伦子伦精品无吗 | 中国一级免费毛片 | 美女被揉胸视频 | 超碰男人天堂 | 欧美日本 | 穿情趣内衣被c到高潮视频 欧美性猛交xxxx黑人猛交 | 日本偷拍一区 | 日韩免费小视频 | 成人性做爰aaa片免费 | 狠狠干,狠狠操 | 夜夜躁狠狠躁日日躁av | 国产精品丝袜黑色高跟 | av直播在线观看 | 91超碰在线观看 | 免费视频91 | 大乳女喂男人吃奶视频 | 亚洲av永久无码精品三区在线 | 成年人免费毛片 | 在线国产欧美 | 上原亚衣在线 | 色婷婷激情五月 | 老熟女毛茸茸 | av大片在线播放 | av鲁丝一区鲁丝二区鲁丝 | 91男女视频 | 一区二区三区高清在线 | 无码国产69精品久久久久网站 | 视频在线免费 | 亚洲自拍偷拍精品 | 高清欧美精品xxxxx在线看 | 亚洲国产精品自拍 | 日韩一二三区 | 97超碰免费在线观看 | xxxx在线视频| 777欧美 | 亚a在线| 春色激情站 | 国产综合福利 | 美女赤身免费网站 | 亚洲国产精品久久AV | 亚洲最大成人网站 | 精品成在人线av无码免费看 | jizz中国少妇 | 日本免费高清视频 | 日韩精品视频网站 | 日韩国产片 | 成人字幕 | 国产一级在线 | www.猫咪av.com | 国产淫片av片久久久久久 | 中文字幕亚洲综合 | 国产丝袜美腿一区二区三区 | 免费久久一级欧美特大黄 | 午夜精品久久久久 | 日本www色 | 久久免费视频网站 | 91在线视频免费播放 | 瑟瑟视频在线看 | 嫦娥性艳史bd | 午夜片在线观看 | 91麻豆精品一二三区在线 | 热逼视频 | 中国黄色1级片 | 男人插女人网站 | 欧美打屁股 | 色一情一乱一乱一区91av | 一级a毛片免费观看久久精品 | 一级在线 | 日韩91精品 | 国产在线视频一区二区三区 | 成人精品三级 | 日韩啪啪网 | 亚洲综合成人av |