Redis数据类型与存储结构

5/21/2020

# Redis数据类型与存储结构

对于Redis来说,Key类似SQL中Table的概念。Key指向某种基本的数据类型。Redis有六种基本的数据类型:Hash, ZSet, Set, String, List, HyperLogLog。其中

  1. 存储0元数据的有:String,HyperLogLog
  2. 存储一元数据的有:List,Set
  3. 存储二元数据的有: Hash[存储K-V对],Zset[Value+分值]

# String

存储字符串,底层采用SDS结构来存储数据,实际上是存储了当前长度和总长度的C Struct.底层存储时有多种不同的策略:

  1. 长度小于44时,根据是否能parse成整数来存储为OBJ_ENCODING_EMBSTR或者OBJ_ENCODING_INT;
  2. 长度大于44时,内部编码方式改为OBJ_ENCODING_RAW;

# 为什么是44字节分割

3.2版本前,是用39字节分割的,原因是redis希望对于短的字符串,只需要一次alloc过程。然而内存分配器jemalloc分配的内存如果超出了64个字节就认为是一个大字符串,就会用到raw编码。因此一次性分配内存限制为64字节。

首先RedisObject的定义:

typedef struct redisObject {
    unsigned type:4; // 4bit
    unsigned encoding:4; // 4bit
    unsigned lru:LRU_BITS; // 24bit
    int refcount; // 32bit
    void *ptr; // 64位系统为64bit
} robj;
1
2
3
4
5
6
7

总计128bit=16字节

然后看一下SDS占用的字节数

3.2版本前:

struct sdshdr {
    unsigned int len; // 4字节
    unsigned int free; // 4字节
    char buf[];
};
1
2
3
4
5

总计8个字节,算上SDS为了共用glibc的字符串函数在byte[] 数组里强制加入的\0结束符1个字节,剩下留给内容的空间为64-16-1-8=39字节

3.2版本后:

struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; // 8bit
    uint8_t alloc;  // 8bit
    unsigned char flags;  //8bit
    char buf[];
};
1
2
3
4
5
6

总共占用3字节,剩余64-16-1-3=44字节

# String命令

  • 基本操作:GET,SET,INCR,APPEND,SETRANGE
  • CAS操作:SETNX,MSETNX,常用于分布式锁
  • Bit操作:BITCOUNT,BITFIELD,BITOP,BITPOS

# List

List顾名思义,提供有序数组的功能,在整体数据较少或者节点数据不大时,用定长数组zipList存储,在某节点长度超过64字节或整体长度超过512时,采用双向链表存储

# List命令

  • 增:LPUSH,RPUSH,LINSERT,CAS操作:LPUSHXRPUSHX
  • 删: LREM,LPOP,RPOP,BLPOP,BRPOP
  • 改:LSET
  • 查:LLENLINDEX
  • RPOPLPUSH:从数组1的尾部移出元素到数组2的头部

# Set

Set存储不重复的数据,底层采用int数组或者hashtable来存储,转换的边界条件一样是节点64字节或者整体长度512个。

# Set基本命令

  • 增:SADD
  • 删:SREM,SPOP[随机删除一个],SMOVE[将元素从一个Set转移到另外一个Set]
  • 查:SMEMBERS,SISMEMBER,SRANDMEMBER,SSCAN

# Set集合命令

  • DIFF:SDIFF,SDIFFSTORE
  • 交:SINTER,SINTERSTORE
  • 并:SUNION,SUNIONSTORE

# HyperLogLog

统计集合中不重复元素的个数,底层使用概率算法来计算,不实际存储数据,每个key对应的数据大小为12K左右,错误率0.81%,算法参考神奇的HyperLogLog算法 (opens new window)

# HyperLogLog基本命令

  • 增:PFADD
  • 查:PFCOUNT
  • 合并两个HyperLogLog:PFMERGE

# Hash

Hash类似Java中常用的HashMap,存储K-V对,底层使用ZipList或者HashTable来存储

# 常用命令

  • 增:HSET,HMSET,HSETNX
  • 删:HDEL
  • 改:HINCRBY,HINCRBYFLOAT
  • 查Value:HGET,HGETALL,HMGET,HVALS
  • 查Key:HEXISTS,HKEYS
  • 查长度与迭代:HLEN,HCASN,HSTRLEN

# ZSET

ZSET与SET的区别在于,ZSET中元素是有序的,在ZSET新增元素时,需要指定一个SCORE参数,ZSET内部会根据SCORE进行排序并存储。底层存储使用zipList或者skiplist,转换条件是单元素长度64字节或者总元素个数128个。

# ZSET命令

  • 增:ZADD,ZINCBY
  • 删:ZREM,ZREMRANGEBYLEX,ZREMRANGEBYRANK,ZREMRANGEBYSCORE以及REV版本
  • 总数:ZCARD,ZCOUNT
  • 最值:ZPOPMAX,BZPOPMAX,ZPOPMIN,BZPOPMIN
  • 范围查询:ZRANGE,ZRANGEBYLEX,ZRANGEBYSCORE以及REV版本
  • 迭代:ZSCAN
  • 带权重交集想加:ZINTERSTORE
  • 并集:ZUNIONSTORE

解释下查询、删除中不带后缀,BYLEX和BYSCORE的区别:

普通不带后缀代表根据index查询,BYLEX表明根据value比较函数来查询范围,BYSCORE,严格按照SCORE查询范围。其中容易混淆的是不带后缀和BYSCORE后缀,两者的区别是不带后缀的是严格按照index来查询的,而对于同SCORE的两个value,index一定是不一样的,后者则按照Score排序。

Last Updated: 1/22/2024, 8:56:53 AM