面试题答案
一键面试可能出现的问题
- 缓存击穿:指一个存在于缓存中的热点数据,在某个时刻,其缓存过期,此时大量并发请求同时尝试读取该数据,发现缓存中没有,就会同时去数据库查询,给数据库带来巨大压力,甚至可能导致数据库崩溃。
- 缓存雪崩:大量缓存数据在同一时间过期失效,大量并发请求同时查询数据库,导致数据库压力骤增,甚至可能使数据库和应用系统崩溃。这通常是由于缓存服务器重启、缓存数据批量过期等原因引起。
- 数据不一致:多个并发请求同时读取缓存数据,然后其中一些请求对数据进行修改并写回缓存,由于并发操作的无序性,可能导致最终缓存中的数据不是最新的、正确的数据,出现数据不一致的情况。
常见的并发控制方法及其原理
- 互斥锁(Mutex)
- 原理:在多个并发请求尝试读取和修改缓存数据时,使用互斥锁来保证同一时间只有一个请求能够对缓存进行操作。当一个请求获取到互斥锁后,其他请求需要等待该请求释放互斥锁才能进行操作。在代码实现中,可以使用操作系统提供的线程同步机制(如POSIX互斥锁、Windows临界区等)或者编程语言自带的锁机制(如Java的
synchronized
关键字、Python的threading.Lock
)。例如在Java中:
- 原理:在多个并发请求尝试读取和修改缓存数据时,使用互斥锁来保证同一时间只有一个请求能够对缓存进行操作。当一个请求获取到互斥锁后,其他请求需要等待该请求释放互斥锁才能进行操作。在代码实现中,可以使用操作系统提供的线程同步机制(如POSIX互斥锁、Windows临界区等)或者编程语言自带的锁机制(如Java的
public class Cache {
private static final Object mutex = new Object();
private static Map<String, Object> cache = new HashMap<>();
public static Object get(String key) {
synchronized (mutex) {
return cache.get(key);
}
}
public static void put(String key, Object value) {
synchronized (mutex) {
cache.put(key, value);
}
}
}
- **优点**:实现简单,能够有效避免并发操作导致的数据不一致问题。
- **缺点**:性能较低,因为同一时间只有一个请求能操作缓存,在高并发场景下,大量请求会被阻塞等待锁的释放,可能成为系统的性能瓶颈。
2. 乐观锁 - 原理:乐观锁假设在大多数情况下,并发操作不会发生冲突。在读取数据时,不进行加锁操作,而是在更新数据时,检查数据在读取后是否被其他线程修改。通常的做法是在数据中添加一个版本号(Version)或者时间戳(Timestamp)。当一个请求读取数据时,同时获取数据的版本号。在更新数据时,将当前版本号与数据库中最新的版本号进行比较,如果版本号相同,则说明数据在读取后没有被其他线程修改,可以进行更新操作,并将版本号加1;如果版本号不同,则说明数据已被其他线程修改,放弃本次更新操作,重新读取数据并再次尝试更新。例如在数据库层面,如果使用SQL语句实现乐观锁更新:
-- 假设表结构为 cache_table (id, value, version)
-- 读取数据及版本号
SELECT value, version FROM cache_table WHERE id =?;
-- 更新数据,同时检查版本号
UPDATE cache_table SET value =?, version = version + 1 WHERE id =? AND version =?;
在代码层面(以Java为例):
public class Cache {
private static Map<String, CacheEntry> cache = new HashMap<>();
public static class CacheEntry {
Object value;
int version;
public CacheEntry(Object value, int version) {
this.value = value;
this.version = version;
}
}
public static CacheEntry get(String key) {
return cache.get(key);
}
public static boolean put(String key, Object value, int expectedVersion) {
CacheEntry entry = cache.get(key);
if (entry == null || entry.version != expectedVersion) {
return false;
}
entry.value = value;
entry.version++;
cache.put(key, entry);
return true;
}
}
- **优点**:性能较高,因为读取操作不加锁,并发性能好,适用于读多写少的场景。
- **缺点**:如果并发冲突频繁发生,会导致大量更新操作失败,需要重试,增加系统开销。同时,实现相对复杂,需要额外维护版本号或时间戳。