Skip to content

缓存

LuckReport 支持自定义缓存扩展,实现 ReportCache 接口,即可快速接入自定义缓存方案。

针对分布式、集群部署的业务系统,为保证多服务节点间的报表缓存数据一致性,需统一采用分布式缓存方案。下文将以 Redis 分布式缓存为例,详细介绍具体的接入方案。

接口方法说明

ReportCache 接口核心方法:

方法名返回类型说明
disabled()boolean返回是否禁用该缓存(Redis 不可用时返回 true)
get(String key)T根据键获取缓存值
get(String key, Class<T> clazz)T根据键获取缓存值并转换为指定类型
put(String key, T value)void存入缓存(不设置过期时间)
put(String key, T value, long time)void存入缓存并设置过期时间(单位:秒)
exists(String key)boolean判断缓存键是否存在
remove(String key)void删除指定缓存键
keys(String keyPatten)Set<String>根据前缀查询匹配的所有缓存键
setExpire(String key, long time)boolean设置缓存键的过期时间(单位:秒)

添加 Redis 依赖

pom.xml 中添加 Spring Data Redis 依赖:

xml
<!-- Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置文件,禁用本地缓存

application.yml 中添加以下配置,禁用本地缓存

yaml
spring:
  redis:
    host: 127.0.0.1        # Redis 地址
    port: 6379             # Redis 端口
    password:              # 有密码就填,无密码删除此行
    database: 0            # 使用的库编号
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

luck-report:
  # ...
  # 禁用本地缓存,分布式/集群环境必须配置
  disableLocalReportCache: true

注入 Redis 工具类 Bean

创建 Redis 配置类,注入 RedisTemplate Bean,序列化策略采用 JSON

java
package com.luck.product.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis 配置类,用于配置 RedisTemplate
 *
 * @author luckyPools
 * @since 2017年3月8日
 */
@Configuration
public class RedisConfig {

    /**
     * 配置 RedisTemplate Bean。
     * 设置 Key 使用 String 序列化器,Value 使用 JSON 序列化器
     *
     * @param connectionFactory Redis 连接工厂,由 Spring Boot 自动注入
     * @return 配置好的 RedisTemplate 实例
     */
    @Bean("remoteRedisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();

        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

实现 ReportCache 接口

创建 Redis 缓存实现类,实现 com.luck.report.core.cache.ReportCache 接口

java
package com.luck.product.boot.cache;

import com.luck.report.core.cache.ReportCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 缓存服务实现,基于 Spring Data Redis 提供分布式缓存能力
 *
 * @author luckyPools
 * @since 2017年3月8日
 */
@Service
public class RedisCache implements ReportCache {

    private static final Logger log = LoggerFactory.getLogger(RedisCache.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 检查 Redis 缓存服务是否可用。
     *
     * @return Redis 不可用返回 true,可用返回 false
     */
    @Override
    public boolean disabled() {
        try {
            redisTemplate.getConnectionFactory().getConnection().ping();
            return false;
        } catch (Exception e) {
            log.error("检查 Redis 连接状态失败", e);
            return true;
        }
    }

    /**
     * 根据键获取缓存值。
     *
     * @param key 缓存键,不能为空
     * @param <T> 返回值类型
     * @return 缓存值,不存在或已过期返回 null
     */
    @Override
    public <T> T get(String key) {
        if (key == null) {
            return null;
        }
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                return null;
            }
            @SuppressWarnings("unchecked")
            T result = (T) value;
            return result;
        } catch (Exception e) {
            log.error("获取缓存失败,key: {}", key, e);
            return null;
        }
    }

    /**
     * 根据键获取缓存值并转换为指定类型。
     *
     * @param key   缓存键,不能为空
     * @param clazz 目标类型,不能为空
     * @param <T>   返回值类型
     * @return 缓存值,不存在或已过期返回 null
     */
    @Override
    public <T> T get(String key, Class<T> clazz) {
        if (key == null || clazz == null) {
            return null;
        }
        try {
            Object value = redisTemplate.opsForValue().get(key);
            if (value == null) {
                return null;
            }
            if (clazz.isInstance(value)) {
                return clazz.cast(value);
            }
            return null;
        } catch (Exception e) {
            log.error("获取缓存并转换类型失败,key: {}, clazz: {}", key, clazz.getName(), e);
            return null;
        }
    }

    /**
     * 存入缓存,不设置过期时间。
     *
     * @param key   缓存键,不能为空
     * @param value 缓存值,不能为空
     * @param <T>   值类型
     */
    @Override
    public <T> void put(String key, T value) {
        if (key == null || value == null) {
            return;
        }
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.error("存入缓存失败,key: {}", key, e);
        }
    }

    /**
     * 存入缓存,指定过期时间。
     *
     * @param key   缓存键,不能为空
     * @param value 缓存值,不能为空
     * @param time  过期时间,单位:秒
     * @param <T>   值类型
     */
    @Override
    public <T> void put(String key, T value, long time) {
        if (key == null || value == null) {
            return;
        }
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
        } catch (Exception e) {
            log.error("存入缓存失败,key: {}, time: {}", key, time, e);
        }
    }

    /**
     * 判断缓存键是否存在。
     *
     * @param key 缓存键,不能为空
     * @return 存在且未过期返回 true,否则返回 false
     */
    @Override
    public boolean exists(String key) {
        if (key == null) {
            return false;
        }
        try {
            Boolean hasKey = redisTemplate.hasKey(key);
            return hasKey != null && hasKey;
        } catch (Exception e) {
            log.error("判断缓存键是否存在失败,key: {}", key, e);
            return false;
        }
    }

    /**
     * 删除指定缓存键。
     *
     * @param key 缓存键,不能为空
     */
    @Override
    public void remove(String key) {
        if (key == null) {
            return;
        }
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            log.error("删除缓存失败,key: {}", key, e);
        }
    }

    /**
     * 根据前缀查询匹配的所有缓存键。
     *
     * @param keyPatten 缓存键前缀,不能为空
     * @return 匹配的缓存键集合
     */
    @Override
    public Set<String> keys(String keyPatten) {
        if (keyPatten == null) {
            return null;
        }
        try {
            return redisTemplate.keys(keyPatten + "*");
        } catch (Exception e) {
            log.error("查询缓存键失败,keyPatten: {}", keyPatten, e);
            return null;
        }
    }

    /**
     * 设置缓存键的过期时间。
     *
     * @param key  缓存键,不能为空
     * @param time 过期时间,单位:秒
     * @return 设置成功返回 true,键不存在或已过期返回 false
     */
    @Override
    public boolean setExpire(String key, long time) {
        if (key == null || time <= 0) {
            return false;
        }
        try {
            Boolean result = redisTemplate.expire(key, time, TimeUnit.SECONDS);
            return result != null && result;
        } catch (Exception e) {
            log.error("设置缓存过期时间失败,key: {}, time: {}", key, time, e);
            return false;
        }
    }
}

配置说明:

配置项说明
disableLocalReportCache设置为 true 禁用本地缓存,分布式环境必须配置

Luck-Report 报表引擎