redis常见问题集

本文探讨了Redis缓存一致性问题,包括双写模式下可能出现的并发问题和缓存数据的脏读。提出了解决方案,如设置数据过期时间、使用Canal订阅binlog以及利用Redis分布式锁。针对不同场景,如用户数据和基础数据,提供了不同的处理策略。同时,推荐使用Redisson作为加锁工具,并详细解释了其加锁和解锁的实现过程。
摘要由CSDN通过智能技术生成

redis缓存一致性

双写模式

双写模式

并发情况

并发问题

  1. 写缓存时间存在差异,导致缓存一段时间的脏数据,失效更新后最终一直

失效模式

失效模式

并发问题

并发问题

1写入db删除缓存 2写入db但未来得及删除缓存 3读取数据库读取的是1写入的数据,导致读取的并不是最新数据还是存在缓存不一致问题

解决方案

无论是双写还是失效模式,都一定程度上有缓存不一致问题,怎么办?

  1. 如果是用户维度数据(订单数据,用户数据),个人操作的数据并发几率小,不用考虑高并发问题,缓存数据设置过期时间,每个一段时间触发更新即可
  2. 如果是菜单,商品等介绍信息等基础数据,可以使用canal订阅binlog的方式(阿里优秀的解决方案)
  3. 缓存数据+过期时间能解决大部分业务对于缓存的要求
  4. 通过加锁保证并发读写,写写的时候阻塞顺序执行,读读无所谓,所以更适合读写锁,(业务不关心脏数据,允许临时的脏数据可以忽略)

总结

  1. 我们能放进缓存本就不应该是实时性的,一致要求高的,所以缓存数据的时候加上过期时间,保证最终一致性即可
  2. 不应该过度设计,增加系统复杂性
  3. 遇到实时性,一致性高的数据,查数据库,即使慢一点

redis分布式锁

redis 加锁
  • 加锁

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

SET lock_key random_value NX PX 5000
值得注意的是:
random_value 是客户端生成的唯一的字符串。
NX 代表只在键不存在时,才对键进行设置操作。
PX 5000 设置键的过期时间为5000毫秒。

  • 解锁

解锁的过程就是将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉。这时候random_value的作用就体现出来。

为了保证解锁操作的原子性,我们用LUA脚本完成这一操作。先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。

if redis.call('get',KEYS[1]) == ARGV[1] then 
   return redis.call('del',KEYS[1]) 
else
   return 0 
end

redisson 加锁

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类

  • 获取锁实例

RLock lock = client.getLock(“lock1”); 这句代码就是为了获取锁的实例,然后我们可以看到它返回的是一个RedissonLock对象。

  • 加锁

当我们调用lock方法,定位到lockInterruptibly。在这里,完成了加锁的逻辑。

public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
    
    //当前线程ID
    long threadId = Thread.currentThread().getId();
    //尝试获取锁
    Long ttl = tryAcquire(leaseTime, unit, threadId);
    // 如果ttl为空,则证明获取锁成功
    if (ttl == null) {
        return;
    }
    //如果获取锁失败,则订阅到对应这个锁的channel
    RFuture<RedissonLockEntry> future = subscribe(threadId);
    commandExecutor.syncSubscription(future);

    try {
        while (true) {
            //再次尝试获取锁
            ttl = tryAcquire(leaseTime, unit, threadId);
            //ttl为空,说明成功获取锁,返回
            if (ttl == null) {
                break;
            }
            //ttl大于0 则等待ttl时间后继续尝试获取
            if (ttl >= 0) {
                getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                getEntry(threadId).getLatch().acquire();
            }
        }
    } finally {
        //取消对channel的订阅
        unsubscribe(future, threadId);
    }
    //get(lockAsync(leaseTime, unit));
}
  • 流程如下

  • 获取锁

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {

    //如果带有过期时间,则按照普通方式获取锁
    if (leaseTime != -1) {
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    
    //先按照30秒的过期时间来执行获取锁的方法
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(
        commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
        TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        
    //如果还持有这个锁,则开启定时任务不断刷新该锁的过期时间
    ttlRemainingFuture.addListener(new FutureListener<Long>() {
        @Override
        public void operationComplete(Future<Long> future) throws Exception {
            if (!future.isSuccess()) {
                return;
            }

            Long ttlRemaining = future.getNow();
            // lock acquired
            if (ttlRemaining == null) {
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值