redis内部存储结构

redis支持的几种数据结构

  1. 字符串
  2. 列表
  3. set
  4. sort-set
  5. map

redisobj 存储结构

结构定义:

typedef struct redisObject {
	unsigned type:4; // 刚刚好32 bits,对象的类型,字符串/列表/集合/哈希表
    unsigned encoding:4; // 编码的方式,Redis 为了节省空间,提供多种方式来保存一个数据
    unsigned lru:22;   // 当内存紧张,淘汰数据的时候用到
    int refcount;  // 引用计数
    void *ptr; // 数据指针
} robj;

type的类型主要有:

#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

encoding类型主要有:

/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */

string存储方式

  1. int:如果是类似于字符串"123456"的字符串,redis会选择存为整形123456,以节省存储占用。
  2. sds(simple dynamic string):sds用于存储字节/字符串和浮点型数据。
struct sdshdr {
   int len;
   int free;
   char buf[]; 
};

为什么使用 char buf[]代替char *buf呢?
1. 内存管理方便,如果使用char *buf需要两次内存申请,释放也需要两次,而 char buf[]只需要一次。
2. 长度为 0 的数组即 char buf[] 不占用内存

优点:
1. sds 获取字符串的长度以及剩余空间的复杂度都是 O(1),而普通字符串都需要O(N)

append 操作优化:
追加内容的长度不超过 free 属性的值, 那么就不需要对 buf 进行内存重分配,如果超过,则申请一倍内存,比如:
append前:
struct sdshdr {
    len = 11;
    free = 0;
    buf = "hello world\0";
}
append后:
struct sdshdr {
    len = 18;
    free = 18;
    buf = "hello world again!\0                  ";     // 空白的地方为预分配空间,共 18 + 18 + 1 个字节
}

list存储方式

  1. 双链表(LinkedList)
  2. 压缩双链表(ziplist)
压缩双链表以连续的内存空间 来表示双链表,压缩双链表节省前驱和后驱指针的空间(8b)

连续内存结构:
<zlbytes><zltail><zllen><entry>...<entry><zlend>

entry内存结构:
<prelen><<encoding+lensize><len>><data>

其中预定义的字符串长度:
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)

整形长度:
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe

ziplist 每次新增数据都会realloc,这时可能会涉及到内存重新申请和拷贝的操作,所以通常用于list长度不长和元素不大的情况,同时因为ziplist不是标准的数组结构,遍历插入删除基本O(N),大量数据的情况下对于linkedlist没有性能上的优势,如果数据小量并且紧凑, ziplist 能够放入 CPU 缓存效率也非常高,同时内存占用非常小。

转化配置:
list-max-ziplist-entries 512 # 最大接受长度为512,超过此长度则转换为linked_list的存储模式。
list-max-ziplist-value 64 # 每个元素的大小,最大不超过64bytes,超过则转换为linked_list。

Map存储方式

  1. hashtable
  2. ziplist(还是数据量比较小的情况下采用,存储的方式奇位为key,偶位为value)

dict结构图
redis的hashtable有两个链表,主要为了能够在不中断服务的情况下扩展(expand)哈希表,使用开链法解决冲突,每次插入选择头部好处:1. 每次插入O(1);2. 数据库系统来说,最新插入的数据往往更有可能频繁的被获取。

正常情况下,比如java的hashmap的扩容,会导致rehash和拷贝,redis的做法和golang相似,使用增量扩容,避免在扩容的时候出现服务阻塞。

redis增量扩容,第一链表拷贝至第二链表的时机:
1. 定时任务
2. curd操作

好处:
1. 在扩容期间,查询收到部分影响,但是要比停止服务要好得多
2. 在扩容期间,写操作会出现多次查询操作,效率比较低 

Set存储方式

  1. hashtable
  2. intset(当添加的所有数据都是整数时,一旦出现字符串型,会转为hashtable)

intset 底层本质是一个有序的、不重复的、整型的数组,支持不同类型整数。

typedef struct intset {
	uint32_t encoding;// 每个整数的类型
	uint32_t length;// intset 长度
	int8_t contents[];// 整数数组
} intset;

encoding(只能升级不能降级):
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

intset 搜索:二分查找

Sort-Set存储方式

  1. ziplist(和map类型)
  2. skiplist+hashtable(通用解决方案)

hashtable 只是用在快速查找某元素在不在集合中,排序主要用skiplist
skiplist
参考:http://blog.jobbole.com/111731/

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值