[Remote Dictionary Service],也就是「远程字典服务」,Redis。
Redis我们都知道有5种基础数据结构:分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。
再说这些基础数据结构的时候,我们先说说Redis的Key:
Key:
Redis的键是二进制安全的,也就是任何binary sequence都可以作为键,比如“foo”到到JPEG文件的内容,空字符串也是有效的值,Redis的键根据自身业务去设计,但不要过于长和短,它最大大小为512MB,当然关于键的设计,官网给出了hashing的处理方式。
string:
它的数据结构是Key-Value,通过key获取相应的值。Redis 的字符串是动态字符串,是可以修改的字符串,在内存中它是以字节数组的形式存在的。
Redis 的字符串叫着「SDS」,也就是 Simple Dynamic String。它的结构是一个带长度信息的字节数组。以'\0'结尾。SDS结构使用的是范型,为什么不直接用 int 呢,这是因为当字符串比较短时,存入的值 和 分配的空间 可以使用 byte 和 short 来表示,
struct SDS<T> { T capacity; // 数组容量 T len; // 数组长度byte flags; // 特殊标识位byte[] content; // 数组内容}
Redis 为了对内存做极致的优化,不同长度的字符串使用不同的结构体来表示。它采用预分配冗余空间的方式来减少内存的频繁分配,Redis内部当前字符串实际分配的空间一般高于字符串长度。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间,
简单操作,格式 {SET 键 值,GET 键}.当批量SETGET的时候,会节省网络耗时开销.(Redis管道机制)。
过期事件,格式{EXPIRE 键 时间}. {setex 键 时间 值}。
计数:如果 value 值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围是signed long 的最大最小值,超过了这个值,Redis 会报错。格式{incr 键}.
list:
它是链表而不是数组,插入和删除操作都是O(1)级别,但是索引慢O(n)。当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。
右边进左边出:队列 右边进右边出:栈
我们会发现,Redis底层存储是一个快速链表的结构(quicklist).首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表(支持双向遍历)。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。ziplist是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么就是长度较短的字符串,那么Redis就会使用压缩列表。
这里,注意观察 debug object 输出的 encoding 字段都是 ziplist,这就表示内部采用压
缩列表结构进行存储。
struct ziplist<T> { int32 zlbytes; // 整个压缩列表占用字节数int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点 int16 zllength; // 元素个数T[] entries; // 元素内容列表,挨个挨个紧凑存储int8 zlend; // 标志压缩列表的结束,值恒为 0xFF}
zltail_offset就是用来快速定位到最后一个元素。倒着遍历。
entry 块随着容纳的元素类型不同,也会有不一样的结构。
struct entry {int<var> prevlen; // 前一个 entry 的字节长度int<var> encoding; // 元素类型编码optional byte[] content; // 元素内容}
encoding 字段存储了元素内容的编码类型信息,ziplist 通过这个字段来决定后面的content 内容的形式、通过前缀位来识别具体存储的数据形式,content 字段在结构体中定义为 optional 类型,表示这个字段是可选的,对于很小的整数而言,它的内容已经内联到 encoding 字段的尾部了。
因为 ziplist 都是紧凑存储,没有冗余空间 (对比一下 Redis 的字符串结构)。意味着插入一个新的元素就需要调用 realloc 扩展内存。取决于内存分配器算法和当前的 ziplist 内存大小,realloc 可能会重新分配新的内存空间,并将之前的内容一次性拷贝到新的地址,也可能在原有的地址上进行扩展,这时就不需要进行旧内容的内存拷贝。如果 ziplist 占据内存太大,重新分配内存和拷贝内存就会有很大的消耗。所以 ziplist 不适合存储大型字符串,存储的元素也不宜过多
当 set 集合容纳的元素都是整数并且元素个数较小时,Redis 会使用 intset 来存储结合元素。intset 是紧凑的数组结构,同时支持 16 位、32 位和 64 位整数。
struct intset<T> { int32 encoding; // 决定整数位宽是 16 位、32 位还是 64 位 int32 length; // 元素个数int<T> contents; // 整数数组,可以是 16 位、32 位和 64 位}
hash
无序字典,数组+链表二位结构。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
Set集合
它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的 value 都是一个值 NULL。set 结构可以用来存储活动中奖的用户 ID,因为有去重功能,可以保证同一个用户不会中奖两次。
zset (有序列表)
一方面它是一个 set,保证了内部value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。zset 可以用来存粉丝列表,value 值是粉丝的用户 ID,score 是关注时间。我们可以对粉丝列表按关注时间进行排序。zset 内部的排序功能是通过「跳跃列表」数据结构来实现的,它的结构非常特殊,也比较复杂。