Java教程:分析数据库与缓存的双写问题(一)

开课吧开课吧锤锤2021-02-22 11:38

    80、90后是这个时代最焦虑的一代人,大部分人都是,上有老,下有小,有房贷,车贷,卡贷。身上的大山很重,很多人的工资是不足以负担着这些的,所以想要给家人,给自己一个好的生活,就要从事一份薪资高的工作,在众多工种中,程序员的工资最高,很多人都在向往着这份工作,但是普通的程序员,也不会有非常可观的薪资,只有那种技术尖端人才,才可以,事业名利双丰收,那怎么样才能做到行业的顶尖呢?,那就呀不断的提升自己,今天就为大家带来分析数据库与缓存的双写问题,希望对您攀登事业顶峰有所帮助

Java

    数据缓存

    在我们实际的业务场景中,一定有很多需要做数据缓存的场景,比如售卖商品的页面,包括了许多并发访问量很大的数据,它们可以称作是是“热点”数据,这些数据有一个特点,就是更新频率低,读取频率高,这些数据应该尽量被缓存,从而减少请求打到数据库上的机会,减轻数据库的压力。

    为何要使用缓存

    缓存是为了追求“快”而存在的。我们用代码举一个例子。

    我在自己的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%的请求都直接失败了。让人很慌张~

    打开日志,报错如下:

Java

    SpringBoot内置的Tomcat最大并发数搞的鬼,其默认值为200,对于10000的并发,单机服务实在是力不从心。当然,你可以修改这里的并发数设置,但是你的小机器仍然可能会扛不住。

Java

    将其修改为如下配置后,我的小机器才在通过缓存拿库存的情况下,保证了10000个并发的100%返回请求:

   server.tomcat.max-threads=10000

   server.tomcat.max-connections=10000

    可以看到,不使用缓存的情况下,吞吐量为668个请求每秒:

Java

    使用缓存的情况下,吞吐量为2177个请求每秒:

Java

    在这种“十分不严谨”的对比下,有缓存对于一台单机,性能提升了3倍多,如果在多台机器,更多并发的情况下,由于数据库有了更大的压力,缓存的性能优势应该会更加明显。

    测完了这个小实验,我看了眼我挂着MySql的小水管腾讯云服务器,生怕他被这么高流量搞挂。这种突发的流量,指不定会被检测为异常攻击流量呢~

Java

    我用的是腾讯云服务器1C4G2M,

Java

    哪类数据适合缓存

    缓存量大但又不常变化的数据,比如详情,评论等。对于那些经常变化的数据,其实并不适合缓存,一方面会增加系统的复杂性(缓存的更新,缓存脏数据),另一方面也给系统带来一定的不稳定性(缓存系统的维护)。

    但一些极端情况下,你需要将一些会变动的数据进行缓存,比如想要页面显示准实时的库存数,或者其他一些特殊业务场景。这时候你需要保证缓存不能(一直)有脏数据,这就需要再深入讨论一下。

    缓存的利与弊

    我们到底该不该上缓存的,这其实也是个trade-off(权衡)的问题。

    上缓存的优点:

    能够缩短服务的响应时间,给用户带来更好的体验。

    能够增大系统的吞吐量,依然能够提升用户体验。

    减轻数据库的压力,防止高峰期数据库被压垮,导致整个线上服务BOOM!

    上了缓存,也会引入很多额外的问题:

    缓存有多种选型,是内存缓存,memcached还是redis,你是否都熟悉,如果不熟悉,无疑增加了维护的难度(本来是个纯洁的数据库系统)。

    缓存系统也要考虑分布式,比如redis的分布式缓存还会有很多坑,无疑增加了系统的复杂性。

    在特殊场景下,如果对缓存的准确性有非常高的要求,就必须考虑缓存和数据库的一致性问题。

    以上内容由开课吧老师Java中文社群提供,更多Java教程尽在开课吧广场Java教程频道。更多免费课程可以关注公众号“码农集散地”

上一篇:Java教程:分析数据库与缓存的双写问题(二)下一篇:Java教程:SQL之WHERE条件的提取与应用(二)

最新文章

文章图0

Java多线程与JUC—死锁的出现与解决(二)

.固定锁顺序避免死锁(针对锁顺序死锁) 上面transferMoney()发生死锁的原因是因为加锁顺序不一致而出现的~ 如果所有线程以固定的顺序来获得锁,那么程序中就不会出现锁顺序死锁问题!

2021-02-26 10:20:15

文章图1

Java多线程与JUC—死锁的出现与解决(一)

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

2021-02-26 10:11:15

文章图2

Java教程:MySQL高可用方案对比(三)

MySQLcluster是官方集群的部署方案,通过使用NDB存储引擎实时备份冗余数据,实现数据库的高可用性和数据一致性。

2021-02-26 09:52:06

文章图3

Java面试题:dispatchServlet怎样分发任务的?

用户发请求-->DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。

2021-02-25 18:03:02

文章图4

Java教程:MySQL高可用方案对比(二)

由于半同步复制,存在接收到一个从机的成功应答即认为半同步复制成功的特性,所以多从半同步复制的可靠性要优于单从半同步复制的可靠性。并且多节点同时宕机的几率也要小于单节点宕机的几率,所以多节点架构在一定程度上可以认为高可用性是好于双节点架构。

2021-02-25 16:52:40