Redis学习

2021-02-21  本文已影响0人  知而乐者

Reids说明

对于Reids来说单CPU的性能更优于多CPU,因为CPU缓存(L1,L2,L3)一致性的问题涉及到CPU之间的通性

支持的数据结构

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;

(encoding表示 ptr 指向的具体数据结构,即这个对象使用了什么数据结构作为底层实现)
encoding的取值,如图:


image.png

不同的数据类型,底层存储结构:


image.png
(Redis的List类型中多了一种quickList,在3.2之后)

Reids底层存储结构(value)

String

底层以int、raw、embstr作为结构存储,redis中规定了string的最大长度为512M
什么时候使用什么类型?

sds结构说明

struct sdshdr {
            // 记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度
            int len;
            // 记录buf数组中未使用字节的数量   
            int free;
            // 字节数组,用于保存字符串
            char buf[];
        }

如:


image.png

为什么使用SDS这种结构,而不是直接存字符串?


image.png
缺点:占用无用内容,典型的用空间换时间

HashMap

哈希对象的编码有两种,分别是:ziplist、hashtable。
当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用压缩列表存储;否则使用 hashtable 存储。


image.png
image.png

HashMap渐进式扩容?


image.png

List

列表对象的编码有两种,分别是:ziplist、linkedlist
ziplist(压缩列表)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在数据量小的时候使用压缩列表存储。

当列表的长度小于 512,并且所有元素的长度都小于 64 字节时,使用压缩列表存储;否则使用 linkedlist 存储。


image.png
image.png

考虑到链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。后续版本对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.
quickList 是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。


image.png

Set

集合对象的编码有两种,分别是:intset、hashtable
intset(整数集合)主要是为节省内存而设计的内存结构,它的优点就是节省内存,但缺点就是比其他结构要消耗更多的时间,所以 Redis 在数据量小的时候使用整数集合存储。

当集合的长度小于 512,并且所有元素都是整数时,使用整数集合存储;否则使用 hashtable 存储。


image.png

ZSet

有序集合对象的编码有两种,分别是:ziplist、skiplist
当有序集合的长度小于 128,并且所有元素的长度都小于 64 字节时,使用压缩列表存储;否则使用 skiplist 存储。


image.png
image.png image.png

AOF

AOF策略就是每次执行修改命令的时候都将命令写到AOF文件中,AOF是有缓存的,什么时候真正落文件,Redis提供了三种策略
redis通过appendfsync配置提供了三种写盘的方式:

AOF重写

AOF随着操作的增加会越来越大,就需要重写(把对一个key的多条操作合并成一条)来减少文件的大小


image.png

Redis通过fork一个子进程来进行文件的重写,在重写的过程中如果有新的操作,则会把操作记录写两份,一份在源AOF的缓存区(避免重写失败导致数据的丢失),一份在重写缓存区,重写完成后将重写缓存区的信息写入重写文件,将原来的文件进行替换

在重写过程中,fork子进程以及重写完成后进行合并替换都会阻塞Reids的主线程的操作

为什么开启子进程而不是线程?
线程的创建和销毁会比子进程快的多,但是子进程会更稳定些,对于需要频繁创建和销毁的场景可以使用线程,如果追求稳定,使用子进程比较合适

内存快照(RDB)

AOF只要不是设置为always策略,对性能不会造成太大影响,但是由于记录的是命令而不是实际数据,所以AOF在恢复的时候就会比较缓慢

内存快照就是指内存中的数据在某一时刻的状态记录,类似于照片,快照对于恢复数据很友好但会有两个问题

对哪些数据做快照?
Redis数据都是在缓存,为了提供可靠性必须对所有数据进行快照,执行全量快照

Reids提供两种快照方法

做快照时数据可以修改吗?
读操作不用关心,和写快照没有冲突,写操作时Reids借助操作系统提供的写时复制技术,在执行快照的同时正常处理写操作(COW)

多久做一次快照?
如果太频繁会影响主进程导致Reids性能下降,如果时间长则会丢失的数据比较多,所以这个时间需要根据具体情况来定,不好把控,Reids4.0提出了一个混合使用AOF和内存快照的方法,简单的来说,内存快照以一定的频率执行,在两次快照之间使用AOF记录这期间的操作(建议在实践中使用)

缓存淘汰策略

参考二八原则,缓存设置比较合理是在总数据量的15%-30%,不过缓存写满是不可避免的,Redis又有哪些淘汰策略?


image.png

LRU和LFU都需要链表来记录最新访问或最频繁访问的数据,这样会对性能造成影响,Reids的做法是在ReidsObject中记录了时间和次数,在淘汰的时候随机选一批进行LFU或者LRU的淘汰,不会维护整个链表,所以是不公平的淘汰策略

LRU的时间字段和LFU的次数都是用一个字段来保存的,只使用了8bit来保存访问次数(最大255),在实现LFU的时候并没有采用每次访问都把对应的counter的值加一,而是,采用一种算法,在访问到一定的量级才counter的值会到某个值

算法源码

double r = (double)rand()/RAND_MAX;
...
double p = 1.0/(baseval*server.lfu_log_factor+1);
if (r < p) counter++;

次数和counter的对应关系


image.png
上一篇下一篇

猜你喜欢

热点阅读