聊聊Redis数据类型
Key的设计
首先聊一下Redis的Key的设计, 一般情况下可以遵循以下原则:
用: 分割
表名作为key的前缀
中间段是主键值
尾段是列名
举个例子: user:1:username, user:1:code
数据类型
//redis db typedef struct redisDb{ int id; //数据库序号, 0-15 long avg_ttl; // 平均ttl dict *dict; //所有的kv dict *expires; // 存储key的过期时间 dict *blocking_keys;//blpop存储阻塞key和客户端对象 dict *ready_keys; //阻塞后push 响应阻塞客户端,存储阻塞后push的key和客户端对象 dict *watched_keys; // 存储watch监控的key和客户端对象 }redisDb; //redisObject typedef struct redisObject{ unsigned type:4; //表示对象类型 String list hash set zset unsigned encoding:4; void *ptr; //底层实现数据结构指针 int refcount;// 引用计数 unsigned lru:LRU_BITS;// LRU_BITS为24bit 记录最后一次被命令程序访问的时间, 高16位存储一个分钟级别的时间戳, 低8位最近被访问的次数 }rObj; 复制代码
String
表达3种值的类型, 字符串,整数,浮点数, encoding是 int,raw, embstr
命令 | 描述 | |
---|---|---|
set | set key value | 赋值 |
get | get key | 取值 |
getset | getset key value | 取值& 赋值 |
setnx | setnx key value | 当key不存在复制 |
append | append key value | 尾部添加 |
strlen | strlen key | 字符串长度 |
incr | incr key | 递增数字 |
incrby | incrby key increment | 增加指定整数 |
decr | decr key | 递减数字 |
decrby | decrby key decrement | 减少指定的整数 |
SDS 存储字符串和整型数据
struct sdshdr{ int len;// 数组中已经使用的字节的数量 int free;// 数组未使用的字节的数量 char buf[]; //用于保存字符串长度 } 复制代码
优势:
在C字符串的基础上加入了free和len字段, 获取字符串长度, SDS是O(1), C字符串是O(n)
SDS由于记录了长度, 在可能造成缓冲去溢出时自动重新分配内存,杜绝了缓冲区溢出
可以存放二进制数据,字符串长度len作为结束标识
场景:
存储字符串和整型数据, 存储key, AOF缓冲区和用户缓冲区
list
存储有序,可重复的元素,最多个数2^32 -1个(40亿), encoding 是quicklist
命令 | 描述 | |
---|---|---|
lpush | lpush key v1 v2 v3 | 从左侧插入列表 |
lpop | lpop key | 从列表左侧取出 |
rpush | rpush key v1 v2 v3 | 从右侧插入列表 |
rpop | rpop key | 从列表右侧取出 |
lpushx | lpushx key value | 将值插入列表头部 |
rpushx | rpushx key value | 将值插入列表尾部 |
blpop | blpop key timeout | 从列表左侧取出,列表为空时阻塞 阻塞时间单位为秒 |
brpop | brpop key timeout | 从列表右侧取出,列表为空时阻塞 阻塞时间单位为秒 |
llen | llen key | 获得列表中元素个数 |
lindex | lindex key index | 获取列表中下标为index的元素 |
lrange | lrange key start end | 返回列表指定区间的元素 |
lrem | lrem key count value | 删除列表中与value相等的元素, count >0, 从左侧删除,count<0从后边删除, count=0, 删除所有值为value的元素 |
lset | lset key index value | 列表index位置的元素设置成value |
ltrim | ltrim key start end | 修剪, 只保留start到end的区间 |
rpoplpush | rpoplpush key1 key2 | 从key1列表弹出并插入到key2列表左侧 |
brpoplpush | brpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧,阻塞 |
linsert | linsert key BEFORE/AFTER pivot value | 将value插入到列表, 位于值pivot之前或之后 |
压缩列表ziplist: 一系列特殊编码的连续内存块组成的顺序型数据结构,节省内存,是一个字节数组,包含多个节点,每个节点保存一个字节数组或一个整数。
zlbytes: 压缩列表的字节长度
zltail: 压缩列表尾元素相对于压缩列表起始地址的偏移量
zlen: 压缩列表的元素个数
entry1.. entryx: 压缩列表的各个节点
zlend: 压缩列表的结尾, 占一个字节 恒为0xFF(255)
entryX元素编码结果:
previous_entry_length: 前一个元素的长度
encoding: 当前元素编码
content: 内容
typedef struct zlentry{ unsigned int prevrawlensize;// previous_entry_length字段长度 unsigned int prevawlen;//previous_entry_length存储内容 unsigned int lensize;//encoding 长度 unsigned int len;// 数据内容长度 unsigned int headersize;// 当前元素首部长度 unsigned char encoding;//数据类型 unsigned char *p;// 元素首地址 } zlentry; 复制代码
quickList 是 linkedList 和ziplist的结合
typedef struct quicklist{ quicklistNode *head; quicklistNode *tail; unsigned long count;// 所有数据项个数 unsigned int len; //ziplist个数 int fill :16; //ziplist大小限定, list-max-ziplist-size; unsigned int compress :16;//节点压缩深度设置, list-compress-depth }quicklist; 复制代码
typedef struct quicklistNode{ struct quicklistNode *prev; struct quicklistNode *next; unsigned char *zl;// 数据指针,没被压缩指向ziplist, 压缩后指向quicklistLZF; unsigned int sz;//指向ziplist总长度 unsigned int count :16; //ziplist中数据项 unsigned int encoding :2;// 编码方式 unsigned int container :2;// 预留字段,1--NONE, 2-ziplist unsigned int recompress :1;//解压标记 unsigned int attepted_compress;//测试 unsigned int extra :10; //扩展字段 }quicklistNode; 复制代码
quicklist每个节点的实际存储为ziplist, 节省内存。 可以对ziplist进行压缩,算法为LZF,基本思想是数据与前面重复的记录重复位置及长度,不重复记录原始数据
typedef struct quicklistLZF{ unsigned int sz;//长度 char compressed[];//数组 }quicklistLZF; 复制代码
Set
无序,唯一元素, 集合中最大的成员数 2^32-1, encoding是inset 和hashtable
命令 | 描述 | |
---|---|---|
sadd | sadd key mem1 mem2 | 添加新成员 |
srem | srem key mem1 mem2 | 删除指定成员 |
smembers | smembers key | 获得集合中所有元素 |
spop | spop key | 返回一个随机元素,并且删除 |
srandmember | srandmember key | 返回一个随机元素,不会删除元素 |
scard | scard key | 获得集合中元素的数量 |
sismember | sismember key member | 判断集合是否在集合里 |
sinter | sinter key1 key2 key3 | 交集 |
sdiff | sdiff key1 key2 key3 | 差集 |
sunion | sunion key1 key2 key3 | 并集 |
intset : 有序的整数集合, 连续存储结构, redis集合类型都是整数且处在64位有符号整数范围内
typedef struct intset{ uint32_t encoding; uint32_t length; int8_t contents[]; }intset; 复制代码
sortedset
元素本身无序不重复,每个元素关联一个分数,可根据分数排序,分数可重复, encoding是ziplist skiplist
命令 | 描述 | |
---|---|---|
zadd | zadd key score1 member1 score2 member2 | 添加新成员 |
zrem | zrem key mem1 mem2 | 删除指定成员 |
zcard | zcard key | 元素数量 |
zcount | zcount key min max | score在[min, max]区间的元素数 |
zincrby | zincrby key increment member | 在member分值上加increment |
zscore | zscore key member | 获取member分值 |
zrank | zrank key member | 获取member排名,从小到大 |
zrevrank | zrevrank key member | 获得集合中member的排名, 从大到小 |
zrange | zrange key start end | 获取集合中指定区间成员,递增 |
zrevrange | zrevrange key start end | 获取集合中指定区间成员, 递减 |
跳跃表: 有序链表分层
typedef struct zskiplistNode{ sds ele; double scrore;// 分值 struct zskiplistNode *backward;// 后退指针,指向当前节点最底层的前一个节点 struct zskiplistLevel{ struct zskiplistNode * forward;//本层下个节点 unsigned int span;// 本层下个节点到本节点的元素个数 }level[]; } zskiplistNode; typedef struct zskiplist{ struct skiplistNode *header, *tail; unsigned long length; int level; }zskiplist; 复制代码
优势:
可以快速找到节点
在O(1)的时间复杂复杂度下,快速获取头节点,尾节点, 长度和高度;
hash
String类型的field和value的映射表,每个hash可以存储2^32-1键值对, encoding 是 hashtable和ziplist
命令名称 | 描述 | |
---|---|---|
hset | hset key field value | 赋值 |
hmset | hmset key field1 value1 field2 value2 | 批量赋值 |
hsetnx | hsetnx key field value | 赋值,存在不操作 |
hexists | hexists key field | 查看是否存在 |
hget | hget key field | 获取一个字段值 |
hmget | hmget key field1 field2 | 获取多个字段值 |
hgetall | hgetall key | 获取所有字段 |
hdel | hdel key field1 field2 | 删除指定字段 |
hincrby | hincrby key field increment | 指定字段自增increment |
hlen | hlen key | 获得字段数量 |
数组
存储数据的容器,头指针+ 偏移量快速定位数据所在的内存地址
Hash函数
任意长度的输入通过散列算法转换成固定类型,固定长度的散列值。
数组下标= hash(key)%数组容量
Hash冲突 redis通过链表解决
typedef struct dictht{ dictEntry **table; unsigned long size; //哈希表数组大小 unsigned long sizemask; // 映射位置掩码, size-1 unsigned long used; // 已有节点数目包含next 单链表数据 }dictht 复制代码
hash表数组初始容量为4,每次扩容为2倍, 索引值为hash值&掩码值。
typedef struct dictEntry{ void *key; union{ void *val; uint64_t u64; int64_t s64; double d; }v; struct dictEntry *next; }dictEntry; 复制代码
typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; int iterators; } 复制代码
typedef struct dictType{ //计算hash值的函数 unsigned int (*hashFuction)(const void *key); // 复制键的函数 void *(*keyDup)(void *privdata, const void *key); //复制值的函数 vood *(*valDup)(void *privdata, const void *obj); //比较键的函数 int (*keyCompare)(void *privdata, const void *key1, const void *key2); //销毁键的函数 void (*keyDestructor)(void *privdata, void *key); //销毁值的函数 void (*valDestructor)(void *privdata, void *obj); }dictType; 复制代码
扩容过程:
初始申请4个dictEntry, 后面申请为当前hash表的一倍
rehashidx=0 表示进行rehash操作
新增加数据在hash表h[1]
修改,删除,查询在老hash表h[0], 新hash表h[1]{rehash中}
h[0]数据重新计算索引后迁移到h[1],称为rehash
渐进式hash: 服务器忙, 一个节点hash,如果闲批量hash
bitmap
位操作
命令 | 描述 | |
---|---|---|
setbit | setbit key offset value | 设置key在offset处的bit |
getbit | getbit key offset | 获取key在offset处的bit值 |
bitcount | bitcount key | 获取key的bit位为1的个数 |
bitpos | bitpos key value | 返回第一个被设置成bit值的索引值 |
bitop | bit op and [or/xor/not] destkey key [key ...] | 对多个key进行逻辑运算后存入destkey中 |
geo
处理位置信息
命令 | 描述 | |
---|---|---|
geoadd | geoadd key 经度 纬度 成员名称1 经度 纬度 成员名称2 | 添加成员 |
geohash | geohash key 成员名称1 成员名称2 | 返回标准的geohash串 |
geopos | geopos key 成员名称1 成员名称2 | 返回成员经纬度 |
geodist | geodist key 成员1 成员2 单位 | 成员间距离 |
georadiusbymember | georadiusbymember key 成员 值 单位 count asc[desc] | 查找附近的人 |
stream
可持久化的消息队列, 底层使用了listPack和Rax树
缓存过期和淘汰策略
maxmemory 默认为0, 不限制, 作为DB使用的时候配合禁止驱逐的淘汰策略.
如果要限制,一般设置物理内存的3/4。此时必须设置maxmemory-policy
删除策略
删除策略有定时删除,惰性删除和主动删除三种方式, 目前采用惰性删除和主动删除的方式
定时删除
设置过期时间的同时,创建定时器, 定时器在键的过期时间来临时,立即执行对键的删除操作。
惰性删除
在key访问时,如果发现已经失效,就删除它
主动删除
配置 maxmemory-policy
主动删除策略
LRU
在数据集中随机挑选几个键值对,取出其中lru最大的键值对淘汰。
volatile-lru: 从设置已过期时间的数据集中挑选
allkeys-lru: 从数据集中挑选
LFU
最不经常使用,相应的也有 volatile-Lfu和allkeys-lfu
random
随机,相应的也有volatile-random和allkeys-random
ttl
volatile-ttl, 从过期时间的表里随机挑选几个键值对, 取出其中ttl最小的键值对淘汰
noenviction
禁止驱逐
作者:Learn1993
链接:https://juejin.cn/post/7023669113918586917