开课吧开课吧锤锤2021-02-22 11:38
80、90后是这个时代最焦虑的一代人,大部分人都是,上有老,下有小,有房贷,车贷,卡贷。身上的大山很重,很多人的工资是不足以负担着这些的,所以想要给家人,给自己一个好的生活,就要从事一份薪资高的工作,在众多工种中,程序员的工资最高,很多人都在向往着这份工作,但是普通的程序员,也不会有非常可观的薪资,只有那种技术尖端人才,才可以,事业名利双丰收,那怎么样才能做到行业的顶尖呢?,那就呀不断的提升自己,今天就为大家带来分析数据库与缓存的双写问题,希望对您攀登事业顶峰有所帮助
数据缓存
在我们实际的业务场景中,一定有很多需要做数据缓存的场景,比如售卖商品的页面,包括了许多并发访问量很大的数据,它们可以称作是是“热点”数据,这些数据有一个特点,就是更新频率低,读取频率高,这些数据应该尽量被缓存,从而减少请求打到数据库上的机会,减轻数据库的压力。
为何要使用缓存
缓存是为了追求“快”而存在的。我们用代码举一个例子。
我在自己的Demo代码仓库中增加了两个查询库存的接口getStockByDB和getStockByCache,分别表示从数据库和缓存查询某商品的库存量。
随后我们用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,
哪类数据适合缓存
缓存量大但又不常变化的数据,比如详情,评论等。对于那些经常变化的数据,其实并不适合缓存,一方面会增加系统的复杂性(缓存的更新,缓存脏数据),另一方面也给系统带来一定的不稳定性(缓存系统的维护)。
但一些极端情况下,你需要将一些会变动的数据进行缓存,比如想要页面显示准实时的库存数,或者其他一些特殊业务场景。这时候你需要保证缓存不能(一直)有脏数据,这就需要再深入讨论一下。
缓存的利与弊
我们到底该不该上缓存的,这其实也是个trade-off(权衡)的问题。
上缓存的优点:
能够缩短服务的响应时间,给用户带来更好的体验。
能够增大系统的吞吐量,依然能够提升用户体验。
减轻数据库的压力,防止高峰期数据库被压垮,导致整个线上服务BOOM!
上了缓存,也会引入很多额外的问题:
缓存有多种选型,是内存缓存,memcached还是redis,你是否都熟悉,如果不熟悉,无疑增加了维护的难度(本来是个纯洁的数据库系统)。
缓存系统也要考虑分布式,比如redis的分布式缓存还会有很多坑,无疑增加了系统的复杂性。
在特殊场景下,如果对缓存的准确性有非常高的要求,就必须考虑缓存和数据库的一致性问题。
以上内容由开课吧老师Java中文社群提供,更多Java教程尽在开课吧广场Java教程频道。更多免费课程可以关注公众号“码农集散地”

最新文章

Java多线程与JUC—死锁的出现与解决(二)
.固定锁顺序避免死锁(针对锁顺序死锁) 上面transferMoney()发生死锁的原因是因为加锁顺序不一致而出现的~ 如果所有线程以固定的顺序来获得锁,那么程序中就不会出现锁顺序死锁问题!
2021-02-26 10:20:15

Java多线程与JUC—死锁的出现与解决(一)
在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。 我们只能通过中止并重启的方式来让程序重新执行。 这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生!
2021-02-26 10:11:15

Java教程:MySQL高可用方案对比(三)
MySQLcluster是官方集群的部署方案,通过使用NDB存储引擎实时备份冗余数据,实现数据库的高可用性和数据一致性。
2021-02-26 09:52:06

Java面试题:dispatchServlet怎样分发任务的?
用户发请求-->DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。
2021-02-25 18:03:02

Java教程:MySQL高可用方案对比(二)
由于半同步复制,存在接收到一个从机的成功应答即认为半同步复制成功的特性,所以多从半同步复制的可靠性要优于单从半同步复制的可靠性。并且多节点同时宕机的几率也要小于单节点宕机的几率,所以多节点架构在一定程度上可以认为高可用性是好于双节点架构。
2021-02-25 16:52:40