MST

星途 面试题库

面试题:高并发场景下Redis缓存MySQL数据更新频率调整面临哪些挑战及应对策略?

假设处于高并发业务场景,Redis缓存MySQL数据,更新频率调整会遇到诸如缓存击穿、缓存雪崩等问题,请详细分析这些挑战,并说明对应的调整策略。
16.3万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试

缓存击穿

  1. 问题分析
    • 在高并发场景下,当一个设置了过期时间的key在过期的瞬间,有大量的请求同时访问该key对应的业务逻辑,这些请求发现缓存中没有数据,就会同时去访问数据库,可能导致数据库瞬间承受巨大压力,甚至崩溃。例如电商平台的某一热门商品缓存过期时,大量用户同时访问该商品详情页,可能出现缓存击穿情况。
  2. 调整策略
    • 互斥锁
      • 当缓存失效时,先获取一把互斥锁(如使用Redis的SETNX命令)。只有获取到锁的请求才能去查询数据库并更新缓存,其他请求等待。获取锁的请求完成后释放锁,其他等待的请求再次尝试获取锁并检查缓存,此时缓存已更新,直接从缓存获取数据。
      • 示例代码(以Python为例,使用redis - py库):
import redis

r = redis.Redis(host='localhost', port = 6379, db = 0)

def get_data(key):
    data = r.get(key)
    if data is None:
        lock_key = 'lock:' + key
        lock_acquired = r.setnx(lock_key, 'locked')
        if lock_acquired:
            try:
                # 从数据库查询数据
                db_data = get_from_db(key)
                r.set(key, db_data)
                return db_data
            finally:
                r.delete(lock_key)
        else:
            # 等待一段时间后重试
            import time
            time.sleep(0.1)
            return get_data(key)
    else:
        return data.decode('utf - 8')


def get_from_db(key):
    # 模拟从数据库获取数据
    return 'data from db for'+ key
  • 永不过期
    • 对于一些重要且不经常变化的数据,可以设置为永不过期。但需要额外的机制去更新数据,比如在数据发生变化时主动去更新缓存,同时可以设置一个逻辑过期时间,在业务代码中判断数据是否逻辑过期,如果过期则异步更新缓存。
    • 示例代码(以Java为例,使用Jedis库):
import redis.clients.jedis.Jedis;

public class RedisUtils {
    private static final Jedis jedis = new Jedis("localhost", 6379);

    public static String getData(String key) {
        String data = jedis.get(key);
        if (data == null) {
            // 这里逻辑过期时间通过额外字段记录
            String expireTimeStr = jedis.get(key + ":expire");
            if (expireTimeStr!= null && Long.parseLong(expireTimeStr) < System.currentTimeMillis()) {
                // 异步更新缓存
                new Thread(() -> {
                    String newData = getFromDb(key);
                    jedis.set(key, newData);
                    jedis.set(key + ":expire", String.valueOf(System.currentTimeMillis() + 3600 * 1000));// 假设1小时逻辑过期
                }).start();
            }
        }
        return data;
    }

    private static String getFromDb(String key) {
        // 模拟从数据库获取数据
        return "data from db for " + key;
    }
}

缓存雪崩

  1. 问题分析
    • 缓存雪崩是指在某一时间段内,大量的缓存key同时过期,导致大量请求直接访问数据库,使数据库压力骤增,甚至可能导致数据库服务崩溃。例如电商大促活动结束后,大量商品的缓存同时过期,大量用户访问商品相关接口,就可能引发缓存雪崩。
  2. 调整策略
    • 设置随机过期时间
      • 避免所有缓存设置相同的过期时间,而是在一个合理的时间范围内设置随机的过期时间。比如原本商品缓存设置过期时间为1小时,可以设置为50 - 70分钟之间的随机值。这样可以分散缓存过期的时间点,降低大量缓存同时过期的风险。
      • 示例代码(以Node.js为例,使用ioredis库):
const Redis = require('ioredis');
const redis = new Redis();

async function setDataWithRandomExpiry(key, value) {
    const minExpiry = 50 * 60; // 50分钟
    const maxExpiry = 70 * 60; // 70分钟
    const randomExpiry = Math.floor(Math.random() * (maxExpiry - minExpiry + 1)) + minExpiry;
    await redis.setex(key, randomExpiry, value);
}
  • 二级缓存
    • 可以设置两级缓存,一级缓存失效时,先从二级缓存获取数据。二级缓存可以设置较长的过期时间或者不设置过期时间。当一级缓存失效后,通过异步任务去更新一级缓存,而用户先从二级缓存获取数据,避免直接访问数据库。
    • 示例代码(以Go语言为例,使用go - redis库):
package main

import (
    "fmt"
    "github.com/go - redis/redis/v8"
    "time"
)

var rdb1 *redis.Client
var rdb2 *redis.Client

func init() {
    rdb1 = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })
    rdb2 = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       1,
    })
}

func getData(key string) (string, error) {
    data, err := rdb1.Get(rdb1.Context(), key).Result()
    if err == redis.Nil {
        data, err = rdb2.Get(rdb2.Context(), key).Result()
        if err == nil {
            // 异步更新一级缓存
            go func() {
                newData, err := getFromDb(key)
                if err == nil {
                    rdb1.Set(rdb1.Context(), key, newData, time.Hour)
                }
            }()
        }
    }
    return data, err
}

func getFromDb(key string) (string, error) {
    // 模拟从数据库获取数据
    return "data from db for " + key, nil
}
  • 缓存预热
    • 在系统上线前或者高并发场景来临前,提前将部分数据加载到缓存中,并设置合理的过期时间。例如电商大促前,提前将热门商品数据缓存起来,避免在活动开始时大量缓存同时过期。可以通过定时任务或者手动调用的方式进行缓存预热。
    • 示例代码(以Python为例,使用redis - py库):
import redis

r = redis.Redis(host='localhost', port = 6379, db = 0)

def warm_up_cache():
    keys = ['product:1', 'product:2', 'product:3']
    for key in keys:
        data = get_from_db(key)
        r.set(key, data)


def get_from_db(key):
    # 模拟从数据库获取数据
    return 'data from db for'+ key