面试题答案
一键面试性能瓶颈
- 网络延迟:大量并发请求可能导致网络拥塞,增加请求响应时间。
- 内存不足:会话数据不断增加,可能使Redis内存耗尽。
- CPU 过载:高并发读写操作会占用大量CPU资源,导致CPU使用率过高。
故障情况
- 缓存雪崩:大量会话缓存同时过期,导致大量请求直接访问后端数据库,可能压垮数据库。
- 缓存穿透:请求的数据在Redis和数据库中都不存在,导致大量无效请求穿透缓存,访问数据库。
- Redis 节点故障:单个Redis节点故障可能导致部分会话数据不可用。
优化策略
- 网络优化:
- 使用连接池:如Jedis连接池,减少频繁创建和销毁连接的开销,示例代码(Java):
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
JedisPool jedisPool = new JedisPool(config, "localhost", 6379);
try (Jedis jedis = jedisPool.getResource()) {
// 执行Redis操作
} catch (Exception e) {
e.printStackTrace();
}
- **优化网络拓扑**:采用高速网络设备,减少网络跳数。
2. 内存优化: - 数据结构优化:使用合适的数据结构,如哈希表(HSET、HGET 命令)存储会话数据,减少内存占用。例如:
HSET session:1 user_id 123 username "john"
HGET session:1 user_id
- **内存淘汰策略**:合理设置内存淘汰策略,如 `volatile - lru`(对设置了过期时间的键,使用LRU算法淘汰),可通过配置文件或 `CONFIG SET maxmemory - policy volatile - lru` 命令设置。
3. CPU 优化:
- 减少大键操作:避免使用如 KEYS
等会阻塞CPU的命令,可使用 SCAN
命令替代。例如:
SCAN 0 MATCH session:* COUNT 100
- **多线程处理**:Redis 6.0 引入多线程I/O,可通过配置文件开启,提高处理性能。
故障处理机制
- 缓存雪崩:
- 设置随机过期时间:在设置会话过期时间时,添加一定的随机值,避免大量会话同时过期。例如(Python + redis - py):
import redis
import random
r = redis.Redis(host='localhost', port=6379, db = 0)
expire_time = 3600 + random.randint(0, 600)
r.setex('session:1', expire_time, 'data')
- **使用互斥锁**:在缓存失效时,只允许一个请求去加载数据并回设缓存,其他请求等待。例如(Java):
public String getSession(String sessionId) {
String value = jedis.get("session:" + sessionId);
if (value == null) {
String lockKey = "lock:session:" + sessionId;
if (jedis.set(lockKey, "1", "NX", "EX", 10) != null) {
try {
// 从数据库加载数据
value = loadSessionFromDB(sessionId);
jedis.setex("session:" + sessionId, 3600, value);
} finally {
jedis.del(lockKey);
}
} else {
// 等待并重试
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSession(sessionId);
}
}
return value;
}
- 缓存穿透:
- 布隆过滤器:在查询前先通过布隆过滤器判断数据是否存在,减少无效查询。例如(Guava布隆过滤器 + Redis):
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
public class BloomFilterExample {
private static final int expectedInsertions = 1000000;
private static final double fpp = 0.01;
private static BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), expectedInsertions, fpp);
public static boolean mightContain(String key) {
return bloomFilter.mightContain(key);
}
public static void put(String key) {
bloomFilter.put(key);
// 同时将数据存储到Redis
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.set(key, "data");
}
}
}
- Redis 节点故障:
- 主从复制:配置主从复制,当主节点故障时,从节点可提升为主节点继续提供服务。例如在从节点配置文件中设置
replicaof <masterip> <masterport>
。 - 哨兵模式:使用Redis Sentinel监控主从节点,自动进行故障转移。配置Sentinel配置文件,指定监控的主节点:
- 主从复制:配置主从复制,当主节点故障时,从节点可提升为主节点继续提供服务。例如在从节点配置文件中设置
sentinel monitor mymaster <masterip> <masterport> 2
- **集群模式**:采用Redis Cluster,数据分布在多个节点,部分节点故障不影响整体服务,通过 `redis - trib.rb create` 命令创建集群。