面试题答案
一键面试可能遇到的问题
- 数据不一致:高并发下,读写操作频繁,可能导致 Redis 缓存与 MySQL 数据库中的数据版本不一致。例如,写操作先更新了 MySQL,还未更新 Redis 时,读操作从 Redis 读取到旧数据。
- 版本冲突:多个并发写操作同时尝试更新数据版本,可能引发冲突,导致数据错误或丢失更新。
- 缓存穿透:高并发读操作时,如果大量请求查询不存在的数据,绕过了缓存直接访问数据库,可能导致数据库压力过大,同时缓存中也不会有对应数据版本记录。
- 缓存雪崩:如果 Redis 中大量缓存数据的版本同时过期,可能会在同一时间内大量请求直接涌向数据库,造成数据库压力骤增甚至崩溃。
设计思路
- 引入版本号机制:在 MySQL 表中添加一个版本号字段,每次数据更新时,版本号递增。Redis 缓存中也存储对应的版本号,读操作时,对比缓存与数据库中的版本号,若不一致则更新缓存。
- 分布式锁:对于写操作,使用分布式锁保证同一时间只有一个写操作可以更新数据和版本号,避免版本冲突。
- 缓存预热:在系统启动时,提前将一些热点数据加载到 Redis 中,并设置合理的过期时间,避免缓存雪崩。
- 布隆过滤器:在缓存层之前添加布隆过滤器,快速判断请求的数据是否存在,减少无效请求对数据库的访问,防止缓存穿透。
关键实现步骤
- 数据库表结构调整:在相关 MySQL 表中添加
version
字段,类型可以为int
,初始值设为 1。
ALTER TABLE your_table_name ADD COLUMN version INT DEFAULT 1;
- 写操作流程:
- 获取分布式锁(如使用 Redis 的 SETNX 命令实现简单分布式锁)。
- 读取当前数据库中的版本号
currentVersion
。 - 更新数据,并将版本号
currentVersion + 1
写入数据库。 - 更新 Redis 缓存,同时写入新的版本号。
- 释放分布式锁。
- 读操作流程:
- 从 Redis 中读取数据及版本号
cacheVersion
。 - 从 MySQL 中读取数据及版本号
dbVersion
。 - 对比
cacheVersion
与dbVersion
,若不一致:- 更新 Redis 缓存数据及版本号为数据库中的值。
- 返回数据。
- 从 Redis 中读取数据及版本号
- 缓存预热:在系统启动脚本中,编写逻辑从数据库加载热点数据到 Redis,并设置合理的过期时间。例如,使用 Python 的 Redis 客户端库:
import redis
import mysql.connector
r = redis.Redis(host='localhost', port=6379, db=0)
mydb = mysql.connector.connect(
host="localhost",
user="your_user",
password="your_password",
database="your_database"
)
mycursor = mydb.cursor()
mycursor.execute("SELECT * FROM your_table_name WHERE is_hot = 1")
result = mycursor.fetchall()
for row in result:
key = f"your_key:{row[0]}"
value = {
"data": row[1:],
"version": row[-1]
}
r.set(key, value)
r.expire(key, 3600) # 设置过期时间为1小时
- 布隆过滤器实现:使用第三方库如
pybloomfiltermmap
来实现布隆过滤器。在系统初始化时,将数据库中已有的数据主键等标识添加到布隆过滤器中。在每次读请求进入缓存层前,先检查布隆过滤器,若不存在则直接返回,不查询缓存和数据库。
from pybloomfiltermmap import BloomFilter
# 初始化布隆过滤器,预计元素数量和错误率可根据实际调整
bf = BloomFilter(capacity=1000000, error_rate=0.001, filename='bloomfilter.bf')
# 从数据库加载已有数据主键到布隆过滤器
mycursor.execute("SELECT id FROM your_table_name")
ids = mycursor.fetchall()
for id in ids:
bf.add(str(id[0]))
在处理读请求时:
def handle_read_request(request_id):
if str(request_id) not in bf:
return None
# 继续处理缓存和数据库查询逻辑