MST

星途 面试题库

面试题:缓存设计:简述数据库与缓存一致性问题的常见场景及简单解决方案

请阐述在后端开发中,数据库与缓存一致性可能出现问题的常见业务场景,并针对每个场景简单描述一种可行的解决方案。
47.1万 热度难度
后端开发缓存设计

知识考点

AI 面试

面试题答案

一键面试

常见业务场景及解决方案

  1. 写操作后读操作
    • 场景描述:在对数据库进行写操作(如插入、更新、删除)后,紧接着进行读操作。如果缓存未及时更新,读操作可能会从缓存中读取到旧数据,导致数据库与缓存数据不一致。
    • 解决方案:采用先更新数据库,再删除缓存的策略。这样在写操作后,下次读操作就会因为缓存中数据已被删除,从而从数据库读取最新数据并重新填充缓存。例如在Java中使用Spring Data JPA操作数据库和Redis作为缓存,代码如下:
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void updateUser(User user) {
        userRepository.save(user);
        stringRedisTemplate.delete("user:" + user.getId());
    }
}
  1. 高并发写操作
    • 场景描述:多个并发的写操作同时进行,不同线程执行更新数据库和更新/删除缓存的顺序可能不同,导致缓存和数据库最终状态不一致。比如线程A先更新数据库,线程B后更新数据库,但线程B先更新缓存,线程A后更新缓存,最终缓存数据可能是线程A的旧数据。
    • 解决方案:使用分布式锁。在进行写操作前获取分布式锁,保证同一时间只有一个线程能进行写操作及后续的缓存更新。以Redis实现分布式锁为例,使用Redisson框架:
@Service
public class ProductService {
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void updateProduct(Product product) {
        RLock lock = redissonClient.getLock("product:lock:" + product.getId());
        try {
            lock.lock();
            productRepository.save(product);
            stringRedisTemplate.delete("product:" + product.getId());
        } finally {
            lock.unlock();
        }
    }
}
  1. 缓存失效时大量读请求
    • 场景描述:当缓存过期失效时,大量读请求同时涌入,这些请求都会去查询数据库,然后再更新缓存。如果部分请求在更新缓存前,其他请求又来读取数据库,就可能导致缓存和数据库不一致,并且还可能造成数据库压力过大。
    • 解决方案:采用缓存预热和互斥锁机制。缓存预热即在系统启动时就将一些热点数据加载到缓存中。对于缓存失效时的读请求,使用互斥锁,只允许一个请求去查询数据库并更新缓存,其他请求等待。当第一个请求更新完缓存后,等待的请求直接从缓存读取数据。以下是Python中使用Flask框架和Redis实现的示例:
import redis
from flask import Flask
import threading

app = Flask(__name__)
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

def get_data_from_db(key):
    # 模拟从数据库获取数据
    return "data from db"

def set_data_to_cache(key, value):
    redis_client.setex(key, 3600, value)

def get_data(key):
    data = redis_client.get(key)
    if data is None:
        lock_key = "lock:" + key
        with redis_client.lock(lock_key, blocking_timeout=10):
            data = redis_client.get(key)
            if data is None:
                data = get_data_from_db(key)
                set_data_to_cache(key, data)
    return data