Redis内存管理与数据一致性

本文介绍Redis的过期时间设置方法及其作用,并详细解释了Redis如何通过过期字典来判断数据是否过期。此外,还介绍了Redis的三种过期数据删除策略:定时过期、惰性过期及定期清除,以及六种内存淘汰机制。最后,讨论了如何在MySQL和Redis之间保持数据一致性,包括四种常见的同步更新方案,并提出了解决脏数据问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 过期时间

除了string数据类型有setex可以在设置数据同时设置过期时间外,其余的数据类型都需要依靠expire命令设置过期时间,这个命令是针对key的。

1.1 过期时间有什么用?

  1. redis的数据保存到内存中,如果每个数据都永久保存,很快就OOM了。
  2. 如果有一个业务场景只需要某个数据在某段时间存在,例如短信验证码在120s内有效,用户登录的token在一天内有效,这种情况,如果把数据存到MySQL,我们需要再去写个方法判断过期,移除数据,这样非常麻烦,而且性能要差很多。这种情况下redis的过期时间就帮上大忙了。

1.2 如何判断数据过期

redis通过一个过期字典(可以看做hash表)保存数据过期的时间。过期字典的键指向redis中某个key,过期字典的值是保存了键所指向的redis的key的过期时间(毫秒精度)
其在C语言中的代码是这样的

typedef struct redisDb {
    ...
    dict *dict;     // 数据库键空间,保存着数据库中所有键值对
    dict *expires   // 过期字典,保存着键的过期时间
    ...
} redisDb;

1.3 过期数据如何删除?

当redis中的数据过期了,redis的过期策略是什么?

过期策略通常有以下三种:

  1. 定时过期
    每个设置过期时间的key都要创建一个定时器,过期时间一到,数据立即清除。
    这种策略对内存很友好,但是会占用大量CPU资源处理过期数据,影响响应时间和吞吐量。
    这种策略redis并没有采用。

  2. 惰性过期
    只有当用户访问数据时,才会判断这个数据是否已经过期,若过期则清除数据。
    这种策略可以节省CPU资源,但是对内存不友好,可能出现大量过期数据未被访问,占用大量内存的情况。

  3. 定期清除
    每隔一段时间,扫描过期字典中一定数量的数据,并清除其中过期的数据。
    该方案可以在内存和CPU中达到一种平衡。
    redis底层也会通过限制删除操作执行的时长和频率减少删除操作对CPU的影响。

redis采用惰性+定期清除的过期策略。

1.4 redis内存淘汰机制

上面采用的两种过期策略还是有问题的,因为还是可能出现漏掉移除大量过期数据的情况,容易导致OOM。
以及,我们如何保证redis中的数据都是热点数据?

这时候我们就要通过内存淘汰机制。
Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used)
    从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl
    从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random
    从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used)
    当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  5. allkeys-random
    从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:
    禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

4.0 版本后增加以下两种

  1. volatile-lfu(least frequently used)
    从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  2. allkeys-lfu(least frequently used)
    当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key# Redis 持久化机制

2. 数据一致性

新增、更改、删除数据库操作同步更新redis,可以用事务机制保证数据一致性。

对于MySQL和redis的同步更新,一般有如下四种方案:

  1. 先更新数据库,后更新缓存
  2. 先更新缓存,后更新数据库
  3. 先删除缓存,后更新数据库
  4. 先更新数据库,后删除缓存

一般来说,第一种和第二种方案,不会被使用。
第一种方案的问题:在并发更新数据库场景下,若更新数据库与更新缓存数据顺序不一致,会导致缓存中是脏数据。若最后一条数据库更新操作成功,缓存更新失败,也会导致缓存中是脏数据。
第二种方案的问题:如果缓存更新成功,而数据库更新失败,会造成数据库缓存数据不一致问题。

三、四两种方案都是不保留缓存的,只能等下次读取数据库操作再把数据更新到缓存上。

2.1 先删除缓存,后更新数据库

这种情况也可能导致脏数据的出现。
有两个请求:A(更新操作)、B(查询操作)

  1. A删除缓存,进行更新数据库操作。
  2. B查询缓存发现数据不存在,查询数据库拿到旧数据。
  3. B把旧数据更新到缓存。
  4. A把新数据写入数据库。

这造成了数据库缓存数据不一致的情况,我们通过以下两种方式解决:

2.1.1 延时双删

用伪代码表示

public void write(String key,Object data){
    redis.delKey(key);
    database.updateData(data);
    Thread.sleep(1000);
    redis.delKey(key);
}

也就是在数据库更新数据完成后,休眠一段时间再次删除缓存,这样可以避免在更新数据库期间有读操作把脏数据更新到缓存中。

2.1.2 更新与读取操作异步串行

一个数据变更的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新的时候,如果此时一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,排在刚才更新库的操作之后,然后同步等待缓存更新完成,再读库。如果这时有多个读该数据的请求,可以做去重处理。

2.2 先更新数据库,后删除缓存

这种情况下可能发生的问题是:数据库更新操作成功了,但是缓存删除操作失败了,导致缓存出现脏数据。
解决方案就是利用消息队列进行删除的补偿

  1. 请求A先对数据库进行更新操作。
  2. 在对缓存进行删除操作时发现删除失败。
  3. 此时将缓存的key作为消息体发送到消息队列。
  4. 系统接收到消息队列发送的消息,再次删除缓存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值