zoukankan      html  css  js  c++  java
  • 《Redis 设计与实现》读书笔记(一)

    @(Redis)

    豆瓣链接

    一、数据结构与对象

    1.SDS

    SDS是Redis实现的一个字符串数据结构。
    结构:

    struct sdshdr {
    	int len; //字符串长度
    	int free; //未使用的字符串长度
    	char buf[] //保存字符
    }
    

    为什么不用c的字符数组

    • SDS记录字符串长度,所以获取字符串长度的操作是O1
    • SDS一次申请较长的内存,例如如果字符串长度是5,它会申请多于5的内存,而下次修改字符串,就不需要重新申请内存了。所以可以减少申请和释放内存的次数,提高效率
    • 由于sds的底层还是c的字符数组,所以可以兼容c的字符串函数

    2.链表

    数据结构:
    链表节点

    typedef struct listNode {
    	struct
    	 listNode *prev //前节点
    	 struct listNode * next // 后节点
    	 void *value; //节点的值
    
    }
    

    链表

    typedef struct list {
    		listNode *head; // 头节点
    		listNode *tail ;// 尾节点
    		unsigned log len;// 节点长度
    	
    }
    

    3.字典

    哈希表节点

    typedef struct dictEntry{
    	void *key; //键
    	union{
    		void *val;
    		uint64_t u64;
    		int64_t s64;
    	} v; // 节点的值,可以是指针(val),也可以是数值(u64,s64)
    	struct dictEntry *next ; //下一个节点,形成链表
    
    }
    

    哈希表

    typedef struct dictht {
    	dictEntry **table; //哈希表数组
    	unsigned log size; //哈希表大小
    	unsigned log sizemask;// 大小掩码,总是等于size-1
    	unsigned log used;//已有节点数量
    
    }
    

    字典

    typedef struct dict {
    	dictType *type; //类型特定函数
    	void *pricdata; // 私有数据
    	dictht ht[2]; // 哈希表
    	int trehashidx; // rehash索引,当rehash不在进行时,值为-1
    }
    

    一个字典有两张哈希表,0用于存储数据,1用于rehash。
    当要存储k0=v0的时候,首先程序会算出k0的hash值,例如是8,假如sizemask是3,size是4,然后计算index=8&3=0(不知道&是什么操作,但是应该是8mode4=0,取余)。然后去ht[0].table数据中,找index等于0的哈希节点,然后是否有哈希节点,如果有,就看节点的next是否为空,直到找到next为空的节点,然后创建一个新的节点,使上一个节点的next指向新节点
    如果是获取k0的值,计算index后,遍历节点连表,直到找到key等于k0的节点。

    所以

    • 如果一个index对应的链表很长,其实要花On的复杂度来遍历,然后获取节点的值的
    • 当一个字典的节点数量(used值)增大到一定值时,字典会执行rehash操作,也就是增大size值,table的长度,这样就会减少节点链表的长度

    rehash

    rehash的过程就是

    • 当触发rehash后,会修改字典的trehashidx值,表示正在执行rehash
    • redis会启动一个新的进程,遍历dt[0]的所有键,然后重新计算index,放到dt[1]中,当然dt[1]的size会比dt[0]大
    • 在rehash的过程中,字典的删除,查找,更新操作会在两个dt里面执行
    • 插入,只在dt1中执行
    • rehash完成后,把dt0执行dt1,把dt1置空。

    负载因子= used / size

    • 当负载因子小于1,执行收缩操作
    • 当服务器没有执行BGSAVE或者BGREWRITEAOF操作,而且负载因子大于1,执行扩展操作
    • 当服务器执行BGSAVE或者BGREWRITEAOF操作,而且负载因子大于5,执行扩展操作

    4.跳跃表

    跳跃表,主要用于加快遍历的速度。
    例如有序列表,1,2,3,4,5,50
    如果要查找50的节点在哪里,需要对前面的5个节点都遍历一次,比较慢。如果再第一个节点记录了50在哪里,或者5在哪里,程序就可以直接跳过前面的节点,直接到达,或者接近目标节点。
    注意列表必须是有序的。

    跳跃表结构

    • header 指向头节点
    • tail 指向尾节点
    • level 表示最多层数的节点的层数
    • length,表示节点的数量

    跳跃表节点结构

    typedef struct zskiplistNode {
    	struct zskiplistNode *backward;// 上一个节点的指针
    	double score;// 节点的分值
    	robj *obj; // 节点的存储对象
    	struct zkiplistLevel{// 节点的层,是一个列表
    		struct zskiplistNode *forward; //下一个节点的指针
    		unsigned int span;  //前进的跨度
    	} level[];
    }
    

    一个列表有多少个元素,就会有多少个跳跃表节点
    当创建一个新节点时,程序会根据幂次定律,随机生成1和32直接的值作为level数组的大小,这个大小就是这个节点的高度。
    例如上面的元素1的节点,会有一个level指向50的节点,跨度是5。这样当程序需要找到50的时候,遍历1的节点,发现1节点的值小于50,就去遍历1节点的level,

    • 如果第一个level就是指向50节点,那就立刻找到了。
    • 如果第一个level是指向5,发现5不符合,就找下一个level

    5.整数集合

    当一个集合(Set)只包含整数元素,而且数量不多时,就会使用整数集合。

    type struct intset {
    	uint32_t encoding;//编码方式,整数的大小,例如32位,16位
    	uint32_t length;//集合的长度
    	int8_t contents[];//集合的元素,类型取决于encoding属性的值
    
    }
    

    升级

    如果集合的encoding是16位,当要插入一个32位的整数时,数据结构就会升级,例如升级为32位的。
    但是不会进行降级操作。

    6.压缩列表

    当一个列表和字典的长度比较短,就会使用压缩列表。

    属性:

    • zlbyte,uint32_t 类型,记录压缩列表的字节数
    • zltail uint32_t 记录尾元素节点距离起始地址有多少字节,通过这个可以快速定位到尾节点
    • zllen 节点数
    • entryX 节点元素
    • zlend 压缩列表的末端

    压缩列表不同上面的数据结构是redis里面的struct,压缩列表是一段内存数据。

    1.节点的构成,也就是entryX里面的结构

    • previous_entry_length 前一个节点的长度,单位是字节
    • encoding,保存的数据类型
    • content ,内容

    如果要查找一个元素,

    • 首先查看zltail,定位到尾节点
    • 读取尾节点的encoding,确定content的长度
    • 读取content的内容
    • 如果内容不符合
    • 根据previous_entry_length,,定位前一个节点的位置,重复上面的过程

    如果新增元素,会在头部插入,然后更新前一个节点的previous_entry_length 。
    如果删除元素,修改前一个节点的previous_entry_length为空。

    压缩列表的优点

    • 实现简单,可以节约内存
    • 读写都比较简单,不需要改太多值

    7.对象

    Redis有五种对象

    • 字符串
    • 列表
    • 哈希
    • 集合
    • 有序集合

    redis中,通过type命令,可以查看一个key的对象类型

    这5中对象在不同的时候,会采用上面介绍的数据结构的其中一种。而且在操作的过程中,数据结构也会变化。

    通过命令object encoding可以查看key使用的数据结构

    redis 127.0.0.1:6701> set test 'aaa'
    OK
    redis 127.0.0.1:6701> type test
    string
    redis 127.0.0.1:6701> object encoding test
    "embstr"
    redis 127.0.0.1:6701> 
    

    embstr就是上面的SDS。

    redis使用的数据结构有

    • 整数 int
    • SDS enbstr,底层使用sds
    • 简单动态字符串 raw ,底层使用sds
    • 字典 hashtable
    • 双端链表 linkedlist
    • 压缩列表 ziplist
    • 整数集合 intset
    • 跳跃表和字典 skiplist

    对象的数据结构

    typedef struct redisObject {
    	unsigned type:4; //对象的类型,对应上面的五种对象
    	unsigned encoding:4; //使用的数据结构,对应上面的几种结构
    	void *ptr ;//底层的数据结构的指针
    }
    

    1.字符串对象

    数据结构可以是int raw和embstr
    如果字符串全是数字,就会使用int类型
    如果字符串的大小大于39字节,使用raw来存储
    如果大小小于39字节,使用embstr来存储

    raw和embstr的区别是,raw需要申请两次内存,embstr只需要一次

    2.列表对象

    列表对象可以是ziplist或者linkedlist
    如果列表元素的长度都小于64字节,而且元素的数量小于512个,就会使用ziplist,否则使用linkedlist

    3.哈希对象

    哈希对象可以是ziplist或者hashtable
    如果键值对的长度都小于64字节,而且键值对的数量小于512个,就会使用ziplist,否则使用linkedlist

    4.集合对象

    可以是intset 或者hashtable

    当元素都是整数值,元素不超过512,使用intset,否则使用hashtable

    5.有序集合

    数据结构可以是ziplist或者skiplist
    当元素小于128个,而且元素长度都小于64字节,使用ziplist,否则使用skiplist。

    6.内存回收

    redis使用引用计数器来实现内存的回收

    每个对象都有一个int refcount属性,来记录被引用的次数。
    在业务端,

    • 每次引用对象,需要调用对象的incrRefCount函数,使用对象的引用计算加一,
    • 每次不引用对象的时候,都需要调用对象的decrRefCount函数,来令refcount属性减1,如果引用计数小于1,就会回收内存。

    7.对象共享

    数字0到9999,这些值的字符串对象,会在redis启动的时候就被创建,然后被共享。

    为什么其他值不共享,因为如果要共享其他值,每次创建的时候,都有查看内存中有没有这个值的对象,这个查询的效率是很低的。

    8.对象的空转时长

    每个对象都有个属性 unsigned lru:22;,用于记录对象最后一次被访问的时间

    通过object idletime命令,可以查看一个key的空转时长,其实就是当前时间戳减去lru。
    在redis回收内存的时候,而且当回收算法是volatile-lru或者allkeys-lru时,会优先回收空转时长较长的key。

  • 相关阅读:
    Python基本命名规范
    复制当前文件夹下所有文件的名称
    Java中返回类型方法名
    BugFree 测试管理系统
    jmeter+badboy免费测试
    Linux的命令
    C语言
    linux基础知识点以及安装
    JMeter安装教程
    linux 操作命令
  • 原文地址:https://www.cnblogs.com/Xjng/p/12085100.html
Copyright © 2011-2022 走看看