前言
本篇文章是我之前系列文章中的一篇,主要讨论了我们在平时的开发过程中,各大系统中都要用到的缓存数据的问题,进一步延伸到数据库和缓存的双写一致性问题,并且给出了所有方案的实现代码方便大家参考。
本篇文章主要内容
- 数据缓存
- 为何要使用缓存
- 哪类数据适合缓存
- 缓存的利与弊
- 如何保证缓存和数据库一致性
- 不更新缓存,而是删除缓存
- 先操作缓存,还是先操作数据库
- 非要保证数据库和缓存数据强一致该怎么办
- 缓存和数据库一致性实战
- 实战:先删除缓存,再更新数据库
- 实战:先更新数据库,再删缓存
- 实战:缓存延时双删
- 实战:删除缓存重试机制
- 实战:读取binlog异步删除缓存
码字不易,只求关注,欢迎关注我的原创技术公众号:后端技术漫谈(二维码见文章底部)
项目源码在这里
https://github.com/qqxx6661/miaosha
数据缓存
在我们实际的业务场景中,一定有很多需要做数据缓存的场景,比如售卖商品的页面,包括了许多并发访问量很大的数据,它们可以称作是是“热点”数据,这些数据有一个特点,就是更新频率低,读取频率高,这些数据应该尽量被缓存,从而减少请求打到数据库上的机会,减轻数据库的压力。
为何要使用缓存
缓存是为了追求“快”而存在的。我们用代码举一个例子。
我在自己的Demo代码仓库中增加了两个查询库存的接口getStockByDB和getStockByCache,分别表示从数据库和缓存查询某商品的库存量。
随后我们用JMeter进行并发请求测试。(JMeter的使用请参考我之前写的文章:点击这里)
需要声明的是,我的测试并不严谨,只是作对比测试,不要作为实际服务性能的参考。
这是两个接口的代码:
/**
* 查询库存:通过数据库查询库存
* @param sid
* @return
*/
@RequestMapping("/getStockByDB/{sid}")
@ResponseBody
public String getStockByDB(@PathVariable int sid) {
int count;
try {
count = stockService.getStockCountByDB(sid);
} catch (Exception e) {
LOGGER.error("查询库存失败:[{}]", e.getMessage());
return "查询库存失败";
}
LOGGER.info("商品Id: [{}] 剩余库存为: [{}]", sid, count);
return String.format("商品Id: %d 剩余库存为:%d", sid, count);
}
/**
* 查询库存:通过缓存查询库存
* 缓存命中:返回库存
* 缓存未命中:查询数据库写入缓存并返回
* @param sid
* @return
*/
@RequestMapping("/getStockByCache/{sid}")
@ResponseBody
public String getStockByCache(@PathVariable int sid) {
Integer count;
try {
count = stockService.getStockCountByCache(sid);
if (count == null) {
count = stockService.getStockCountByDB(sid);
LOGGER.info("缓存未命中,查询数据库,并写入缓存");
stockService.setStockCountToCache(sid, count);
}
} catch (Exception e) {
LOGGER.error("查询库存失败:[{}]", e.getMessage());
return "查询库存失败";
}
LOGGER.info("商品Id: [{}] 剩余库存为: [{}]", sid, count);
return String.format("商品Id: %d 剩余库存为:%d", sid, count);
}
首先设置为10000个并发请求的情况下,运行JMeter,结果首先出现了大量的报错,10000个请求中98%的请求都直接失败了。让人很慌张~
打开日志,报错如下:
SpringBoot内置的Tomcat最大并发数搞的鬼,其默认值为200,对于10000的并发,单机服务实在是力不从心。当然,你可以修改这里的并发数设置,但是你的小机器仍然可能会扛不住。
将其修改为如下配置后,我的小机器才在通过缓存拿库存的情况下,保证了10000个并发的100%返回请求:
server.tomcat.max-threads=10000
server.tomcat.max-connections=10000
可以看到,不使用缓存的情况下,吞吐量为668个请求每秒:
使用缓存的情况下,吞吐量为2177个请求每秒:
在这种“十分不严谨”的对比下,有缓存对于一台单机,性能提升了3倍多,如果在多台机器,更多并发的情况下,由于数据库有了更大的压力,缓存的性能优势应该会更加明显。
测完了这个小实验,我看了眼我挂着MySql的小水管腾讯云服务器,生怕他被这么高流量搞挂。这种突发的流量,指不定会被检测为异常攻击流量呢~
我用的是腾讯云服务器1C4G2M,活动买的,很便宜。这里打个免费的广告,请腾讯云看到后联系我给我打钱