面试题答案
一键面试技术方案
- 缓存删除策略:
- 当新闻数据在数据库更新后,立即删除对应的Redis缓存。这样下次请求该新闻数据时,缓存中不存在,会从数据库读取并重新写入缓存。例如在使用Java语言和Jedis客户端时,代码如下:
Jedis jedis = new Jedis("localhost", 6379); // 假设新闻数据在数据库更新后,获取新闻的唯一标识newsId String newsId = "12345"; String cacheKey = "news:" + newsId; jedis.del(cacheKey);
- 双写一致性方案:
- 先更新数据库,再更新缓存。但这种方式在高并发下可能出现问题,所以通常结合版本号机制。在数据库表中增加一个版本号字段,每次更新数据时版本号加1。更新缓存时,将版本号也写入缓存。读取数据时,先从缓存获取数据和版本号,然后与数据库中的版本号比较,如果一致则直接使用缓存数据,否则从数据库读取并更新缓存。例如,使用Python和Redis - Py库实现:
import redis r = redis.Redis(host='localhost', port = 6379, db = 0) # 假设新闻数据在数据库更新后,获取新闻的唯一标识news_id和新的版本号new_version news_id = '12345' new_version = 5 news_data = get_news_from_db(news_id) # 从数据库获取新闻数据 cache_key = f'news:{news_id}' r.hset(cache_key, 'data', news_data) r.hset(cache_key,'version', new_version)
- 异步更新缓存:
- 使用消息队列(如Kafka、RabbitMQ等)。当新闻数据更新时,先发送一条消息到消息队列,然后由消费者从消息队列中取出消息,进行缓存的更新或删除操作。以使用Kafka和Python的Kafka - Python库为例:
- 生产者:
from kafka import KafkaProducer producer = KafkaProducer(bootstrap_servers=['localhost:9092']) # 假设新闻数据在数据库更新后,获取新闻的唯一标识news_id news_id = '12345' message = news_id.encode('utf - 8') producer.send('news - update - topic', message)
- 消费者:
from kafka import KafkaConsumer consumer = KafkaConsumer('news - update - topic', bootstrap_servers=['localhost:9092']) for message in consumer: news_id = message.value.decode('utf - 8') cache_key = f'news:{news_id}' # 这里可以选择删除缓存或更新缓存操作 r = redis.Redis(host='localhost', port = 6379, db = 0) r.del(cache_key)
潜在问题
- 缓存删除失败:可能由于网络问题、Redis服务异常等原因导致缓存删除操作失败,这样就会出现缓存与数据库不一致的情况。
- 双写一致性问题:在高并发场景下,先更新数据库后更新缓存时,可能存在一个短暂的时间窗口,在这个窗口内其他请求读取到旧的缓存数据。同时,如果更新缓存失败,也会导致不一致。
- 异步更新缓存延迟:消息队列可能存在消息积压、消费延迟等问题,导致缓存更新不及时,在这期间也会出现缓存与数据库不一致的情况。
解决方案
- 缓存删除失败:
- 采用重试机制。例如在Java中,可以使用Spring Retry框架,对缓存删除操作进行重试。代码示例:
@Retryable(value = JedisException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000)) public void deleteCache(String cacheKey) { Jedis jedis = new Jedis("localhost", 6379); jedis.del(cacheKey); }
- 记录删除失败的缓存键,使用定时任务或异步线程进行二次删除。
- 双写一致性问题:
- 引入分布式锁(如RedLock)。在更新数据库和缓存时,先获取分布式锁,确保同一时间只有一个线程进行更新操作,避免高并发下的不一致问题。例如使用Redisson实现分布式锁:
Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient redisson = Redisson.create(config); RLock lock = redisson.getLock("news - update - lock"); try { if (lock.tryLock()) { // 更新数据库 updateNewsInDB(news); // 更新缓存 updateNewsInCache(news); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); }
- 对于更新缓存失败的情况,同样采用重试机制,并记录失败日志,便于后续排查。
- 异步更新缓存延迟:
- 监控消息队列的积压情况,设置合理的消息队列容量和消费者数量。例如在Kafka中,可以通过Kafka - Manager等工具监控消息积压,动态调整消费者数量。
- 对于关键新闻数据,可以设置一个较短的缓存过期时间,在缓存过期后强制从数据库读取最新数据,减少不一致的时间窗口。