介绍一下Redis的缓存穿透击穿雪崩

介绍一下Redis的缓存穿透击穿雪崩

缓存穿透 🔍

定义 :请求的数据既不在 Redis 缓存,也不在数据库,导致每次请求都穿透缓存直接打到数据库,数据库压力骤增。

产生原因:

  • 恶意攻击:黑客构造大量不存在的 ID 请求(如 ID=-1、ID=999999)
  • 业务 bug:参数校验不严格,传入非法值
  • 数据未同步:数据库已删除但缓存未清理

核心解决方案:

  1. 布隆过滤器(首选):将所有存在的 key 预存入布隆过滤器,请求先过过滤器,不存在直接拦截
  2. 空值缓存:对数据库查询为空的 key,缓存一个空值并设置短过期时间(如 5 分钟)
  3. 接口层校验:对入参做合法性校验(如 ID>0、格式校验)

缓存击穿 💥

定义:单个热点 key 突然过期,瞬间有大量并发请求打到数据库,数据库瞬间被打垮。

产生原因:

  • 秒杀商品、热门文章、爆款视频等超高访问量的 key
  • 热点 key 设置了过期时间,到期时恰好有大量请求

核心解决方案:

  1. 互斥锁(保证一致性):使用 Redis 的 SETNX 加锁,只有一个请求去查数据库并更新缓存,其他请求等待重试
  2. 逻辑过期(高性能):热点 key 不设置物理过期时间,在 value 中存过期时间,后台异步线程更新缓存
  3. 热点 key 永不过期:对于极端热点 key,直接不设置过期时间,后台定时更新

缓存雪崩 🌋

定义:大量 key 同时过期 ,或者Redis 集群整体宕机,导致所有请求都打到数据库,数据库雪崩式崩溃。

产生原因:

  • 批量导入数据时,给所有 key 设置了相同的过期时间
  • Redis 主从切换、集群故障导致服务不可用
  • 缓存服务器资源耗尽宕机

核心解决方案:

  1. 过期时间加随机值:给每个 key 的过期时间加上一个随机偏移量(如 1-5 分钟),避免同时过期
  2. Redis 高可用集群:搭建主从 + 哨兵、Redis Cluster 集群,保证服务可用性
  3. 服务熔断与降级:使用 Hystrix/Sentinel 做熔断,数据库压力过大时暂时关闭非核心接口
  4. 多级缓存:本地缓存(Caffeine)+ Redis 缓存 + 数据库,层层拦截请求

三大问题核心对比表 📊

问题类型 核心特征 典型触发场景 核心解决方案
缓存穿透 不存在的数据 恶意攻击、参数非法 布隆过滤器、空值缓存
缓存击穿 单个热点 key过期 秒杀、爆款商品、热搜 互斥锁、逻辑过期
缓存雪崩 大量 key同时过期 / Redis 宕机 批量导入数据、集群故障 随机过期时间、高可用集群、熔断降级

缓存读写流程与异常路径图 🗺️

面试加分项 ✨

  1. 实际项目中我遇到过缓存雪崩问题:当时做 618 活动页时,批量导入了 12 万条商品数据,都设置了 1 小时过期时间,导致整点时数据库 CPU 直接打满到 100%。后来给每个 key 的过期时间加上了 0-300 秒的随机偏移量,问题彻底解决。
  2. 对于缓存击穿 ,我更推荐使用逻辑过期方案,因为互斥锁在高并发下会有大量线程阻塞,而逻辑过期只需要一个后台线程更新,性能更好,适合秒杀这种对响应时间要求极高的场景。
  3. 布隆过滤器存在误判率(约 0.1%-1%),所以不能完全替代空值缓存,两者结合使用效果最佳。

核心代码实现(技术亮点版)💻

1 缓存穿透:布隆过滤器(Guava 实现)

java 复制代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Component
public class ProductBloomFilter {
    // 预期插入数据量100万,误判率0.01%
    private static final int EXPECTED_INSERTIONS = 1000000;
    private static final double FPP = 0.0001;
    
    private BloomFilter<String> bloomFilter;
    
    @PostConstruct
    public void initBloomFilter() {
        // 初始化布隆过滤器
        bloomFilter = BloomFilter.create(
            Funnels.stringFunnel(StandardCharsets.UTF_8),
            EXPECTED_INSERTIONS,
            FPP
        );
        
        // 项目启动时预加载所有存在的商品ID到布隆过滤器
        List<String> allProductIds = productMapper.selectAllProductIds();
        for (String productId : allProductIds) {
            bloomFilter.put(productId);
        }
    }
    
    // 判断商品ID是否可能存在
    public boolean mightContain(String productId) {
        return bloomFilter.mightContain(productId);
    }
}

技术亮点

  • 精确控制误判率和内存占用(100 万数据 + 0.01% 误判率仅需约 1.7MB 内存)
  • 项目启动时预加载,避免运行时动态构建的性能损耗

2 缓存击穿:互斥锁方案(Redis SETNX)

java 复制代码
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class ProductService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProductMapper productMapper;
    
    private static final String CACHE_KEY_PREFIX = "product:";
    private static final String LOCK_KEY_PREFIX = "lock:product:";
    private static final int LOCK_EXPIRE_TIME = 10; // 锁过期时间10秒
    private static final int CACHE_EXPIRE_TIME = 30; // 缓存过期时间30分钟
    
    public Product getProductById(String productId) {
        String cacheKey = CACHE_KEY_PREFIX + productId;
        
        // 1. 先查缓存
        String productJson = redisTemplate.opsForValue().get(cacheKey);
        if (productJson != null) {
            return JSON.parseObject(productJson, Product.class);
        }
        
        // 2. 缓存未命中,加互斥锁
        String lockKey = LOCK_KEY_PREFIX + productId;
        boolean lockAcquired = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
        
        if (lockAcquired) {
            try {
                // 3. 拿到锁后,再次查询缓存(双重检查)
                productJson = redisTemplate.opsForValue().get(cacheKey);
                if (productJson != null) {
                    return JSON.parseObject(productJson, Product.class);
                }
                
                // 4. 查询数据库
                Product product = productMapper.selectById(productId);
                if (product == null) {
                    // 空值缓存,防止缓存穿透
                    redisTemplate.opsForValue()
                        .set(cacheKey, "", 5, TimeUnit.MINUTES);
                    return null;
                }
                
                // 5. 更新缓存
                redisTemplate.opsForValue()
                    .set(cacheKey, JSON.toJSONString(product), 
                         CACHE_EXPIRE_TIME, TimeUnit.MINUTES);
                
                return product;
            } finally {
                // 6. 释放锁(必须在finally中执行)
                redisTemplate.delete(lockKey);
            }
        } else {
            // 7. 没拿到锁,等待50ms后重试
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return getProductById(productId); // 递归重试
        }
    }
}

技术亮点

  • 双重检查锁机制,避免重复查询数据库
  • 锁设置过期时间,防止死锁
  • finally 块保证锁一定会释放
  • 空值缓存与互斥锁结合,同时解决穿透和击穿问题

3 缓存击穿:逻辑过期方案(高性能版)

java 复制代码
import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Service
public class HotProductService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProductMapper productMapper;
    
    private static final String CACHE_KEY_PREFIX = "hot:product:";
    private static final int LOGIC_EXPIRE_TIME = 30 * 60; // 逻辑过期时间30分钟
    
    // 线程池,用于异步更新缓存
    private static final ExecutorService CACHE_REFRESH_EXECUTOR = 
        Executors.newFixedThreadPool(10);
    
    // 缓存数据对象,包含逻辑过期时间
    private static class CacheData<T> {
        private T data;
        private Long expireTime; // 逻辑过期时间戳
        
        public CacheData(T data, Long expireTime) {
            this.data = data;
            this.expireTime = expireTime;
        }
        
        // 判断是否过期
        public boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }
    
    public Product getHotProductById(String productId) {
        String cacheKey = CACHE_KEY_PREFIX + productId;
        
        // 1. 查询缓存
        String cacheJson = redisTemplate.opsForValue().get(cacheKey);
        if (cacheJson == null) {
            return null; // 热点key提前预热,理论上不会走到这里
        }
        
        CacheData<Product> cacheData = JSON.parseObject(cacheJson, 
            new TypeReference<CacheData<Product>>() {});
        Product product = cacheData.getData();
        
        // 2. 判断是否逻辑过期
        if (!cacheData.isExpired()) {
            return product; // 未过期,直接返回
        }
        
        // 3. 已过期,异步更新缓存
        String lockKey = "lock:hot:product:" + productId;
        boolean lockAcquired = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        
        if (lockAcquired) {
            // 提交异步任务更新缓存
            CACHE_REFRESH_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    Product newProduct = productMapper.selectById(productId);
                    // 更新缓存,设置新的逻辑过期时间
                    CacheData<Product> newCacheData = new CacheData<>(
                        newProduct,
                        System.currentTimeMillis() + LOGIC_EXPIRE_TIME * 1000L
                    );
                    redisTemplate.opsForValue().set(cacheKey, 
                        JSON.toJSONString(newCacheData));
                } finally {
                    redisTemplate.delete(lockKey);
                }
            });
        }
        
        // 4. 无论是否拿到锁,都返回旧数据
        return product;
    }
}

技术亮点

  • 无阻塞:所有请求都能立即返回数据,不会有线程等待
  • 异步更新:使用独立线程池处理缓存更新,不影响主线程
  • 热点 key 提前预热:项目启动时将热点数据加载到缓存,避免冷启动问题

4 缓存雪崩:随机过期时间设置

java 复制代码
// 给每个key的过期时间加上0-300秒的随机偏移量
int baseExpireTime = 30 * 60; // 基础过期时间30分钟
int randomOffset = new Random().nextInt(300); // 随机偏移量0-5分钟
int finalExpireTime = baseExpireTime + randomOffset;

redisTemplate.opsForValue().set(
    cacheKey,
    JSON.toJSONString(product),
    finalExpireTime,
    TimeUnit.SECONDS
);

技术难点与解决方案汇总 🎯

技术难点 问题本质 解决方案 最佳实践
布隆过滤器误判 哈希冲突导致不存在的 key 被误判为存在 1. 降低误判率(增加哈希函数数量、扩大位图大小)2. 结合空值缓存兜底 误判率设置为 0.01%-0.1%,定期重建布隆过滤器
互斥锁死锁 服务宕机导致锁未释放 1. 给锁设置过期时间2. 使用 Redis Lua 脚本实现原子性锁释放 锁过期时间设置为数据库查询时间的 2-3 倍
互斥锁惊群效应 大量线程同时等待锁释放,瞬间并发请求数据库 1. 增加重试间隔时间2. 使用分段锁3. 优先使用逻辑过期方案 重试间隔设置为 50-100ms,避免频繁重试
逻辑过期缓存不一致 异步更新期间,用户可能读到旧数据 1. 缩短逻辑过期时间2. 关键业务使用互斥锁保证强一致性 非核心业务(如商品详情、文章)用逻辑过期,核心业务(如订单)用互斥锁
Redis 集群宕机雪崩 缓存层完全失效,所有请求打到数据库 1. 搭建 Redis Cluster 集群 + 哨兵2. 本地缓存兜底3. 服务熔断降级 本地缓存使用 Caffeine,设置最大容量和过期时间
批量导入数据雪崩 大量 key 同时过期 1. 过期时间加随机偏移量2. 分批次导入数据3. 低峰期导入数据 随机偏移量设置为基础过期时间的 10%-20%
缓存预热冷启动 系统刚启动时缓存为空,大量请求打到数据库 1. 项目启动时预加载热点数据2. 流量逐步切入3. 使用降级机制 提前统计热点数据,启动时异步加载到缓存

真实面试模拟

真实面试模拟

面试官 👨‍💻:

"同学,前面基础知识聊得不错。现在来个场景设计题:介绍一下 Redis 的缓存穿透、击穿、雪崩,最好能说清楚它们的区别和解决方案。你可以开始了。"

候选人 🧑‍💻:

"好的面试官。这三个问题本质上都是缓存没挡住流量,导致大量请求砸到数据库,但触发原因不一样,解决方案侧重点也不同。

我先快速区分一下:

  • 🔍 穿透 :查的是数据库里根本没有的数据,缓存自然也没法存,请求直接'穿透'到底。
  • 💥 击穿 :某个热点 key 刚好过期了,那一瞬间大量请求同时去查库重建缓存,把数据库'击穿'。
  • ❄️ 雪崩大量 key 在同一时间段内过期,或者 Redis 直接挂了,请求像雪崩一样全压到数据库上。

我可以结合画图,逐个说一下原理和落地方法吗?"

面试官 👨‍💻:

"可以,你边画边讲。"

候选人 🧑‍💻:

"好的,先说 🔍 缓存穿透

比如有人恶意用 id = -1 不断请求,这个数据库里根本没有,缓存里也不会有,每次请求都打到库上。

解决思路是两个:

  1. 布隆过滤器:把所有合法 id 提前存进去。请求来的时候先过一遍过滤器,不存在的直接挡掉,相当于门禁 🚪。
  2. 缓存空对象:就算数据库返回 null,也往 Redis 里存一个短期的'空标记',比如 "",过期时间 30 秒。下次同样请求就能在缓存层拦住了。

生产上,我一般布隆过滤器前置拦截 + 空值兜底,双保险。"

面试官 👨‍💻:

"嗯,逻辑清晰。那击穿呢?"

候选人 🧑‍💻:

"再说 💥 缓存击穿

最典型的是秒杀商品,key 为 hot_item:123。一旦这个热点 key 失效,刚好又赶上高并发,瞬间所有请求都奔向数据库去抢着重建缓存。

应对方法就一个核心:避免并发重建。

  1. 互斥锁(Mutex) :用 Redis 的 SETNX 抢一个轻量级锁,只允许一个线程去查库,其他就 sleep 几十毫秒再重试拿缓存。
  2. 逻辑过期:热点 key 不设 TTL,在 value 里存逻辑过期时间。发现数据逻辑过期后,先返回旧值,后台异步开线程去更新。这是高性能场景的王道方案 🤖。

伪代码大概这样:

java 复制代码
public String getHotData(String key) {
    String value = redis.get(key);
    if (value == null) {
        if (redis.setnx(lockKey, "1", 10, TimeUnit.SECONDS)) {
            try {
                value = db.query();
                redis.set(key, value, 3600);
            } finally {
                redis.del(lockKey);
            }
        } else {
            Thread.sleep(50);
            return getHotData(key);
        }
    }
    return value;
}

面试官 👨‍💻:

"那如果用逻辑过期,value 里怎么存?"

候选人 🧑‍💻:

"我会包装一层对象,比如 HotDataWrapper,里面两个字段:dataexpireAt(逻辑过期时间戳)。取出来先判断 expireAt < now,如果过期了,先用旧数据返回,同时开一个线程去抢锁重建。"

面试官 👨‍💻:

"可以,思路到位。最后说说雪崩吧。"

候选人 🧑‍💻:

"最后是 ❄️ 缓存雪崩,这个破坏力最大。

一般是两种场景叠加:大量 key 在同一时间过期,或者 Redis 突然挂掉。

解决是多管齐下:

  1. 过期时间加随机盐:在基础 TTL 上随机加个 1~5 分钟,让 key 分散失效。这是最简单也最有效的 😊。
  2. 高可用架构:Redis 主从 + 哨兵/集群,保证缓存层别单点挂。
  3. 熔断降级:用 Sentinel 或 Hystrix,当数据库负载过高,就直接返回降级内容(本地缓存、默认值、'系统繁忙'),并配合限流。
  4. 多级缓存:本地 Caffeine + Redis 二级缓存,Redis 挂了还有本地兜底。"

面试官 👨‍💻:

"总结对比一下?"

候选人 🧑‍💻:

"好的,我用一个表格串一下:

类型 诱因 核心思路 一句话
🔍 穿透 查不存在的数据 布隆过滤器 / 空对象 防不存在
💥 击穿 热点 key 过期 互斥锁 / 逻辑过期 防抢重建
❄️ 雪崩 大量 key 同时过期或宕机 过期随机化 / 高可用 / 降级 防同时挂

面试官 👨‍💻:

"不错,整体框架很清晰,方案能落地。我追问一个点:你用互斥锁的时候,如果业务是集群部署,SETNX 锁要是业务执行超时或者节点宕机了,会不会死锁?怎么解决?"

候选人 🧑‍💻:

"确实有死锁风险。我会给锁加一个过期时间 ,并且用 Redisson 的看门狗机制自动续期------只要线程还活着,锁就不会超时释放;如果节点真挂了,锁也会在到期后自动放开。不会出现死锁解不掉的情况。"

面试官 👨‍💻:

"嗯,方案都说得挺清楚。那你能不能贴几段核心代码 ,展示下技术亮点?然后把这一整套场景中的技术难点和解决方案整理一下?"

候选人 🧑‍💻:

"没问题,面试官。我按场景来贴代码,顺便把难点和亮点都说明白。😄"

🔍 缓存穿透 --- 核心代码

技术亮点:布隆过滤器 + 空值缓存双保险,兼顾性能和防穿透。

java 复制代码
// 1. 布隆过滤器初始化(基于Redisson,启动时执行)
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("userIdFilter");
// 预计插入100万,误判率0.01%
bloomFilter.tryInit(1_000_000L, 0.0001);

// 全量ID预热,可从DB分批加载
List<Long> allUserIds = userMapper.getAllIds();
allUserIds.forEach(id -> bloomFilter.add("user:" + id));

// 2. 查询逻辑
public User getUserById(Long id) {
    String key = "user:" + id;
    
    // 布隆过滤器拦截
    if (!bloomFilter.contains(key)) {
        return null; // 直接返回,不查库
    }
    
    // 查缓存
    String cacheValue = redisTemplate.opsForValue().get(key);
    if (cacheValue != null) {
        // 判断是否为空对象占位符
        if ("NULL_PLACEHOLDER".equals(cacheValue)) {
            return null;
        }
        return JSON.parseObject(cacheValue, User.class);
    }
    
    // 查数据库
    User user = userMapper.selectById(id);
    if (user == null) {
        // 缓存空对象,过期时间30秒,防止占用空间
        redisTemplate.opsForValue().set(key, "NULL_PLACEHOLDER", 30, TimeUnit.SECONDS);
    } else {
        redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 1, TimeUnit.HOURS);
    }
    return user;
}

💥 缓存击穿 --- 核心代码

技术亮点 :使用 Redisson 分布式锁 + 看门狗自动续期,避免死锁,保障高并发。

java 复制代码
public Product getHotProduct(Long id) {
    String key = "hot_product:" + id;
    String cacheData = redisTemplate.opsForValue().get(key);
    if (cacheData != null) {
        return JSON.parseObject(cacheData, Product.class);
    }
    
    // 分布式锁key
    String lockKey = "lock:product:" + id;
    RLock lock = redissonClient.getLock(lockKey);
    try {
        // 尝试加锁,最多等待10秒,锁超时时间(看门狗会续期,这里设为30秒兜底)
        if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
            // 双重检查
            cacheData = redisTemplate.opsForValue().get(key);
            if (cacheData != null) {
                return JSON.parseObject(cacheData, Product.class);
            }
            // 查询数据库
            Product product = productMapper.selectById(id);
            if (product != null) {
                // 设置缓存,加随机过期时间(300~420秒),防止雪崩
                redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 
                        300 + ThreadLocalRandom.current().nextInt(120), TimeUnit.SECONDS);
            } else {
                // 空值缓存,防止穿透
                redisTemplate.opsForValue().set(key, "NULL_PLACEHOLDER", 30, TimeUnit.SECONDS);
            }
            return product;
        } else {
            // 获取锁失败,休眠后重试
            Thread.sleep(50);
            return getHotProduct(id); // 递归重试
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return null;
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

❄️ 缓存雪崩 --- 核心代码

技术亮点:过期时间加随机盐 + 多级缓存兜底(Caffeine)。

java 复制代码
// 本地缓存配置(Caffeine)
Cache<Long, Object> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(60, TimeUnit.SECONDS)
        .build();

public Object getDataWithMultiLevel(Long id) {
    String key = "data:" + id;
    
    // 1. 一级:本地缓存
    Object localVal = localCache.getIfPresent(id);
    if (localVal != null) {
        return localVal;
    }
    
    // 2. 二级:Redis
    String cacheVal = redisTemplate.opsForValue().get(key);
    if (cacheVal != null) {
        localCache.put(id, JSON.parse(cacheVal)); // 回填本地
        return cacheVal;
    }
    
    // 3. 查库并回填(带锁防止击穿)
    Object dbVal = queryFromDbWithLock(id);
    if (dbVal != null) {
        // 设置Redis过期时间,基础1小时 + 随机0~600秒
        long expireSec = 3600 + ThreadLocalRandom.current().nextInt(600);
        redisTemplate.opsForValue().set(key, JSON.toJSONString(dbVal), expireSec, TimeUnit.SECONDS);
        localCache.put(id, dbVal);
    }
    return dbVal;
}

🧩 此场景技术难点 & 解决方案

场景 技术难点 解决方案 经验要点 💡
🔍 穿透 1. 布隆过滤器无法删除 ,数据新增/删除后需重建。2. 误判率会放过少量无效请求。3. 空值缓存占用内存,可能被恶意构造不同key。 1. 使用支持扩容的布隆过滤器(RedisBloom插件)或定时重建。2. 误判率设定0.01%以下,并靠空值缓存兜底。3. 空值缓存设置极短TTL,并对请求限流。 实际误判率要压测,不能光看理论值。
💥 击穿 1. 锁的粒度与性能平衡 :热点越多,锁竞争越严重。2. 逻辑过期 存在最终一致性问题,异步更新可能返回旧数据。3. 分布式锁死锁风险 1. 单机ConcurrentHashMap加轻量锁,集群用Redisson细粒度锁。2. 逻辑过期时,保证旧值仍可用,提前预加载或后台刷新。3. 使用Redisson看门狗自动续期 + finally解锁。 逻辑过期适合读多写少且能容忍短暂不一致的业务。
❄️ 雪崩 1. 随机过期算法 仍可能出现局部热点集中失效。2. 多级缓存同步问题,本地缓存与Redis数据可能不一致。3. 熔断降级后,服务恢复时的"脉冲式"压力。 1. 不仅随机,还要结合业务预热,提前加载高频key。2. 本地缓存短TTL,消息队列通知更新。3. 半开状态逐步放量,采用令牌桶或预热算法。 生产必做多级缓存,核心接口要压测极限QPS并设定好阈值。 👍

面试官 👨‍💻:

"很好,代码细节到位,难点拆解得也清晰。你这种把代码和方案能对应讲出来的习惯,是面试加分项。这场缓存三兄弟的考察,给你 Strong Hire ✅。"

相关推荐
Rain的Java大神实战圈1 天前
对千万级单表进行分页查询
场景设计题
Rain的Java大神实战圈2 天前
MySQL建表时需要注意什么
场景设计题
Rain的Java大神实战圈5 天前
千万级大表如何新增字段
场景设计题
Rain的Java大神实战圈7 天前
日增量500万的表结构如何设计
场景设计题
Rain的Java大神实战圈9 天前
数据库主从有延迟怎么处理
架构设计·场景设计题
Rain的Java大神实战圈13 天前
分库分表会带来哪些问题如何解决
架构设计·场景设计题
Rain的Java大神实战圈14 天前
介绍一下分库分表
架构设计·场景设计题
Rain的Java大神实战圈15 天前
线上慢SQL的排查思路
经验分享·架构设计·场景设计题
Rain的Java大神实战圈16 天前
MySQL单表有10亿数据如何做迁移
架构设计·面试经验·场景设计题