Redis中雜湊分佈不均勻該怎麼辦

itread01 2021-01-21 01:44:43
redis 数据库/缓存 itread01 不均


# 前言`Redis` 是一個鍵值對資料庫,其鍵是通過雜湊進行儲存的。整個 `Redis` 可以認為是一個外層雜湊,之所以稱為外層雜湊,是因為 `Redis` 內部也提供了一種雜湊型別,這個可以稱之為內部雜湊。當我們採用雜湊物件進行資料儲存時,對整個 `Redis` 而言,就經過了兩層雜湊儲存。# 雜湊物件雜湊物件本身也是一個 `key-value` 儲存結構,底層的儲存結構也可以分為兩種:`ziplist`(壓縮列表) 和 `hashtable`(雜湊表)。這兩種儲存結構也是通過編碼來進行區分:| 編碼屬性 | 描述 | object encoding命令返回值 || -------------------- | ------------------------ | ------------------------- || OBJ_ENCODING_ZIPLIST | 使用壓縮列表實現雜湊物件 | ziplist || OBJ_ENCODING_HT | 使用字典實現雜湊物件 | hashtable |## hashtable`Redis` 中的 `key-value` 是通過 `dictEntry` 物件進行包裝的,而雜湊表就是將 `dictEntry` 物件又進行了再一次的包裝得到的,這就是雜湊表物件 `dictht`:```ctypedef struct dictht { dictEntry **table;//雜湊表陣列 unsigned long size;//雜湊表大小 unsigned long sizemask;//掩碼大小,用於計算索引值,總是等於size-1 unsigned long used;//雜湊表中的已有節點數} dictht;```注意:上面結構定義中的 `table` 是一個數組,其每個元素都是一個 `dictEntry` 物件。### 字典字典,又稱為符號表(symbol table),關聯陣列(associative array)或者對映(map),字典的內部嵌套了雜湊表 `dictht` 物件,下面就是一個字典 `ht` 的定義:```ctypedef struct dict { dictType *type;//字典型別的一些特定函式 void *privdata;//私有資料,type中的特定函式可能需要用到 dictht ht[2];//雜湊表(注意這裡有2個雜湊表) long rehashidx; //rehash索引,不在rehash時,值為-1 unsigned long iterators; //正在使用的迭代器數量} dict;```其中 `dictType ` 內部定義了一些常用函式,其資料結構定義如下:```ctypedef 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;```當我們建立一個雜湊物件時,可以得到如下簡圖(部分屬性被省略):![](https://img2020.cnblogs.com/blog/2232223/202101/2232223-20210120213440752-2100115347.png)### rehash 操作`dict` 中定義了一個數組 `ht[2]`,`ht[2]` 中定義了兩個雜湊表:`ht[0]` 和 `ht[1]`。而 `Redis` 在預設情況下只會使用 `ht[0]`,並不會使用 `ht[1]`,也不會為 `ht[1]` 初始化分配空間。當設定一個雜湊物件時,具體會落到雜湊陣列(上圖中的 `dictEntry[3]`)中的哪個下標,是通過計算雜湊值來確定的。如果發生雜湊碰撞(計算得到的雜湊值一致),那麼同一個下標就會有多個 `dictEntry`,從而形成一個連結串列(上圖中最右邊指向 `NULL` 的位置),不過需要注意的是最後插入元素的總是落在連結串列的最前面(即發生雜湊衝突時,總是將節點往連結串列的頭部放)。當讀取資料的時候遇到一個節點有多個元素,就需要遍歷連結串列,故連結串列越長,效能越差。為了保證雜湊表的效能,需要在滿足以下兩個條件中的一個時,對雜湊表進行 `rehash`(重新雜湊)操作: - 負載因子大於等於 `1` 且 `dict_can_resize` 為 `1` 時。 - 負載因子大於等於安全閾值(`dict_force_resize_ratio=5`)時。PS:負載因子 = 雜湊表已使用節點數 / 雜湊表大小(即:`h[0].used/h[0].size`)。### rehash 步驟擴充套件雜湊和收縮雜湊都是通過執行 `rehash` 來完成,這其中就涉及到了空間的分配和釋放,主要經過以下五步:1. 為字典 `dict` 的 `ht[1]` 雜湊表分配空間,其大小取決於當前雜湊表已儲存節點數(即:`ht[0].used`): - 如果是擴充套件操作則 `ht[1]` 的大小為 `2 的 `n` 次方中第一個大於等於 `ht[0].used * 2` 屬性的值(比如 `used=3`,此時`ht[0].used * 2=6`,故 `2` 的 `3` 次方為 `8` 就是第一個大於 `used * 2` 的值(2 的 2 次方 < 6 且 2 的 3 次方 > 6))。` - 如果是收縮操作則 `ht[1]` 大小為 2 的 n 次方中第一個大於等於 `ht[0].used` 的值。2. 將字典中的屬性 `rehashix` 的值設定為 `0`,表示正在執行 `rehash` 操作。3. 將 `ht[0]` 中所有的鍵值對依次重新計算雜湊值,並放到 `ht[1]` 陣列對應位置,每完成一個鍵值對的 `rehash`之後 `rehashix` 的值需要自增 `1`。4. 當 `ht[0]` 中所有的鍵值對都遷移到 `ht[1]` 之後,釋放 `ht[0]` ,並將 `ht[1]` 修改為 `ht[0]`,然後再建立一個新的 `ht[1]` 陣列,為下一次 `rehash` 做準備。5. 將字典中的屬性 `rehashix` 設定為 `-1`,表示此次 `rehash` 操作結束,等待下一次 `rehash`。### 漸進式 rehash`Redis` 中的這種重新雜湊的操作**因為不是一次性全部 `rehash`,而是分多次來慢慢的將 `ht[0]` 中的鍵值對 `rehash` 到 `ht[1]`,故而這種操作也稱之為漸進式 `rehash`**。漸進式 `rehash` 可以避免集中式 `rehash` 帶來的龐大計算量,是一種分而治之的思想。在漸進式 `rehash` 過程中,因為還可能會有新的鍵值對存進來,此時** `Redis` 的做法是新新增的鍵值對統一放入 `ht[1]` 中,這樣就確保了 `ht[0]` 鍵值對的數量只會減少**。當正在執行 `rehash`操作時,如果伺服器收到來自客戶端的命令請求操作,則**會先查詢 `ht[0]`,查詢不到結果再到`ht[1]` 中查詢**。## ziplist關於 `ziplist` 的一些特性,之前的文章中有單獨進行過分析,想要詳細瞭解的,可以[點選這裡](https://www.cnblogs.com/lonely-wolf/p/14281136.html)。但是需要注意的是雜湊物件中的 `ziplist` 和列表物件中 `ziplist` 的有一點不同就是雜湊物件是一個 `key-value` 形式,所以其 `ziplist` 中也表現為 `key-value`,`key` 和 `value` 緊挨在一起:![](https://img2020.cnblogs.com/blog/2232223/202101/2232223-20210120213514656-935498919.png)## ziplist 和 hashtable 的編碼轉換當一個雜湊物件可以滿足以下兩個條件中的任意一個,雜湊物件會選擇使用 `ziplist` 編碼來進行儲存: - 雜湊物件中的所有鍵值對總長度(包括鍵和值)小於等於 `64`位元組(這個閾值可以通過引數 `hash-max-ziplist-value` 來進行控制)。 - 雜湊物件中的鍵值對數量小於等於 `512` 個(這個閾值可以通過引數 `hash-max-ziplist-entries` 來進行控制)。一旦不滿足這兩個條件中的任意一個,雜湊物件就會選擇使用 `hashtable` 編碼進行儲存。## 雜湊物件常用命令- hset key field value:設定單個 `field`(雜湊物件的 `key` 值)。- hmset key field1 value1 field2 value2 :設定多個 `field`(雜湊物件的 `key` 值)。- hsetnx key field value:將雜湊表 `key` 中域 `field` 的值設定為 `value`,如果 `field` 已存在,則不執行任何操作。- hget key field:獲取雜湊表 `key` 中的域 `field` 對應的 `value`。- hmget key field1 field2:獲取雜湊表 `key` 中的多個域 `field` 對應的 `value`。- hdel key field1 field2:刪除雜湊表 `key` 中的一個或者多個 `field`。- hlen key:返回雜湊表key中域的數量。- hincrby key field increment:為雜湊表 `key` 中的域 `field` 的值加上增量 `increment` ,`increment` 可以為負數,如果 `field` 不是數字則會報錯。- hincrbyfloat key field increment:為雜湊表 `key` 中的域 `field` 的值加上增量 `increment`,`increment` 可以為負數,如果 `field` 不是 `float` 型別則會報錯。- hkeys key:獲取雜湊表 `key` 中的所有域。- hvals key:獲取雜湊表中所有域的值。瞭解了操作雜湊物件的常用命令,我們就可以來驗證下前面提到的雜湊物件的型別和編碼了,在測試之前為了防止其他 `key` 值的干擾,我們先執行 `flushall` 命令清空 `Redis` 資料庫。然後依次執行如下命令:```javahset address country chinatype addressobject encoding address```得到如下效果:![](https://img2020.cnblogs.com/blog/2232223/202101/2232223-20210120213546323-1888508446.png)可以看到當我們的雜湊物件中只有一個鍵值對的時候,底層編碼是 `ziplist`。現在我們將 `hash-max-ziplist-entries` 引數改成 `2`,然後重啟 `Redis`,最後再輸入如下命令進行測試:```javahmset key field1 value1 field2 value2 field3 value3object encoding key```輸出之後得到如下結果:![](https://img2020.cnblogs.com/blog/2232223/202101/2232223-20210120213612035-857436495.png)可以看到,編碼已經變成了 `hashtable`。# 總結本文主要介紹了 `Redis` 中 `5` 種常用資料型別中的雜湊型別底層的儲存結構 `hashtable` 的使用,以及當 `hash` 分佈不均勻時候 `Redis` 是如何進行重新雜湊的問題,最後瞭解了雜湊物件的一些常用命令,並通過一些例子驗證了本文的結論。
版权声明
本文为[itread01]所创,转载请带上原文链接,感谢
https://www.itread01.com/content/1611162184.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云