Redis作redis的二级缓存
一、搭建redis
docker run --name redis-6379 -p 6379:6379 --restart=always --name redis -d redis --requirepass "123456"
二、依赖
<!-- database start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!-- database end -->
三、application.yml
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://url/database characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: 123456
# redis设置
redis:
database: 0 # Redis 数据库索引(默认为 0)
host: 192.168.37.139 # Redis 服务器地址
port: 6379 # Redis 服务器连接端口
password: 123456 # Redis 服务器连接密码(默认为空)
lettuce:
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-idle: 8 # 连接池中的最大空闲连接 默认 8
min-idle: 0 # 连接池中的最小空闲连接 默认 0
四、相关类
package com.sunyo.wlpt.message.bus.service.cache;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.DigestUtils;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author 子诚
* Description:自定义Redis作为mybatis二级缓存实现
* 问题描述:缓存穿透、缓存雪崩
* <p>
* 缓存穿透:就是说利用一些列措施,使得访问避开了缓存,直接访问数据库,使得数据库不堪重负引起的问题。比如(压测)访问数据库中不存在的数据
* 解决方案:读取数据库,不存在;依旧生成对应的key,放到缓存中,但是对应的value是null(mybatis的二级缓存是这样解决的)。
* 下次再次访问的话,就是读取缓存。
* <p>
* 缓存雪崩:是指在某一特殊时刻,缓存中的缓存全部失效,然后这一时刻又有大量的数据库访问,导致数据库不堪重负。
* 解决方案:根据业务的不同设置不同的缓存失效时间。
* 比如:这个项目,做了3个namespace的缓存,其中一个namespace,共有5个Mapper指向它。所以选择使用范围内的随机值,来做缓存失效时间
* <p>
* 时间:2020/8/6 9:37
*/
public class RedisCache implements Cache {
/**
* slf4j的日志记录器
*/
private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
/**
* 用于事务性缓存操作的读写锁
*/
private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 缓存对象的是失效时间,30分钟
*/
private static final long CACHE_TIME_IN_MINT = 30;
/**
* 当前放入缓存的mapper的namespace
*/
private final String id;
/**
* 必须存在构造方法
*/
public RedisCache(String id)
{
this.id = id;
}
/**
* 返回cache唯一标识
*/
@Override
public String getId()
{
return this.id;
}
/**
* 缓存放入值 redis RedisTemplate StringRedisTemplate
*
* @param key hash_key
* @param value hash_value
*/
@Override
public void putObject(Object key, Object value)
{
RedisTemplate redisTemplate = getRedisTemplate();
// 使用redis的hash类型作为缓存存储模型
redisTemplate.opsForHash().put(id.toString(), encryptionKey(key.toString()), value);
// 设置缓存时间
redisTemplate.expire(id.toString(), CACHE_TIME_IN_MINT, TimeUnit.MINUTES);
}
/**
* 缓存中中获取数据
*/
@Override
public Object getObject(Object key)
{
RedisTemplate redisTemplate = getRedisTemplate();
//根据key 从redis的hash类型中获取数据
return redisTemplate.opsForHash().get(id.toString(), encryptionKey(key.toString()));
}
/**
* 注意:这个方法为mybatis保留方法 默认没有实现 后续版本可能会实现
*
* @param key hash_key
* @return
*/
@Override
public Object removeObject(Object key)
{
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key);
return null;
}
@Override
public void clear()
{
logger.info("清空->{}<-缓存", id);
RedisTemplate redisTemplate = getRedisTemplate();
// 清空 namespace
redisTemplate.delete(id.toString());
}
/**
* 用来计算缓存数量
*/
@Override
public int getSize()
{
RedisTemplate redisTemplate = getRedisTemplate();
// 获取hash中key value数量
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
/**
* 封装redisTemplate
*
* @return RedisTemplate
*/
private RedisTemplate getRedisTemplate()
{
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
/**
* 封装一个对key进行md5处理方法
*/
private String encryptionKey(String key)
{
return DigestUtils.md5DigestAsHex(key.getBytes());
}
@Override
public ReadWriteLock getReadWriteLock()
{
return readWriteLock;
}
}
package com.zicheng.cache;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.DigestUtils;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author 子诚
* Description:自定义Redis作为mybatis二级缓存实现
* 时间:2020/8/6 9:37
*/
public class RedisCache implements Cache {
/**
* slf4j的日志记录器
*/
private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
/**
* 用于事务性缓存操作的读写锁
*/
private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 缓存对象的是失效时间,30分钟
*/
private static final long CACHE_TIME_IN_MINT = 30;
/**
* 当前放入缓存的mapper的namespace
*/
private final String id;
/**
* 必须存在构造方法
*/
public RedisCache(String id)
{
this.id = id;
}
/**
* 返回cache唯一标识
*/
@Override
public String getId()
{
return this.id;
}
/**
* 缓存放入值 redis RedisTemplate StringRedisTemplate
*
* @param key hash_key
* @param value hash_value
*/
@Override
public void putObject(Object key, Object value)
{
RedisTemplate redisTemplate = getRedisTemplate();
// 使用redis的hash类型作为缓存存储模型
redisTemplate.opsForHash().put(id.toString(), key.toString(), value);
redisTemplate.expire(id.toString(), CACHE_TIME_IN_MINT, TimeUnit.MINUTES);
}
/**
* 缓存中中获取数据
*/
@Override
public Object getObject(Object key)
{
RedisTemplate redisTemplate = getRedisTemplate();
//根据key 从redis的hash类型中获取数据
return redisTemplate.opsForHash().get(id.toString(), key.toString());
}
/**
* 注意:这个方法为mybatis保留方法 默认没有实现 后续版本可能会实现
*
* @param key hash_key
* @return
*/
@Override
public Object removeObject(Object key)
{
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key);
return null;
}
@Override
public void clear()
{
logger.info("清空->{}<-缓存", id);
RedisTemplate redisTemplate = getRedisTemplate();
// 清空 namespace
redisTemplate.delete(id.toString());
}
/**
* 用来计算缓存数量
*/
@Override
public int getSize()
{
RedisTemplate redisTemplate = getRedisTemplate();
// 获取hash中key value数量
return redisTemplate.opsForHash().size(id.toString()).intValue();
}
/**
* 封装redisTemplate
*
* @return RedisTemplate
*/
private RedisTemplate getRedisTemplate()
{
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
/**
* 封装一个对key进行md5处理方法
*/
private String getKeyToMD5(String key)
{
return DigestUtils.md5DigestAsHex(key.getBytes());
}
@Override
public ReadWriteLock getReadWriteLock()
{
return readWriteLock;
}
}
五、开启二级缓存
在mybatis的Mapper.xml中
<!-- 开启二级缓存 -->
<cache type="com.zicheng.cache.RedisCache"/>
<!-- 关联其他的namespace -->
<cache-ref namespace="com.zicheng.UserMapper""/>
关联其他的namespace:
比如 工资Mapper关联了员工Mapper:员工表执行增删改操作,
不仅员工Mapper(namespace)的缓存清空,工资Mapper的缓存(namespace)也清空
atis的Mapper.xml中
<!-- 开启二级缓存 -->
<cache type="com.zicheng.cache.RedisCache"/>
<!-- 关联其他的namespace -->
<cache-ref namespace="com.zicheng.UserMapper""/>
关联其他的namespace:
比如 工资Mapper关联了员工Mapper:员工表执行增删改操作,
不仅员工Mapper(namespace)的缓存清空,工资Mapper的缓存(namespace)也清空