什么是缓存?

缓存就是数据交换的缓冲区,用于临时存储数据(使用频繁的数据)。当用户请求数据时,首先在缓存中寻找,如果找到了则直接返回。如果找不到,则去数据库中查找。缓存的本质就是用空间换时间,牺牲数据的实时性,从而减轻数据库压力,尽可能提高吞吐量,有效提升响应速度。

缓存的分类

缓存的应用范围十分广泛,常见的有文件缓存、浏览器缓存、数据库缓存等等。但今天我们着重关注的是 WEB 应用服务领域,根据缓存与应用的耦合度,可以分为本地缓存和分布式缓存:

  • 本地缓存

    指在应用中的缓存组件,最大的优点是应用和缓存是在同一个进程内部,请求缓存速度快;同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接共享缓存,各应用或集群的各节点都需要维护自己的单独缓存

  • 分布式缓存

    指的是与应用分离的缓存组件或服务,最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接共享缓存

缓存的特点

缓存也是一个数据模型对象,那么必然有它的一些特征:

  • 命中率

    命中率 = 返回正确结果数 / 请求缓存次数,命中率问题是缓存中的一个非常重要的问题,它是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。

  • 最大元素

    缓存中可以存放的最大元素的数量,一旦缓存中元素数量超过这个值,将会触发缓存清空策略。根据不同的场景合理设置最大元素值,可以在一定程度上提高缓存的命中率,从而更有效的利用缓存。

缓存清空策略

缓存的存储空间有限制,当缓存空间被用满时,就需要缓存清空策略来处理。常见的一般策略有:

  • 先进先出策略

    先进入缓存的数据,在缓存空间不足时会被优先被清理掉,以腾出新的空间接受新的数据。先进先出策略主要比较缓存元素的创建时间,在数据实效性要求较高的场景下可选择该类策略,优先保障最新数据可用

  • 最少使用策略

    无论是否过期,根据元素被使用的次数判断,清除使用次数较少的元素。最少使用策略主要比较元素的命中次数,在保证高频数据有效性场景下,可选择该类策略

  • 最近最少使用策略

    无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素。策略算法主要比较元素最近一次被使用的时间。适用于热点数据场景,优先保证热点数据的有效性

此外,还有一些简单策略,比如:

  • 根据过期时间判断,清理过期时间最长的元素
  • 根据过期时间判断,清理最近要过期的元素
  • 随机清理
  • 根据关键字(或元素内容)清理等等

Redis 实现分布式缓存

可以利用 Mybatis 自带的本地缓存,结合 Redis 实现分布式缓存。主要思路是将 Mybatis 二级缓存的存放地点从本地改为配置了 Redis 的远程服务器。

第一步,创建一个 SpringBoot 工程,整合 MyBatis 和 Redis,在 Mapper 文件中加入 <cache/> 标签开启二级缓存。

<cache/> 标签默认采用 PrepetualCache,该类是 Cache 接口的实现类,维护一个 Map 来保存数据。我们要作改造,就要自定义一个实现类并替换 <cache type="xxxx.RedisCache">

实现自定义 RedisCache

public class RedisCache implements Cache {

  	// 当前放入缓存的 mapper 的 namespace,也是缓存的唯一标识
private final String id; public RedisCache(String id) {
System.out.println("id:" + id);
this.id = id;
} /**
* 返回 cache 的唯一标识
*/
@Override
public String getId() {
return this.id;
} /**
* 缓存放入值
*/
@Override
public void putObject(Object key, Object value) {
System.out.println("放入缓存");
// 通过工具类获取 redisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
// 使用 redishash 类型作为缓存存储模型
redisTemplate.opsForHash().put(id.toString(), key.toString(), value);
} /**
* 获取缓存中的值
*/
@Override
public Object getObject(Object key) {
System.out.println("获得缓存");
// 通过工具类获取 redisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
// 根据 key 从 redis 的 hash 类型中获取数据
return redisTemplate.opsForHash().get(id.toString(), key.toString()); } /**
* 根据指定的 key 删除缓存
* 该方法为 mybatis 保留方法,默认没有实现
*/
@Override
public Object removeObject(Object key) {
System.out.println("根据指定的 key 删除缓存");
return null;
} @Override
public void clear() {
System.out.println("清空缓存");
// 通过工具类获取 redisTemplate
RedisTemplate redisTemplate = getRedisTemplate();
// 清空 namespace
redisTemplate.delete(id.toString());
} /**
* 计算缓存数量
*/
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().size(id.toString()).intValue();
} /**
* 获取 redisTemplate
*/
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}

到此为止,使用 Redis 实现分布式缓存的目标就完成了。还有一点要注意的是,涉及到多表查询时,结果会包含另一个表的对象信息。由于 Mybatis 二级缓存清理只会清理自身 namespace 的缓存,所以被包含的对象信息不会被清理。如果此时表信息发生改变,将导致数据不一致。解决办法是每个 namespace 都使用同一个缓存

<!-- 共享其他 namespace 的缓存 -->
<cache-ref namespace="..."/>

缓存穿透(击穿)

客户端查询了一个数据库中没有的数据,导致缓存在这种情况下无法利用(数据库都没有则缓存更不可能有了)。此情况下可绕过缓存直接攻击数据库。

对于这种恶意访问,一种思路是先做校验,对恶意数据直接过滤掉,不要发送至数据库层;第二种思路是缓存空结果,就是对查询不存在的数据也记录在缓存中,这样就可以有效的减少查询数据库的次数。MyBatis 正是使用了第二种方式。

缓存雪崩

在系统运行的某一时刻,缓存全部失效,恰好这一时刻涌来大量客户端请求,导致数据库阻塞或挂起。导致缓存失效的原因有很多,常见的是缓存到了失效时间(所有的缓存设置了同样的过期时间),而没有作合适的处理。

要解决这个问题,一种方式是设置缓存永久存储(不推荐),另一种方式是针对不同业务数据设置不同的超时时间,防止集体失效。

最新文章

  1. knockout学习笔记目录
  2. Linux简介及常用命令使用3--vi编辑器
  3. centos7 mariadb
  4. discuz被别人评论一段代码,然后页面就变样了
  5. 2.使用JDK开发webService
  6. js 图片处理 Jcrop.js API
  7. AlwaysOn数据同步问题探究
  8. Android 如何解决数据库多线程锁的问题
  9. GBDT(MART) 迭代决策树入门教程 | 简介
  10. Xperf Basics: Recording a Trace(转)
  11. jenkins插件 查看job下次运行时间
  12. Windows 服务开发框架介绍 - Topshelf
  13. EncryptionHelper
  14. flex 3 rows layout
  15. 【转】C# HttpWebRequest\HttpWebResponse\WebClient发送请求解析json数据
  16. andriod 开发记录apidemos 错误解决
  17. FreeBsdb FAMP Lamp环境
  18. Android 显示YUV编码格式
  19. Group Commit of Binary Log
  20. 关于post与get请求参数存在特殊字符问题

热门文章

  1. Linux下启动、关闭SVN服务
  2. 个人项目作业WC(JAVA)
  3. Word操作之参考文献自动关联和引用
  4. python的各种包安装地址
  5. Java I/O体系从原理到应用(非原创)
  6. 从零开始的SpringBoot项目 ( 三 ) 项目打包( jar包篇 )
  7. JS获取时间(当前-过去-未来)
  8. GeneralUpdate2.1.0发布
  9. win环境下安装配置openCV-4.3.0
  10. 解决git add README.md 时报错 fatal: pathspec &#39;README.md&#39; did not match any files