首页 > 编程知识 正文

redis 数据结构,redis5种数据结构

时间:2023-05-04 22:13:13 阅读:31374 作者:3731

在使用内存优化进行内存优化之前,您需要知道redis有哪些类型,使用了哪些数据结构,以及每个数据结构的使用场景。 说明各种数据类型

RedisObject对象存储在redis内部的所有数据都使用RedisObject封装。

结构redis对象{

unsigned type:4; 对象类型,如zset/set/hash

未编码:4; 对象代码,如ziplist/intset/skiplist

unsigned lru:24; //对象的“热”

int refcount; //引用数

void *ptr; //数据指针

(;

字符串string编码Redis中的字符串称为“SDS”,即简单动态字符串。 的结构是具有长度信息的字节数组。

结构sdst { t capacity; //排列容量T len; //数组长度byte flags; //特殊标志位,忽略byte[] content; //数组内容} Redis可以通过len快速获取字符串的长度,并表示任何字符串,包括在字符串的中间允许c字符串的结尾符号’ 0’。 redis还会在字符串的末尾添加结束符’ 0’。 使用capacity可以快速了解字符串中还剩多少空间,从而使字符串更容易编辑。 需要注意的是,在append等字符串编辑操作中,redis需要扩大两倍的容量,最多为1MB的可用容量,从内存节约的观点出发,需要尽量减少。

为了最大限度地节省内存,Redis对不同长度的字符串使用不同的类型。 如果字符串较短,则可以使用字节和短整型来定义len和容量。

字符串string编码优化

分析一下为什么embstr编码方式的字符串的最大长度是44。 默认情况下,Redis使用jmalloc内存分配器,jmalloc按2的倍数分配内存,最小为32B,如果超过32字节,则按64字节分配内存。

已知redisObject的大小为16字节,并且使用jmalloc分配器分配32字节的内存空间。 为了节省内存空间,redis在字符串较短时使用embstr编码,将字符串的SDS对象存储在ptr指针附近,如下图所示

现在可以使用剩下的16字节空间。 但是,如果SDS redisObject的大小超过64个字节,则redis将识别为大字符串,并使用SDS和redisObject内存不连续的原始编码,如下图所示

如上所述,如果字符串较短,请使用byte类型定义len和capacity。 SDS的len capacity flags全长为3,redis对象的大小为16。 64-16-3=45。 正在减去redis字符串的结束符’ 0’。 剩下的是44字节。 因此,如果字符串长度=44字节,则使用embstr编码。

所以我说key的长度最好在44字节以下。

字符串string扩展策略字符串在长度小于1M之前,扩展空间采用两倍的策略,即确保100%的冗馀空间。 如果长度超过1米,则每次增加容量时只会额外分配1米大小的冗馀空间,以避免冗馀空间翻倍太大而浪费。

扩展往往会导致内存碎片化,为此需要减少append等编辑操作。

hashtable数据结构redis使用的哈希表是使用链表方法解决哈希冲突的哈希表。 有关哈希表的详细信息,请参阅我的博客哈希表如何有效管理亿级数据。 需要注意的是哈希表的扩展和缩小

在Hashtable再redis中使用非常广泛,key-value、key-expire_time、hash、zset、set都用于Hashtable。

跳表skiplist跳表的基本结构

其追加删除重新评估的时间复杂度可以稳定在o(lgn )上。

ziplist压缩列表优化Redis为了节约内存使用,在元素数和元素大小比较小时,对hash、zset、list使用ziplist编码方式节约内存。 Ziplist通过将每个值直接放在存储上而不使用redisObject和SDS来节省大量内存空间。

结构ziplistt { int 32 ZL bytes; //整个压缩列表的占用字节数int32 zltail_offset; //从最后一个元素压缩列表的开始位置的偏移//元素的数量T[] entries,用于快速移动到最后一个节点int16 zllength; //元素内容列表,int8 zlend逐一精简保存; //表示压缩列表的结束,值始终为0xFF}

结构入口{ intvar prev len; //上一个条目的字节长度intvar编码; 内容长度信息包含optional byte[] content的元素类型的编码//元素内容}

p>Ziplist编码方式有如下的优点:
1、通过zltail_offset与entry.prevlen实现了逆序遍历查找功能,从而使得ziplist可以高效的访问头节点与尾节点。
2、除了少量几个用于表示长度信息的空间开销,数据都是一个挨着一个存放的,非常节约内存空间。当数据比较小时,节约的内存空间尤其明显。
Ziplist编码方式有如下的缺点:
1、由于数据都是一个挨着一个存放的,对于增删改查需要遍历整个ziplist,时间复杂度是O(n),n表示ziplist的数据个数。为此元素个数不宜过多,最好不超过1000个。
2、插入删除数据时,可能需要重新分配内存空间,是一个重量级的操作,为此只适合于数据比较小的场景。
3、在ziplist中间插入删除修改数据,需要挪动数据
4、redis为了节约内存空间,prevlen是一个可变长度,当数据长度小于254(0XFE)时,使用一个字节,超过使用3个字节。这样如果在ziplist中间修改、删除或者插入数据时,可能导致联级更新。不过ziplist使用的场景,数据一般都比较小。
ziplist使用场景:

intset优化

struct intset {
int32 encoding; // 决定整数位宽是 16 位、32 位还是 64 位
int32 length; // 元素个数
int contents; // 整数数组,可以是 16 位、32 位和 64 位
}

redis为了节省内存开销,对于集合类型,如果集合的数据都是整数,且个数比较少的情况下,会根据整数大小,选用16位、32位或者64位的整数类型,优先选用位数少的整数,一旦大小超过相应位数的整数所能表示的最大值,就会升级整数类型。这个升级是不可逆的。
redis为了能够快速的查找,会使用一个数组有序存储数据,插入删除查找的时间复杂度都是O(lgn),插入删除时,就会移动数据。为此使用intset数据结构,不宜保存过多数量的数据,一般不超1000个。redis提供配置参数
intset

快速列表优化

redis的list数据类型使用的是一个双向链表。双向链表的prev与next指针会占用大量的内存空间。为了节约内存开销,redis从3.2版本开始,使用一个快速列表quicklist保存数据。
quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。

redis为了进一步节约内存空间,每个ziplist还可以压缩存储。redis使用如下两个参数控制单个ziplist的大小与压缩深度

list-max-ziplist-size -2list-compress-depth 0

list-max-ziplist-size默认值为-2,可以取如下的值:

# -5: max size: 64 Kb <-- not recommended for normal workloads# -4: max size: 32 Kb <-- not recommended# -3: max size: 16 Kb <-- probably not recommended# -2: max size: 8 Kb <-- good# -1: max size: 4 Kb <-- good

list-compress-depth 默认为0,表示不压缩。如果取值1表示首尾2个ziplist不压缩。

编码优化总结

需要注意的是,对于ziplist与intset编码优化,一旦其中一个条件不满足,redis就会升级编码方式,比如升级到hashtable,这个升级过程是一个耗费CPU的操作。而且这个升级过程是不可逆的。

控制键数量

当redis保存大量的数据时,通常会存在大量的键,过多的键,除了键值对数据占用的内存空间,redis还要保存大量的元数据信息,占用大量的空间。为了节省内存空间,我们需要充分的利用redis的各种数据类型与编码优化方案。比如我们可以再客户端手动哈希分组键值对到hash数据类型里面:

1、如果key离散度较高时,可以截取其中2位或者3位作为hash的field,其他部分作为键。如key=123456789,hash key可以为g-123456,hash filed为789。
2、如果key分布离散度比较低,可以使用一些分布较随机的hash算法,如对可以做CRC32(key)运算,得到一个离散度很高的值,然后取CRC32值得后3位加上key本身的值,作为field。
3、根据hash元素的个数与大小设置hash-max-ziplist-value与hash-max-ziplist-entries的值。需要注意的是hash-max-ziplist-entries最好不超过1000,hash-max-ziplist-value最好不超过100。
但是这个方案也有明显的缺点:
1、我们不能再单独争对特定的键设置过期时间了,需要客户端自己维护删除过期键,不能再使用redis内置的LRU、LFU缓存淘汰策略。
2、客户端需要做哈希分组,加重客户端的开发设计难度。
3、Redis的吞吐量下降。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。