常见业务场景及解决方案
- 写操作后读操作
- 场景描述:在对数据库进行写操作(如插入、更新、删除)后,紧接着进行读操作。如果缓存未及时更新,读操作可能会从缓存中读取到旧数据,导致数据库与缓存数据不一致。
- 解决方案:采用先更新数据库,再删除缓存的策略。这样在写操作后,下次读操作就会因为缓存中数据已被删除,从而从数据库读取最新数据并重新填充缓存。例如在Java中使用Spring Data JPA操作数据库和Redis作为缓存,代码如下:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void updateUser(User user) {
userRepository.save(user);
stringRedisTemplate.delete("user:" + user.getId());
}
}
- 高并发写操作
- 场景描述:多个并发的写操作同时进行,不同线程执行更新数据库和更新/删除缓存的顺序可能不同,导致缓存和数据库最终状态不一致。比如线程A先更新数据库,线程B后更新数据库,但线程B先更新缓存,线程A后更新缓存,最终缓存数据可能是线程A的旧数据。
- 解决方案:使用分布式锁。在进行写操作前获取分布式锁,保证同一时间只有一个线程能进行写操作及后续的缓存更新。以Redis实现分布式锁为例,使用Redisson框架:
@Service
public class ProductService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductRepository productRepository;
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void updateProduct(Product product) {
RLock lock = redissonClient.getLock("product:lock:" + product.getId());
try {
lock.lock();
productRepository.save(product);
stringRedisTemplate.delete("product:" + product.getId());
} finally {
lock.unlock();
}
}
}
- 缓存失效时大量读请求
- 场景描述:当缓存过期失效时,大量读请求同时涌入,这些请求都会去查询数据库,然后再更新缓存。如果部分请求在更新缓存前,其他请求又来读取数据库,就可能导致缓存和数据库不一致,并且还可能造成数据库压力过大。
- 解决方案:采用缓存预热和互斥锁机制。缓存预热即在系统启动时就将一些热点数据加载到缓存中。对于缓存失效时的读请求,使用互斥锁,只允许一个请求去查询数据库并更新缓存,其他请求等待。当第一个请求更新完缓存后,等待的请求直接从缓存读取数据。以下是Python中使用Flask框架和Redis实现的示例:
import redis
from flask import Flask
import threading
app = Flask(__name__)
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
def get_data_from_db(key):
# 模拟从数据库获取数据
return "data from db"
def set_data_to_cache(key, value):
redis_client.setex(key, 3600, value)
def get_data(key):
data = redis_client.get(key)
if data is None:
lock_key = "lock:" + key
with redis_client.lock(lock_key, blocking_timeout=10):
data = redis_client.get(key)
if data is None:
data = get_data_from_db(key)
set_data_to_cache(key, data)
return data