redis 学习笔记(一)

本文深入解析Redis中的数据结构,包括简单动态字符串(SDS)、链表、字典、跳跃表、整数集合、压缩列表及对象等核心组件。介绍了它们的作用、结构特性以及在Redis中的应用,有助于理解Redis高效运作的原理。

第一部分---------数据结构与对象



  第一章.简单动态字符串
  第二章.链表
  第三章.字典
  第四章.跳跃表
  第五章.整数集合
  第六章.压缩列表
  第七章.对象
  
  

第一章.简单动态字符串(SDS)



          作用一: 存值,当数据库

 1.redis里,C字符串只作为字面量,用在无需对字符串值进行修改的地方;
 2.而在redis里,字符串存值,并且该值有可能被修改,则一致使用SDS来表示字符串值。
SDS:    在redis数据库里,包含字符串的键值对,在底层都是由SDS实现的。
 3.键值对的:eg:  一个在redis中,数据库中存在的键值对:
 1.》键:是一个字符串对象,对象的底层实现是一个包含着字符串"msg"的SDS
 2.》值:字符串对象,对象的底层实现是一个包含着字符串"hello"的SDS  (键值对的值是字符串);
 值:列表对象, 包含了N个字符串(或其他)对象,这N个字符串(其他)对象分别由N个SDS实现;

 作用二 :当做缓冲区(buffer)

              AOF模块中的缓冲区,以及客户端状态缓冲区    

                 
SDS:结构
        用sds.h/sdshdr 结构表示
  struct sdshdr {
int len;  //记录buf数组中已被使用字节的数量, 等于SDS所保存的字符串长度;
int free;  //记录buf数组中,未被使用字节的数量;
char buf[]; //字节数组,用于保存字符串
  }   
        buf数组是一个char类型的数组,数组的最后一字节,保存‘\0’ ,空字符串 便于保存C字符串一些特性 以便公用函数
SDS与C字符串区别:
            1.获取常数复杂度,redis 直接访问Len属性,复杂度为N(1),C:C(O/N);
2.空间预分配策略,减少了字符串值增大时,缓冲区溢出问题,避免了相邻key值被意外修改(API在执行时,会自动检查是否内存足够,不足则自动扩大,1mb为界限)
3.惰性空间释放策略:如果C在字符串值减少时,会立即释放空间,进行内存重分配,但是效率低下,如果不执行重分配则内存泄漏;
                  redis 不会直接立即释放内存,而是将这些空间暂时分配给未使用空间,以便下次使用;
  redis 必要时也有释放内存的函数,程序不用每次进行内存重分配;
  
    SDS通过未使用空间,解除了字符串长度和底层数组长度之间的关联,在SDS中,buf数组的长度不一定就是
 字符数量加1,数组里可以包含未使用的字节,而这些字节的数量就由SDS的free记录;

注意:*******通过free属性,SDS实现了空间预分配和惰性空间释放两种优化策略;
           4.二进制安全
    C字符串只能存放文本数据,字符串以'\0'空字符结尾,但是真实场景,数据格式复杂多变,不适用
redis使用SDS,SDS的API都是二进制安全的,所有API都会以处理二进制的方法来处理SDS存放在buf数组里的数据,
程序不会对存在其中的数据进行修改,过滤,存时什么样,取时什么样。
注意:******** buf数组不是字符数组,是字节数据,是保存一系列二进制数据;
            5.保存部分C字符串函数,可以直接重用;

第二章.链表

     
作用  一:列表键的底层实现之一;
           针对redis中,列表键底层就是一个链表,链表中的每个节点都保存了一个整数值。
                
 二: 发布与订阅,慢查询,监视器等也有用到了链表;
       
结构
       
       每个链表节点 使用 listNode来表示:
       typeof struct listNode{
  
    struct listNode * prev;  //前置节点
 
struct listNode * next;  //后置节点
  
    void * value             //节点的值
  
  }


    多个listNode之间 根绝prev 和next 组成redis链表是双端链表;
注意:多个listNode 之间虽然就可以组成链表,但使用adlist.h/list结构来持有链表的话,操作方便:
     
每个链表使用 list来表示:  
typeof struct list{
  
     listNode * head;  //表头节点
 
 listNode * tail;  //表尾节点
  
     unsigned long len;   //链表所包含的节点数量
 
 dup   :节点值赋值函数
 
 free  : 节点值释放函数
 
 match :  节点值对函数
 
  } 
  
特点:
     一: 双端: 链表节点带有prev和next指针,获取某个节点的前置和后置节点的复杂度:O(1)
二: 无环,每个链表表头节点的prev指针和表节点的next指针,都指向NULL,对链表的访问以NULL为终点,所以redis链表实际是五环链表;
三: 带表头指针和表尾指针,通过ist结构的head 和tail 指针,程序获取链表的表头节点和表尾节点的复杂度都是O(1);
四: 带链表长度计数器,程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度都是O(1);
五: 多态,链表节点使用void* 指针来保存节点的值,并且可以通过List结构的dup,free,match三个属性为节点
    值设置类型特定函数,所以redis的链表可以用于保存各种不同类型的值;
 

第三章.字典

    字典又称符号表,或者映射,是用来保存键值队的抽象数据结构;

一:作用:   1: redis数据库使用字典作为底层实现,对数据库的增删改查操作也是构建在字典操作之上; 
            eg:set mykey 'fanqian' 就是保存在代表数据库的字典里面;
        2: 是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,
   又或者键值对中的元素都是比较长的字符串时候,redis使用字典作为哈希键的底层实现;
例如 webiste 是一个包含10086个键值对的哈希键,哈希键的键都是数据库名,值是数据库网址
 
        3:....等作用;

简单说明:redis的字典实现使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,
         每个哈希表节点就保存了字典中的一个键值对;


------字典的实现  
二:结构: 结构层级包含关系


                ----->  字典的结构
                   
                        ------>   哈希表的结构




                                    --------->哈希节点的机构


     
  1.字典的结构:   --->redis字典用dict结构定义    
           typeof struct dict{
                     type ---> dictType        //类型特定函数
                     privdata      //私有数据
                     dictht ht[2]  // 哈希表
                     rehashidx(-1) // rehash索引,当rehash不在进行时,值为-1;

                   }dict

        type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的;
   <1>.type属性是一个指向dictType结构的指针,每个dictType结构保存了一组用于操作
    特定类型键值对的函数,redis会为用途不同的字典设置不同类型的特定函数;
            <2>.privdata则保留了需要传给那些类型特定函数的可选参数;
<3>.ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,
   一般情况下,字典只用ht[0]哈希表,ht[1]哈希表值会在对ht[0]哈希表进行rehash时使用.
<4>.除了ht[1]之外,另外一个和rehash 有关的属性就是rehashidx,它记录了rehash目前的进度,
   如果目前没有在进行的rehash,那么它的值为-1;


   2.字典类型结构
                typeof struct dictType{
                    1.计算哈希值的函数
2.复制键的函数
3.复制值的函数
4.对比键的函数
5.销毁键的函数
6.销毁值的函数

                   } dictType

   3.哈希表的结构:  --->redis字典所使用的哈希表由dichht结构定义
                typeof struct dictht{
                     dictEntry table;         //哈希表数组
                     unsigned long size       //哈希表大小
                     unsigned Long sizemask   // 哈希表大小掩码,用于计算索引值(总是等于size-1)
                     unsigned long used       //哈希表已有的节点的数量

                   } dictht


        <1>.table属性是一个数组,数组中每一个元素都是一个指向 dictEntry 结构的的指针,
             每个dictEntry 结构保存着一个键值对。
        <2>.size属性记录了哈希表的大小,也即使table数组的大小;
<3>.used属性记录了哈希表目前已有的节点(键值对)数量;
<4>.sizemask值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的那个索引上 ;

    
   4.哈希表节点结构:-->哈希表节点使用dictEntry结构表示,每个dictEntry结构都保存这一个键值对
                typeof struct dictEntry{
                     void *key;         //键
                     union{
        void val;
        void uint64_tu64;
int64_ts64
       } v;      //值

                          struct dictEntry *next; //指向吓个哈希表节点,形成链表
                   } dictEntry;
           
   <1>.key属性保存着键值对中的键;
             每个dictEntry 结构保存着一个键值对。
        <2>.v属性保存在键值对中的值,其中键值对中的值可以是一个指针,或者是一个int64_t整数,又或者是一个uint_64整数;
<3>.next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,
   以此来解决键冲突问题;

三:图形表示(没有进行rehash)字典
  dict                      dictht            
  type                      table   -------> dictEntry[4]
  privdata                  size (4)              0        ----NULL   
  ht       --------> ht[0]  sizemask(3)           1        ----dictEntry【k0/v0】---NULL
  rehashidx(-1)             used(2)               2        ----NULL      
                                             3        ----dictEntry【k1/v1】---NULL
 
eg1:  
 
  --------> ht[1] dictht
                   table   -------->NULL   
size (0)  
sizemask(0)  
used(0)  
 
四:哈希算法  
当要将一个新的键值对,添加到字典里时,程序需要先根据键值对的键计算出哈希值(type)和索引值(sizemask);
    然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上。
    1.hash=dict-->type-->hashFunction(key);
2.使用哈希表的sizemask 和哈希值计算出索引值  (根据情况不同,ht[x]可以是ht[0],也可以是ht[1]),
 index=hash & dict-->ht[x].sizemask;
3. 例如 k0/v0 对k0计算哈希值,并与sizemask共同计算得出index=0;
       如eg1所示: 则k0/v0 键值对被放在 ht[0] ---》dictEntry[4]-- 索引为0的位置;
   
注意:关于计算哈希值
 如果字典被用来做数据库的底层实现或者哈希键的底层实现时,Redis使用的是MurmurHash2算法来计算键的哈希值;
 
五:解决键冲突  ----next
     当两个或者以上的键被分配到了哈希表数组的同一个索引上时,我们称这些键发生了冲突
redis的哈希表使用链地址法解决键冲突的问题,每个哈希节点都有一个next指针,多个哈希表节点可以用next指针
构成一个单向链表,被分配到同一个索引的多个哈希节点,可以用这个单向链表连接起来,解决键冲突问题;
 
六:rehash
      随着操作,哈希表里的键值对越多或者越少时,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的
键值对过大或过少时,程序需要对哈希表的大小进行相应的扩展或收缩;
 1>.如果执行的是扩展工作,那么ht[1]的大小为第一个大于等于ht[0].userd*2的2的n次方幂.  2N
    如果是收缩函数,那个ht[1]的大小为第一个大于等于ht[0].userd的2n
 2>.将保存在ht[0]中的所有键值对rehash到ht[1]上,rehash指的是重新计算键的哈希值和索引值,然后将键值对
    保存在ht[1]哈希表的指定位置上;
 3>.当ht[0]包含的所有键值对都迁移到ht[1]上之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],
    并在ht[1]中新建一个空白哈希表,为下次rehash做准备;
 
    流程 :计算并设置ht[1]大小--->将ht[0]所有键值对,重新rehash到ht[1]-->释放ht[0],设置ht[1];
哈希表的负载因子=哈希表已保存的键值对数量、哈希表大小;
    当哈希表的负载因子小于0.1时,程序会自动开始对哈希表执行收缩操作;

七:渐进式rehash  
    渐进式rehash在执行期间的哈希表操作
 在渐进式哈希的过程中,字典会同时对ht[0],ht[1]俩表进行操作,例如查找--先去ht[0]查,再去ht[1]里查;
  添加--会直接去ht[1]里添加,保证了ht[0]里包含的键值对数量只减不增,并伴随rehash操作的执行而最终变成空表;


第四章.跳跃表---一种有序的数据结构;每个节点维持多个指向其他节点的指针,可以快速访问节点

       每个节点维持多个指向其他节点的指针,可以快速访问节点.大部分情况下,跳跃表的效率可以和平衡树相媲美,
  并且因为其实现比平衡树简单,因此不少程序经常用来代替平衡树;
一:作用:
     redis 使用跳跃表作为有序集合键的底层实现之一(一个有序集合中包含的元素数量比较多,
      有序集合中元素成员是比较长的字符串)。
 注意:和链表,字典等数据结构被广泛用于redis中不同,redis只在两个地方用到跳跃表:
     1>.实现有序集合键; SORTSET
 2>.集群节点中用作内部数据结构;


第五章. 整数集合 --SET

       
一:作用:
       整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis....


二:结构
    tyoeof struct intset{
     encoding            //编码方式
 length              //长度(整数集合包含的元素数量)
 int8_t contents[];  //保存元素的数组


  }intset
  
  contents数组是整数集合的底层实现,整数集合的每个元素都是contents数组的一个数组项itms,各个项在数组中按值的
  大小从小到大有序的排列,并且数组中不包含任何重复项。
  contents里边的元素,取决于encoding编码:
     encoding:intset_enc_int16; 那么contents就是一个int16_t类型的整数值(最小值为-32768,最大值32767);
           intset_enc_int32;那么contents就是一个int32_t类型的整数值(最小值为-2147483648,最大值2147483648);
               intset_enc_int64;那么contents就是一个int64_t类型的整数值(最小值为-9223372036.....,最大值29223372036.....); 
           
contents.size:encoding*length
   根据整数集合升级规则来看,当一个向底层为int16_t数组的整数集合添加一个int64_t类型的数据时,整数集合已有元素
会都被转换成为int64_t类型;
三:
   策略:升级   ------->需三部
        降级   目前整数集合只支持升级操作,不支持降级操作;

四: 数据特点
    整数集合的底层为数组,这个数组以有序无重复方式保存集合元素;

第六章. 压缩列表



一:作用:是列表键和哈席间的底层实现之一。






第七章. 对象

   以上介绍的6种数据结构,redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构
   创建了一个对象系统,这个系统包含了字符串对象,列表对象,哈希对象,集合对象和有序集合对象这五种类型对象,
   每种对象都用到了至少一种介绍的数据结构;
一:作用
    1.可以在执行命令之前,根据对象类型,来判断一个对象是否可以执行给定的命令;
    2.可以针对不同的使用场景,为对象设置多种不同的数据结构实现,优化对象在不同场景下的使用效率;
3.redis对象系统,还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象做占用的内存会被释放:
4.redis还通过引用计数计数实现了对象共享机制,这一机制在适当条件下通过让多个数据库共享同一个对象来节约内存;
5.redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启动了maxmemory功能的情况下,空转时长较大的那些键可能会被优先被服务器删除。


二:对象的结构
       redis使用对象来表示数据库的键和值,每次当我们在redis的数据库中新建一个键值对时,我们至少会创建俩对象,
  一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象);
    
tyoeof struct redisObject{
     type            //类型
 encoding        //编码方式
 ptr            //指向底层实现数据结构指针
 refcount       //引用计数
 ...


  } 
  
type:记录对象类型----->redis的集中基本数据结构
        对应redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象,列表对象,哈希对象,集合对象和有序集合其中之一,
    因此:
        当我们称呼一个数据库键为“字符串键时",我们指的是 "这个数据库键所对应的值为字符串对象";
    当我们称呼一个数据库键为“列表键时",我们指的是 "这个数据库键所对应的值为列表对象";
          同样,当我们对数据库中的键对象执行type函数时,返回的也是:这个数据库键所对应的值对象的类型;
    eg:  set mykey fanqian 
         type mykey   type mykey  type mykey  type mykey   type mykey
         string       list         hash       set           zset
  
 ptr:ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定;
 encoding:这个属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现。这个属性值可以是:
            INT-----------Long类型的整数
EMBSTR--------embstr编码的简单动态字符串
RAW-----------简单动态字符串
HT------------字典
LINKEDLIST----双端链表
ZIPLIST--------压缩列表
10816 INTSET---------整数集合
SKIPLIST--------跳跃表和字典
注:OBJECT ENCODING key 命令查看一个数据库键的值对象的编码  
      
三:编码和底层实现
    TYPE: STRING, LIST, HASH, SET, ZSET, 

    ECCODING :编码常量                      编码对应的底层数据结构
    REDIS_ECCODING_INT------------------------>Long类型的参数
REDIS_ECCODING_EMBSTR--------------------->embstr编码的简单动态字符串
REDIS_ECCODING_RAW------------------------>简单动态字符串
REDIS_ECCODING_HT------------------------->字典
REDIS_ECCODING_LINKEDLIST----------------->双端链表
REDIS_ECCODING_ZIPLIST--------------------->压缩链表
REDIS_ECCODING_INTSET--------------------->整数集合
REDIS_ECCODING_SKIPLIST--------------------->跳跃表和字典

   每种类型的对象,都至少使用了两种不同的编码
    TYPE                    编码                                     对象

    REDIS_STRING        REDIS_ECCODING_INT              使用整数值实现的字符串对象
                       REDIS_ECCODING_EMBSTR           使用embstr编码的简单动态字符串实现的字符串对象(跟raw编码一样,都是使用SDS结构来表示字符串对象,是一种专门用于保存短字符串的优化编码方式)
                            REDIS_ECCODING_RAW              使用简单动态字符串实现的字符串对象

    REDIS_LIST              REDIS_ECCODING_LINKEDLIST       使用双端链表实现的列表对象
                            REDIS_ECCODING_ZIPLIST          使用压缩链表实现的列表对象

REDIS_HASH              REDIS_ECCODING_HT               使用字典实现的哈希对象
                          REDIS_ECCODING_ZIPLIST          使用压缩链表实现的哈希对象

REDIS_SET REDIS_ECCODING_INTSET           使用整数集合实现的集合对象
                            REDIS_ECCODING_HT               使用字典实现的集合对象
     
REDIS_ZSET REDIS_ECCODING_ZIPLIST          使用压缩链表实现的有序集合对象
                       REDIS_ECCODING_SKIPLIST         使用跳跃表和字典实现的有序集合对象


每种对象,底层实现至少有两种不同编码,而这些对象底层是如何从一种编码转换成另一种编码所需条件:

注意:通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大地提升了redis的灵活性和效率,
              eg:在列表对象包含的元素较少时,redis使用压缩列表作为列表对象的底层实现:
    1.因为压缩列表比双端链表更节约内存,并且在元素较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快被载入到
  内存中;
2.随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失时,对象就会将底层实现从压缩列表转向功能更强,
  也更适合保存大量元素的双端链表上面;
其他类型的对象,也会通过使用多种不同的编码来进行类似的优化;


    1.REDIS_STRING 字符串对象:编码有三种  
       如果key值是一个int型对象,像保存 1,2,3,4....等这样整型数据,那么TYPE是:REDIS_STRING 对应的底层编码实现是:REDIS_ECCODING_INT
如果key值是一个字符串对象,并且字符串对象长度大于32字节,那么TYPE是:REDIS_STRING 对应的底层编码实现是: REDIS_ECCODING_RAW
如果key值是一个字符串对象,并且字符串对象长度小于32字节,那么TYPE是:REDIS_STRING 对应的底层编码实现是: REDIS_ECCODING_EMBSTR
REDIS_ECCODING_RAW 与 REDIS_ECCODING_EMBSTR:
    这两种都是使用SDS结构来表示字符串对象,但是REDIS_ECCODING_EMBSTR:是一种专门用于保存短字符串的优化编码方式,
 但是raw编码会调用两次内存分配函数分别创建REDISOBJECT和SDS结构,而REDIS_ECCODING_EMBSTR 编码则调用一次内存分配函数来
 分配一块连续的空间,空间一次包含着两种结构;
    释放的时候,REDIS_ECCODING_EMBSTR编码对象只需要一次内存释放,而释放raw编码的字符串对象需要
redis:INCRBYFLOAT  KEY VALUE;
      redis 命令,在指定key对应的值上,相加给定的增量,并返回相加后的结果,如果该键不存在,则创建该键,设置初始值为:0
图1:一个字符串对象结构:hello  
                     redisObject                                       sdshdr

        type           encoding               ptr   ...    free         len              buf  
                                                             0           5         'h' 'e' 'l' 'l' 'o' '\0'
    REDIS_STRING     REDIS_ECCODING_EMBSTR     -------------------------------------->   


 

        2.REDIS_LIST 列表对象:编码有两种
      REDIS_ECCODING_ZIPLIST:压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素。
  REDIS_ECCODING_LINKEDLIST:双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素;  
列表编码的转化条件:
 默认情况下:REDIS_ECCODING_ZIPLIST:当列表中保存的每个元素,长度小于64字节,并且元素数量小于512个。
  (上限值可以修改,配置文件:list-max-ziplist-value和list-max-ziplist-entries选项来指定)
 否则:列表对象都使用REDIS_ECCODING_LINKEDLIST编码格式保存;
 
                  redisObject                                       sdshdr

        type           encoding               ptr   ...    free         len              buf  
                                                             0           5         'h' 'e' 'l' 'l' 'o' '\0'
    REDIS_LIST     REDIS_ECCODING_ZIPLIST     --------------------->     
 
 
redisObject                                       

    type                                                                                                                 0           5         'h' 'e' 'l' 'l' 'o' '\0'
    REDIS_LIST             
     
encoding
REDIS_ECCODING_LINKEDLIST                   链表
                                                            ------------------>图1,存hello的是字符串对象结构
    ptr -------------------------------------->StringObject,StringObject,StringObject
                                               1        hello           5
   注意:字符串对象是Redis五种类型的对象唯一一个会被其他四种类型对象嵌套的对象;



   3.REDIS_HASH 哈希对象:编码有两种,zipList(压缩列表)和hashtable(字典)底层实现;
    zipList(压缩列表):每当有一个键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表尾部,
                    然后将保存了值的压缩列表节点推入到压缩列表尾部.每个集合元素使用两个紧挨在一起的压缩列表节点
来保存,第一个节点保存元素的成员,而第二个元素保存元素的分值;
压缩列表内的集合元素按照分值从小到大进行排序,分值较小的元素被放置在靠近头部的方向,分值大的放在靠尾部;
 
    hashtable(字典):    哈希对象中的每个键值对都使用一个字典键值对来保存;
  
特点:zipList:1.>保存了同一键值对的两个节点总是紧凑在一起,键在前值在后;
              2.>先添加的键值对在所列表头部,后来者尾部;
 
 hashtable:1.>字典的每个键都是一个字符串对象,对象里保存了键值对的键;
           2.>字典的每个值都是一个字符串对象,对象里保存了键值对的值;
  
当哈希对象满足以下两种条件,使用ziplist:
 默认情况下:当哈希对象中保存的所有键值对的键和值长度小于64字节,并且哈希对象保存的键值对数量小于512个。
  (上限值可以修改,配置文件:hash-max-ziplist-value和hash-max-ziplist-entries选项来指定)
 否则:列表对象都使用hashtable编码格式保存;
             
                  redisObject                                    

        type           encoding               ptr 
                                                           
      REDIS_HASH     REDIS_ECCODING_ZIPLIST     --------------------->     ZIPLIST  .... name fanqain age 24 sex girl ...
 


                  redisObject                                      dict

        type           encoding               ptr   --------------------->  StringObject “age” --> StringObject “25”   
                                                                            StringObject name  --> StringObject FanQian   
      REDIS_HASH     REDIS_ECCODING_HT                                   
                                                                    
   4.REDIS_SET 集合对象:编码有两种,intset(整数集合)和hashtable(字典)底层实现;
    intset(整数集合):  每当有一个键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表尾部,
                    然后将保存了值的压缩列表节点推入到压缩列表尾部。
    hashtable(字典):    集合对象中的每个键值对都使用一个字典键值对来保存;
 
编码转换条件:
   当集合对象可以同时满足一下俩条件时,对象使用inset编码
            1.集合对象保存的所有元素都是整数值;
            2.集合对象保存的元素数量不超过512个;(上限值可以被修改  set-max-inset-entries)
              其他都是hashtable 编码
 
 
eg: intset 存储

     redisObject                                                           intset
 
type           encoding                 ptr                           encoding  INTSET_ENC_INT16
 
REDIS_SET REDIS_ECCODING_INTSET          ptr   ------------>           length    3
                                                                    
                                                                     contents----->1 3 5 
 
 
 
hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,
         而字典的值则全部 被设置为NULL。 
 
eg: hashtable 编码 底层实现

     redisObject                                                           dict
 
type           encoding                 ptr                           StringObject   cheery  ....>NULL
 
REDIS_SET REDIS_ECCODING_HT              ptr   ------------>           StringObject   apple   ....>NULL
                                                                    
                                                                     StringObject   banana  .....>NULL
 
注:不同的编码,在添加,获取元素数量,移除等不同的操作时,调用不用的函数;

5.REDIS_SORTSET 有序集合对象:编码有两种,ziplist(整数集合)和skiplist(跳跃表)底层实现;
      1.ziplist编码保存数据的结构在之前已经讲述,在此不再阐述;
  2.skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含了一个字典(ht)和一个跳跃表(ziplist)。
  
              为什么 REDIS_SORTSET 有序集合需要同时使用跳跃表和字典来实现?
     在理论上使用其中一种都可以;但是 为了效率问题;
   eg:跳跃表编码的有序集合对象
                    redisObject          encoding                           ptr                
                type                                                                   zset
                           REDIS_SET REDIS_ECCODING_SKIPLIST ptr ------------>  dict ----->
                                                                                          zsl  ----->
  
跳跃表编码的有序集合对象(该对象所使用的zset结构是):
                  .....此处省略  P79页。
             
编码转换条件:
            当有序集合对象同时满足:
                1.有序集合里的元素不超过128个;
                2.有序集合里的每个元素长度不超过64个字节;
                 否则该有序集合对象将使用skiplist编码
        (上限值可以被修改  zset-max-ziplist-entries,和 zset-max-ziplist-value)  
问题:有序集合键的值都是哈希对象????????????   
  
四:类型检查与命令多态
        redis中用于操作键的命令有两类
            可以操作任何类型:例:del,expire rename,type,object等  --->属于类型多态命令
            可以操作特定类型:例:set,get,append,strlen 对字符串键
                                hdel,hset,hget,hlen等对哈希键
                                rpush,lpop,linsert,llen对列表键 --->属于编码多态命令
                                sadd,sopo,sinter,scard 的对集合键
                                zadd,zcard,zrank,zscore有序集合键
    一:类型检查的实现
   为了确保指定的类型的键可以执行某些特定的命令,在执行命令之前,会先检查输入烦人键的类型是否正确,然后决定是否执行。
    类型特定命令所进行的类型检查是通过redisObject结构的type属性来实现的。
二:多态命令的实现
        redis除了会根据值对象的类型来判断是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。
         例如:列表对象 ziplist   linkedlist 
                 当我们对一个键执行llen命令,redis不仅会判断执行的命令是列表键,还需要根据键的值对象所使用的编码来选择正确的
                 LLEN命令; ziplist     --->ziplistlen
            linkedlist  --->listlength  
       
五:内存回收
        redis采用引用计数内存回收机制,跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息由redisObject结构的refcount属性记录仪。
        对象的生命周期:创建对象,操作对象,释放对象。

对象的引用计数会随着对象的使用状态不断变化;
 在创建一个新对象时,引用计数的值会被初始化为1;
 当对象被一个新程序使用时,它的引用计数值就会增1;
 当对象不再被一个程序使用时,它的引用计数值会被-1;
 当对象的引用计数值变为0的时候,对象所占用的内存会被释放;
 
六:对象共享
          当创建两个键值对,值对象一样时,该值对象只被创建一次,供键A,键B指针指向值对象的地址,该值对象的引用计数会增加1
          其他的对象属性都不变。
             对象共享机制对于节约内存空间很有帮助,数据库中保存的想用值对象越多,对象共享机制就越占有优势;P85
         eg:redis会在初始化的时,创建一万个字符串对象,包括0到9999的所有整数值,当服务器需要这些字符串时,服务器就会使用
   这些共享对象,而不是新创建对象。
    set mykey1 100
 ok
             object refcount mykey1 
              2
             set mykey2 100
 ok
object refcount mykey1 
              3  
object refcount mykey2 
              3 
引用该对象的程序,持有该对象程序 以及引用该对象键A,B。
 
七:对象的空转时长
       对象的lru属性 记录对象最后一次被命令程序访问的时间;
  OBJECT IDLETIME 命令可以打印出给定键的空转时长;
   

第八章.回顾

    
               
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值