rude3knife

前言

本篇文章是我之前系列文章中的一篇,主要讨论了我们在平时的开发过程中,各大系统中都要用到的缓存数据的问题,进一步延伸到数据库和缓存的双写一致性问题,并且给出了所有方案的实现代码方便大家参考。

本篇文章主要内容

  • 数据缓存
    • 为何要使用缓存
    • 哪类数据适合缓存
    • 缓存的利与弊
  • 如何保证缓存和数据库一致性
    • 不更新缓存,而是删除缓存
    • 先操作缓存,还是先操作数据库
    • 非要保证数据库和缓存数据强一致该怎么办
  • 缓存和数据库一致性实战
    • 实战:先删除缓存,再更新数据库
    • 实战:先更新数据库,再删缓存
    • 实战:缓存延时双删
    • 实战:删除缓存重试机制
    • 实战:读取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,活动买的,很便宜。这里打个免费的广告,请腾讯云看到后联系我给我打钱

分类:

技术点:

相关文章: