Redis CoreDataTypes And Commands

Redis 命令不区分大小写,基础命令:

ping # 测试链接是否 OK(PONG 为成功)
echo redis # 输出 redis
quit # 退出客户端
save # 持久化到本地
bgsave
dbsize # 查看当前键空间中数据量
select 0 # 切换键空间,默认为 0
move key db # 数据移动
flushdb # 清除当前键空间数据(info 中的 Keyspace)
flushall # 清除所有键空间数据
auth 密码 # 密码认证
help @list # 查看帮助命令
help @string
help @set
help @zset
help @hash
help @generic
info # 已连上 Redis,查看系统信息(Keyspace 显示每个键空间中的数据数量)
redis-server -v # 查看版本信息
redis-cli --memkeys
redis-cli --bigkeys

存入 Redis 的 key 区分大小写,Key 通用命令: 

set a abc # 添加字符串类型 a-abc
hset hsah hahaha zhangsan # 添加哈希类型 a-abc
del a # 删除 a-abc,返回 1 成功,0 失败
# * 匹配任意数量的任意符号,? 配合-个任意符号,[] 匹配一个指定符号
keys * # 查询所有,尽量避免使用,会锁住数据库,阻塞其它请求,可使用 scan 代替
keys it* # 查询所有以 it 开头
keys *hm # 查询所有以 hm 结尾
keys ??hm # 查询所有前面两个字符任意,后面以 hma 结尾
keys user:? # 查询所有以 user: 开头,最后一个字符任意
keys u[st]er:1 # 查询所有以 u 开头,以 er:1 结尾,中间包含一个字母,s 或 t
exists a # 判断是否存在,返回 1 为存在,0 为不存在
expire a 10 # 设置 a 的生命周期为 10 秒
pexpire a 10
expireat a 10
pexpireat a 10
persist key # 切换 key 从时效性转换为永久性
ttl a # 查看这条数据的生存时间,单位秒,返回 -1 为不过期,-2 表示 key 不存在
pttl a
type a # 查看键 a 对应 value 的数据类型
randomkey # 随机获取 key 的值
rename a b # 重命名 key,把 a 命名为 b,会覆盖已存在的 key
renamenx a b # 重命名 key,如果 b 存在会重命名失败
sort # 对所有 key 排序

Redis 中所有 key-value 都以 redisObject(robj) 结构组织:

#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
#define OBJ_TYPE_BASIC_MAX 5 /* Max number of basic object types. */
#define OBJ_MODULE 5 /* Module object. */
#define OBJ_STREAM 6 /* Stream object. */
#define OBJ_TYPE_MAX 7 /* Maximum number of object types */

#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
#define OBJ_ENCODING_LISTPACK_EX 12 /* Encoded as listpack, extended with metadata */

#define LRU_BITS 24
struct redisObject { // 16 byte
    unsigned type:4; // 4 bit,数据类型:String(字符串)、Hash(散列表)、List(有序可重复集合)、Set(无序不可重复集合)、SortedSet(zset,有序不可重复集合)、Bitmap(位图)、HyperLogLog(统计)、GEO(地理)等
    unsigned encoding:4; // 4 bit,编码方式:raw、int、ht、zipmap、linkedlist、ziplist、intset
    unsigned lru:LRU_BITS; /* 24 bit,LRU time (relative to global lru_clock) or LFU data (least significant 8 bits frequency and most significant 16 bits access time). */
    int refcount; // 4 byte,引用计数
    void *ptr; // 8 bye,数据指针
};

 

type

String(OBJ_STRING)

set a abc # 添加字符串类型 a-abc,成功返回 OK
setex a 100 abc # 添加字符串类型 a-abc,存活时间 100秒
psetex a 10000 abc # 添加字符串类型 a-abc,存活时间 10000 毫秒(10秒)
getrange a 0 2 # 获取指定下表字符串---(abc),闭合空间,包前后边界
getset a zxc # 设置新值,返回旧值---(abc)
mset a abc z zxc # 设置多个值 a-abc,z-zxc
mget a z # 获取多个值
setnx a abc # 添加字符串类型 a-abc,键 a 存在会设置失败
msetnx a abc z zxc # 添加字符串类型 a-abc,其中一个 key 存在就会全部设置失败(有原子性)
strlen a # 获取字符串长度---(3)
set s 21 # key 对应 value 为数值,value 会加一
incr s
set s 21 # key 对应 value 为数值,value 会加制定数量
incrby s 100
decr # 同上为减操作
decrby
append a xyz # 在 key 对应 value 的末尾添加字符串,会返回添加后的字符串
STRALGO # 处理大字符串
MEMORY STATS # 显示 SDS 内存细节

encoding 规则:2.6 及之前:OBJ_ENCODING_INT、OBJ_ENCODING_RAW。3.0 引入 OBJ_ENCODING_EMBSTR。3.2 调整 EMBSTR 长度阈值(从 39 字节扩展至 44 字节(因 jemalloc 内存分配器优化))

#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); // 两次内存分配
}
int isSdsRepresentableAsLongLong(sds s, long long *llval) {
    return string2ll(s,sdslen(s),llval) ? C_OK : C_ERR;
}

 

List(OBJ_LIST)

lpush list 1 2 3 4 5 6 7 8 9 10 # 向列表左边添加元素
rpush list 1 2 3 4 5 6 7 8 9 10 # 向列表右边添加元素
llen list # 获取长度
lrange list 0 2 # 查看列表,栈("10" "9" " 8"),0 -1 可以查看全部
lset list 0 100 # 设置指定下标元素 (10->100)
lindex list 5 # 获取指定下标元素 ("5")
lpop list # 移除第一个元素,并返回(100)
rpop list # 移除最后一个元素,并返回(1)

encoding 规则:3.2 之前:OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_LINKEDLIST。3.2 及之后只有 OBJ_ENCODING_QUICKLIST

robj *createQuicklistObject(int fill, int compress) {
    quicklist *l = quicklistNew(fill, compress);
    robj *o = createObject(OBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
}

 

Set(OBJ_SET)

sadd set a b c d # 添加
sadd set a # 会失败,set 是不可重复集合
scard set # 获取集合长度 4
smembers set # 获取集合中所有元素
srandmember set 2 # 随机获取指定个数的元素,不会删除元素
sismember set a # 判断元素是否在集合中,返回 1 是,返回 0 不是
srem set a b # 移除指定元素
srandmember set # 从集合中随机弹出一个元素,元素不删除
spop set # 从集合中随机弹出一个元素,出一个删一个
sadd set1 c d e f # 获取差集
sdiff set set1 # ("b" "a")
sdiff set1 set # ("f" "e")
sadd set1 c d e f # 获取交集
sinter set set1 # ("c" "d")
sadd set1 c d e f # 获取并集
sunion set set1 # ("d" "f" "b" "a" "c" "e")

encoding 规则:7.0 及之前 OBJ_ENCODING_INTSET、OBJ_ENCODING_HT。7.2 新增 OBJ_ENCODING_LISTPACK

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType);
    robj *o = createObject(OBJ_SET,d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}
robj *setTypeCreate(sds value, size_t size_hint) { // https://github.com/redis/redis/blob/7.4.2/src/t_set.c#L25
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK && size_hint <= server.set_max_intset_entries)
        return createIntsetObject(); // 用 OBJ_ENCODING_INTSET
    if (size_hint <= server.set_max_listpack_entries)
        return createSetListpackObject(); // 用 OBJ_ENCODING_LISTPACK

    /* We may oversize the set by using the hint if the hint is not accurate, but we will assume this is acceptable to maximize performance. */
    robj *o = createSetObject(); //  用 OBJ_ENCODING_HT
    dictExpand(o->ptr, size_hint);
    return o;
}

验证

redis-server.exe --enable-debug-command yes
redis-cli SADD int_set 1 2 3
redis-cli SADD small_str_set a b c
redis-cli SADD large_set $(seq 1 200)
redis-cli DEBUG OBJECT int_set
redis-cli DEBUG OBJECT small_str_set
redis-cli DEBUG OBJECT large_set

 

SortedSet(OBJ_ZSET)

zadd sortedset 100 a 200 b 300 c # 添加
zrem sortedset a # 删除
zremrangebyrank sortedset 0 220 # 按照排名范围删除元素
zcard sortedset # 元素个数---(3)
zcount sortedset 0 220 # 获得指定分数范围内的元素个数---(2)
zscore sortedset a # 获取元素分数---(100)
zcount sortedset 0 220 # 查看分数区间的元素个数---(2)
zrank sortedset a # 获取元素排名,从小到大---(0)
zrank sortedset b # ---(1)
zrank sortedset c # ---(2)
zrevrank sortedset a # 获取元素排名,从大到小---(2)
zincrby sortedset 1000 a # 增加元素分数
# 按照元素分数从小到大的顺序 返回索引从 start 到 stop 之间的所有元素---("b" "c" "a")
zrange sortedset 0 10
# 同上,会带上分数---("b" 200 "c" 300 "a" 1100)
zrange sortedset 0 10 withscores
# 获取指定分数范围的元素
zrangebyscore sortedset 0 220 [WITHSCORES] [LIMIT offset count]

encoding 规则:Redis 7.0 之前:OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_SKIPLIST。Redis 7.0 及之后用 OBJ_ENCODING_LISTPACK 替代了 OBJ_ENCODING_ZIPLIST

robj *createZsetObject(void) {
    zset *zs = zmalloc(sizeof(*zs));
    robj *o;

    zs->dict = dictCreate(&zsetDictType);
    zs->zsl = zslCreate();
    o = createObject(OBJ_ZSET,zs);
    o->encoding = OBJ_ENCODING_SKIPLIST;
    return o;
}
robj *zsetTypeCreate(size_t size_hint, size_t val_len_hint) { // https://github.com/redis/redis/blob/7.4.2/src/t_zset.c#L1239
    if (size_hint <= server.zset_max_listpack_entries && val_len_hint <= server.zset_max_listpack_value) {
        return createZsetListpackObject(); // 用 OBJ_ENCODING_LISTPACK
    }

    robj *zobj = createZsetObject(); // 用 OBJ_ENCODING_SKIPLIST
    zset *zs = zobj->ptr;
    dictExpand(zs->dict, size_hint);
    return zobj;
}

 

Hash(OBJ_HASH)

hset map name jim # 给 key 为 map 的键值设置键为 name 值为 jim
hset map age 18
hincrby map age 1 # 年龄加一
hexisit map name # 判断 key 对应 value 是否有存在的 key,有返回 1,无返回 0
hget map name # 获取 key 对应 value 中的 key 对应的值,不存在返回 nil
hgetall map # 获取 key 对应 value 中的所有键值对
hkeys map # 获取 key 对应 value 中的所有 key
hvals map # 获取 key 对应 value 中的所有 value
hlen map # 获取 key 对应 value 中的键值对数量
hmget map name age # 获取多个
hmset map sex nan phone 1234568798 # 设置多个
hdel map phone sex # 删除 key 对应 value 中的键值对
hsetnx map name newjim # 若 map 中存在 name 会添加失败

encoding 规则:7.0 之前:OBJ_ENCODING_ZIPLIST、OBJ_ENCODING_HT。7.0 及之后用 OBJ_ENCODING_LISTPACK 替代了 OBJ_ENCODING_ZIPLIST

robj *createHashObject(void) {
    unsigned char *zl = lpNew(0);
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_LISTPACK;
    return o;
}
void hashTypeTryConversion(redisDb *db, robj *o, robj **argv, int start, int end) { // https://github.com/redis/redis/blob/7.4.2/src/t_hash.c
    int i;
    size_t sum = 0;

    if (o->encoding != OBJ_ENCODING_LISTPACK && o->encoding != OBJ_ENCODING_LISTPACK_EX) return;

    /* We guess that most of the values in the input are unique, so if there are enough arguments we create a pre-sized hash, which might over allocate memory if there are duplicates. */
    size_t new_fields = (end - start + 1) / 2;
    if (new_fields > server.hash_max_listpack_entries) {
        hashTypeConvert(o, OBJ_ENCODING_HT, &db->hexpires); // 转为 OBJ_ENCODING_HT
        dictExpand(o->ptr, new_fields);
        return;
    }

    for (i = start; i <= end; i++) {
        if (!sdsEncodedObject(argv[i])) continue;
        size_t len = sdslen(argv[i]->ptr);
        if (len > server.hash_max_listpack_value) {
            hashTypeConvert(o, OBJ_ENCODING_HT, &db->hexpires); // 转为 OBJ_ENCODING_HT
            return;
        }
        sum += len;
    }
    if (!lpSafeToAdd(hashTypeListpackGetLp(o), sum)) hashTypeConvert(o, OBJ_ENCODING_HT, &db->hexpires);
}

 

BitMaps(OBJ_STRING)

setbit bits 888 1 # 设置第 888 位为 1,之前位上的数据会都补成 0
getbit bits 888 1 # 获取指定位上的值,不存在会返回 0
# 对指定key按位进行交并、非异或操作,并将结果保存到 destKey 中
# 位运算:and:交、or:并、not:非、xor:异或
bitop op destKey key1 [key2...]
bitcount key [start end] # 统计指定 key 中 1 的数量

 

encoding

OBJ_ENCODING_RAW(SDS(Simple Dynamic String))

SET long_str "This is a very long string that exceeds 44 bytes..."
OBJECT ENCODING long_str # 输出 raw

当字符串长度 > 44 byte 或 包含二进制数据,redisObject 与 sds 字符串会分两次分配内存。支持原地修改(COW(copy-on-write,写时复制)机制)

struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

flags:sdshdr5(长度 < 32)、sdshdr8(32 ≤ 长度 < 0xFF)、sdshdr16(0xFF ≤ 长度 < 0xFFFF)、sdshdr32(0xFFFF ≤ 长度 < 0xFFFFFFFF)、sdshdr64(超大长度)

 

OBJ_ENCODING_INT(整数编码)

当字符串值可表示为 long 类型整数时,会直接存储 64 位有符号整数

SET num 12345
OBJECT ENCODING num # 输出 int

 

OBJ_ENCODING_HT(哈希表)

struct dict {
    dictType *type; // 类型特定函数(哈希计算、键值复制/销毁等),实现多态

    dictEntry **ht_table[2]; // 两个哈希表数组(通常只用 ht_table[0],Rehash 时同时使用)
    unsigned long ht_used[2]; // 两个哈希表已用槽位数(ht_used[0] + ht_used[1] = 总元素数)

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */ // Rehash 进度索引:-1 = 未进行 Rehash,>=0 = 当前迁移的桶索引下标(渐进式 Rehash 标志)

    /* Keep small vars at end for optimal (minimal) struct padding */
    unsigned pauserehash : 15; /* If >0 rehashing is paused */ // Rehash 暂停计数(用 15 位位域计数)器:>0 时暂停 Rehash(用于 Lua 脚本执行等原子性场景),支持嵌套暂停(如 Lua 脚本执行期间多次触发 RDB 保存)

    unsigned useStoredKeyApi : 1; /* See comment of storedHashFunction above */ // 启用存储键 API 优化标志:当键为 SDS 类型且无需销毁时,可复用内存(详见 storedHashFunction)
    signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */ // 哈希表容量指数(实际容量 = 1 << ht_size_exp[N]):用指数存储减少内存占用,同时支持快速扩容计算
    int16_t pauseAutoResize;  /* If >0 automatic resizing is disallowed (<0 indicates coding error) */ // 自动扩容控制:>0 时禁止自动扩容(如 RDB/AOF 加载时,避免写时复制(Copy-on-Write)内存膨胀),<0 表示编码错误(断言用)
    void *metadata[]; // 元数据扩展槽(用于特殊场景的附加数据,如 Streams 结构)
};
struct dictEntry { // https://github.com/redis/redis/blob/7.4.2/src/dict.c#L45
    void *key; // 存储键(通常为 SDS 类型)
    union { // 值存储联合体(支持多种数据类型)
        void *val; // 通用指针(用于复杂类型)
        uint64_t u64; // 无符号 64 位整型(用于整数存储优化)
        int64_t s64; // 有符号 64 位整型(如过期时间)
        double d; // 双精度浮点型(SortedSet 分数存储)
    } v;
    struct dictEntry *next;     /* Next entry in the same hash bucket. */ // 链式哈希冲突解决
};
typedef struct { // 无值版哈希节点(节省 val 字段的 8 字节内存):专为仅需存储键的场景优化(如 Redis Set 类型)
    void *key;
    dictEntry *next;
} dictEntryNoValue;

渐进式 Rehash(数据逐步增多,会触发 rehash 操作) 双哈希表:通过 ht_table[2] 和 rehashidx 实现平滑迁移,避免单次 Rehash 导致的延迟抖动。每次增删改查操作时迁移 1 个桶(_dictRehashStep),保证高性能

负载因子 = ht_used[0] / (1 << ht_size_exp[0]),即负载因子 = 已用槽位数 / 哈希表总槽位数。触发扩容的两个关键阈值:

  • 阈值 1(默认 1):当 负载因子 ≥ 1 且 dict_can_resize 为真(允许自动扩容),触发扩容
  • 阈值 2(强制 5):当 负载因子 ≥ 5 无论是否允许自动扩容,强制扩容(避免哈希冲突恶化)
static void _dictExpandIfNeeded(dict *d) {
    /* Automatic resizing is disallowed. Return */
    if (d->pauseAutoResize > 0) return; // 如果自动扩容被暂停(例如在RDB/AOF操作中),直接返回

    dictExpandIfNeeded(d);
}
int dictExpandIfNeeded(dict *d) {
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK; // 如果当前正在执行渐进式 Rehash,直接返回 OK

    /* If the hash table is empty expand it to the initial size. */
    if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) { // 如果哈希表为空(初始化场景),扩展为默认初始大小:4
        dictExpand(d, DICT_HT_INITIAL_SIZE);
        return DICT_OK;
    }
    /* 扩容条件判断:
     * 1. 当负载因子 ≥1(used/size ≥1)且允许扩容(dict_can_resize 为 DICT_RESIZE_ENABLE)
     * 2. 或负载因子 ≥ 强制扩容阈值(默认5),即使全局禁止扩容(dict_can_resize != DICT_RESIZE_FORBID) */
    /* If we reached the 1:1 ratio, and we are allowed to resize the hash table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling the number of buckets. */
    if ((dict_can_resize == DICT_RESIZE_ENABLE && d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) ||
        (dict_can_resize != DICT_RESIZE_FORBID && d->ht_used[0] >= dict_force_resize_ratio * DICTHT_SIZE(d->ht_size_exp[0]))
    ) {
        if (dictTypeResizeAllowed(d, d->ht_used[0] + 1)) // 检查类型特定限制(例如某些场景禁止扩容)
            dictExpand(d, d->ht_used[0] + 1); // 触发扩容:新大小为 used+1(实际会向上取整到 2^n)
        return DICT_OK;
    }
    return DICT_ERR;
}
#define DICT_HT_INITIAL_EXP 2
#define DICT_HT_INITIAL_SIZE (1<<(DICT_HT_INITIAL_EXP)) // https://github.com/redis/redis/blob/7.4.2/src/dict.h#L146

 

OBJ_ENCODING_LINKEDLIST(双向链表(大列表))

内存碎片化

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;
typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr); // 节点值复制函数指针,参数:ptr - 待复制的值指针,返回值:新分配的值指针副本
    void (*free)(void *ptr); // 节点值释放函数指针,参数:ptr - 待释放的值指针
    int (*match)(void *ptr, void *key); // 节点值匹配函数指针,参数:ptr - 链表存储的值指针,key - 待匹配的键指针,返回值:匹配成功返回1,失败返回0
    unsigned long len; // 链表长度计数器(O(1)时间复杂度获取长度)
} list;

支持泛型数据存储,通过函数指针实现多态

 

OBJ_ENCODING_ZIPLIST(压缩列表(小列表优化))

扩展性/插入/删除效率低、级联更新问题。元素少且成员较小时使用

// <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
// 注意:如果没有另外指定,所有字段都以 little endian 保存
// <uint32_t zlbytes> 保存 ziplist 占用的字节数,包括 zlbytes 字段本身的 4 个字节。需要存储该值,以便能够调整整个结构的大小,而无需先遍历该结构
// <uint32_t zltail> ziplist 中最后一个 entry 的偏移量。这样就可以在列表的远端进行 pop 操作,而无需遍历整个列表
// <uint16_t zllen> entry 的数量。当条目数超过 2^16-2 时,该值将设置为 2^16-1,需要遍历整个 ziplist 才能知道它有多少个 entyr
// <uint8_t zlend> 一个特殊的 entry,表示 ziplist 的结尾。编码为固定等于 255(0xFF) 的单字节。其它正常 entry 不会以 0xFF 开头

entry 完整结构:<prevlen> <encoding> <entry-data>,prevlen 以便能从后向前遍历。encoding 表示条目类型(整数或字符串),如果是字符串,它还表示字符串长度。有时 encoding 也存储数据(小整数)。这种情况下 entry 就没有 <entry-data> 部分

prevlen 编码:

  • 前一个 entry 小于 254 byte,prevlen 用 1 byte 表示(unsigned 8 bit integer):<prevlen from 0 to 253> <encoding> <entry>
  • 前一个 entry 大于 253 byte,pervlen 用 5 byte 表示(第 1 个 byte 设为 254 (0xFE),后 4 个 byte 保存前一个 entry 的长度):0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry>

encoding 编码:第一个 byte 始终是确定条目类型

  • 当 entry 是字符串,encoding 第 1 个 byte 的前 2 bit 保存字符串长度的编码类型,后面是字符串的实际长度
  • 当 entry 是整数,encoding 第 1 个 byte 的前 2 bit 设为 1。随后的 2 bit 用于指定此标头后将存储的整数类型
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, newlen; // 当前 ziplist 总字节数
    unsigned int prevlensize, prevlen = 0; // 前驱节点的长度
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0; // 数据编码类型(ZIP_STR_* 或 ZIP_INT_*)
    long long value = 123456789; /* initialized to avoid warning. Using a value that is easy to see if for some reason we use it uninitialized. */
    zlentry tail;

    /* Find out prevlen for the entry that is inserted. */
    if (p[0] != ZIP_END) { // 非尾部插入时需处理前驱长度
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen); // 解码前驱长度
    } else {
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLengthSafe(zl, curlen, ptail); // 尾部插入时获取最后一个元素的长度
        }
    }

    /* See if the entry can be encoded */
    if (zipTryEncoding(s,slen,&value,&encoding)) { // 尝试整数编码
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipStoreEntryEncoding will use the string length to figure out how to encode it. */
        reqlen = slen; // 字符串直接使用长度
    }
    /* We need space for both the length of the previous entry and the length of the payload. */
    reqlen += zipStorePrevEntryLength(NULL,prevlen); // 添加 prevlen 存储空间
    reqlen += zipStoreEntryEncoding(NULL,encoding,slen); // 添加 encoding 头

    /* When the insert position is not equal to the tail, we need to make sure that the next entry can hold this entry's length in its prevlen field. */
    int forcelarge = 0;
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
    if (nextdiff == -4 && reqlen < 4) { // 处理级联更新
        nextdiff = 0; // 强制使用大长度编码
        forcelarge = 1;
    }

    /* Store offset because a realloc may change the address of zl. */
    offset = p-zl;
    newlen = curlen+reqlen+nextdiff;
    zl = ziplistResize(zl,newlen); // 重新分配内存
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff); // 移动后续数据

        /* Encode this entry's raw length in the next entry. */
        if (forcelarge) zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
        else zipStorePrevEntryLength(p+reqlen,reqlen);

        /* Update offset for tail */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take "nextdiff" in account as well. Otherwise, a change in the size of prevlen doesn't have an effect on the *tail* offset. */
        assert(zipEntrySafe(zl, newlen, p+reqlen, &tail, 1)); // 安全写入检查
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else {
        /* This element will be the new tail. */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so we need to cascade the update throughout the ziplist */
    if (nextdiff != 0) {
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen); // 级联更新
        p = zl+offset;
    }

    /* Write the entry */
    p += zipStorePrevEntryLength(p,prevlen); // 写入 prevlen
    p += zipStoreEntryEncoding(p,encoding,slen); // 写入 encoding
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen); // 写入字符串
    } else {
        zipSaveInteger(p,value,encoding); // 写入整数
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}
View Code

 

OBJ_ENCODING_INTSET(整数集合)

typedef struct intset {
    uint32_t encoding; // 编码方式(INTSET_ENC_INT16/INTSET_ENC_INT32/INTSET_ENC_INT64)
    uint32_t length; // 集合元素数量(非字节数)
    int8_t contents[]; // 柔性数组(实际存储元素的字节数组,类型由 encoding 决定)
} intset;

第一个元素为 int16_t,第二个元素为 int32_t,这时需要升级,即改变 contents 数组的类型

柔性数组(flexible array member)是 C99 标准特性,允许结构体最后一个成员是未知大小的数组。实际内存分配时通过malloc动态控制数组大小。访问时通过指针运算实现类型转换

 

OBJ_ENCODING_SKIPLIST(跳表)

typedef struct zskiplistNode { // 跳跃表节点结构
    sds ele; // 成员字符串
    double score; // 排序分值
    struct zskiplistNode *backward; // 后向指针
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 前向指针
        unsigned long span; // 跨度(用于排名)
    } level[]; // 柔性数组实现多层级
} zskiplistNode;
typedef struct zskiplist { // 跳跃表结构
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
typedef struct zset {
    dict *dict; // 哈希表:存储成员到分值的映射(O(1)查找)
    zskiplist *zsl; // 跳跃表:维护有序结构
} zset;

Skip Lists(跳表): A Probabilistic Alternative to Balanced Trees(平衡树):https://github.com/redis/redis/blob/7.4.2/src/t_zset.c#L50 & https://news.ycombinator.com/item?id=1171423

 

OBJ_ENCODING_EMBSTR(嵌入式字符串)

SET short_str Hello
OBJECT ENCODING short_str # 输出 embstr

当字符串长度 ≤ 44 byte (Redis 5.0+),redisObject 与字符串数据会连续存储(所以小 Key 效率更高),减少内存碎片。应用了 L1 缓存行对齐,但只读(修改需转 raw 编码)

robj *createEmbeddedStringObject(const char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); // 单次内存分配:robj(16 byte) + sdshdr8(17 byte) + data(44 byte) + 结束符\0(1 byte)
    struct sdshdr8 *sh = (void*)(o+1); // robj 头部初始化,内存连续

    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    o->lru = 0;

    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len); // 数据拷贝
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

 

OBJ_ENCODING_QUICKLIST(快速列表)

5.0 至 6.2:使用 ZIPLIST 作为底层节点。7.0 及之后换为 LISTPACK 作为底层节点

将多个 listpack 节点通过双向链表连接,结合了 listpack 的内存紧凑性和 linkedlist 的插入效率。解决了内存碎片化,同时优化了大规模数据的操作性能

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all listpacks */
    unsigned long len;          /* number of quicklistNodes */
    signed int fill : QL_FILL_BITS;       /* fill factor for individual nodes */
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;
typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *entry; // 指向 listpack(紧凑链表)
    size_t sz;             /* entry size in bytes */
    unsigned int count : 16;     /* count of items in listpack */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* PLAIN==1 or PACKED==2 */ // PLAIN=1 表示节点存储的是单个元素(直接存储值不压缩)。PACKED=2 表示节点存储的是 listpack(替代了旧版的 ziplist)
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
    unsigned int extra : 9; /* more bits to steal for future usage */
} quicklistNode;

 

OBJ_ENCODING_LISTPACK(紧凑列表)

避免了 ZIPLIST 的级联更新

typedef struct { // https://github.com/redis/redis/blob/7.4.2/src/listpack.h#L28
    /* When string is used, it is provided with the length (slen). */
    unsigned char *sval; // 存储字符串时使用
    uint32_t slen; // 字符串长度
    /* When integer is used, 'sval' is NULL, and lval holds the value. */
    long long lval; // 存储整数时使用
} listpackEntry;
#define LP_HDR_SIZE 6       /* 32 bit total len + 16 bit number of elements. */
#define LP_EOF 0xFF
unsigned char *lpNew(size_t capacity) { // https://github.com/redis/redis/blob/7.4.2/src/listpack.c#L220
    unsigned char *lp = lp_malloc(capacity > LP_HDR_SIZE+1 ? capacity : LP_HDR_SIZE+1);
    if (lp == NULL) return NULL;
    lpSetTotalBytes(lp,LP_HDR_SIZE+1); // listpack 总字节数
    lpSetNumElements(lp,0); // listpack 元素总数
    lp[LP_HDR_SIZE] = LP_EOF; // 结尾标识
    return lp;
}
// <tot-bytes> <num-elements> <element-1> ... <element-N> <listpack-end-byte> // General structure
// <encoding-type><element-data><element-tot-len> // This is an element

 


https://redis.io/topics/data-types-intro

https://redis.io/docs/latest/commands

https://redis.io/docs/reference/internals

posted @ 2019-12-03 14:22  江湖小小白  阅读(385)  评论(0)    收藏  举报