redis 过期策略及内存回收机制解析

目录
  • 1. 过期策略
    • 1.1 过期的 key 集合
    • 1.2 定时扫描策略
    • 1.3 从库的过期策略
  • 2. 懒惰删除
    • 2.1 异步线程
    • 2.2 flush
    • 2.3 异步队列
    • 2.4 AOF Sync很慢的问题
    • 2.5 更多异步删除点
  • 3. 过期淘汰配置
  • 4. LRU 算法
    • 4.1 近似 LRU 算法
  • 5. LRU
    • 5.1 LRU 模式
    • 5.2 LFU 模式

redis作为缓存的场景下,内存淘汰策略决定的redis的内存使用效率。考虑到这个很多大厂给出的“送分题”,但一般人很少能讲清楚,除非你对真的对过期策略、懒惰删除、LRU、LRU有一定的研究。

1. 过期策略

Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。就像死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割。

站在死神的角度思考:会不会在同一时间太多的 key 过期(Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙),以至于忙不过来?会不会导致线上读写指令出现卡顿?

1.1 过期的 key 集合

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。

redis 采用两种策略:

  • 定时删除是集中处理
  • 惰性删除是零散处理

1.2 定时扫描策略

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

  • 从过期字典中随机 20 个 key;
  • 删除这 20 个 key 中已经过期的 key;
  • 如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

如果Redis 实例中所有的 key (几十万个)在同一时间过期会怎样?

Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。内存管理器需要频繁回收内存页,此时会产生一定的 CPU 消耗,必然导致线上读写请求出现明显的卡顿现象。

当客户端请求到来时(服务器如果正好进入过期扫描状态),客户端的请求将会等待至少 25ms 后才会进行处理,如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。

其实这个故障在社区中时常爆出 ,业务开发人员一定要注意不宜全部在同一时间过期,可以给目标过期时间的基础上再加一个随机范围(redis.expire_at(key, random.randint(86400) + expire_ts)),分散过期处理的压力。

1.3 从库的过期策略

从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。

因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在,分布式锁的算法漏洞就是因为这个同步延迟产生的。

2. 懒惰删除

懒惰删除(lazy free),在客户端访问key时再进行检查如果过期了就立即删除

为什么要懒惰删除?

Redis内部实际上并不是只有一个主线程,它还有几个异步线程专门用来处理一些耗时的操作。删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟。

不过如果删除的 key 是一个非常大的对象,那么删除操作就会导致单线程卡顿,怎么破?

Redis 4.0 版本引入了 unlink 指令(为了解决这个卡顿问题),它能对删除操作进行懒处理,丢给后台线程来异步回收内存。

> unlink key

OK

你肯定会担心这里的线程安全问题,会不会出现多个线程同时并发修改数据结构的情况存在?

这里我打个比方:可以将整个 Redis 内存里面所有有效的数据想象成一棵大树。当 unlink 指令发出时,它只是把大树中的一个树枝别断了,然后扔到旁边的火堆里焚烧 (异步线程池)。树枝离开大树的一瞬间,它就再也无法被主线程中的其它指令访问到了,因为主线程只会沿着这颗大树来访问。

2.1 异步线程

异步线程在 Redis 内部有一个特别的名称,它就是BIO,全称是Background IO,意思是在背后默默干活的 IO 线程。不过内存回收本身并不是什么 IO 操作,只是 CPU 的计算消耗可能会比较大而已。

异步线程演进之路

实现懒惰删除时,它并不是一开始就想到了异步线程。最初的尝试是在主线程里,使用类似于字典渐进式搬迁那样来实现渐进式删除回收。懒惰删除是采用类似于 scan 操作的方法,通过遍历第一维数组来逐步删除回收第二维链表的内容,等到所有链表都回收完了,再一次性回收第一维数组。这样也可以达到删除大对象时不阻塞主线程的效果。

但是说起来容易做起来却很难。渐进式回收需要仔细控制回收频率,它不能回收的太猛,这会导致 CPU 资源占用过多,也不能回收的蜗牛慢,因为内存回收不及时可能导致内存持续增长。

Antirez 需要采用合适的自适应算法来控制回收频率。他首先想到的是检测内存增长的趋势是增长 (+1) 还是下降 (-1) 来渐进式调整回收频率系数,这样的自适应算法实现也很简单。但是测试后发现在服务繁忙的时候,QPS 会下降到正常情况下 65% 的水平,这点非常致命。

所以 Antirez 才使用了如今使用的方案——异步线程。异步线程这套方案就简单多了,释放内存不用为每种数据结构适配一套渐进式释放策略,也不用搞个自适应算法来仔细控制回收频率。

不过使用异步线程也是有代价的,主线程和异步线程之间在内存回收器 (jemalloc) 的使用上存在竞争。这点竞争消耗是可以忽略不计的,因为 Redis 的主线程在内存的分配与回收上花的时间相对整体运算时间而言是极少的。

异步线程方案相当复杂,具体可参阅引用资料。

2.2 flush

Redis 提供了 flushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作。

Redis 4.0 同样给这两个指令也带来了异步化,在指令后面增加 async 参数就可以将整棵大树拔起,扔给后台线程慢慢焚烧。

> flushall async

OK

2.3 异步队列

Redis4.0,主线程将对象的引用从「大树」中摘除后,会将这个 key 的内存回收操作包装成一个任务,塞进异步任务队列,后台线程会从这个异步队列中取任务。任务队列被主线程和异步线程同时操作,所以必须是一个线程安全的队列。

不是所有的 unlink 操作都会延后处理,如果对应 key 所占用的内存很小,延后处理就没有必要了,这时候 Redis 会将对应的 key 内存立即回收,跟 del 指令一样。

2.4 AOF Sync很慢的问题

Redis需要每秒一次(可配置)同步AOF日志到磁盘,确保消息尽量不丢失,需要调用sync函数,这个操作会比较耗时,会导致主线程的效率下降,所以Redis也将这个操作移到异步线程来完成。

执行AOF Sync操作的线程是一个独立的异步线程,和前面的懒惰删除线程不是一个线程,同样它也有一个属于自己的任务队列,队列里只用来存放AOF Sync任务。

2.5 更多异步删除点

Redis 回收内存除了 del 指令和 flush 之外,还会存在于在 key 的过期、LRU 淘汰、rename 指令以及从库全量同步时接受完 rdb 文件后会立即进行的 flush 操作。

Redis4.0 为这些删除点也带来了异步删除机制,打开这些点需要额外的配置选项。

  • slave-lazy-flush 从库接受完 rdb 文件后的 flush 操作
  • lazyfree-lazy-eviction 内存达到 maxmemory 时进行淘汰
  • lazyfree-lazy-expire key 过期删除
  • lazyfree-lazy-server-del rename 指令删除 destKey

3. 过期淘汰配置

当 Redis 已使用内存超出物理内存限制时,内存中的数据会开始和磁盘产生频繁的交换 (swap),交换会让 Redis 的性能急剧下降,而此时Redis的存取效率简直是龟速(基本上等于不可用)。在生产环境中这是不允许的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。

那如果实际内存超出 maxmemory 时该怎么办?

Redis提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。

noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key
allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
allkeys-random 跟上面一样,不过淘汰的策略是随机的 key

redis.conf中配置

maxmemory <bytes> #最大内存(单位字节)

maxmemory-policy noeviction #默认

小结

  • volatile-xxx 策略只会针对带过期时间的 key 进行淘汰
  • allkeys-xxx 策略会对所有的 key 进行淘汰

那该如何抉择呢?

如果你只是拿 Redis 做缓存,那最好使用 allkeys-xxx,客户端写缓存时不必携带过期时间。

如果你还想同时具备持久化功能,那就使用 volatile-xxx 策略,好处就是,没有设置过期时间的 key 不会被 LRU 算法淘汰

4. LRU 算法

实现 LRU(最近最少) 算法除了需要 key/value 字典外,还需要附加一个链表,链表中的元素按照一定的顺序进行排列。

当空间满的时候,会踢掉链表尾部的元素。当字典的某个元素被访问时,它在链表中的位置会被移动到表头。所以链表的元素排列顺序就是元素最近被访问的时间顺序。

4.1 近似 LRU 算法

Redis 使用的是一种近似 LRU 算法,它跟 LRU 算法还不太一样。之所以不使用 LRU 算法,是因为需要消耗大量的额外的内存,需要对现有的数据结构进行较大的改造。近似 LRU 算法则很简单,在现有数据结构的基础上使用随机采样法来淘汰元素,能达到和 LRU 算法非常近似的效果。

Redis 为实现近似 LRU 算法,它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳。

前面讲过key 过期方式分为集中处理和懒惰处理,LRU 淘汰不一样,它的处理方式只有懒惰处理。当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行一次 LRU 淘汰算法。这个算法也很简单,就是随机采样(可以通过maxmemory-policy配置)出 5个 key,然后淘汰掉最旧的 key,如果淘汰后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory为止。

下面是随机 LRU 算法和严格 LRU 算法的效果对比图:绿色部分是新加入的 key,深灰色部分是老旧的 key,浅灰色部分是通过 LRU 算法淘汰掉的 key。可以看出采样数量越大,近似 LRU 算法的效果越接近严格 LRU 算法。同时 Redis3.0 在算法中增加了淘汰池,进一步提升了近似 LRU 算法的效果。

淘汰池是一个数组,它的大小是 maxmemory_samples,在每一次淘汰循环中,新随机出来的 key 列表会和淘汰池中的 key 列表进行融合,淘汰掉最旧的一个 key 之后,保留剩余较旧的 key 列表放入淘汰池中留待下一个循环。

5. LRU

Redis 4.0 里引入了一个新的淘汰策略 —— LFU (Least Frequently Used)模式,作者认为它比 LRU 更加优秀。它表示按最近的访问频率进行淘汰,它比 LRU 更加精准地表示了一个 key 被访问的热度。

它淘汰策略配置参数maxmemory-policy增加了 2 个选项,分别是 volatile-lfu 和 allkeys-lfu,分别是对带过期时间的 key 进行 lfu 淘汰以及对所有的 key 执行 lfu 淘汰算法。

如果一个 key 长时间不被访问,只是刚刚偶然被用户访问了一下,那么在使用 LRU 算法下它是不容易被淘汰的,因为 LRU 算法认为当前这个 key 是很热的。而 LFU 是需要追踪最近一段时间的访问频率,如果某个 key 只是偶然被访问一次是不足以变得很热的,它需要在近期一段时间内被访问很多次才有机会被认为很热。

Redis 的所有对象结构头中都有一个 24bit 的字段,这个字段用来记录对象的「热度」。

// redis 的对象头
typedef struct redisObject {
    unsigned type:4; // 对象类型如 zset/set/hash 等等
    unsigned encoding:4; // 对象编码如 ziplist/intset/skiplist 等等
    unsigned lru:24; // 对象的「热度」
    int refcount; // 引用计数
    void *ptr; // 对象的 body
}robj;

5.1 LRU 模式

lru 字段存储的是 Redis 时钟server.lruclock,Redis 时钟是一个 24bit 的整数,默认是 Unix 时间戳对 2^24 取模的结果,大约 97 天清零一次。当某个 key 被访问一次,它的对象头的 lru 字段值就会被更新为server.lruclock,该值一直是递增的,通过这个逻辑就可以精准计算出对象多长时间没有被访问——对象的空闲时间。如果超过server.lruclock折返了。

有了对象的空闲时间,就可以相互之间进行比较谁新谁旧,随机 LRU 算法靠的就是比较对象的空闲时间来决定谁该被淘汰了。

默认 Redis 时钟值每毫秒更新一次,在定时任务serverCron里主动设置,在serverCron里面其实有很多很多定时任务,比如大型 hash 表的渐进式迁移、过期 key 的主动淘汰、触发 bgsave、bgaofrewrite 等等。

为什么 Redis 要缓存系统时间戳?

在java中我们使用System.currentTimeInMillis(),而Redis 不能这样,因为每一次获取系统时间戳都是一次系统调用,系统调用相对来说是比较费时间的,这样的消耗对redis而言是伤不起的,所以获取时间都直接从缓存中直接拿。

5.2 LFU 模式

lru 字段 24 个 bit 用来存储两个值,分别是ldt(last decrement time)和logc(logistic counter)。

logc是 8 个 bit,用来存储访问频次(最大整数值为 255)。存储频次远远不够(太小),所以这 8 个 bit 存储的是频次的对数值,并且这个值还会随时间衰减。如果它的值比较小,就很容易被回收,为了确保新创建的对象不被回收,新对象的初始化默认是LFU_INIT_VAL=5。

ldt 是 16 个位,用来存储上一次 logc 的更新时间(精度不可能很高),它取的是分钟时间戳对 2^16 进行取模,大约每隔 45 天就会折返。同 LRU 模式一样,我们也可以使用这个逻辑计算出对象的空闲时间,只不过精度是分钟级别的。

server.unixtime 是当前 redis 记录的系统时间戳,和 server.lruclock 一样,它也是每毫秒更新一次。

// nowInMinutes
// server.unixtime 为 redis 缓存的系统时间戳
unsigned long LFUGetTimeInMinutes(void) {
    return (server.unixtime/60) & 65535;
}
// idle_in_minutes
unsigned long LFUTimeElapsed(unsigned long ldt) {
    unsigned long now = LFUGetTimeInMinutes();
    if (now >= ldt) return now-ldt; // 正常比较
    return 65535-ldt+now; // 折返比较
}

ldt 的值不是在对象被访问时更新的,它在 Redis 的淘汰逻辑进行时进行更新,淘汰逻辑只会在内存达到 maxmemory 的设置时才会触发,在每一个指令的执行之前都会触发。每次淘汰都是采用随机策略,随机挑选若干个 key,更新这个 key 的「热度」,淘汰掉「热度」最低的。因为 Redis 采用的是随机算法,如果 key 比较多的话,那么 ldt 更新的可能会比较慢。不过既然它是分钟级别的精度,也没有必要更新的过于频繁。

ldt 更新的同时也会一同衰减 logc 的值,衰减也有特定的算法。它将现有的 logc 值减去对象的空闲时间 (分钟数) 除以一个衰减系数,默认这个衰减系数lfu_decay_time是 1。如果这个值大于 1,那么就会衰减的比较慢。如果它等于零,那就表示不衰减,它是可以通过配置参数lfu_decay_time进行配置。

logc 的更新和 LRU 模式的 lru 字段一样,都会在 key 每次被访问的时候更新,只不过它的更新不是简单的+1,而是采用概率法进行递增,因为 logc 存储的是访问计数的对数值,不能直接+1。

总结,通过LFU 和LRU的介绍,我们知道redis的设计有多优秀,不浪费一丁点内存!

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Redis中的数据过期策略详解

    1.Redis中key的的过期时间 通过EXPIRE key seconds命令来设置数据的过期时间.返回1表明设置成功,返回0表明key不存在或者不能成功设置过期时间.在key上设置了过期时间后key将在指定的秒数后被自动删除.被指定了过期时间的key在Redis中被称为是不稳定的. 当key被DEL命令删除或者被SET.GETSET命令重置后与之关联的过期时间会被清除 127.0.0.1:6379> setex s 20 1 OK 127.0.0.1:6379> ttl s (intege

  • 浅谈Redis中的内存淘汰策略和过期键删除策略

    目录 8种淘汰策略 过期键的删除策略 总结 redis是我们现在最常用的一个工具,帮助我们建设系统的高可用,高性能. 而且我们都知道redis是一个完全基于内存的工具,这也是redis速度快的一个原因,当我们往redis中不断缓存数据的时候,其内存总有满的时候(而且内存是很贵的东西,尽量省着点用),所以尽可能把有用的数据,或者使用频繁的数据缓存在redis中,物尽其用. 那么如果正在使用的redis内存用完了,我们应该怎么取舍redis中已存在的数据和即将要存入的数据呢,我们要怎么处理呢? re

  • Redis数据过期策略的实现详解

    目录 一.设置过期时间 1.常用方式 2.字符串独有方式 二.3种过期策略 三.Redis采用的过期策略 四.RDB对过期key的处理 五.AOF对过期key的处理 本文对Redis的过期机制简单的讲解一下 讲解之前我们先抛出一个问题,我们知道很多时候服务器经常会用到redis作为缓存,有很多数据都是临时缓存一下,可能用过之后很久都不会再用到了(比如暂存session,又或者只存放日行情股票数据)那么就会出现一下几个问题了 Redis会自己回收清理不用的数据吗?如果能,那如何配置?如果不能,如何

  • 大家都应该知道的Redis过期键与过期策略

    今天,我和大家分享一篇关于 Redis 有关过期键的内容,主要有四个内容: 如何设置过期键 如何取消设置的过期时间 过期键的过期策略是怎样的 RDB.AOF 和复制对过期键的处理又是怎样的 设置键的生存时间或过期时间 redis 一共有 4 个命令来设置键的生存时间(可以存活多久)或过期时间(什么时候被删除) expire <key> <ttl>:将 key 的生存时间设置为 ttl 秒 pexpire <key> <ttl>:将 key 的生存时间设置为

  • redis 过期策略及内存回收机制解析

    目录 1. 过期策略 1.1 过期的 key 集合 1.2 定时扫描策略 1.3 从库的过期策略 2. 懒惰删除 2.1 异步线程 2.2 flush 2.3 异步队列 2.4 AOF Sync很慢的问题 2.5 更多异步删除点 3. 过期淘汰配置 4. LRU 算法 4.1 近似 LRU 算法 5. LRU 5.1 LRU 模式 5.2 LFU 模式 redis作为缓存的场景下,内存淘汰策略决定的redis的内存使用效率.考虑到这个很多大厂给出的"送分题",但一般人很少能讲清楚,除非

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

  • 关于C++为什么不加入垃圾回收机制解析

    Java的爱好者们经常批评C++中没有提供与Java类似的垃圾回收(Gabage Collector)机制(这很正常,正如C++的爱好者有时也攻击Java没有这个没有那个,或者这个不行那个不够好),导致C++中对动态存储的官吏称为程序员的噩梦,不是吗?你经常听到的是内存遗失(memory leak)和非法指针存取,这一定令你很头疼,而且你又不能抛弃指针带来的灵活性. 在本文中,我并不想揭露Java提供的垃圾回收机制的天生缺陷,而是指出了C++中引入垃圾回收的可行性.请读者注意,这里介绍的方法更多

  • 图文详解java内存回收机制

    在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了像C/C++直接操作内存的危险.但是,也正因为内存管理完全由JVM负责,所以也使Java很多程序员不再关心内存分配,导致很多程序低效,耗内存.因此就有了Java程序员到最后应该去了解JVM,才能写出更高效,充分利用有限的内存的程序. 1.Java在内存中的状态  首先我们先写一个代码为例子: Person.java package test

  • javascript 内存回收机制理解

    1.唠叨 javascript语言是一门优秀的脚本语言.其中包含脚本语言的灵活性外还拥有许多高级语言的特性.例如充许构建和实例化一个对象,垃圾回收机制(GC:Garbage Collecation).通常我们使用new创建对象,GC负责回收对象占用内存区域.因此了解GC,可以加深对javascript垃圾回收机制的理解. 2.用局部变量和全局变量解释GC GC在回收内存时,首先会判断该对象是否被其它对象引用.在确定没有其它对象引用便释放该对象内存区域.因此如何确定对象不再被引用是GC的关键所在.

  • springboot+redis过期事件监听实现过程解析

    1 修改 redis.conf配置文件: K Keyspace events, published with keyspace@ prefix事件 E Keyevent events, published with keyevent@ prefix g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, - $ String commands l List commands s Set commands h Hash co

  • 深入理解Java垃圾回收机制以及内存泄漏

    前言 在segmentfault上看到一个问题:java有完善的GC机制,那么在java中是否会出现内存泄漏的问题,以及能否给出一个内存泄漏的案例.本问题视图给出此问题的完整答案. 垃圾回收机制简介 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据.如果只是不停的分配内存,那么程序迟早面临内存不足的问题.所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用. 内存回收机制按照实现角色的不同可以分为两种,一种是程序员手动实现内存的释放(比如C语

  • 详细聊聊Redis的过期策略

    保存过期时间 Redis可以为每个key设置过期时间,会将每个设置了过期时间的key放入一个独立的字典中. typedef struct redisDb { int id; //id是数据库序号,为0-15(默认Redis有16个数据库) long avg_ttl; //存储的数据库对象的平均ttl(time to live),用于统计 dict *dict; //存储数据库所有的key-value dict *expires; //存储key的过期时间 dict *blocking_keys;

  • java内存泄漏与内存溢出关系解析

    这篇文章主要介绍了java内存泄漏与内存溢出关系解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory!

随机推荐