Appearance
redis缓存如何处理
项目配置
在xbase里面,项目连接redis的配置,在application.yml或者application-xxx.yml配置文件
yaml
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password:
lettuce:
pool:
max-active: 100
max-idle: 10
max-wait: 100000
cluster:
refresh:
adaptive: true
period: 20
timeout: 0
database: 2
spring:
data:
redis:
host: 127.0.0.1
port: 6379
password:
lettuce:
pool:
max-active: 100
max-idle: 10
max-wait: 100000
cluster:
refresh:
adaptive: true
period: 20
timeout: 0
database: 2
在xbase-common包里面的cn.xplaza.common.framework.config.RedisConfig中配置了一些相关配置
java
package cn.xplaza.common.framework.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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(connectionFactory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
//key haspmap序列化
template.setHashKeySerializer(redisSerializer);
return template;
}
}
package cn.xplaza.common.framework.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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(connectionFactory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
//key haspmap序列化
template.setHashKeySerializer(redisSerializer);
return template;
}
}
service
在xbase-common包的cn.xplaza.common.middleware.service中提供了相关的操作方法
java
package cn.xplaza.common.middleware.service;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import cn.xplaza.common.tool.JacksonTool;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class RedisService {
private final StringRedisTemplate stringRedisTemplate;
/**
* 分布式锁过期时间,单位秒
*/
private static final Long DEFAULT_LOCK_EXPIRE_TIME = 60L;
String checkKeyRestraint(String key){
// TODO: 限制只能使用字母、数字、特定连接符(":",".","-"),长度限制512
return key.toLowerCase();
}
public void putStr(String dir, String key, String value, int minute) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.opsForValue().set(finalKey, value, minute, TimeUnit.MINUTES);
}
public String getStr(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
return stringRedisTemplate.opsForValue().get(finalKey);
}
public void delStr(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.delete(finalKey);
}
public void putObject(String dir, String key, Object value, int minute) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.opsForValue().set(finalKey, JacksonTool.toJson(value), minute, TimeUnit.MINUTES);
}
public <T> T getObject(String dir, String key, Class<T> type) {
String finalKey = checkKeyRestraint(dir + ":" + key);
String json = stringRedisTemplate.opsForValue().get(finalKey);
return JacksonTool.fromJson(json, type);
}
public boolean deleteObject(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
return Boolean.TRUE.equals(stringRedisTemplate.delete(finalKey));
}
/**
* 生成序列ID,如果key不存在并且初始化值没有传,则会返回null
*
* @param key
* @param initValue 初始化值
* @return
*/
public Long getNextSequenceId(String key, Long initValue) {
if (!stringRedisTemplate.hasKey(key) && initValue == null) {
return null;
}
RedisAtomicLong counter = null;
if (initValue != null) {
counter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory(), initValue);
} else {
counter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
}
counter.expire(12, TimeUnit.HOURS);
return counter.getAndIncrement();
}
/**
* 尝试在指定时间内加锁
*
* @param key
* @param value
* @param timeout 锁等待时间
* @return
*/
public boolean tryLock(String key, String value, Duration timeout) {
long waitMills = timeout.toMillis();
long currentTimeMillis = System.currentTimeMillis();
do {
boolean lock = lock(key, value, DEFAULT_LOCK_EXPIRE_TIME);
if (lock) {
return true;
}
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
Thread.interrupted();
}
} while (System.currentTimeMillis() < currentTimeMillis + waitMills);
return false;
}
/**
* 直接加锁
*
* @param key
* @param value
* @param expire
* @return
*/
public boolean lock(String key, String value, Long expire) {
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<Long>(luaScript, Long.class);
Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expire));
return result.equals(Long.valueOf(1));
}
/**
* 释放锁
*
* @param key
* @param value
* @return
*/
public boolean releaseLock(String key, String value) {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
return result.equals(Long.valueOf(1));
}
}
package cn.xplaza.common.middleware.service;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import cn.xplaza.common.tool.JacksonTool;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class RedisService {
private final StringRedisTemplate stringRedisTemplate;
/**
* 分布式锁过期时间,单位秒
*/
private static final Long DEFAULT_LOCK_EXPIRE_TIME = 60L;
String checkKeyRestraint(String key){
// TODO: 限制只能使用字母、数字、特定连接符(":",".","-"),长度限制512
return key.toLowerCase();
}
public void putStr(String dir, String key, String value, int minute) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.opsForValue().set(finalKey, value, minute, TimeUnit.MINUTES);
}
public String getStr(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
return stringRedisTemplate.opsForValue().get(finalKey);
}
public void delStr(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.delete(finalKey);
}
public void putObject(String dir, String key, Object value, int minute) {
String finalKey = checkKeyRestraint(dir + ":" + key);
stringRedisTemplate.opsForValue().set(finalKey, JacksonTool.toJson(value), minute, TimeUnit.MINUTES);
}
public <T> T getObject(String dir, String key, Class<T> type) {
String finalKey = checkKeyRestraint(dir + ":" + key);
String json = stringRedisTemplate.opsForValue().get(finalKey);
return JacksonTool.fromJson(json, type);
}
public boolean deleteObject(String dir, String key) {
String finalKey = checkKeyRestraint(dir + ":" + key);
return Boolean.TRUE.equals(stringRedisTemplate.delete(finalKey));
}
/**
* 生成序列ID,如果key不存在并且初始化值没有传,则会返回null
*
* @param key
* @param initValue 初始化值
* @return
*/
public Long getNextSequenceId(String key, Long initValue) {
if (!stringRedisTemplate.hasKey(key) && initValue == null) {
return null;
}
RedisAtomicLong counter = null;
if (initValue != null) {
counter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory(), initValue);
} else {
counter = new RedisAtomicLong(key, stringRedisTemplate.getConnectionFactory());
}
counter.expire(12, TimeUnit.HOURS);
return counter.getAndIncrement();
}
/**
* 尝试在指定时间内加锁
*
* @param key
* @param value
* @param timeout 锁等待时间
* @return
*/
public boolean tryLock(String key, String value, Duration timeout) {
long waitMills = timeout.toMillis();
long currentTimeMillis = System.currentTimeMillis();
do {
boolean lock = lock(key, value, DEFAULT_LOCK_EXPIRE_TIME);
if (lock) {
return true;
}
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
Thread.interrupted();
}
} while (System.currentTimeMillis() < currentTimeMillis + waitMills);
return false;
}
/**
* 直接加锁
*
* @param key
* @param value
* @param expire
* @return
*/
public boolean lock(String key, String value, Long expire) {
String luaScript = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<Long>(luaScript, Long.class);
Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value, String.valueOf(expire));
return result.equals(Long.valueOf(1));
}
/**
* 释放锁
*
* @param key
* @param value
* @return
*/
public boolean releaseLock(String key, String value) {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), value);
return result.equals(Long.valueOf(1));
}
}
常见问题及解决事例
在使用 Redis 时,需要注意以下几个方面:
- 缓存穿透 缓存穿透 是指客户端发送的请求在 Redis 缓存和数据库中都无法查询到数据。为了避免大量请求直接访问数据库,可以在 Redis 中设置空对象或者默认值。
示例:
java
// 假设 Redis 中的键为 id,值为对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置空对象作为默认值
redisTemplate.opsForValue().set("1", new Object());
// 当查询不存在的 id 时,返回空对象
Object obj = redisTemplate.opsForValue().get("2");
// 假设 Redis 中的键为 id,值为对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置空对象作为默认值
redisTemplate.opsForValue().set("1", new Object());
// 当查询不存在的 id 时,返回空对象
Object obj = redisTemplate.opsForValue().get("2");
- 缓存雪崩 缓存雪崩 是指当缓存中的大量数据在同一时间内失效,导致大量请求直接访问数据库,从而对数据库造成压力甚至宕机。为了避免缓存雪崩,可以设置不同的过期时间、设置数据不同时刻失效、使用多级缓存等。
示例:
java
// 设置不同的过期时间
redisTemplate.opsForValue().set("1", "hello", 10, TimeUnit.SECONDS);
redisTemplate.opsForValue().set("2", "world", 20, TimeUnit.SECONDS);
// 过了过期时间后,键值对会从缓存中失效
Object obj1 = redisTemplate.opsForValue().get("1");
Object obj2 = redisTemplate.opsForValue().get("2");
// 设置不同的过期时间
redisTemplate.opsForValue().set("1", "hello", 10, TimeUnit.SECONDS);
redisTemplate.opsForValue().set("2", "world", 20, TimeUnit.SECONDS);
// 过了过期时间后,键值对会从缓存中失效
Object obj1 = redisTemplate.opsForValue().get("1");
Object obj2 = redisTemplate.opsForValue().get("2");
- 缓存击穿 缓存击穿 是指当缓存中不存在的数据被大量请求访问,导致请求直接访问数据库,从而对数据库造成压力甚至宕机。为了避免缓存击穿,可以使用互斥锁、设置热点数据、限流等方法。
示例:
java
// 创建一个互斥锁
Lock lock = new ReentrantLock();
// 模拟大量请求访问 Redis
for (int i = 0; i < 100; i++) {
new Thread(() -> {
lock.lock();
try {
// 判断缓存中是否存在该数据
Boolean exists = redisTemplate.hasKey("hot_data");
if (exists) {
Object obj = redisTemplate.opsForValue().get("hot_data");
System.out.println(obj);
}
} finally {
lock.unlock();
}
}).start();
}
// 创建一个互斥锁
Lock lock = new ReentrantLock();
// 模拟大量请求访问 Redis
for (int i = 0; i < 100; i++) {
new Thread(() -> {
lock.lock();
try {
// 判断缓存中是否存在该数据
Boolean exists = redisTemplate.hasKey("hot_data");
if (exists) {
Object obj = redisTemplate.opsForValue().get("hot_data");
System.out.println(obj);
}
} finally {
lock.unlock();
}
}).start();
}
- 分布式缓存 在分布式系统中,需要注意 Redis 集群的搭建和优化,如主从复制、负载均衡、数据分区等。
示例,展示了如何使用 Redis 集群:
java
// 连接 Redis 集群
RedisClusterClient redisClusterClient = RedisClusterClient.create("redis://127.0.0.1:6379");
// 获取集群中的一个节点
RedisClusterNode node = redisClusterClient.getNode("mymaster");
// 设置数据到缓存
redisClusterClient.set("key", "value");
// 获取缓存中的数据
String value = redisClusterClient.get("key");
// 连接 Redis 集群
RedisClusterClient redisClusterClient = RedisClusterClient.create("redis://127.0.0.1:6379");
// 获取集群中的一个节点
RedisClusterNode node = redisClusterClient.getNode("mymaster");
// 设置数据到缓存
redisClusterClient.set("key", "value");
// 获取缓存中的数据
String value = redisClusterClient.get("key");
- 数据一致性 在分布式场景下,确保 Redis 缓存与数据库数据的一致性至关重要。
示例,展示了如何使用 Redis 事务确保数据一致性:
java
// 开启 Redis 事务
Transaction transaction = redisTemplate.startTransaction();
// 执行缓存操作
redisTemplate.opsForValue().set("key", "value");
// 提交事务
transaction.commit();
// 开启 Redis 事务
Transaction transaction = redisTemplate.startTransaction();
// 执行缓存操作
redisTemplate.opsForValue().set("key", "value");
// 提交事务
transaction.commit();
- 监控与优化 持续关注 Redis 的性能监控数据,如内存使用情况、缓存命中率、异常请求等。
示例,展示了如何监控 Redis 性能:
java
// 连接 Redis
RedisUtil redisUtil = new RedisUtil("redis://127.0.0.1:6379");
// 获取 Redis 性能数据
Map<String, String> performanceData = redisUtil.getPerform
// 连接 Redis
RedisUtil redisUtil = new RedisUtil("redis://127.0.0.1:6379");
// 获取 Redis 性能数据
Map<String, String> performanceData = redisUtil.getPerform