zoukankan      html  css  js  c++  java
  • Spring+SpringMVC+MyBatis+easyUI整合进阶篇(十四)Redis缓存正确的使用姿势

    作者:13
    GitHub:https://github.com/ZHENFENG13
    版权声明:本文为原创文章,未经允许不得转载。

    简介

    这是一篇关于Redis使用的总结类型文章,会先简单的谈一下缓存的应用场景、缓存的使用逻辑及注意事项,然后是Redis缓存与数据库间结合以进行系统优化,当然文章的最后也会给出具体的代码实现,不至于看到文章的你一头雾水,理论要讲,项目代码也要分享,这是我写博客的基本出发点。
    1

    应用场景

    Redis能做什么呢?

    这是个好问题,不同的人可能会给出不同的答案,因为它的应用场景真的很多,作为一个优秀的nosql数据库可以结合其他产品做很多事情,比如:tomcat集群的session同步、与nginx和lua结合做限流工具、基于Redis的分布式锁实现、分布式系统唯一主键生成策略、秒杀场景中也会看到它、它还能够作为一个消息队列.....

    Redis的应用场景很多很多,以上也只是列举了一部分而已,由于本文是围绕我的开源项目perfect-ssm来写的,所以在本文的场景就是一个缓存中间层,对于读多写少的应用场景,我们经常使用缓存来进行优化以提高系统性能。

    我曾经写过一篇《一次线上Mysql数据库崩溃事故的记录》的文章,里面记录了Web请求是如何毫不留情的摧垮mysql数据库,进而导致网站应用无法正常运转。当时的情况就是数据库读请求太多,事故的主要原因也是这个,后续的解决方案也就是在项目中添加缓存层,使得热点数据得以存入缓存,不会重复的去读取mysql,将大部分请求压力转移至Redis缓存中以减轻mysql的负担。

    2

    接入缓存后的处理逻辑

    请求过来后,首先判断Redis里面有没有,有数据则直接返回Redis中的数据给用户,没有则查询数据库,如果数据库中也没有则返回空或者提醒语句即可。

    当然,针对不同的操作,对于Redis和mysql的操作也是不同的:

    添加操作

    如果是需要放入缓存的数据,那么在向mysql数据库中插入成功后,生成对应的key至,并存入Redis中。

    修改操作

    向mysql数据库中修改成功后,修改Redis中的数据,但是Redis并没有更新语句,所以只能先删除,再添加完成更新操作。

    需要注意的是,考虑到程序对于Redis的操作可能会失败,这时mysql中的数据已经修改,但是Redis中的数据依然是上一次的数据,导致数据不一致的问题,所以是先操作Redis还是先操作mysql需要慎重考虑。

    删除操作

    与修改操作相同,先删除数据,再更新缓存,但是同样会有出现数据不一致问题的可能性需要注意,如果数据库中的数据删除了,但是Redis中的数据没删除,又会出现业务问题。

    查询操作

    首先通过Redis查询,如果缓存中已经存在数据则直接返回即可,此时就不再需要通过mysql数据库来获取数据,减少对mysql的请求,如果缓存中不存在数据,则依然通过mysql数据库查询,查询到数据后,存入Redis缓存中。

    本项目中的代码是先操作mysql,再操作Redis,有概率会出现上文中提到的数据库与缓存数据不一致的情况,所以需要注意,本文的代码只做参考,用到实际项目中还是需要根据具体的业务逻辑进行合理的修改。

    使用缓存的建议

    缓存存储策略:

    可以缓存的数据的特征基本上是以下几点:

    • 热点数据
    • 实时性要求不高的数据
    • 业务逻辑简单的数据

    至于什么数据,不同的系统、不同的项目要求肯定不同,这里不做过多讨论,只简单的说一下自己的想法,结合以上的特征总结如下:

    • 1.首页数据、分类数据这些数据属于热点数据,首页数据更是热得发烫,而且这类数据一般实时性不高,不会频繁的去操作,比较适合放入缓存。
    • 2.详情数据,比如文章详情、商品详情、广告详情、个人信息详情,这些数据库中单条的的数据可以以其id生成不同的key保存到Redis,操作比较简单明了,在更新或者删除的时候需要同步更新Redis中的数据,这类数据也适合放入缓存中。
    • 3.列表数据不是特别推荐,除非是实时性和改变频率真的很低的情况下,因为列表往往牵涉的数据和操作很多,处理起来比较复杂,如果对实时性要求低的话、或者部分字段更新频率低的话,可以换成这部分数据。

    缓存存储策略的制定说难也难,说容易也容易,主要是根据具体的业务场景合理的操作即可,以上只是做了一个简单的总结。

    缓存失效策略:

    失效策略一定要做好,血的教训。

    • 定时删除

    含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除

    优点:保证内存被尽快释放

    缺点:
    若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
    定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重

    • 惰性删除

    含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期则删除,返回null。

    优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)

    缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)

    • 定期删除

    含义:每隔一段时间执行一次删除过期key操作

    优点:
    通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
    定期删除过期key--处理"惰性删除"的缺点

    缺点
    在内存友好方面,不如"定时删除"
    在CPU时间友好方面,不如"惰性删除"
    难点
    合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

    参考《Redis设计与实现》

    缓存操作顺序策略:

    在上文中已经讲到了操作顺序的问题,是先操作mysql呢?还是先操作Redis呢?这个需要根据自己的业务逻辑来考量,尽量选择影响较小且结合友好的方案来做。

    代码实现:

    这里只贴出主要的逻辑代码,想要完整实现的可以到代码仓库去取。

    //添加
    @Override
        public int addArticle(Article article) {
            if (articleDao.insertArticle(article) > 0) {
                log.info("insert article success,save article to Redis");
                RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
                return 1;
            }
            return 0;
        }
    
    //修改
        @Override
        public int updateArticle(Article article) {
            if (article.getArticleTitle() == null || article.getArticleContent() == null || getTotalArticle(null) > 90 || article.getArticleContent().length() > 50000) {
                return 0;
            }
            if (articleDao.updArticle(article) > 0) {
                log.info("update article success,delete article in Redis and save again");
                RedisUtil.del(Constants.ARTICLE_CACHE_KEY + article.getId());
                RedisUtil.put(Constants.ARTICLE_CACHE_KEY + article.getId(), article);
                return 1;
            }
            return 0;
        }
    
    //删除
        @Override
        public int deleteArticle(String id) {
            RedisUtil.del(Constants.ARTICLE_CACHE_KEY + id);
            return articleDao.delArticle(id);
        }
    
    //查询
        @Override
        public Article findById(String id) {
            log.info("get article by id:" + id);
            Article article = (Article) RedisUtil.get(Constants.ARTICLE_CACHE_KEY + id, Article.class);
            if (article != null) {
                log.info("article in Redis");
                return article;
            }
            Article articleFromMysql = articleDao.getArticleById(id);
            if (articleFromMysql != null) {
                log.info("get article from mysql and save article to Redis");
                RedisUtil.put(Constants.ARTICLE_CACHE_KEY + articleFromMysql.getId(), articleFromMysql);
                return articleFromMysql;
            }
            return null;
        }
    

    结语

    首发于我的个人博客,新的项目演示地址:perfect-ssm,登录账号:admin,密码:123456

    如果有问题或者有一些好的创意,欢迎给我留言,也感谢向我指出项目中存在问题的朋友。

    如果你想继续了解该项目可以查看整个系列文章Spring+SpringMVC+MyBatis+easyUI整合系列文章,也可以到我的GitHub仓库或者开源中国代码仓库中查看源码及项目文档。

  • 相关阅读:
    关于C_Sharp集中处理异常
    关于Java连接SQL Sever数据库
    MongoDB 的主键 _id 为什么不是自增数字
    svn 命令行基本操作
    如何删除 Git 仓库中的历史提交记录
    .git 文件太大时怎样处理
    Git 提交到多个远程仓库
    Git SSH keygen 生成与配置
    Git 远端回滚
    Git 合并或修改线上 commit
  • 原文地址:https://www.cnblogs.com/han-1034683568/p/8406497.html
Copyright © 2011-2022 走看看