ehcacheh缓存

redis -> nosql 数据库
-> 分布式的缓存

1. 缓存简介

斐波那契

提高效率的一种方式, 将计算结果进行缓存

// key 是n  value 是 fbnq(n)
static Map<Integer, Integer> map = new HashMap<>();

// 1 1 2 3 5 8 13 ...
// 递归实现
public static int fbnq(int n) {
    if(n==1 || n==2) {
        return 1;
    }
    // 获取n的 fbnq结果
    Integer value = map.get(n);
    if(value == null) {
        // 首次执行, 将结果放入缓存
        System.out.println("调用了fbnq..." + n);
        value = fbnq(n - 1) + fbnq(n - 2);
        map.put(n, value);
    }
    return value;
}

public static void main(String[] args) {
    int fbnq = fbnq(7);
    System.out.println(fbnq);
}

2. 缓存简单实现

如何实现一个 LRU (最近最少使用 元素,会优先从缓存中踢掉) 缓存

1 2 3 4 5 key

get(1)

2 3 4 5 1 再加入 6

3 4 5 1 6
LRU 是一种缓存淘汰算法:既会考虑元素放入缓存的时间先后(先放入的先淘汰),还会考虑元素的命中次数(命中次数少的应当优先淘汰)

利用 LinkedHashMap 来实现了简单的 LRU 缓存

// 默认模式: accessOrder=false 可以保持元素放入集合时的顺序
//          accessOrder=true 可以根据访问(get方法)调整元素在集合中的顺序, 最新访问的排在最后
// 还要重写 removeEldestEntry 方法, 当方法返回为true 时, 表示要移除最老的元素
LinkedHashMap<String, String> map = new LinkedHashMap(16, 0.75f, true){
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        // 希望集合中放5个元素, 超过5个要移除最老的元素
        if(this.size() <= 5) {
            return false;
        } else {
            return true;
        }
    }
};

缓存分类:

  • 对象缓存 (缓存的是一个个对象)
  • 页面缓存 (缓存的是一张张页面)

3. 对象缓存

常见的缓存实现: ehcache, oscache, jboss cache

ehcache 2.x
ehcache 3.x (*)

3.1 ehcache 缓存

  • 添加 maven 依赖
<!-- cache 的实现 -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.6.3</version>
</dependency>
<!-- cache 的接口 -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>
  • 添加配置文件-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'
        xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
        xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.3.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.3.xsd">
</config>
<cache alias="cache1">
    <!-- key-type: key的类型  value-type: 值的类型 -->
    <key-type>java.lang.String</key-type>
    <value-type>java.lang.String</value-type>

    <!-- 元素放于什么位置 -->
    <resources>
        <heap unit="entries">200</heap> <!-- java 堆内存(最快) 其中 entries 是以个数为单位进行缓存, 标签之间的200表示具体数量-->
        <!-- <offheap></offheap>  系统(非堆)内存(速度次之, 但容量大, 可以减轻垃圾回收的压力) -->
        <!--<disk></disk>  磁盘(速度最慢, 容量大, 支持持久化) -->
    </resources>

    <!-- 用来控制元素的过期时间 -->
    <!--<expiry>
        <tti unit="seconds">60</tti>  &lt;!&ndash; time to idle 10:21 放入元素, 10:21:30 使用了元素, 从使用开始算到空闲, 超过60秒&ndash;&gt;
        <ttl unit="seconds">60</ttl> &lt;!&ndash; time to live 10:21 放入元素  <ttl unit="seconds">60</ttl> 从放入开始算活60s&ndash;&gt;
    </expiry>-->
</cache>
  • 创建 cacheManager 缓存管理器
  • 获取 cache 对象(具体缓存)
  • 存、取、移除元素
// 1. 类对象.getResource() 获取类路径上的一个资源文件(url对象)
URL url = GunsApplication.class.getResource("/ehcache.xml");
// 2. 准备配置对象
XmlConfiguration config = new XmlConfiguration(url);
// 3. 创建一个新的缓存管理器类
CacheManager cacheManager = CacheManagerBuilder.newCacheManager(config);

// 4. 初始化缓存管理器
cacheManager.init();

// 5. 获取缓存对象
Cache<String, String> cache = cacheManager.getCache("cache1", String.class, String.class);

// 6. 存储键值
cache.put("beijing", "北京");

// 7. 根据键获取值
System.out.println(cache.get("beijing"));

// 8. 删除键
cache.remove("beijing");
System.out.println(cache.get("beijing"));

// 9. 清空缓存
cache.clear();

3.2 与 springboot 整合

spring:
  cache:
    jcache:
      config: ehcache.xml
spring.cache.jcache.config=ehcache.xml
@EnableCaching  -- 启用缓存功能(内部就会创建 cacheManager(spring中), 并交给spring容器管理)

在其他需要用到缓存的代码中,就可以利用 cacheManager,来使用缓存,例如:

@Autowired
private CacheManager cacheManager;

public Dept detail(@PathVariable("deptId") Long deptId) {
    Cache cache = cacheManager.getCache("cache1");
    Cache.ValueWrapper value = cache.get(String.valueOf(deptId));
    if(value != null) {
        Dept dept = (Dept) value.get();
        return dept;
    } else {
        Dept dept = deptService.getById(deptId);
        cache.put(String.valueOf(deptId), dept);
        return dept;
    }
}

上面的代码虽然实现了缓存功能,但发现有太多重复代码,所以利用aop思想改造它。
spring 已经把缓存的切面类写好了,我们只需要通过一些注解标记哪些方法需要缓存

  • @Cacheable 加在需要将结果进行缓存的方法上

执行流程:

/**
  * 1) 请求过来, 先访问的是 DeptController 的代理对象 (cglib 生成 DeptController 的子类对象作为代理对象)
  * 2) 代理对象重写了 detail, 在 detail 方法内, 通过 缓存管理器 找到名为 cache2 的缓存
  * 3) Cache.get(deptId) 去获取 value, 第一次访问 value 为空, 放行执行真正的 DeptController 的 detail 方法
  * 4) 真正的 DeptController 的 detail 方法返回结果作为 value 放入 cache2 缓存
  *
  * 5) 第二次请求过来, 先访问的是 DeptController 的代理对象
  * 6) 代理对象重写了 detail, 在 detail 方法内, 通过 缓存管理器 找到名为 cache2 的缓存
  * 7) Cache.get(deptId) 去获取 value, 返回不为空, 直接返回缓存中的 value, 没有执行真正的 DeptController 的 detail 方法
  */
@RequestMapping(value = "/detail/{deptId}")
@ResponseBody
@Cacheable("cache2")
public Dept detail(@PathVariable("deptId") Long deptId) {
    return deptService.getById(deptId);
}
  • @CacheEvict - 用来删除某个key value,或者清空整个缓存, 用在执行了数据的增删改时,这三种情况下,都应该让缓存失效
    • 有时,需要将整个缓存清空, 这时使用 @CacheEvict(allEntries=true)
  • @CachePut - 也是加在增删改方法之上,执行增删改方法,把方法的返回值当做value,用它更新缓存的内容
  • key 的生成,默认使用的方法参数作为key,但容易产生key冲突问题
    cache2 /dept/detail/30 30->Dept
    cache3 /user/detail/30 30->User
// 30    dept_30
// 29    dept_29
// #deptId 就是在方法参数中找一个名为 deptId 的参数, 取得值后前面拼接 一个 'dept_' 字符串, 拼接后的结果作为 key
// 这种写法称之为 spring 表达式, SPEL  (spring expression language)
@Cacheable(cacheNames = "cache2", key = "'dept_' + #deptId")

压测工具 jmeter(java语言编写的)

缓存的使用场景

  • 提高系统吞吐量的有效手段
  • 经常查询,但很少修改的数据,需要配置缓存,经常修改的数据,不要配置缓存,反而会影响增删改效率 (读多写少)
  • ehcache 经常用于本地缓存(单机)
  • 如果数据要被多台机器共享访问,需要用到分布式缓存 redis
  • 要注意数据一致性问题
    • 考虑性能,不用加锁,能够保证最终一致性即可(中间会有一小段时间读取到旧数据)
    • 对一致性要求高,加锁,但性能会受到影响

4. 页面静态化

把页面从动态页面,变为静态页面(静态页面会具备各种各样的缓存功能)

之前配置 springmvc.xml

<beans>
    <!-- 视图解析器 -->
    <mvc:view-resolvers>
        <mvc:jsp prefix="" suffiex=""/>
    </mvc:view-resolvers>
    <!-- 拦截器 -->
    <!-- 静态资源配置 -->
    <mvc:default-servlet-handler/>
    <!-- 上传文件解析器 -->
    <mvc:resources mapping="url路径" location="服务器磁盘路径"/> <!-- 把一个web路径映射到服务器上的一个静态资源 -->
</beans>

通过java 类配置

@Configuration // 表示他是一个配置类
public class MyWebConfig implements WebMvcConfigurer { // 这个接口中有一些抽象方法,分别对应xml中的各项配置
    // <!-- 静态资源配置 -->
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    // <!-- 拦截器 -->
    public void addInterceptors(InterceptorRegistry registry) {

    }
    ...
}

4.1 浏览器缓存

// 最强,离最终使用者越近,缓存带来的性能提升越高

// 调试快捷键 f5-普通刷新 ctrl+f5-强制刷新

  1. 第一次请求,从服务器得到了一个静态页面,把静态页面放入浏览器的缓存
  2. 后续访问这个页面,不会发送请求给服务器,直接从浏览器的缓存中获取这个静态页面
  3. 点击 f5 刷新,仍然会从服务器获取
  4. html 缓存有一个 max-age的最大存活期,超过了这个时间就会绕过缓存去找服务器了

4.2 cdn 缓存

网络请求静态资源时,常常不需要真正到达最终服务器,会找到一个离自己最近的cdn服务器,由cdn服务器返回结果
cdn中缓存的大部分都是静态资源(图片,html等)

4.3 304

请求确实发送给服务器了,但服务器发现网页并没有发生变化,返回一个304状态码(告诉浏览器,这个网页没有被修改,请从自己缓存中获取网页),网页的正文并没有传输

4.4 localstorage

html5 引入的一个js对象
服务器如果返回的是json, 希望缓存这些数据的话,可以将其存入 localStorage 中,即使浏览器关闭,localStorage中的内容仍然存在

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值