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

歡迎訪問 生活随笔!

生活随笔

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

数据库

long 转为string_面试必问 Redis数据结构底层原理String、List篇

發布時間:2023/12/20 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 long 转为string_面试必问 Redis数据结构底层原理String、List篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊關注上方“Java大廠面試官”,第一時間送達技術干貨。

閱讀文本大概需要 8 分鐘。

前言

今天來整理學習下Redis有哪些常用數據結構,都是怎么使用的呢?首先看下全局存儲結構。

全局存儲結構

基礎你們肯定都知道,redis支持的基礎數據結構如下: String(字符串)、List(鏈表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合),那我來給你整個的畫一畫redis全局存儲結構模型。( redis版本不同,代碼也不盡相同,但是看原理夠用了),從redis源碼開始分析:

  • 首先是redis啟動會初始化redisServer,默認創建16個數據庫redisDb,默認我們用的都是第一個編號0的數據庫。

    struct?redisServer?{
    ?//?…
    ?//?redis數據庫數組
    ?redisDb?*db;
    ????//?數據庫的數量?默認16
    ????int?dbnum;
    ?//...
    }
  • 每個redisDb數據庫用dict(字典)保存著數據庫中的所有鍵值對

    ?struct?redisDb?{
    ????//?數據庫鍵空間,保存著數據庫中的所有鍵值對
    ?dict?*dict;?
    ?//...
    }
  • dict(字典)使用一對hashtable哈希表實現,跟Java中的HashMap很像

    typedef?struct?dict?{
    ????//?包含2個hashtable
    ????dictht?ht[2];
    ????//?...
    }?

    typedef?struct?dictht?{
    ????//?哈希表數組
    ????dictEntry?**table;
    ????//?...
    }?

    typedef?struct?dictEntry?{
    ????//?鍵
    ????void?*key;
    ????//?值
    ????void?*val;
    ????//?指向下個哈希表節點,形成鏈表
    ????dictEntry?*next;

    }
  • 字典中實際存儲的Redis對象

    typedef?struct?redisObject?{???
    ????//?類型????
    ????unsigned?type:4;//?string,list,set,zset,hash等
    ????//?編碼????
    ????unsigned?encoding:4;??//?int,raw,embstr,ziplist,intset,quicklist,skiplist等??
    ????//?對象最后一次被訪問的時間????
    ????unsigned?lru:REDIS_LRU_BITS;??
    ????//?引用計數????
    ????int?refcount;???
    ????//?指向實際值的指針???
    ????void?*ptr;
    }?

    從上面分析可得Redis全局存儲結構如下:

在這里插入圖片描述

(這個圖直接把我畫裂開了,如有錯誤歡迎指正)

下面我們用"3w"方法來一一介紹下,每個數據類型,底層所用到了哪些數據結構(編碼 )。

String 字符串

是什么

內部其實就是一個帶長度信息的字節數組,原理類似Java中的ArrayList,可以動態擴容,所以很多特性都類似了,原理是相通的。內容是以二進制的形式存儲的,所以 SDS(Simple Dynamic ?String) 可以存儲任何類型的二進制數據,同時也不需要擔心數據格式轉換的問題。

struct?SDS?{
????//?...
??T?capacity;?//?數組容量
????T?len;?//?數組長度
????byte[]?content;?//?數組內容
}???
在這里插入圖片描述

為什么

1.為什么申請空間比實際占用空間大,冗余了很多空位?

字符串支持append修改操作,如果沒有冗余空間,那么追加操作必會引起頻繁的數組擴容,而擴容是個耗時操作,所以通過空間預分配的方式來解決,即用冗余空間換時間。

2.實際使用長度len字段存在的意義是什么?

我們來用反證法證明,如果沒有len來記錄字符串長度,那么每次獲取字符串長度時,就要調用默認的strlen函數來獲取,而這個函數的時間復雜度是O(n),如果有了len,每次獲取長度可以直接訪問它,時間復雜度立馬降至為O(1)。查詢效率迎來質的飛躍,這塊跟Arraylist的size原理一樣。

如何實現

我們來直接用redis自帶的debug命令看下實際存儲對象的底層編碼encoding,來看下底層使用了什么數據結構。

本文實例用的是redis版本:6.0.6

int編碼

>?set?key1?2000222222
OK
>?debug?object?key1
Value?at:0x7f21f2eadd20?refcount:1?encoding:int?serializedlength:5?lru:13142802?lru_seconds_idle:25

embstr編碼

>?set?key2?01234567890123456789012345678901234567890123??//?44個字符
OK
>?debug?object?key2
Value?at:0x7f21f2e15140?refcount:1?encoding:embstr?serializedlength:21?lru:13145749?lru_seconds_idle:5

raw編碼

>?set?key2?012345678901234567890123456789012345678901234?//?45個字符
OK
>?debug?object?key2
Value?at:0x7f21f2eadd40?refcount:1?encoding:raw?serializedlength:21?lru:13145765?lru_seconds_idle:2

總結:

為了節省內存空間,會按照實際存儲字符串長度類型來選用不同編碼

  • 存儲的字符串可以轉為long型,則用long類型存儲,編碼為int

  • 存儲的字符串長度不大于44個字節時,用embstr編碼

  • 存儲的字符串長度大于44個字節時,用raw編碼

編碼類型分這么細的原因?為了優先使用更緊湊的數據結構來解決問題,終極目標就是為了壓縮內存、壓縮內存、壓縮內存。

raw和embstr的區別?embstr編碼: RedisObject的元數據,指針和SDS是連續的,可以避免內存碎片

raw編碼: Redis會給SDS分配獨立的空間,并用指針指向SDS結構

擴容策略

  • 字符串長度小于1MB時,采用加倍策略,ArrayList是1.5倍
  • 字符串長度大于1MB時,采用每次擴容只加固定1MB

這個擴容策略,就比ArrayList高明了,當字符串比較大時,比如200M,每次還是double的話,400M,那就太浪費空間了,為了避免這種過大的空間浪費,使用了這種閾值判斷方式,針對原始數據的不同大小采用相應的有效策略。

Reids規定了字符串最大長度不能超過512MB。

使用場景

常用于緩存用戶信息、原子加減。

注意: 原子計數是有范圍的(long的范圍),超過了會報錯異常

List 鏈表

是什么

  • 版本3.2之前在Redis中使用的是壓縮列表ziplist+雙向鏈表linkedlist.
  • 版本3.2之后快速鏈表quickList

3.2之前初始化的 List 使用的壓縮列表ziplist,隨著數據增多,轉化為雙向鏈表linkedlist。壓縮列表轉化成雙向鏈表的條件:

  • 如果添加的字符串元素長度超過默認值64
  • zip包含的節點數超過默認值512

這兩個條件是可以修改的,在redis.conf中

list-max-ziplist-value?64?
list-max-ziplist-entries?512??
linkedlist

原理類似Java中的LinkedList,增刪時間復雜度O(1),查詢O(n).

typedef?struct?list{
?????//表頭節點
?????listNode?*head;
?????//表尾節點
?????listNode?*tail;
?????//鏈表所包含的節點數量
?????unsigned?long?len;
??//?...
}
typedef??struct?listNode{
???????//前置節點
???????struct?listNode?*prev;
???????//后置節點
???????struct?listNode?*next;
???????//節點的值
???????void?*value;??
}
在這里插入圖片描述
ziplist

ziplist是什么

ziplist壓縮列表是內存地址連續,元素之間緊湊存儲,功能類似鏈表的一種數據結構。

struct?ziplist?{
??int32?zlbytes;???//?整個列表占用字節數
??int32?zltail_offset;?//?達到尾部的偏移量
??int16?zllength;?//?存儲元素實體個數
??T[]?entries;?//?存儲內容實體
??int8?zlend;?//?尾部標識
}
struct?entry?{
??int?prevlen;???//?前一個entry的字節長度int?encoding;;?//?元素類型編碼
??optional?byte[]?content;?//?元素內容
}在這里插入圖片描述

為什么用ziplist?

因為普通的鏈表要附加prev、next前后指針,浪費空間(64位操作系統每個指針占用8個字節),另外每個節點的內存是單獨分配,會加劇內存的碎片化,影響內存管理效率。

如何實現

簡單的來說就是用非指針連接的方式實現了雙向鏈表的能力,能從頭部和尾部(zltail)雙向遍歷,沒有維護雙向指針prev next;而是存儲上一個 entry的長度和 當前entry的長度,通過長度推算下一個元素在什么地方。犧牲讀取的性能,獲得高效的存儲空間,因為(簡短字符串的情況)存儲指針比存儲entry長度 更費內存。這是典型的“時間換空間”。只有字段、值比較小,才會用ziplist。

優點:

  • 內存地址連續,省去了每個元素的頭尾節點指針占用的內存,節省空間

缺點:

  • 插入數據、刪除數據會導致連鎖更新問題,有點兒類似Arraylist為保證內存連續性的數據移動的原理
quicklist

quicklist是什么

quickList是一個ziplist組成的雙向鏈表。每個節點使用ziplist來保存數據。

為什么

為什么用quicklist

結合了 zipList 和 linkedList 的優點設計出來的,ziplist會引入頻繁的內存申請和釋放,而linkedlist由于指針也會造成內存的浪費,而且每個節點是單獨存在的,會造成很多內存碎片,所以結合兩個結構的特點,設計了quickList。

如何實現

debug看下encoding: quicklist

>?rpush?key3?a?b?c
3
>?debug?object?key3
Value?at:0x7f21f2eaddb0?refcount:1?encoding:quicklist?serializedlength:22?lru:13150287?lru_seconds_idle:17?ql_nodes:1?ql_avg_node:3.00?ql_ziplist_max:-2?ql_compressed:0?ql_uncompressed_size:20
struct?quicklist?{
??quicklistNode?*head;???
??quicklistNode?*tail;?
??long?count;?//?元素總數
??//?...?
}
struct?quicklistNode??{
??quicklistNode?*prev;??
??quicklistNode?*next;?
??ziplist?*zl;//?壓縮列表

quickList 的每個節點使用 ziplist 來保存數據,有head 有tail,每一個節點是一個quicklistNode,包含prev和next指針。每一個quicklistNode 包含 一個ziplist,*zp 壓縮鏈表里存儲鍵值。所以quicklist是對ziplist進行一次封裝,使用小塊的ziplist來既保證了少使用內存,也保證了性能。

結構如下圖:

在這里插入圖片描述

每個quicklist節點上的ziplist大小可以配置

-5: 每個quicklist節點上的ziplist大小不能超過64 Kb。

-4: 每個quicklist節點上的ziplist大小不能超過32 Kb。

-3: 每個quicklist節點上的ziplist大小不能超過16 Kb。

-2: 每個quicklist節點上的ziplist大小不能超過8 Kb。(默認值)

-1: 每個quicklist節點上的ziplist大小不能超過4 Kb。

list-max0ziplist-size -2

中間節點壓縮策略可配置

0: 是個特殊值,表示都不壓縮。這是Redis的默認值。
1: 表示quicklist兩端各有1個節點不壓縮,中間的節點壓縮。
2: 表示quicklist兩端各有2個節點不壓縮,中間的節點壓縮。
以此類推
list-compress-depth 0

總結

  • 整個redis全局存儲模型,是用字典完成的,類似Java中的HashMap原理
  • String類型,底層是動態字符串,會根據字符串類型和大小決定使用int編碼raw編碼或者embstr編碼
  • List類型,3.2版本之前會根據數據大小判斷用ziplist還是linkedlist,3.2版本之后優化為quicklist方式編碼。

參考:

  • 《Redis深度歷險 核心原理與應用實踐》
  • https://juejin.cn/post/6863256540439117831

往期推薦

看故事學知識-三年工齡了還講不清redis持久化!

生產環境下,如何排查CPU異常,定位鬼畜代碼

為什么JDK源碼中,無限循環大多使用for(;;)而不是while(true)?

一起進大廠

成為架構師

長按加關注

總結

以上是生活随笔為你收集整理的long 转为string_面试必问 Redis数据结构底层原理String、List篇的全部內容,希望文章能夠幫你解決所遇到的問題。

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