Redis为什么这么快?万一内存满了咋办?【Redis执行流程篇】

这篇文章若是对你有帮助,还请点个小赞赞呢o.o~

1.Redis底层存储结构

Redis是内存存储,都是以键值对形式存放在内存。其实就是存放到RedisDB中名叫dict的HASHTABLE中。

/* Redis数据库结构体 */
typedef struct redisDb {
    // 数据库键空间,存放着所有的键值对(键为key,值为相应的类型对象)
    dict *dict;                 
    // 键的过期时间
    dict *expires;              
    // 处于阻塞状态的键和相应的client(主要用于List类型的阻塞操作)
    dict *blocking_keys;       
    // 准备好数据可以解除阻塞状态的键和相应的client
    dict *ready_keys;           
    // 被watch命令监控的key和相应client
    dict *watched_keys;         
    // 数据库ID标识
    int id;
    // 数据库内所有键的平均TTL(生存时间)
    long long avg_ttl;         
} redisDb;

从上面可以看到,Redis底层结构最主要的部分是dict结构。也就是Hash对象结构。

typedef struct dict {
    dictType *type;  // 字典类型,例如哈希表
    void *privdata;  // 额外的私有数据
    dictEntry **table;  // 哈希表数组,存储键值对
    unsigned long size;  // 哈希表的大小
    unsigned long sizemask;  // 大小掩码,用于哈希计算
    unsigned long used;  // 已使用的槽位数量
} dict;

RedisDB即数据库对象,字典里主要是kv数据,k是字符串,v是任意Redis对象。即以下结构:

[图片]

1、添加数据。key必须为String对象,value可以是任何类型的对象。
2、查询数据。在dict找到对应key查询。
3、更新数据。在原有key基础上赋值。
4、删除数据。将key和value从dict结构里删除。

我们还发现在RedisDB里有一个过期键的字段: dict *expires;

如果设置了过期时间就如同以下效果:

[图片]

这里的dict中和expires中的key对象,实际都是存储的String对象指针。

哎,那么这里的key过期之后,不仅在dict里还在expires里,这是占用了两份内存?

无论是数据字典还是过期字典里面的key对象,实际都是存储的String对象指针,所以不会重复占用内容,Redis对内存的使用还是很珍惜的。

那如果对象过期了,要怎么处理?

其实,过期之后的键不是立即删除的,一般有三种清除策略,分别是定时删除、定期删除和惰性删除。

2.为什么Redis这么快?

Redis在处理客户端的请求时包括获取socket读、解析、执行、内容返回socket写等都由一个顺序串行的主线程处理,这就是所谓的单线程。

为什么Redis是单线程?

Redis定位是内存kv存储,一般来说执行会很快,而往往影响性能的是网络IO,处理逻辑多线程没有太大收益。
多线程会带来上下文切换成本、同步机制的开销、线程本身使用的内存资源等。

言归正传,为什么Redis这么快呢?

按理说,Redis核心的请求是单线程,要比多线程差很多,但是Redis却能用单线程模型达到每秒数万级别的处理能力。主要有以下几个关键因素:

  1. 大部分操作内存上完成,内存操作本就很快。
  2. 对象底层实现选用了许多高效的数据结构,如ZIPLIST、Hash、跳表,并且一种对象底层可能根据不同条件选用多个高效结构。
  3. 多路复用机制,使其在网络IO操作上能并发处理大量客户端请求,实现高吞吐量。

接下来重点探讨一下,为什么要有多路复用机制。

尽管 Redis 的核心操作是在内存中完成的,但为了保证数据的可靠性和高可用性,磁盘 I/O 操作不可忽视。优化磁盘 I/O 的性能,尤其是在持久化和复制过程中,能够显著提高 Redis 在高负载情况下的整体性能。

IO多路复用就是有触发IO操作的时候,就会产生通知,收到通知,再去处理对应的事件,针对IO多路复用,Redis做了一层包装,叫Reactor模型(本质就是监听各种事件,当事件发生时,将事件分发给不同的处理器)(并发)。
[图片]

综上,有以下几个问题:

1、Redis是多线程还是单线程的?
Redis的核心处理逻辑一直都是单线程的,某些异步流程从4.0开始用多线程,如UNLINK、FLUSHALL ASYNC等非阻塞操作,网络IO解包从6.0开始使用多路复用。

2、为什么单线程快?
首先,Redis是在内存层面操作的本身就快,同时很多对象底层根据不同场景选用高效的数据结构,大大提高了性能;还采用了多路复用的机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐量。

3.万一内存满了?咋办?

在32位操作系统中,maxmemory默认是3G,对于64位操作系统默认1024KB、MB、GB。

那我们就肯定会想,要么阻止再次写入数据,要么就淘汰原来的数据。这不很简单吗,那我们一起来看看这是怎么处理的。

内存淘汰策略

  • noeviction(不开启数据淘汰)
  • 开启数据淘汰
    • 针对有过期时间的数据volatile
      • LRU(最近访问最少使用,尝试回收最长时间未使用的)
      • LFU(回收最不常用的键)
      • RANDOM(回收随机的键)
      • TTL(回收在过期集合的键,优先回收存活时间较短的键)
    • 对于所有数据allkeys
      • LRU
      • LFU
      • RANDOM

1.LRU(最近最久未使用)

LRU是记录每个key最近访问时间,维护一个访问时间的数据。这需要维护一个全局链表,对Redis来说是个巨大的成本,所以Redis选择采样的方式来做,叫近似LRU算法。

那么这个近似LRU算法是怎么实现的呢?

在LRU模式下,RedisObject对象中的字段lru存储的是key被访问时的时钟,当key被访问,Redis会更新这个lru字段。当内存不足时,就会执行近似LRU算法,就是随机采样n个key,根据字段lru淘汰掉最旧的那个key,如果淘汰后仍不足,就继续这个步骤。
采样范围

  1. Allkeys
  2. volatile从有过期时间的key随机采样
    缺点
    它不是一个完整的LRU,随机采样导致结果不是全局真正的最久未访问。
    优化
    Redis3.0会维护一个大小为16的池子,池中数据根据访问时间排序。每次选取的key都会放到池中,然后淘汰掉最久未访问的。
    来个RANDOM和LRU的思考,嗯?
    volatile-RANDOM随机选取有过期时间的元素后就直接淘汰被选取的元素,而LRU在随机挑选出元素后,淘汰掉其中最久没有被访问的那个元素。

2.LFU(最不常用淘汰)

LFU优先淘汰活跃最低,使用频率最低的。在4.0引入LFU,其实LRU就可以解决大部分问题了,但是脱离频率,只谈最近访问过的key,在某些场景里是不合适的。

从上面我们知道了lru这个字段(24位)是记录时间戳的,那么在这里LFU复用这个字段,前16位存储上一次访问时间戳(精度是1分钟),低8位存储访问计数。

那么这个lfu(lru)字段数据怎么更新呢?
1、相对于上次访问时间间隔,使用函数LFUDecrAndReturn计算访问计数。
2、一定概率增加访问计数。
3、当前时间更新高16位,次数更新低8位。

总结

1、内存回收什么时候发起?
每次读写的时候,都会调用processCommand函数,转而调用freememoryIfNeeded函数。

2、介绍下Redis LRU算法?
LRU是最近最久未使用算法,记录每个key的最近访问时间,淘汰最久未没访问到的数据,但是这个LRU算法不是标准的。在Redis中,对LRU算法做了优化,这块需要讲一下吗?
(如果需要)
标准LRU算法需要维护双链表,内存消耗大,所以Redis采用近似LRU算法采样进行淘汰,就是随机采样n个key,默认为5个,然后根据时间戳淘汰最旧的那个key。3.0版本后,有对近似LRU做了优化,就是维护了一个候选池,池中的数据根据访问时间进行排序。每次随机选取的key都会放入池中,然后淘汰掉最久未访问的。

3、什么是LFU算法,为什么Redis引入LFU算法?
LFU算法是考虑到了访问频率的影响,LFU记录了上一次访问时间戳和访问计数,访问计数同时受上一次访问时间和访问频率的影响,因为每次访问都可能会增加计数。

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大华Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值