Redis 基础数据结构

Redis 是 Redis remote dictionary server 远程字典服务的缩写。

Redis 所有的数据结构都以唯一的key 作为名称,然后通过唯一的key 来获取value 数据,所以不同类型的数据结构的差异就在于value 的结构不一样。

0. redisObject

  src/redis.h 中 redisObject 对象是redis 对内部存储的数据定义的抽象, 定义如下:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

type: 数据类型

encoding: 编码格式,也就是存储数据使用的数据结构。同一个类型的数据,redis 会根据数据量、内存等使用不同的编码,最大限度的节省内存。

lru: 24位, LRU时间戳或者LFU计数

refcount: 引用计数, 为了节约内存,redis 会在多处用同一个redisObject 对象

ptr: 指向实际的数据结构,如sds, 真正的数据存储在该数据结构中

  redisObject 负责装载所有的键值。 ptr 指向真正的存储数据的结构,refcount、lru 等属性用于数据管理(数据共享、数据过期等)。

redis 定义了以下数据类型和编码:

 

1. String 类型

可以理解为一个字符数组,内部实现类似于ArrayList。 采用预分配冗余空间减少内存的频繁分配。 一般 capacity > length。 字符串最大长度是512MB.

Redis 为当前字符串分配的实际空间capacity 一般要高于实际字符串长度len。 当字符串长度小于1MB时,扩容是加倍现有的空间,超过1MB时每次多扩容1MB。

127.0.0.1:6379> set strkey strval
OK
127.0.0.1:6379> get strkey
"strval"

(1) 其sds 结构如下:

struct sdshdr8 {
    uint_8 len; // 已使用字节长度
    uint_8 alloc; // 已申请字节长度。 alloc - len = 空闲空间
    char buf[];  // 字符串内容。遵循c语言字符串的, 保存一个空字符作为buf 的结尾。并且不计入 len 和 alloc 属性
    unsigned char flags;
}

(2) 字符串一共有三种编码, redis 对数据进行编码的主要目的是最大幅度地节省内存。

OBJ_ENCODING_EMBSTR 长度小于等于44字节的字符串
OBJ_ENCODING_RAW 长度大于44字节的字符串
OBJ_ENCODING_INT 将数值型字符串转为整型, 可以大幅度减少使用的内存空间

(3) 测试:

127.0.0.1:6379> set num1 1
OK
127.0.0.1:6379> type num1
string
127.0.0.1:6379> object encoding num1
"int"
127.0.0.1:6379> set mytest "myvalue"
OK
127.0.0.1:6379> type mytest
string
127.0.0.1:6379> get mytest
"myvalue"
127.0.0.1:6379> object encoding mytest
"embstr"

2.list 列表

相当于java 的 LinkedList。 是双向链表而不是数组。

插入和删除非常快,时间复杂度为o(1),但是索引定位非常慢,时间复杂度为O(n)。 而且列表中的每个元素都是双向链表,支持前向后向遍历。

当列表弹出了最后一个元素之后,该数据结构会被自动删除,内存会被回收。

右进左出: 队列

右进右出:栈(很少用作这种数据结构)

深入理解:快速列表。 list 不仅仅是一个简单的LinkedList,是可以称之为快速列表的结构。

在列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist,即压缩列表。它将所有的元素彼此紧挨着一起存储,分配的是一块连续的内存。当元素较多的时候改成quicklist。因为普通的链表需要的附加指针空间会浪费空间,比如链表只是存一个int,但是pre、next 就需要两个额外的指针。所以redis 将 链表和ziplist组合在一起形成了quicklist, 也就是将多个ziplist 用双向指针串起来使用。

 

  ziplist 是一个类似于数组的结构。 

3. hash 字典

可以理解为java 的hashmap。 它是无序字典。内部结构也是基于数组+链表。碰撞之后变为链表结构。不同的是,Redis的字典的值只能是字符串,另外rehash 的方式不一样。

当hash移除了最后一个元素之后该数据结构会被自动删除,内存会被回收。

hash 也有缺点,hash 的存储消耗高于单个字符串。

编码格式有两种:dict、ziplist。

    优先使用ziplist 存储,使用一个ziplist 存放键、后驱节点存放值,查找时需要遍历ziplist。使用该编码需要满足下列条件: 相关配置都在源码server.h 文件可以找到

(1)所有键值长度小于等于 server.hash_max_ziplist_value (64)

(2)所散列中键值对的数量小于 server.hash_max_ziplist_entries (512)

    使用dict 存储时,字典的key、value 都是sds 类型。 

测试如下:

127.0.0.1:6379> hset myhash key1 value1
(integer) 1
127.0.0.1:6379> keys myh*
1) "myhash"
127.0.0.1:6379> type myhash
hash
127.0.0.1:6379> object encoding myhash
"ziplist"
127.0.0.1:6379> hset myhash key1 value11111111111111111111111112sdfsdddddddddddfdsfsd
(integer) 0
127.0.0.1:6379> object encoding myhash
"ziplist"
127.0.0.1:6379> hset myhash key2 value11111111111111111111111112sdfsdddddddddddfdsfsdvdsvkdsfhdshfkdhsfkhdskhfhsdddddddddddddddddddddddddfskhdgdsffffffffffffffffffffffffffffffffggggggggggfsgfdsgddddddsfgdfsgfdsfgdsfgfdsgdfsgds
(integer) 1
127.0.0.1:6379>  object encoding myhash
"hashtable"
127.0.0.1:6379> debug object myhash
Value at:00007FA5D106AED0 refcount:1 encoding:hashtable serializedlength:150 lru:1141830 lru_seconds_idle:606

4. set 集合

相当于java 的hashset,它内部的键值对是无序的、唯一的。它的内部实现先当与一个特殊的字典,字典中的所有value 都是一个NULL。(Java 中所有的值都是一个Object 对象)

当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。

典型场景: 保存中奖用户的用户ID,因为有去重功能,所以不会重复。

编码: 有两种编码格式

(1) dict, 字典类型。 key为值,value 为空。

(2)intset: 优先使用这个。 类似于一个int[] 结构, 且有条件:

    集合中只存在整数型元素;集合元素数量小于等于server.set_max_intset_entries(512)

测试如下:

127.0.0.1:6379> sadd myset1 1 2 3
(integer) 3
127.0.0.1:6379> sadd myset2 whatisyou 1 2
(integer) 3
127.0.0.1:6379> debug object myset1
Value at:00007FA5D106AF20 refcount:1 encoding:intset serializedlength:15 lru:1143061 lru_seconds_idle:26
127.0.0.1:6379> debug object myset2
Value at:00007FA5D106AF40 refcount:1 encoding:hashtable serializedlength:15 lru:1143077 lru_seconds_idle:17
127.0.0.1:6379> smembers myset1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> smembers myset2
1) "2"
2) "1"
3) "whatisyou"

5. zset 有序列表 - 类似于数组和链表的合体

它类似于java 的 SortedSet 和 HashMap的结合体。一方面它是set保证内部value 的唯一性,另一方面它可以为每个value 赋予一个score代表这个value 的排序权重。

zset 中的最后一个value被移除后也会被内存回收。

典型场景: 保存用户的分数,value 是学生姓名,score 是学生分数

zset 内部是通过跳跃列表的数据结构实现的。跳跃列表类是一种层级结构,最下面一层所有的元素都会串起来。然后每隔几个元素挑处一个代表,再将这几个代表使用另外一级指针串起来。形成金字塔结构。

 

跳表是一个多层级的链表结构。跳表特点:

(1)上层链表是下层链表的子集

(2)头节点层数不小于其他节点的层数

(3)每个节点(除了头节点)都有一个随机的层数。

编码:

(1)ziplist, 优先使用这个,且有条件:

    有序集合元素数量小于等于server.zset_max_ziplist_entries (128)

    有序集合所有元素的长度都小于等于server.zset_max_ziplist_value (64)

(2)skiplist: 跳表结构

测试如下:

127.0.0.1:6379> zadd zset1 1 test1 2 test2
(integer) 2
127.0.0.1:6379> zadd zset2 1 test1 2 ljkdsfsdddddddddddddddddddddddddddddddddddddfsgdfdfhdgdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdgdjlkjkjkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
(integer) 2
127.0.0.1:6379> debug object zset1
Value at:00007FA5D106AF70 refcount:1 encoding:ziplist serializedlength:30 lru:1144067 lru_seconds_idle:38
127.0.0.1:6379> debug object zset2
Value at:00007FA5D106AF90 refcount:1 encoding:skiplist serializedlength:62 lru:1144099 lru_seconds_idle:13
127.0.0.1:6379> zrange zset1 0 -1 WITHSCORES
1) "test1"
2) "1"
3) "test2"
4) "2"
127.0.0.1:6379> zrange zset2 0 -1 WITHSCORES
1) "test1"
2) "1"
3) "ljkdsfsdddddddddddddddddddddddddddddddddddddfsgdfdfhdgdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdgdjlkjkjkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj"
4) "2"
127.0.0.1:6379>

容器数据结构通用规则:(list、set、zset、hash 是容器数据结构)

1. create if not exists: 如果容器不存在就创建一个

2. drop if no elements: 没有元素则删除后释放内存

关于过期时间:

redis 的所有数据结构都可以设置过期时间,注意hash 的过期时间是整个hash,不是某个key。 而且字符串用set 方法修改后相当于新开对象,过期时间为-1(永久)。

 

补充:debug 进行测试 

127.0.0.1:6379> set mykey myvalue
OK
127.0.0.1:6379> debug object mykey
Value at:00007FD84A412FE0 refcount:2 encoding:embstr serializedlength:8 lru:1127617 lru_seconds_idle:7

127.0.0.1:6379> lpush mylist 11 22 33
(integer) 3
127.0.0.1:6379> hset myhash key1 value1
(integer) 1
127.0.0.1:6379> sadd myset 11 22
(integer) 2
127.0.0.1:6379> zadd myzset 1 key1 2 key2
(integer) 2
127.0.0.1:6379> debug object myhash
Value at:00007FD84A46AED0 refcount:1 encoding:ziplist serializedlength:26 lru:1127883 lru_seconds_idle:68
127.0.0.1:6379> debug object mylist
Value at:00007FD84A46AEB0 refcount:1 encoding:quicklist serializedlength:21 lru:1127845 lru_seconds_idle:122 ql_nodes:1
ql_avg_node:3.00 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:19
127.0.0.1:6379> debug object myset
Value at:00007FD84A46AEC0 refcount:1 encoding:intset serializedlength:13 lru:1127917 lru_seconds_idle:59
127.0.0.1:6379> debug object myzset
Value at:00007FD84A46AF10 refcount:1 encoding:ziplist serializedlength:28 lru:1127941 lru_seconds_idle:48

补充 :redis object 查看数据信息, 可以查看空闲时间(距离上次访问结束时间)、访问次数、引用次数

xxx> object help
1) OBJECT <subcommand> arg arg ... arg. Subcommands are:
2) ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.
3) FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.
4) IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.
5) REFCOUNT <key> -- Return the number of references of the value associated with the specified key.

 

posted @ 2021-06-07 22:21  QiaoZhi  阅读(106)  评论(0编辑  收藏  举报