Redis数据结构原理解析:字符串篇

Redis对象通用对象

由于所有的redis对象都包含此结构以便redis管理对象的内存、创建、销毁等等,所以这里需要先介绍此对象结构。

// 所有redis结构都有这个头:server.h
#define LRU_BITS 24
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

此对象可以表示所有的redis对象,它包含:

type:对象类型

encoding:数据编码类型

lru:数据淘汰记录的24bits时间戳

refcount:引用计数数量(低版本的redis中使用对象共享策略,但是由于后来redis异步线程删除过期等数据带来的诸多问题,作者放弃了这一做法)


字符串string

Redis中实现字符串并没有使用C语言的方式实现,而是自己定义的简单动态字符串(simple dynamic string,SDS),它的实现具有动态、数据可修改、预分配空间的特点。

为什么不用C字符串?

  1. 使用C库获取字符长度需要遍历一遍,时间复杂度O(n),SDS使用len即可。
  2. Redis作为缓存组件,如果使用普通char[]字符串数组,使用栈资源有限所以必须使用char*,这样每次涉及到字符串追加都需要额外申请内存,SDS使用预分配方式减少内存频繁分配。

常用操作

set key value [EX seconds] [PX milliseconds] [NX|XX]
get key
mset key value [key value ...]
mget key [key ...]
setnx key value:如果不存在则set,否则不做操作
setex key seconds value:设置并且过期时间为seconds秒
xpire key seconds:设置过期时间为seconds秒
incr key:自增数字值类型的key
incrby key increment:自增数值increment的数字值类型的key
append key value:字符串追加操作
getrange key start end:获取一段字符串从start-end
setrange key offset value:设置一段字符串从offset开始覆盖value大小

数据结构

// 这里SDS有5种header类型,主要还是为了不同长度的字符串使用不同的头以便节省内存:sds.h
// len和alloc字段从8bit到64bit
struct __attribute__((__packed__)) sdshdr5 { // 暂未使用
struct __attribute__((__packed__)) sdshdr8 {
struct __attribute__((__packed__)) sdshdr16 {
struct __attribute__((__packed__)) sdshdr32 {
struct __attribute__((__packed__)) sdshdr64 {
    uint64_t len;        /* buf数组已使用的长度 */
    uint64_t alloc;      /* 分配的buf数组长度,不包括偷和空字符结尾 */
    unsigned char flags; /* 低3位表示类型,另外5位没有使用 */
    char buf[];
};
    
// 标志位定义
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

存储方式

# 文件:object.c
/* Create a string object with EMBSTR encoding if it is smaller than
 * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 44 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
// 如果len小于44则使用embstr方式编码,否则使用raw方式编码
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

当字符串的长度小于44字节使用embstr方式存储,否则使用raw方式存储,可以使用debug查看存储数据类型

127.0.0.1:6379> debug object g1
Value at:0x7f4cd72a43c8 refcount:1 encoding:embstr serializedlength:4 lru:15045668 lru_seconds_idle:2

在这里插入图片描述

为什么是44字节

  1. jemalloc/tcmalloc等内存分配器都可以2、4、8、16、32、64的单位分配内存大小,如果大小超过64字节则将被认为是大内存。
RedisObject:16字节
SDS:3字节
64字节 - 19字节 = 45字节(其中\0为redis字符串的结尾)
  1. embstr字符串长度和cpu cache line保持一致可以更快加载、重用CPU缓存

扩容策略

字符串长度小于1M使用加倍扩容方式,如果大小超过1M则扩容时以1M大小扩容。但是如果字符串总长度不能超过512M。Redis Data Types A String value can be at max 512 Megabytes in length.

// 扩容的源码文件:sds.c
#define SDS_MAX_PREALLOC (1024*1024)

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值