面试题答案
一键面试1. 架构总体设计
- 客户端:发起多媒体数据查询请求。
- 应用层:接收请求,先查询Redis缓存。若缓存命中,直接返回数据;若未命中,则查询MySQL数据库,将从MySQL获取的数据存入Redis缓存,并返回给客户端。
- Redis层:存储高频访问的多媒体数据,提供快速查询。
- MySQL层:作为数据的持久化存储,保存所有多媒体数据。
2. 数据在Redis和MySQL之间的流动
- 读操作:
- 客户端发送查询请求到应用层。
- 应用层首先查询Redis,若缓存命中,直接返回数据给客户端。
- 若Redis未命中,应用层查询MySQL,获取数据后,将数据写入Redis缓存(设置合适的过期时间),然后返回数据给客户端。
- 写操作:
- 客户端发送写请求到应用层。
- 应用层先更新MySQL数据,成功后删除Redis中对应的缓存数据,确保数据一致性。
3. 缓存预热策略
- 启动时预热:在系统启动阶段,从MySQL批量读取热门多媒体数据,将其写入Redis缓存。可以通过定时任务或者一次性脚本完成。例如,利用Python的
pymysql
连接MySQL,redis - py
连接Redis,批量查询并写入。
import pymysql
import redis
# 连接MySQL
mysql_conn = pymysql.connect(host='mysql_host', user='user', password='password', database='database')
mysql_cursor = mysql_conn.cursor()
# 连接Redis
redis_conn = redis.StrictRedis(host='redis_host', port=6379, db=0)
# 查询热门数据
mysql_cursor.execute('SELECT * FROM multimedia_data WHERE is_popular = 1')
rows = mysql_cursor.fetchall()
for row in rows:
# 假设row的第一个字段为id,其他为数据,以JSON格式存储
data_id = row[0]
data = json.dumps(row[1:])
redis_conn.set(data_id, data)
mysql_conn.close()
- 定时预热:使用定时任务(如Linux的
crontab
或Spring Boot的@Scheduled
),定期从MySQL中查询更新热门数据到Redis,保证缓存中数据的时效性。
4. 缓存失效处理
- 主动更新:当MySQL数据发生变化(如更新、删除操作)时,及时删除Redis中对应的缓存数据。在应用层的写操作逻辑中,先更新MySQL,成功后执行Redis删除操作。
- 被动更新:当缓存过期,客户端请求时,应用层从MySQL重新读取数据并更新Redis缓存。
5. 应对Redis缓存雪崩、穿透和击穿问题
- 缓存雪崩:
- 设置不同过期时间:避免大量缓存同时过期。在设置Redis缓存过期时间时,采用随机时间,例如原本过期时间为1小时,可以设置为50 - 70分钟之间的随机值。
- 启用持久化:开启Redis的持久化机制(RDB或AOF),当Redis重启时,能够快速恢复缓存数据,减少因缓存大量丢失导致的雪崩问题。
- 搭建集群:采用Redis集群,增加系统的可用性和容错性,即使部分节点故障,其他节点仍能提供服务。
- 缓存穿透:
- 布隆过滤器:在应用层使用布隆过滤器,在查询Redis和MySQL之前,先通过布隆过滤器判断数据是否存在。如果布隆过滤器判断不存在,则直接返回,避免无效查询穿透到MySQL。例如,使用Python的
pybloom - filter
库。
- 布隆过滤器:在应用层使用布隆过滤器,在查询Redis和MySQL之前,先通过布隆过滤器判断数据是否存在。如果布隆过滤器判断不存在,则直接返回,避免无效查询穿透到MySQL。例如,使用Python的
from pybloomfilter import BloomFilter
# 初始化布隆过滤器,预计元素数量10000,误判率0.01
bloom = BloomFilter(capacity=10000, error_rate=0.01)
# 假设从MySQL获取所有数据id,添加到布隆过滤器
mysql_cursor.execute('SELECT id FROM multimedia_data')
ids = mysql_cursor.fetchall()
for id in ids:
bloom.add(str(id[0]))
# 查询时先检查布隆过滤器
def query_data(data_id):
if str(data_id) not in bloom:
return None
# 继续查询Redis和MySQL逻辑
- **空值缓存**:当查询MySQL发现数据不存在时,将空值也缓存到Redis中,并设置较短的过期时间,避免下次相同的无效查询穿透到MySQL。
3. 缓存击穿: - 互斥锁:在应用层,当缓存失效,查询MySQL前,使用互斥锁(如Redis的SETNX命令实现分布式锁)。只有获取到锁的线程才能查询MySQL并更新缓存,其他线程等待,避免大量请求同时查询MySQL。
import time
def get_data_with_mutex(data_id):
lock_key = 'lock:' + data_id
lock_acquired = redis_conn.setnx(lock_key, 1)
if lock_acquired:
try:
data = redis_conn.get(data_id)
if not data:
data = get_data_from_mysql(data_id)
redis_conn.set(data_id, data)
return data
finally:
redis_conn.delete(lock_key)
else:
# 等待一段时间后重试
time.sleep(0.1)
return get_data_with_mutex(data_id)
- **热点数据不过期**:对于一些极其热点的数据,不设置过期时间,同时使用后台线程定期更新缓存数据,保证数据的实时性。
6. 确保系统的高可用性和高性能
- 高可用性:
- Redis集群:搭建Redis集群,采用主从复制和哨兵模式或Cluster模式,提高Redis的可用性,自动进行故障转移。
- MySQL主从复制:配置MySQL主从复制,主库负责写操作,从库负责读操作,提高MySQL的可用性和读写性能。同时可以使用MHA(Master High Availability)等工具实现自动故障切换。
- 负载均衡:在应用层前端部署负载均衡器(如Nginx),将请求均匀分配到多个应用服务器实例,避免单个服务器压力过大。
- 高性能:
- 优化SQL查询:对MySQL中的多媒体数据查询语句进行优化,添加合适的索引,避免全表扫描。
- 缓存优化:合理设置Redis的缓存策略,如采用LRU(Least Recently Used)等淘汰策略,确保热点数据常驻缓存。同时,根据业务需求调整Redis的内存配置,提高缓存命中率。
- 异步处理:对于一些非关键的操作,如缓存预热、缓存更新等,可以采用异步方式处理,减少对主线程的影响,提高系统的响应速度。例如,使用消息队列(如Kafka)将缓存更新任务异步化。