当前位置: 首页 > article >正文

Redis缓存和Mysql数据一致性问题

        在高并发环境下,保持 Redis 缓存和 MySQL 数据库的数据一致性是一个复杂但至关重要的任务。下面是对这一问题的详细讲解,并结合 PHP 代码示例来展示如何解决这些一致性问题。

问题背景

Redis 缓存和 MySQL 数据库的主要挑战在于:

  1. 缓存和数据库之间的延迟:在缓存更新与数据库更新之间,可能存在时间差,导致数据不一致。
  2. 高并发下的缓存击穿:缓存被并发请求同时击穿,导致大量请求直接访问数据库。
  3. 缓存雪崩:大量缓存同时过期,导致瞬间对数据库的压力激增。
  4. 缓存穿透:恶意请求绕过缓存直接访问数据库,造成数据库的压力。

解决方案

1. 缓存一致性策略
Cache Aside Pattern (旁路缓存模式) 是最常用的缓存一致性策略:
  • 读取:先从缓存读取数据,如果缓存中有数据,直接返回;如果缓存中没有数据,则从数据库中读取,并将数据写入缓存。
  • 写入:先更新数据库,然后删除缓存,使下一次读取时重新从数据库获取数据。

示例代码

function getFromCacheOrDb($key) {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

    // 从缓存中读取数据
    $cachedData = $redis->get($key);
    if ($cachedData !== false) {
        return $cachedData;
    }

    // 缓存未命中,从数据库中查询
    $data = getDataFromDb($key);
    if ($data) {
        $redis->setex($key, 3600, $data); // 缓存 1 小时
    }
    return $data;
}

function updateData($key, $newData) {
    $db = new PDO('mysql:host=localhost;dbname=test', 'root', '');

    // 更新数据库
    $stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
    $stmt->execute([':value' => $newData, ':key' => $key]);

    // 删除缓存
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->del($key);
}

优点

  • 简单易用,符合大多数场景。

缺点

  • 在高并发下,可能会有缓存击穿问题。
2. 延迟双删

延迟双删策略是针对 Cache Aside 模式的改进,主要用于防止缓存和数据库的更新顺序导致数据不一致问题:

  1. 更新数据库
  2. 删除缓存
  3. 等待一段时间(例如 500ms),然后再次删除缓存

示例代码

function updateDataWithDelayDelete($key, $newData) {
    $db = new PDO('mysql:host=localhost;dbname=test', 'root', '');

    // 更新数据库
    $stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
    $stmt->execute([':value' => $newData, ':key' => $key]);

    // 删除缓存
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->del($key);

    // 延迟再次删除缓存
    usleep(500 * 1000); // 延迟 500ms
    $redis->del($key);
}

优点

  • 有效减少了因为缓存删除延迟导致的数据不一致。

缺点

  • 延迟设置不当可能仍会导致短暂的不一致。
3. 分布式锁

分布式锁可以确保在高并发情况下,只有一个线程可以进行数据和缓存的更新操作。Redis 提供了实现分布式锁的能力。

示例代码

function updateDataWithLock($key, $newData) {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

    $lockKey = "lock:" . $key;
    if ($redis->set($lockKey, 1, ['nx', 'ex' => 5])) { // 获取锁,超时 5 秒
        try {
            // 更新数据库
            $db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
            $stmt = $db->prepare("UPDATE table_name SET value = :value WHERE key = :key");
            $stmt->execute([':value' => $newData, ':key' => $key]);

            // 删除缓存
            $redis->del($key);
        } finally {
            // 释放锁
            $redis->del($lockKey);
        }
    } else {
        // 获取不到锁,稍后重试
        return false;
    }

    return true;
}

优点

  • 确保在高并发情况下的数据一致性,避免并发更新问题。

缺点

  • 实现复杂,锁的超时和释放需要精确控制。
4. 异步更新

使用消息队列来处理数据更新,使得写操作与缓存更新异步进行。这种方式可以解耦数据写入和缓存更新的操作。

示例流程

  1. 写请求将更新操作发送到消息队列。
  2. 消费者从消息队列中取出消息,进行数据库和缓存的更新操作。

优点

  • 解耦了写操作和缓存更新,可以灵活处理失败重试。

缺点

  • 增加了系统的复杂性和延迟,适用于对一致性要求不那么严格的场景。

解决方案总结

在高并发场景下,确保 Redis 缓存和 MySQL 数据库的一致性需要结合多种策略:

  1. Cache Aside Pattern 适用于大多数场景,但需要结合缓存击穿的处理。
  2. 延迟双删 可以减少缓存和数据库的不一致问题,适用于较高要求的场景。
  3. 分布式锁 适用于需要严格一致性的场景,通过锁机制确保数据更新的一致性。
  4. 异步更新 解耦写操作与缓存更新,适用于业务复杂度较高的场景。

根据实际业务需求和并发压力选择合适的策略,并在生产环境中进行充分的测试和优化,以确保系统的稳定性和数据一致性。


http://www.kler.cn/a/304138.html

相关文章:

  • 论软件维护及其应用子问题
  • LabVIEW 实现 find_nearest_neighbors 功能(二维平面上的最近邻查找)
  • 【CICD】GitLab Runner 和执行器(Executor
  • springboot参数校验
  • LeetCode【0016】最接近的三数之和
  • Linux中线程的基本概念与线程控制
  • Mybatis接受查询结果的情况
  • 使用 @NotEmpty、@NotBlank、@NotNull 注解进行参数校验
  • 多线程爬虫接入代理IP:高效数据抓取的秘诀
  • 工具包(Commons-io)工具包(hutool)
  • flink中disableOperatorChaining() 的详解
  • R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习技术应用
  • 理解大型语言模型(LLM)中的隐藏层
  • 【Hot100】LeetCode—198. 打家劫舍
  • 解决缺少genconfig
  • Rust 变量基础知识
  • Linux:命令行参数
  • DX-5009N 10G交换机 SFP接口+猫棒 代替运营商光猫 【注册状态O5但是无法PPPoe拨号踩坑——交换机VLAN配置】
  • Leetcode面试经典150题-69.X的平方根
  • AI教你学Python 第4天:函数和模块
  • 【HTML】可展开的顶层菜单栏
  • 拳皇97确反笔记
  • Go语言现代web开发08 if和switch分支语句
  • Spring Boot Admin集成与自定义监控告警
  • 【C++ 高频面试题】指针和引用、关于内存泄漏和野指针问题
  • 云服务器中的MinIO 配置 HTTPS 过程(图文)