for the dream

light a fire for the dream

Spring boot 中缓存的使用

1.内存缓存的使用

1.1 自定义HashMap实现

如题所说,自己实现key-value的map,使用定时器管理缓存的清空之类的, 比较麻烦,不建议使用

1.2 使用现有的内存缓存框架

spring引入缓存框架是非常容易的, 详细使用方式可以参考Spring Boot 缓存配置

在之前做的项目中使用的是 caffenie 框架, 该框架为 google guava 框架内缓存模块的升级版本, 提高了性能, 增加了更多的功能, 使用方式可以参考Caffeine 缓存框架

简单介绍一下使用方式

1.2.1 通用配置方式

在application.yml文件中增加配置

  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=%d,expireAfterWrite=%ds

在SpringBoot启动类中增加注解

@EnableCaching
@SpringBootApplication
class Application {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(Application::class.java, *args)
        }
    }
}

在需要被缓存的方法上增加注解

/* 对方法增加缓存 */
@Override
@Cacheable
public List<ConfigAllSite> listConfigAllSite() {
    return configMapper.listConfigAllSite();
}

/* 对类中的所有方法增加缓存 */
@Component
@CacheConfig(cacheNames = CacheNames.ConfigRefer)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//````
}

其中CacheConfig指定了该缓存块的名称,使用其他注解进行更新和清除操作时会用到

1.3 使用redis

内存缓存框架有一定的局限性, 包括分布式缓存以及大数据量缓存的恢复和重建等

redis很好的解决了这些问题, 而且性能也并没有差太多, 比数据库快许多

使用redis的方式

1.3.1 添加依赖

spring-data-redis是spring为redis操作开发的一套框架, 对于redis的简单使用引用这个包就十分方便了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在application.yml中声明

// 主从模式, 配置host,port,password
spring:
  redis:
    host: xxx
    password: xxx
    port: xxx

// cluster模式, 配置集群所有节点host, port
spring:
  redis:
    cluster: 
      nodes: 127.0.0.1:6379,127.0.0.1:6380...
    password: xxx
    port: xxx

我们可能引入一些序列化工具, 因为spring 默认使用 jvmserialiser 来序列化对象, 存入redis的对象必须继承 serialer 接口, 非常不方便,
spring 自带 jackson, 我们可以使用jackson, 也可以使用 alibaba 的 fastjson

使用 jackson

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setDefaultSerializer(serializer);
        return redisTemplate;
    }

}

使用 fastjson (文档), 首先需要引用依赖

<!--https://github.com/alibaba/fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.38</version>
</dependency>

然后配置, 基本同上

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setDefaultSerializer(serializer);
        return redisTemplate;
    }

}

fastjson其他使用方法可以参考文档

2 使用多个缓存时发生冲突的问题

有时可能我们的工程既需要使用redis, 又因为对速度的需求需要使用内存缓存, 这时就需要增加配置来适配多个manager的问题.

一般情况下, 引用了 spring-data-redis 模块后, 在不进行额外配置的情况下spring会默认使用redis的cachemanager作为全局的cachemanager. 也就是@Cacheable相关注解也会使用redis作为管理器, 这是与我们的需求违背的.

首先还是需要增加依赖和相关配置, 如前几点所说, 这里不做叙述

接下来需要增加一个新的 cachemManager

@Configuration
@EnableCaching
public class SimpleCaffeineCacheManager {

    // CacheManager bean name
    public static final String CACHE_MANAGER_BEAN_NAME = "caffeineCacheManager";

    // 最多存储的缓存条数
    private static final int DEFAULT_MAX_SIZE = 20000;
    // 默认过期时间
    private static final int DEFAULT_TTL_SECONDS = 300;

    @Bean
    @Qualifier(CACHE_MANAGER_BEAN_NAME)
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();

        String spec = String.format("maximumSize=%d,expireAfterWrite=%ds", DEFAULT_MAX_SIZE, DEFAULT_TTL_SECONDS);
        CaffeineSpec caffeineSpec = CaffeineSpec.parse(spec);
        manager.setCaffeineSpec(caffeineSpec);

        return manager;
    }

}

然后在其他地方使用@Cacaheable相关主解时, 需要指定CacheManager

@CacheConfig(cacheNames = CacheNames.ConfigRefer, cacheManager = SimpleCaffeineCacheManager.CACHE_MANAGER_BEAN_NAME)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//...
}

这样就解决了多个CacheManager的冲突问题

备注

今天使用时候碰到了个bug, 使用@Cacheable标注类, 在类的方法中使用mybatis的mapper的查询结果作为返回值, 会出现说返回值是单个无法转换为list的bug.

查询了一下发现是由于key的生成策略导致的:

默认的key是通过KeyGenerator生成的,其默认策略如下:
1. 如果方法没有参数,则使用0作为key;
2. 如果只有一个参数的话则使用该参数作为key;
3. 如果参数多于一个则使用所有参数的hashcode作为key;

所以添加了一个keygenerator来避免这个问题

用 Cachemanager 继承 CachingConfigurerSupport 类, 并实现keygenerator方法

public class SimpleCaffeineCacheManager extends CachingConfigurerSupport {
    //...

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName() + " :");
            sb.append(method.getName() + "=-=");
            for (Object object : objects) {
                if (object == null) sb.append("|-|-|");
                else sb.append(object.toString());
            }
            return sb.toString();
        };
    }
}
阅读更多
版权声明:本文为博主原创文章,转载请声明来源。 https://blog.csdn.net/u010797771/article/details/77916931
文章标签: spring 缓存 内存
个人分类: java
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭