一、需求:
1、根据课程id查询发布的课程信息;
2、使用缓存提高系统白名单数据的访问效率,提高系统吞吐量;
二、基本功能实现:
1、功能代码:
public CoursePublish getCoursePublish(Long courseId) {
return coursePublishMapper.selectById(courseId);
}
2、功能测试效果:
3、压力测试:
基本功能的实现经过测试后我们可以看出,系统的效率并不是很高,只有88左右;
三、加入Redis缓存:
1、功能代码:
//注入redisTemplate
@Autowired
private RedisTemplate redisTemplate;
public CoursePublish getCoursePublish(Long courseId) {
//从redis里面查询
Object object = redisTemplate.opsForValue().get("course:" + courseId);
if(object != null){
//提取数据
String objectString = object.toString();
CoursePublish coursePublish = JSON.parseObject(objectString, CoursePublish.class);
//返回课程信息
return coursePublish;
}else{
//缓存中不存在,则从数据库中查询
CoursePublish coursePublish = coursePublishMapper.selectById(courseId);
if(coursePublish == null){
return null;
}
//数据存在则存入缓存
String jsonString = JSON.toJSONString(coursePublish);
redisTemplate.opsForValue().set("course:" + courseId,jsonString);
//返回数据
return coursePublish;
}
}
2、功能测试效果:
3、压力测试:
使用redis缓存后再使用jMeter压力测试,这时吞吐量直接到650+,系统效率显著提高。
四、使用reids缓存后可能出现的问题:
1、缓存穿透
(1)概念:我们在调用接口时查询一个数据库不存在的数据,导致请求仍然会指向数据库;
(2)解决思路:数据库查询不到返回的结果也是空,那就将null也存入到缓存中,由于课程不可能一直不存在,所以需要设置过期时间,避免结果错误;
(4)修改代码:
public CoursePublish getCoursePublish(Long courseId) {
//从redis里面查询
Object object = redisTemplate.opsForValue().get("course:" + courseId);
if(object != null){
//提取数据
String objectString = object.toString();
if(objectString.equals("null")){
return null;
}
CoursePublish coursePublish = JSON.parseObject(objectString, CoursePublish.class);
//返回课程信息
return coursePublish;
}else {
//缓存中不存在,则从数据库中查询
CoursePublish coursePublish = coursePublishMapper.selectById(courseId);
String jsonString = JSON.toJSONString(coursePublish);
redisTemplate.opsForValue().set("course:" + courseId, jsonString, 30, TimeUnit.SECONDS);
//返回数据
return coursePublish;
}
}
}
(5)压力测试:
吞吐量到690左右,稍有提高。
2、缓存雪崩
(1)概念:高并发的场景下,多个缓存同时过期,导致大量请求同时涌入数据库,使数据库压力陡增;
(2)解决思路:添加缓存时设置不同的过期时间;
(3)修改代码:
//缓存更新
redisTemplate.opsForValue().set("course:" + courseId, jsonString, 30 + new Random().nextInt(100), TimeUnit.SECONDS);
3、缓存击穿
(1)概念:高并发的场景下,大量数据同时访问一条即将过期的缓存,一旦缓存过期,大量请求同时会同时涌向数据库,数据库压力变大,系统效率也会降低;
(2)解决思路:
1、本地使用同步锁,使得缓存更新只进行一次,同时在获得锁之后再查询一次缓存,避免后续请求获得锁后重复更新缓存
2、多个微服务的情况下使用分布式锁(mysql乐观锁、redis的setNX、redisson),使得只有一个微服务更新缓存。我们这里模拟微服务场景下,使用redisson实现分布式锁。
(3)修改代码:
//注入Redisson
@Autowired
private RedissonClient redissonClient;
public CoursePublish getCoursePublish(Long courseId) {
//从redis里面查询
Object object = redisTemplate.opsForValue().get("course:" + courseId);
if(object != null){
//提取数据
String objectString = object.toString();
if(objectString.equals("null")){
return null;
}
CoursePublish coursePublish = JSON.parseObject(objectString, CoursePublish.class);
//返回课程信息
return coursePublish;
}else {
//获取锁
RLock lock = redissonClient.getLock("courseLock:" + courseId);
lock.lock();
try {
//再次从redis里面查询
object = redisTemplate.opsForValue().get("course:" + courseId);
if(object != null) {
//提取数据
String objectString = object.toString();
if (objectString.equals("null")) {
return null;
}
CoursePublish coursePublish = JSON.parseObject(objectString, CoursePublish.class);
//返回课程信息
return coursePublish;
}
//缓存中不存在,则从数据库中查询
CoursePublish coursePublish = coursePublishMapper.selectById(courseId);
String jsonString = JSON.toJSONString(coursePublish);
redisTemplate.opsForValue().set("course:" + courseId, jsonString, 30 + new Random().nextInt(100), TimeUnit.SECONDS);
//返回数据
return coursePublish;
}finally{
//释放锁
lock.unlock();
}
}
}
(4)压力测试:
ok,吞吐稳定在600左右,性能提升问题解决。