redis数据结构 redis全名(Remote Dictionary Server),即远程字典服务
redis的值的数据结构类型有String、List、Set、Hash、zset(sorted set,有序集合)、Bitmaps(位图)、HyperLogLogs
注意:我使用的版本是6.0.10,不同版本可能略有差别
redis中key的最大长度为512M
对象 1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct redisObject { unsigned type:4 ; unsigned encoding:4 ; unsigned lru:LRU_BITS; int refcount; void *ptr; } robj;
类型总览 redis中的数据类型,即为redisObject的type属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define OBJ_STRING 0 #define OBJ_LIST 1 #define OBJ_SET 2 #define OBJ_ZSET 3 #define OBJ_HASH 4 #define OBJ_MODULE 5 #define OBJ_STREAM 6
1 2 # 可以使用type 命令来查看该键所对应值的类型 type key
redis的编码分类 对应于redisObject的ecoding属性,encoding是决定对象底层实现数据结构的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define OBJ_ENCODING_RAW 0 #define OBJ_ENCODING_INT 1 #define OBJ_ENCODING_HT 2 #define OBJ_ENCODING_ZIPMAP 3 #define OBJ_ENCODING_LINKEDLIST 4 #define OBJ_ENCODING_ZIPLIST 5 #define OBJ_ENCODING_INTSET 6 #define OBJ_ENCODING_SKIPLIST 7 #define OBJ_ENCODING_EMBSTR 8 #define OBJ_ENCODING_QUICKLIST 9 #define OBJ_ENCODING_STREAM 10
可以使用命令来查看存储方式
来查看该key使用的是哪种编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0 .0 .1 :6379 > set test_int 1 OK 127.0 .0 .1 :6379 > object encoding test_int"int" 127.0 .0 .1 :6379 > set test_raw zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzOK 127.0 .0 .1 :6379 > strlen test_raw(integer) 44 127.0 .0 .1 :6379 > object encoding test_raw"embstr" 127.0 .0 .1 :6379 > set test_raw zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzOK 127.0 .0 .1 :6379 > object encoding test_raw"raw" 127.0 .0 .1 :6379 > strlen test_raw(integer) 45
String类型 string类型属于单值单value,是一个可变的字节数组,就像StringBuilder似的,可以追加、获取长度、截取等操作
扩容:当字符串长度小于1M时,扩容是现有空间进行加倍;当字符串长度超过1M,扩容只会多扩1M的空间。且字符串最长为512M
源码结构 其内部使用的是SDS简单动态字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct __attribute__ ((__packed__ )) sdshdr5 { unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__ )) sdshdr8 { uint8_t len; uint8_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__ )) sdshdr16 { uint16_t len; uint16_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__ )) sdshdr32 { uint32_t len; uint32_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__ )) sdshdr64 { uint64_t len; uint64_t alloc; unsigned char flags; char buf[]; };
编码格式
编码
对象
OBJ_ENCODING_INT
使用整数值实现的字符串对象
OBJ_ENCODING_EMBSTR
使用embstr编码的简单动态字符串实现的字符串对象
OBJ_ENCODING_RAW
使用简单动态字符串实现的字符串对象
string有三种编码格式
embstr和raw两种编码的区别 embstr编码用于保存短字符串,与raw编码一样,都是使用redisObject结构和sdshdr结构来表示字符串对象,但是raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的内存空间,空间中依次包含redisObject和sdshdr两个结构
操作命令 值拼接
1 2 3 4 # 给key对应的value进行拼接,返回的是拼接之后的字符长度 append k1 qwe (integer) 5
值长度
数字运算操作
1 2 3 4 5 6 7 8 # 加一 INCR k2 # 减一 DECR k2 # 加3,第二个参数是步长 INCRBY k2 3 # 减2,第二个参数是步长 DECRBY k2 2
注意:这几个命令只能对数字进行操作,范围是Long.MIN~Long.MAX
获取值
1 2 3 4 get k1 # 只获取该key所对应value的部分字符 getrange k1 0 2
设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # setrange覆盖部分值 其中0为从下标为0的字符开始替换 setrange k1 0 zx (integer) 5 # 设置值时设置过期时间 # setex key seconds value setex k3 20 b # 如果key不存在设置值,防止覆盖(set if not exists) # setnx key value setnx k1 1 # 设置key的值,并返回key的旧值 getset k1 vv1
多值操作
1 2 3 4 5 6 7 8 9 # 同时set 多个键值 # mset key value [key value ...] mset k4 v4 k5 v5 # 同时获取多个键的值 # mget key [key ...] mget k4 k5 # 同时set 多个键值(全部不存在时才会设置值) # msetnx key value [key value ...] msetnx k6 v6 k7 k8
List类型 List属于单值多value,链表用的是双向链表 结构,list支持pop、push来操作头部和尾部,既可以用做栈也可以用做队列
在3.2版本之前使用的是ziplist和linkedlist,在3.2版本之后使用的是quicklist
源码结构 quicklist结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 typedef struct quicklist { quicklistNode *head; quicklistNode *tail; unsigned long count; unsigned long len; int fill : QL_FILL_BITS; unsigned int compress : QL_COMP_BITS; unsigned int bookmark_count: QL_BM_BITS; quicklistBookmark bookmarks[]; } quicklist; typedef struct quicklistNode { struct quicklistNode *prev ; struct quicklistNode *next ; unsigned char *zl; unsigned int sz; unsigned int count : 16 ; unsigned int encoding : 2 ; unsigned int container : 2 ; unsigned int recompress : 1 ; unsigned int attempted_compress : 1 ; unsigned int extra : 10 ; } quicklistNode;
而对于quicklist编码的定义是
1 OBJ_ENCODING_QUICKLIST 9
看代码像是存到quicklist中,然后又使用ziplist压缩的,毕竟不是专业的c语言开发,看的并不是那么明白
操作命令 设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # lpush(指从左边进,头部,先进后出,像栈) # lpush key element [element ...] lpush list1 1 2 3 4 5 # rpush(指从右边进,尾部,先进先出,像队列) # rpush key element [element ...] rpush list2 1 2 3 4 5 # 给某个索引位置赋值 # lset key index element lset list1 0 q # 在某个元素之前或之后插入一个元素 # linsert key before|after pivot element linsert list1 before q zz
取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 弹出栈顶元素(从左边开始弹出,弹出之后,list中就不存在该元素了) # lpop key lpop list1 # 弹出栈顶元素(从右边开始弹出,弹出之后,list中就不存在该元素了) # rpop key rpop list1 # 按照索引位置取值 # lindex key index lindex list1 0 # 取值,从头部到尾部 # lrange key start end (负下标表示的是从后往前,如-1表示倒数第一) lrange list1 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1"
列表长度
删除值
1 2 3 4 5 6 7 8 9 10 11 12 # 删除几个某个元素 # lrem key count element lrem list1 1 "hi" # 截取指定范围的值赋给key(范围是索引) # ltrim key start stop ltrim list1 0 0 # 从头部删除元素 出栈 lpop list1 # 从尾部删除元素 rpop list1
出栈入栈
1 2 3 # 从源列表右出栈压入左目标列表 # rpoplpush source dest rpoplpush list2 list1
Set类型 set类型是单值多value,与List的区别在于不可重复,类似于HashSet,通过hash table实现的
set除了基本的增删改查操作之外,还可以使用集合来取并集、交集、差集,可以用来实现好友推荐等功能
编码格式
编码
对象
OBJ_ENCODING_INTSET
使用整数集合实现的集合对象
OBJ_ENCODING_HT
使用的hash table
set有两种编码格式
intset 保存的元素全都是整数,且元素数量不超过512
hash table 保存的不是整数
1 2 set-max-intset-entries 512
操作命令 设置值
1 2 3 4 # 设置值 # sadd key member [member ...] sadd set1 vv vc vb vv
取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 取值 # smembers key smembers set1 1) "vc" 2) "vb" 3) "vv" # 判断该元素是否在set 中 # sismember key member sismember set1 vv # 获取随机的元素,不会删除元素(count表示获取的数量,默认为1) # srandmember key [count] srandmember set1 2
查看集合元素个数
删除
1 2 3 4 5 6 # srem key member srem set1 vb # 随机出栈(默认数量为1) # spop key [count] spop set1
随机抽取
1 2 3 # 随机从集合中抽取2个(默认1个) # srandmember key [count] srandmember set1 2
1 2 3 # 将源集合中的值移至目标集合 # smove source dest member smove set1 set2 test
差集 交集 并集
1 2 3 4 5 6 7 8 9 10 # 差集 返回的是其他key与第一个key的差集 # sdiff key [key ...] sdiff set1 set2 # 交集 # sinter key [key ...] sinter set1 set2 # 并集 # sunion key [key ...] sunion set1 set2
Hash类型 value是键值对,相当于HashMap,对于hash碰撞也是采用的HashMap的处理方式,数组+链表
更适合存储对象,将一个对象存储在hash类型中会占用更少的内存,且可以更方便的存取整个对象
编码格式
编码
对象
OBJ_ENCODING_ZIPLIST
使用ziplist
OBJ_ENCODING_HT
使用的hash table
set有两种编码格式
ziplist 一开始存储使用的ziplist,但是当满足一定条件时会转换为hash table
hash table
1 2 3 hash-max -ziplist-entries 512 hash-max -ziplist-value 64
源码结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; 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; int16_t pauserehash; } dict;
哈希冲突 redis哈希冲突是使用链地址法进行解决的,将新节点添加在链表的表头
扩容 redis的字典源码中的dictht是一个容量为2的数组,其作用就是用于进行扩容的
最初始的数据是存放在ht[0]中的,当要进行扩展或收缩时,ht[1]会根据ht[0]的容量来计算容量,ht[1]扩展的大小是比当前ht[0].used值的两倍大的第一个2的整数幂;收缩的大小是ht[0].used的第一个大于等于的2的整数幂。然后将ht[0]的所有键值重新散列到ht[1]中,重新计算所有的数组下标,当数据迁移完后ht[0]就会释放,然后将ht[1]改为ht[0],为下一次扩展或收缩做准备
由于有时候redis中的数据比较多,不能瞬间完成扩容操作,会将rehash操作进行分步进行,其字段rehashidx就用于标识rehash过程,如果是-1表示当前没有进行rehash操作,当进行rehash操作时会将该值改为0,在进行更新、删除、查询操作时会在ht[0]和ht[1]中都进行,先更新ht[0],在更新ht[1],而进行新增操作就直接新增到ht[1]中,保证ht[0]只减不增,直到rehash完成
操作命令 设置值
1 2 3 4 5 6 7 8 9 10 11 # Redis4.0之后可以设置多个值,与hmset功能相同 # hset key field value [field value ...] hset user id 123 # 设置多个字段 # hmset key field value [field value ...] hmset user name zhangsan age 18 # 不存在则设置值 # hsetnx key field value hsetnx user id 12
获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # hget key field hget user id # 获取多个字段的值 # hmget key field [field ...] hmget user id name sex age # 可以获取该key所对应的map # hgetall key hgetall user # 获取key对应的map有几个字段 # hlen key hlen user # 判断该key的某个字段是否存在 # hexists key field hexists user id # 获取该key下所有的字段 # hkeys key hkeys user # 获取该key下所有的字段值 # hvals key hvals user
删除字段值
1 2 3 # 支持删除多个字段 # hdel key field [field ...] hdel user age
数值操作
1 2 3 4 5 6 7 # 加2 # hincrby key field increment hincrby user id 2 # 减2 # hdecrby key field increment hdecrby user id 2
Zset类型 zset是sorted set,即有序的集合,在set的基础上加了一个score,类似于TreeSet,使用score来进行排序,也可以根据score的范围来获取元素的列表
原本set值为k1 v1 k2 v2,而zset是k1 score1 v1 k2 score2 v2
底层使用的是hash和跳表,hash用来关联value和score,保证value的唯一性,同时通过value可以获取到score。跳表的作用在于给value排序以及根据score的范围来获取元素列表
编码格式
编码
对象
OBJ_ENCODING_ZIPLIST
使用ziplist
OBJ_ENCODING_SKIPLIST
使用的skiplist
zset有两种编码格式
ziplist 满足条件使用ziplist,否则使用skiplist
skiplist
1 2 3 zset-max -ziplist-entries 128 zset-max -ziplist-value 64
操作命令 设置值
1 2 3 # 向zset中添加元素,并且会根据score进行排序,如果元素存在,会更新score # zadd key score member [score member ...] zadd zset1 1 q 2 w
取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 # WITHSCORES表示查询结果是否带着score,score从小到大 # zrange key start stop [WITHSCORES] zrange zset1 0 -1 # WITHSCORES表示查询结果是否带着score,score从大到小 # zrevrange key start stop [WITHSCORES] zrevrange zset1 0 -1 # 根据score范围查询 # zrangebyscore key min max [WITHSCORES] [LIMIT offset count] zrangebyscore zset2 0 1 # 根据score的范围来计数 # zcount key min max zcount zset2 0 10 # 根据值来获取下标(根据score从小到大来排的序) # zrank key member zrank zset2 d # 根据值来获取下标(根据score从大到小来排的序) # zrevrank key member zrevrank zset2 d # 根据值来获取score # zscore key member zscore zset2 c
查看集合元素个数
删除
1 2 3 4 5 6 7 8 9 10 # zrem key member zrem zset2 s # 根据下标区间来删除(根据score从小到大来排的序) # zremrangebyrank key start stop zremrangebyrank zset1 5 8 # 根据score区间来删除 # zremrangebyscore key min max zremrangebyscore zset1 7 10
数值操作
1 2 3 # 如果key中存在该元素,则该元素的score增加increment,否则新增该元素 # zincrby key increment member zincrby zset1 2 b
Bitmap类型 位图不是一个真实的数据类型,是定义在字符串类型之上的面向位操作的集合。位图的最大优势在于节省空间
设置值
1 2 3 # setbit key offset value # value只能是0或者1 setbit bittest 10 1
取值
1 2 3 # getbit key offset # 如果offset超出范围的话返回0 getbit bittest 10
HyperLogLogs类型 暂时没有了解
各个类型的应用
String: 缓存、限流、计数器、分布式锁、分布式Session
List: 队列
Set: 点赞、标签
Hash: 存储对象
Zset: 排行榜