加入收藏 | 设为首页 | 会员中心 | 我要投稿 柳州站长网 (https://www.0772zz.cn/)- 基础存储、数据迁移、云安全、数据计算、数据湖!
当前位置: 首页 > 云计算 > 正文

Redis 内存优化神技,小内存保存大数据

发布时间:2022-08-02 14:49:20 所属栏目:云计算 来源:互联网
导读:这次码哥跟大家分享一些优化神技,当你面试或者工作中你遇到如下问题,那就使出今天学到的绝招,一招定乾坤! 如何用更少的内存保存更多的数据? 我们应该从 Redis 是如何保存数据的原理展开,分析键值对的存储结构和原理。 从而继续延展出每种数据类型底层
  这次码哥跟大家分享一些优化神技,当你面试或者工作中你遇到如下问题,那就使出今天学到的绝招,一招定乾坤!
 
  如何用更少的内存保存更多的数据?
 
  我们应该从 Redis 是如何保存数据的原理展开,分析键值对的存储结构和原理。
 
  从而继续延展出每种数据类型底层的数据结构,针对不同场景使用更恰当的数据结构和编码实现更少的内存占用。
 
  为了保存数据, Redis 需要先申请内存,数据过期或者内存淘汰需要回收内存,从而拓展出内存碎片优化。
 
  最后,说下 key、value 使用规范和技巧、 Bitmap 等高阶数据类型,运用这些技巧巧妙解决有限内存去存储更多数据难题……
 
  这一套组合拳下来直接封神。
 
  具体详情,且看「码哥」一一道来。
 
  主要优化神技如下:
 
  键值对优化。
  小数据集合的编码优化。
  使用对象共享池。
  使用 Bit 比特位或 byte 级别操作。
  使用 hash 类型优化。
  内存碎片优化。
  使用 32 位的 Redis。
  在优化之前,我们先掌握 Redis 是如何存储数据的。
 
  Redis 如何存储键值对
  Redis 以 redisDb为中心存储,redis 7.0 源码在 https://github.com/redis/redis/blob/7.0/src/server.h:
 
  图片
 
  redisDb
 
  dict:最重要的属性之一,就是靠这个定义了保存了对象数据键值对,dcit 的底层结构是一个哈希表。
  expires:保存着所有 key 的过期信息。
  blocking_keys 和 ready_keys 主要为了实现 BLPOP 等阻塞命令。
  watched_keys用于实现watch命令,记录正在被watch的一些key,与事务相关。
  id 为当前数据库的id,redis 支持单个服务多数据库,默认有16个。
  clusterSlotToKeyMapping:cluster 模式下,存储key 与哈希槽映射关系的数组。
  Redis 使用「dict」结构来保存所有的键值对(key-value)数据,这是一个全局哈希表,所以对 key 的查询能以 O(1) 时间得到。
 
  所谓哈希表,我们可以类比 Java 中的 HashMap,其实就是一个数组,数组的每个元素叫做哈希桶。
 
  dict 结构如下,源码在 https://github.com/redis/redis/blob/7.0/src/dict.h:
 
  复制
  struct dict {
      // 特定类型的处理函数
      dictType *type;
    // 两个全局哈希表指针数组,与渐进式 rehash 有关
      dictEntry **ht_table[2];
      // 记录 dict 中现有的数据个数。
      unsigned long ht_used[2];
     // 记录渐进式 rehash 进度的标志, -1 表示当前没有执行 rehash
      long rehashidx;
     // 小于 0 表示 rehash 暂停
      int16_t pauserehash;
      signed char ht_size_exp[2];
  };
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  9.
  10.
  11.
  12.
  13.
  dictType:存储了hash函数,key和value的复制等函数。
  ht_table:长度为 2 的 数组,正常情况使用 ht_table[0] 存储数据,当执行 rehash 的时候,使用 ht_table[1]  配合完成 。
  key 的哈希值最终会映射到 ht_table  的一个位置,如果发生哈希冲突,则拉出一个哈希链表。
 
  大家重点关注 dictEntry 类型的 ht_table,ht_table 数组每个位置我们也叫做哈希桶,就是这玩意保存了所有键值对。
 
  码哥,Redis 支持那么多的数据类型,哈希桶咋保存?
 
  哈希桶的每个元素的结构由 dictEntry 定义:
 
  复制
  typedef struct dictEntry {
     // 指向 key 的指针
      void *key;
      union {
          // 指向实际 value 的指针
          void *val;
          uint64_t u64;
          int64_t s64;
          double d;
      } v;
      // 哈希冲突拉出的链表
      struct dictEntry *next;
  } dictEntry;
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  9.
  10.
  11.
  12.
  13.
  key 指向键值对的键的指针,key 都是 string 类型。
  value是个 union(联合体)当它的值是 uint64_t、int64_t 或 double 类型时,就不再需要额外的存储,这有利于减少内存碎片。(为了节省内存操碎了心)当然,val 也可以是 void 指针,指向值的指针,以便能存储任何类型的数据。
  next指向另一个 dictEntry 结构, 多个 dictEntry 可以通过 next 指针串连成链表, 从这里可以看出, ht_table 使用链地址法来处理键碰撞:当多个不同的键拥有相同的哈希值时,哈希表用一个链表将这些键连接起来。
  哈希桶并没有保存值本身,而是指向具体值的指针,从而实现了哈希桶能存不同数据类型的需求。
 
  而哈希桶中,键值对的值都是由一个叫做 redisObject 的对象定义,源码地址: 
 
  复制
  typedef struct redisObject {
      unsigned type:4;
      unsigned encoding:4;
      unsigned lru:LRU_BITS;
      int refcount;
      void *ptr;
  } robj;
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  type:记录了对象的类型,string、set、hash 、Lis、Sorted Set 等,根据该类型才可以确定是哪种数据类型,使用什么样的 API 操作。
  encoding:编码方式,表示 ptr 指向的数据类型具体数据结构,即这个对象使用了什么数据结构作为底层实现保存数据。同一个对象使用不同编码实现内存占用存在明显差异,内部编码对内存优化非常重要。
  lru:LRU_BITS:LRU 策略下对象最后一次被访问的时间,如果是 LFU 策略,那么低 8 位表示访问频率,高 16 位表示访问时间。
  refcount :表示引用计数,由于 C 语言并不具备内存回收功能,所以 Redis 在自己的对象系统中添加了这个属性,当一个对象的引用计数为 0 时,则表示该对象已经不被任何对象引用,则可以进行垃圾回收了。
  ptr 指针:指向对象的底层实现数据结构,指向值的指针。

(编辑:柳州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读