一、Strings
redis中String的底层没有用c的char来实现,而是使用SDS数据结构( char buf[])。
缺点:浪费空间
优势:
1.c字符串不记录自身的长度,所以获取一个字符串长度的复杂度是O(N),但是SDS记录分配的长度alloc,已使用长度len,获取长度的复杂度为O(1)。比如,为char,必须一个个遍历,直到遍历到\0,字符串越长,那么速度越慢
2.可以减少字符串修改带来的内存重分配次数
字符串更改必须要先申明内存,否则会导致内存溢出。trim(str)时,还需要把不再使用的空间回收,不然会内存泄漏,并且如果操作频率过多,还会导致性能下降。这两点redis是如何优化的呢?
2.1空间预分配:SDS长度如果小于1MB,预分配跟长度一样的,大于1M,每次跟len的大小多1M
2.2惰性空间释放:截取的时候,不马上释放空间,供下次使用!同时提供相应的释放SDS未使用空间的API
2.3二进制安全 :C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符串被误认为是字符串结尾。这是因为c的字符是以空字符来判断这个字符串是否结束的。这些限制使得C字符串只能保存文本数据,而不能保存像图像、音频、视频、压缩文件这样的二进制数据。 SDS字符串是否结束是根据len来, 所以也就不会有这样的问题
二、Hashes
存储结构为ziplist压缩列表,当超过某种条件时,会转换为hashtable
优势:节省内存空间。压缩列表会根据存入的数据的不同类型以及不同大小,分配不同大小的空间。
缺点:因为是一块完整的内存空间,因此当里面的元素发生变更时,会产生连锁更新,严重影响我们的访问性能。所以,只适用于数据量比较小的场景。
那么redis是如何处理该缺陷的呢?
redis中会有相关的配置,hashes只有小数据量时会用到ziplist,当hash对象同时满足以下两个条件的时候,才会使用ziplist编码:
a.hash对象保存的键值对的数量<512个
b.所有的键值对的键和值的字符串长度都<64byte(一个英文字母一个字节)
redis.conf配置
bash
hash-max-ziplist-value 64 // ziplist中最大能存放的值长度
hash-max-ziplist-entries 512 // ziplist中最多能存放的entry节点数量
dict hashtable如图(在下篇博客讲到扩容会着重提到)
三、Lists
存储结构为quicklist快速列表(c源码)
cpp
typedef struct
{
struct quicklistNode *prev; //前指针
struct quicklistNode *next; //后指针
unsigned char *zl; //数据指针 指向ziplist结果
unsigned int sz; //ziplist大小 /* ziplist
size in bytes */
unsigned int count : 16; /* count of items in
ziplist */ //ziplist的元素
unsigned int encoding : 2; /* RAW==1 or LZF==2 */ //
是否压缩, 1没有压缩 2 lzf压缩
unsigned int container : 2; /* NONE==1 or ZIPLIST==2
*/ //预留容器字段
unsigned int recompress : 1; /* was this node previous
compressed? */
unsigned int attempted_compress : 1; /* node can't
compress; too small */
unsigned int extra : 10; /* more bits to steal for
future usage */ //预留字段
} quicklistNode;
quicklist兼顾了ziplist的节省内存,并且一定程度上解决了连锁更新的问题,quicklistNode每个节点里面是一个ziplist,每个节点又是分开的,那么就算发生了连锁更新,也只会发生在一个quicklistNode节点
quicklist中的每个node的ziplist元素的大小也是可以配置的(redis.conf)
cpp
# Lists are also encoded in a special way to save a lot of
space.
# The number of entries allowed per internal list node can
be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -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
# Positive numbers mean store up to _exactly_ that number
of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size)
or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as
necessary.
list-max-ziplist-size -2
list-max-ziplist-size如果这个配置值是正数,就代表quickListNode的ziplist的node的数量;如果为负数 固定的是-5到-1,则代表ziplist的大小(上图注释中有说明)
四、Sets集合
Redis的数据类型及使用场景-CSDN博客这篇博客中提到了set中如果存储的是整数的话,会按顺序存储;那么sets集合的存储方式为inset或者hashtable存储。满足元素为整型,并且元素个数小于配置(redis.conf),就用inset存储。
a.如果不是整数类型,就用dict hashtable(数组+链表)
b.如果元素个数超过512个,也会用hashtale存储。
cpp
set-max-intset-entries 512
问题:set的key没有value,怎么用hashtable存储呢?value存null就好了
五、Sorted Sets(ZSet)
默认使用的是ziplist(hash的小编码,quicklist的Node都是ziplist)
在ziplist的内部,会按照score排序递增来存储。插入的时候要移动之后的数据。若元素数量大于等于128,或者任一member长度大于等于64字节 则会采用skiplist+dict(跳表)存储。
redis.conf配置
cpp
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
skiplist跳表
结构定义(c源码)
cpp
* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele; //sds数据
double score; //score
struct zskiplistNode *backward; //后退指针
//层级数组
struct zskiplistLevel {
struct zskiplistNode *forward; //前进指针
unsigned long span; //跨度
} level[];
} zskiplistNode;
//跳表列表
typedef struct zskiplist {
struct zskiplistNode *header, *tail; //头尾节点
unsigned long length; //节点数量
int level; //最大的节点层级
} zskiplist;
跳表原理图:
如图:已有数据3.7.11.19.22.27.35.40,假如我找27的数据。
只有一个链表的场景下:我需要一个一个遍历,随着数据量越大,效率就会越慢
随机多层跳表:找27,会从最外层开始找,在22-40之间,再找第二层,在22到35之间,就能找到27。在外层的数据,查询的速度越高,比如找22,只需要找一次。
跳表的层级是在redis.conf中配置的(默认为32)
cpp
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64elements */