后端爬虫爬取数据缓存至Redis中,前端通过查询接口展现爬取数据。考虑到高并发场景下可能会导致多个客户端连接的多个线程在Redis中的数据过期时同时执行数据爬取逻辑来重构缓存于Redis当中由于Redis是单线程的写操作与读操作是互斥的所以会导致接口响应时间过长并且数据爬取逻辑被多个线程冗余执行多次完全是没必要的(数据过期时只需要一个线程执行数据爬取逻辑更新缓存即可)。
目标:优化接口响应速度,解决多线程同时更新缓存的问题,提升用户体验。
行动:采用逻辑过期+异步更新策略
代码:
新闻查询接口
@Override
public List<Object> getNewList() {
//获取当前时间戳
long currentTime = System.currentTimeMillis();
//获取缓存中的新闻数据逻辑过期时间戳
Long baiduNewsExpires = RedisUtil.getExpiresFromRedis("BaiduNewsExpires");
//获取缓存中的新闻数据 key为新闻标签+逻辑过期时间戳
List<Object> NewList = RedisUtil.getDataFromRedis("BaiduNews"+baiduNewsExpires);
//现在时间>=设置过期时间说明数据过期
if (NewList.isEmpty()||currentTime>=baiduNewsExpires ) {
//线程尝试获取锁获取不到直接返回旧数据
if(lock.tryLock()){
try {
//异步更新开启一个线程loadRedis 重构缓存数据-新闻
new Thread(new LoadNewsToRedisTask(1L)).start();
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
return NewList;
}
return NewList;
}
缓存重构线程任务
public void run() {
//线程尝试获取锁获取到则执行缓存更新逻辑否则线程执行完毕
//此操作是为了保证只有一个线程执行更新操作防止多个线程同时执行缓存更新操作造成空间浪费
if(lock.tryLock()){
try {
//逻辑过期时间戳转换
Expires=System.currentTimeMillis()+ (Expires * 60 * 1000);
//爬虫
List<Object> NewList = BaiduProcessor.spiderBaidu();
//缓存更新数据
RedisUtil.saveDataToRedis(NewList, "BaiduNews"+Expires);
//逻辑过期数据保存
RedisUtil.saveDataToRedis(Expires, "BaiduNewsExpires");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
结果:
将新闻爬取数据缓存至Redis,并采用逻辑过期策略结合异步更新机制,系统接口的高可用性
得到了有效保障。优化后,接口响应速度从679ms大幅提升至17ms,显著提升了系统性能。
对于Redis中的过期数据我们可以使用LRU算法数据淘汰策略来优化缓存空间占用,且它能保证缓存中的数据都是热点数据