Redis源码篇

原理篇

在这里插入图片描述

字符串

Redis的字符串叫着"SDS",也就是Simple Dynamic String,是一个带长度信息的字符数组。包含

struct SDS{ 
    T capacity  容量
    T len 数组长度
    byte flags 标志位
    char[] content 数组内容
}

SDS的好处?

为了避免频繁分配新数组空间,支持动态扩容,规定字符串长度不得超过512MB,长度小于1MB时,扩容采用加倍策略,超过1MB后,每次扩容只会多分配1MB.

字符串的存储方式?

有三种储存方式。分别为embstr,raw,int。
先讲区别,当字符串是数字时,存储类型为int,当字符串长度小于等于44时,为embstr,它将RedisObject对象头和SDS对象连续存放在一起,malloc一次,当字符串长度大于44时,为raw,malloc两次,对象头和SDS对象不连续,ptr指针指向SDS对象。
为什么是44呢?
先来看Redis对象头的数据结构

struct RedisObject{
      int type //4bit 类型
      int encoding  //4bit 存储方式
      int lru  //24bit lru信息(lfu也用这个字段)
      int refcount  //4byte  引用计数
      void *ptr  //8byte  指向内容
}

RedisObject对象头需要占用16个字节。SDS开始占用3字节,后面补一位‘\0’,jemalloc一般一次会分配64字节,64-16-3-1=44字节,所有一般以44字节区分字符串的大小。

List(列表)

Redis的列表相当于链表而不是数组。
当元素比较小的时候采用Ziplist(压缩列表),将所有元素彼此紧挨着,分配的是一个连续的空间,当数据比较多的是时候变成quciklist(快速列表) 将多个ziplist用指针连接起来。

压缩列表

为什么不用链表实现压缩列表?因为链表必须维护指针,增加了内存损耗和内容碎片。

struct ziplist{ 
       int zlbytes  //整个压缩列表占用字节数
       int zltail_offset //最后一个元素距离压缩列表其实位置的偏移量
       int zllength  //元素个数
       T[] entries //元素内容
       int zlend //标准压缩列表的结束 值恒为 0XFF
}

offset字段快速定位到最后一个元素,然后倒着遍历。,entry随着元素类型不同,也会有不一样的结构。

struct entry{
    int prevlen     //前一个entry的字节长度
    int encoding   //元素类型编码
    byte[] content  //元素内容
}

用prevlen字段实现倒序遍历,是一个可变整数,当字符串小于254时,采用一个字节表示,如果达到或者超过254时,就使用5个字节表示(第一个字节是0xFE),剩下的四个字节表示字符串长度。
我们可能会有疑问,1个字节最大能表示到255,为什么要拿254作为临界值?因为255这个数用作了特殊的用途——标示ziplist的结尾,即用作了zlend字段。之前说过,为了防止混淆,每个真正存储数据的entry的第一个字段都不会为255,而prelen就是entry的第一个字段。
缺点:第一点是ziplist都是紧凑存储,没有冗余空间,每一次添加新元素都要重新分配内存。第二点是一个entry的长度发生改变,后边的prelen也发生改变,会出现级联更新现象。

快速列表

struct quicklist{
       quicklistNode* head
       quicklistNode* tail
       long count         //元素总和
       int nodes         //ziplist 节点的个数
       int compressDepth  //LZF算法压缩深度
}
struct   quicklistNode{
       quicklistNode* prev
       quicklistNode* next
       ziplist* zl     //如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。     
       int32 size      //ziplist的字节总数
       int16 count    //ziplist中的元素数量
       int2  encoding//存储类型 是元素字节数组话说LZF压缩存储
}

在这里插入图片描述

每个ziplist存多少元素

默认单个ziplist长度为8Kb,超过后就会另起一个ziplist。

优点

中合了数组和链表的优点。

hash(字典)

默认hash表的长度是4.当哈希对象保存的所有键值对的键和值的字符串长度都小于64字节,
哈希对象保存的键值对数量小于512个底层采用ziplist,数据量大变成hashtable。Redis默认的hash函数是siphash。

redisDb{
  dict
  expires
  blocking_keys
  ready_keys
  watched_keys
  id
  avg_ttl
  expires_cursor
  defray_later
}
dict{
    type
    privdata
    dictht ht[2]  //包含两个hashtable,一个存放值,一个用于字典扩容缩容
    rehashidx
    iterators
}
dictht{
    dictEntry table  //链表
    size    //第一维数组的长度
    sizemask   //
    userd    //hash表中元素个数
}
dictEntry{
   key
   union{
   val
   u64
   s64
   }
   dictEntry* next  //链表下一个节点
}

渐进式rehash

在这里插入图片描述

扩容和缩容

没有执行BGSAVE和BGREWRITEAOF指令的情况下,哈希表的负载因子大于等于1时进行扩容;
正在执行BGSAVE和BGREWRITEAOF指令的情况下,哈希表的负载因大于等于5时进行扩容;
负载因子小于0.1时,Redis自动开始对哈希表进行收缩操作;

set(集合)

struct intset{
 int32  encoding      //决定整数位宽
 int32 lenght       //元素个数
 int<T>  contents   //整数数组
}

它的内部是键值对是无序的,唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。
当数据可以用整形表示时,set集合将被编码为intset数据类型,intset是紧凑的数组结构,两个条件任务不满足时set将用dict存储数据。1.元素个数小于set-max-intset-entries,2.元素无法用整形表示

ZSET(有序集合)

struct zslnode{
     string value   //key
     double score  //分数
     zslnode*[] forwards   //多层连接指针
     zslnode*    backward   //回溯指针
}
struct  zsl{ 
      zslnode* header   //跳跃列表头指针
      int maxLevel       //跳跃列表当前的最高层
      map<String,zslnode*> ht      //hash结构的所有键值对
}

更新过程

当value存在,score更新时,一个简单的策略是先删除这个元素,然后再插入这个元素。

listpack

sstruct listpakc{
    int32 total_bytes  //占用的总字节数
    int16 size          //元素个数
    T[] entries        //紧凑排列的元素列表
    int8 end           //同zlend一样,恒为0xFF
}

比ziplist少了一个zltail_offset字段,这个字段可以通过total_bytes和最后一个元素的长度字段计数出来。但目前只有Stream结构使用。

SCAN

keys是遍历算法,复杂度是O(n),会阻塞主线程。

  1. SCAN的复杂度虽然也是O(n),但是它是通过游标分步进行的,不会阻塞主线程
  2. 提供limit参数
  3. 服务器不需要为游标保持状态,游标的唯一状态就是scan返回给客户端的游标整数

RDB

fork一个进程,遍历hashtable,利用copy on write ,把整个db dump保存下来。save,shutdown,slave命令会触发这个操作。
粒度较大,如果save之前宕机了,中间的操作没法恢复。

COW(copy on write)

怎么解决子进程做持久化,父进程的修改不影响持久化。
操作系统的cow机制,当父进程对其中一个页面的数据进行修改时,会将共享的页面复制一份分离出来,然后对这个复制的页面进行修改
写时复制技术的定义是:创建子进程不分配物理空间,只分配和父进程相同的虚拟内存空间,公用物理内存,无论是父进程还是子进程对物理内存进行改动,回复制相应的内存区域。

AOF

先执行指令再日志存盘
AOF 重写,使用bgrewriteaof指令对AOF日志进行瘦身。当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回磁盘。
fsync可以设置1s一次,一个指令一次。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值