Redis常见问题

50 阅读4分钟

1、如何保证数据库和缓存的一致性

更新数据库 -》 删除缓存 + 延迟双删 + 设置过期时间

读逻辑: 读到缓存则返回,未读到则读库并写缓存

写逻辑: 先更新数据库,再删除缓存

可能存在的问题:

当主库更新完成后,删除缓存。其他线程发起读操作,数据库主从同步未完成,导致缓存被更新成旧值。

可以采用延迟双删的策略,在一段时间后再发起一次删除缓存的操作,这是数据库的主从同步大概率已经完成。给缓存加上TTL过期时间。保证最终一致性。

2、布隆过滤器

image.png

原理是将一个数据,分别使用多个hash函数进行运算,得到多个结果,将每次运算结果的位置标记为1。

如果数据进行多次hash运算后,发现有个位置为0,那这个数据肯定不存在。

反之,如果一个数据使用多个hash函数进行运算后,发现所有运算结果所在的位置都为1,也不能说明元素一定存在,因为存在hash冲突,可能位数组里存在着与当前数据有hash冲突的元素。

3、redis实现延时队列

假设我们现在要处理订单超时关单业务,设置订单在创建后 30分钟 未支付则自动取消。

1. 存入延迟任务(例如:下午 2:00)

// 1. 计算这条任务的【执行时间戳】(不是延迟时长)
long delayTime = 30 * 60 * 1000; // 30分钟的毫秒数
long executeTime = System.currentTimeMillis() + delayTime; // 假设当前时间戳是 1650000000000,那么 executeTime = 1650001800000

// 2. 将执行时间戳作为 SCORE,任务详情作为 MEMBER 存入 Sorted Set
//    键名: order_delay_queue
//    SCORE: 1650001800000 (代表这个时间点该执行了)
//    MEMBER: "order_id:10086" (我们把订单ID作为value,也可以存JSON字符串)
zadd order_delay_queue 1650001800000 "order_id:10086"

现在,Sorted Set 里有一条数据:

Member (任务内容)Score (执行时间戳)
order_id:100861650001800000

2. 轮询检查任务(例如:下午 2:15)

我们的轮询线程开始工作,每秒执行一次以下逻辑:

// 1. 获取【当前时间戳】
long currentTime = System.currentTimeMillis(); // 假设现在是 1650000900000 (比执行时间早)

// 2. 核心查询:从Sorted Set中找出所有Score在 [0, currentTime] 区间内的任务
//    意思是:找出所有"执行时间戳"小于等于"当前时间"的任务
//    语法:ZRANGEBYSCORE key min max
Set<String> dueTasks = jedis.zrangeByScore("order_delay_queue", 0, currentTime);
  • 会发生什么?

    • currentTime (1650000900000) < executeTime (1650001800000)
    • 我们的命令是 ZRANGEBYSCORE order_delay_queue 0 1650000900000
    • Redis 会寻找所有 Score 小于等于 1650000900000 的成员。
    • 我们的任务 Score 是 1650001800000,比 1650000900000 大,所以不符合条件
    • 因此,dueTasks 这个集合是空的

轮询线程发现没有到期任务,休眠1秒后继续下一轮检查。

3. 时间到达,触发任务(下午 2:30:00)

时间流逝,现在到了执行时间 1650001800000

轮询线程再次工作:

long currentTime = System.currentTimeMillis(); // 现在 currentTime = 1650001800000
Set<String> dueTasks = jedis.zrangeByScore("order_delay_queue", 0, currentTime); 
// 命令变为:ZRANGEBYSCORE order_delay_queue 0 1650001800000
  • 会发生什么?

    • Redis 寻找所有 Score 小于等于 1650001800000 的成员。
    • 我们的任务 Score 等于 1650001800000符合条件
    • dueTasks 集合里现在包含了字符串 "order_id:10086"

4. 处理并移除任务

拿到到期任务后,我们必须先移除再处理,防止多个消费者重复处理。

// 1. 遍历所有到期的任务
for (String task : dueTasks) {
    // 2. 【关键】先从集合中原子性地移除这个任务,移除成功的那个线程才获得处理权
    Long removedCount = jedis.zrem("order_delay_queue", task);
    
    // 3. 如果 removedCount > 0,说明本线程移除成功,获得处理权
    if (removedCount > 0) {
        // 4. 开始处理业务(例如:查询数据库,检查订单状态,如果未支付则取消)
        handleExpiredOrder(task); // 处理订单ID为10086的订单
    }
    // 如果 removedCount == 0,说明该任务已被其他线程或进程移除,则忽略。
}