Skip to content

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 时,需要注意以下几个方面:

  1. 缓存穿透 缓存穿透 是指客户端发送的请求在 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");
  1. 缓存雪崩 缓存雪崩 是指当缓存中的大量数据在同一时间内失效,导致大量请求直接访问数据库,从而对数据库造成压力甚至宕机。为了避免缓存雪崩,可以设置不同的过期时间、设置数据不同时刻失效、使用多级缓存等。

示例:

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");
  1. 缓存击穿 缓存击穿 是指当缓存中不存在的数据被大量请求访问,导致请求直接访问数据库,从而对数据库造成压力甚至宕机。为了避免缓存击穿,可以使用互斥锁、设置热点数据、限流等方法。

示例:

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();  
}
  1. 分布式缓存 在分布式系统中,需要注意 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");
  1. 数据一致性 在分布式场景下,确保 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();
  1. 监控与优化 持续关注 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