GitHub:https://github.com/ZHENFENG13
简介
这是一篇关于 Redis 使用的总结类型文章, 会先简单的谈一下缓存的应用场景缓存的使用逻辑及注意事项, 然后是 Redis 缓存与数据库间结合以进行系统优化, 当然文章的最后也会给出具体的代码实现, 不至于看到文章的你一头雾水, 理论要讲, 项目代码也要分享, 这是我写博客的基本出发点
应用场景
Redis 能做什么呢?
这是个好问题, 不同的人可能会给出不同的答案, 因为它的应用场景真的很多, 作为一个优秀的 nosql 数据库可以结合其他产品做很多事情, 比如: tomcat 集群的 session 同步与 nginx 和 lua 结合做限流工具基于 Redis 的分布式锁实现分布式系统唯一主键生成策略秒杀场景中也会看到它它还能够作为一个消息队列.....
Redis 的应用场景很多很多, 以上也只是列举了一部分而已, 由于本文是围绕我的开源项目 perfect-ssm 来写的, 所以在本文的场景就是一个缓存中间层, 对于读多写少的应用场景, 我们经常使用缓存来进行优化以提高系统性能
我曾经写过一篇一次线上 Mysql 数据库崩溃事故的记录的文章, 里面记录了 web 请求是如何毫不留情的摧垮 mysql 数据库, 进而导致网站应用无法正常运转当时的情况就是数据库读请求太多, 事故的主要原因也是这个, 后续的解决方案也就是在项目中添加缓存层, 使得热点数据得以存入缓存, 不会重复的去读取 mysql, 将大部分请求压力转移至 Redis 缓存中以减轻 mysql 的负担
接入缓存后的处理逻辑
请求过来后, 首先判断 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 仓库或者开源中国代码仓库中查看源码及项目文档
来源: https://www.cnblogs.com/han-1034683568/p/8406497.html