Java 缓存简介

发布于 更新于
本文目录
  1. 缓存(Cache)简介
  2. 为什么要使用缓存
  3. 缓存在各种Web形态项目中有广泛应用
  4. Java后端开发中有哪些缓存
  5. 本地缓存
  6. 分布式缓存
  7. 主要概念
  8. Spring缓存框架
  9. 概述
  10. 声明式缓存
  11. 编程式缓存
  12. SpringBoot中使用缓存
  13. 概述
  14. SpringBoot中使用Redis
  15. 使用方式
  16. 声明式使用
  17. 编程式使用

技术文章测试开发

位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存

缓存(Cache)简介

  • 位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存

  • 典型的如CPU与内存之间L1、L2、L3缓存,能让CPU更加聪明、更高效的执行任务

  • 在软件项目中,相比于访问网络、磁盘、DB等介质或设备,内存具有更高的效率,所以很多的时候会利用内存作为缓存载体,以提高软件的性能

为什么要使用缓存

image.png

缓存在各种Web形态项目中有广泛应用

image.png

Java后端开发中有哪些缓存

本地缓存

  • 成员变量或局部变量:像Map或List系统的,可以将数据库中的一些数据加载出来,在内存中做一些复杂的处理,不要每次都访问数据库,减少数据库的IO;但受JVM的堆大小和GC影响

  • 全局变量或静态变量:能实现跨类或整个系统常用数据的缓存,比如一些基础的字典数据,如城市列表等,能快速访问;同样受JVM的堆大小和GC影响

  • ConcurrentHashMap:使用Spring缓存框架的默认缓存机制,简单、易用

  • EhCache:纯Java开源缓存框架,配置简单、结构清晰、功能强大,是一个非常轻量级的缓存实现

分布式缓存

  • Memcached:应用较广的开源分布式缓存产品之一,它本身其实不提供分布式解决方案,是通过客户端Hash算法取相应的服务器节点ID形式实现分布式;采用将内存分Page和Chunk形式分配缓存空间

  • Redis:是一个远程内存数据库(非关系型数据库),具有高性能、丰富数据类型、操作原子性、可持久化等特点

  • ...

主要概念

  • 命中率:从缓存中读取的次数/总的读取次数,命中率越高,表示缓存设计的越好;所以缓存尽量针对更更新频率低读取频率高的数据,缓存数据颗粒度尽量小

  • 缓存更新策略:由于缓存都是放在内存中,而内存相比如硬盘等介质,其容量有限,所以必须定期的清理未过期的数据;常用的缓存更新策略有:

    • FIFO(First In First Out):先进先出,最简单的缓存更新策略

    • LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除,借助计数器实现

    • LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除,借助计数器和队列实现,Redis采用类似算法

  • 常用指标:标记缓存数据项过期的度量指标

    • TTL(Time To Live ):存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)

    • TTI(Time To Idle):空闲期,即一个缓存数据项多久没被访问将从缓存中移除的时间

  • 最大缓存容量:一般的情况下越大越好,但需要配合更好的算法来实现

Spring缓存框架

概述

  • Spring主要使用CacheManager和Cache来统一不同的缓存技术,CacheManager提供各种缓存技术的抽象,Cache接口则包含了对缓存的操作抽象

  • 同时,针对不同的缓存技术,还提供了对CacheManager实现,如ConcurrentMapCacheManager、EhCacheCacheManager、ReidsCacheManager等

  • 是针对所有缓存产品的一种通用处理机制

  • 一般分为声明式缓存和编程式缓存

声明式缓存

  • Spring提供了一些注解来声明缓存规则

  • @EnableCaching:声明式缓存启用标识,标识在启动类或配置类上,让声明式缓存生效

  • @CacheConfig:缓存配置,标识类,标识类中缓存的一些配置,如缓存名称

  • @Cacheable:新增缓存,标识方法;如果指定缓存key存在缓存,直接取缓存;如果不存在,执行方法,并将返回数据使用指定缓存key存入缓存

  • @CachePut:更新缓存,标识方法;执行方法,并将方法返回内容更新指定缓存key对应缓存

  • @CacheEvict:删除缓存,标识方法;执行方法,并将指定缓存名称或key的缓存删除

  • @Caching:组合使用缓存,标识方法;可组合@Cacheable、@CachePut、@CacheEvict实施在一个方法上

编程式缓存

  • 主要通过CacheManager、Cache两上接口及相应实现类来实现对缓存的操作

  • 与声明式缓存类似,需要定义缓存名称(类似于分组)、Key来实现缓存

  • 相比声明式缓存,更加灵活

SpringBoot中使用缓存

概述

  • 在Spring Boot中,可以使用Spring缓存框架,集成丰富的第三方缓存技术,使用非常简单

  • 一般会通过在application.properties中spring.cache.type配置相应的缓存类型,并附加配置一些相应配置完成缓存的使用

  • 常用缓存类型:

    • simple:内存缓存,使用ConcurrentMapCacheManager管理缓存,缓存数据存储于JVM中的ConcurrentHashMap;一般不可设置过期时间,需要手动删除或依赖于GC回收

    • ehcache:EhCache缓存,使用EhCacheCacheManager管理缓存,缓存数据也存储于JVM,但可落地于磁盘;通过xml可配置过期时间等

    • redis:Redis缓存,使用RedisCacheManager管理缓存,缓存数据存储于Redis缓存服务器 一般结合缓存名称(类似于分组)、缓存Key来确定一个缓存

SpringBoot中使用Redis

使用方式

  1. 在pom.xml中添加spring-boot-starter-cache、spring-boot-starter-data-redis依赖,以及commons-pool2依赖,这个是Lettuce依赖

  2. 在启动类添加@EnableCaching注解

  3. 在application.properties中添加配置:spring.cache.type=redis,并添加redis相关的配置,包括连接的服务器ip、端口、过期时间等,具体见示例

  4. 在程序中使用声明式缓存、编程式缓存方式进行操作

声明式使用

  • 使用方式如下代码片断:

    package com.lemon.demo.springboot5redis.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.lemon.demo.springboot5redis.dto.common.ResponseData;
    import com.lemon.demo.springboot5redis.dto.input.grade.GradeCreateInputDto;
    import com.lemon.demo.springboot5redis.dto.input.grade.GradeInputDto;
    import com.lemon.demo.springboot5redis.dto.output.grade.GradeOutputDto;
    import com.lemon.demo.springboot5redis.entity.GradeEntity;
    import org.springframework.cache.annotation.CacheConfig;
    import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.cache.annotation.CachePut;
    import org.springframework.cache.annotation.Cacheable;
    
    import java.util.List;
    
    /**
     * 等级服务
     * 【缓存】4、使用缓存,声明式使用
     */
    //【缓存】配置缓存名称
    @CacheConfig(cacheNames = "grade")
    public interface GradeService extends IService<GradeEntity> {
        /**
         * 分页查询
         * @param pageIndex
         * @param pageSize
         * @param name
         * @return
         */
        ResponseData<List<GradeOutputDto>> query(Long pageIndex, Long pageSize, String name);
    
        /**
         * 查询所有
         * @return
         */
        ResponseData<List<GradeOutputDto>> queryAll();
    
        /**
         * 根据Id获取
         * @param id
         * @return
         */
        //【缓存】添加缓存,使用第1个参数做为缓存的key,这里使用了简单的SpEL(Spring Expression Language)表达式
        // unless表示除非...某个条件才不缓存,在这里,只有返回数据不为null才缓存
        //#result表示取返回值
        @Cacheable(key = "#p0" , unless = "#result.data==null")
    //    @Cacheable(key = "#id")  //相同效果
        ResponseData<GradeOutputDto> getById(Integer id);
    
        /**
         * 创建
         * @param inputDto
         * @return
         */
        ResponseData create(GradeCreateInputDto inputDto);
    
        /**
         * 更新
         * @param inputDto
         * @return
         */
        //【缓存】删除缓存,使用第1个参数的id做为要删除缓存的key
        @CacheEvict(key = "#p0.id")
        ResponseData update(GradeInputDto inputDto);
    
        /**
         * 删除
         * @param id
         */
        //【缓存】删除缓存,使用第1个参数做为要删除缓存的key
        @CacheEvict(key = "#p0")
        ResponseData<Boolean> delete(Integer id);
    }
    

编程式使用

  • 使用方式如下面代码片断:

public ResponseData<List<GradeOutputDto>> queryAll() {
        ResponseData<List<GradeOutputDto>> responseData = new ResponseData<>();
        try {
            //返回DTO引用
            List<GradeOutputDto> gradeOutputDtos = null;
            //【缓存】根据缓存Key获取缓存,如果存在,从缓存中获取数据
            if(redisTemplate.hasKey(CACHE_KEY_FOR_ALL_GRADE) == true){
                //根据Key获取缓存信息
                gradeOutputDtos = (List <GradeOutputDto>)redisTemplate.opsForList().range(CACHE_KEY_FOR_ALL_GRADE,0,redisTemplate.opsForList().size(CACHE_KEY_FOR_ALL_GRADE));
            }
            //如果不存在,则从数据库中获取
            if(gradeOutputDtos == null) {
                //查询
                List<GradeEntity> gradeEntities = this.list();
                //Entity转Dto
                gradeOutputDtos = gradeEntities.stream().map(s->modelMapper.map(s,GradeOutputDto.class)).collect(Collectors.toList());

                //【缓存】从数据库获取后,将数据缓存到Redis
                redisTemplate.opsForList().leftPushAll(CACHE_KEY_FOR_ALL_GRADE,gradeOutputDtos);
                //【缓存】设置过期时间,使用动态过期时间
                redisTemplate.expire(CACHE_KEY_FOR_ALL_GRADE,redisTemplateUtil.getExpire(CACHE_NAME));
            }

            responseData = ResponseData.success(gradeOutputDtos);
        } catch (Exception ex) {
            responseData = ResponseData.failure(ex.getMessage());
            ex.printStackTrace();
        }

        return responseData;
    }

来源:testingpai

继续阅读