Redis主从读数据不一致与hmget()获取字段为null的问题解析

一、Redis主从读数据不一致

大家在使用redis的时候,经常会用expire来设置key的过期时间,以为某个key到期就会马上清除。但在设置为主写随机读时,发现存在key未失效的情况,下面具体分析:


原因一过期策略的问题

3.2之后的版本已不存在以下问题


Redis key的三种过期策略


惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key,很明显,这是被动的!


定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 redis 会定期主动淘汰一批已过期的key。(在第二节中会具体说明)


主动删除:当前已用内存超过maxMemory限定时,触发主动清理策略。主动设置的前提是设置了maxMemory的值


Redis删除过期Key的源码


<pre name="code" class="java">int expireIfNeeded(redisDb *db, robj *key) {  
    time_t when = getExpire(db,key);  
  
    if (when < 0) return 0; /* No expire for this key */  
  
    /* Don't expire anything while loading. It will be done later. */  
    if (server.loading) return 0;  
  
    /* If we are running in the context of a slave, return ASAP: 
     * the slave key expiration is controlled by the master that will 
     * send us synthesized DEL operations for expired keys. 
     * 
     * Still we try to return the right information to the caller,  
     * that is, 0 if we think the key should be still valid, 1 if 
     * we think the key is expired at this time. */  
    if (server.masterhost != NULL) {  
        return time(NULL) > when;  
    }  
  
    /* Return when this key has not expired */  
    if (time(NULL) <= when) return 0;  
  
    /* Delete the key */  
    server.stat_expiredkeys++;  
    propagateExpire(db,key);  
    return dbDelete(db,key);  
}  


 
通过以上源码发现,4行:没有设置超时时间,则不删;7行:在"loading"时不删;16行:非主库不删;21行未到期不删。25行同步从库和文件。
所以说,在从库执行主动删除操作,或者通过惰性删除的方式触发删除key的操作,最终都不会执行成功。原因就在上面的第16行代码。

原因二快照模式自身的特性

大家都知道,redis支持两 种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是 Append-only file(缩写 aof)的方式。 Snapshotting(快照)的持久化方式:
这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。可以通过配置设置自动做快照持久 化的方式。我们可以配置 redis 在 n 秒内如果超过 m 个 key 被修改就自动做快照,下面是默认的快照保存配置。


save 900 1 #900 秒内如果超过 1 个 key 被修改,则发起快照保存

save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存

Save 60 10000 #60 秒内容如超过 10000个 key 被修改,则发起快照保存

下面介绍详细的快照保存过程

1.redis 调用 fork,现在有了子进程和父进程。

2. 父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时 os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数 据是 fork 时刻整个数据库的一个快照。

3.当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

由于快照模式是定时进行,可能会导致数据在上次持久化完成到本次持久化开始之前,redis服务器宕机,丢失内存中相关数据,这也是主从读取内容不一致的原因之一


二、 Redis 获取字段为null的问题解析

    不知道大家是否碰到过一个问题,使用redis的hmget()获取列表形式的数据时,有时偶然会发生获取的列表中,所有字段都是null的情况。本人项目中碰到过,下面解释下部门大牛的调查结果:当存储的数据时存在有效期时,且数据在过期的时间临界点,此时,过期数据会被清理,关键是清理过程,是先删除List里每个元素的数据,再删除整个List的,这就导致小概率事件的发生了,hmget时,结果是List<String>中里面的元素都为null,也许这也是很多项目都使用hgetAll()的原因了。

    所以我们可以在hmget增加一个校验处理:

                // 使用hexists的地方必须对查询结果进行校验
                if (CollectionUtils.isEmpty(result)|| isAllItemNull(result)){
                    return new ArrayList<String>();
                }
    这可以算是hmget()的一个坑了,但同样, hgetAll()也存在一个坑的,只是一般不会发生,
    详细参见:http://huoding.com/2013/01/21/214
    


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值