Redis

未匹配的标注

Spring Boot 集成 Redis 完整指南

Redis 是一个开源的内存数据结构存储系统,常用作数据库、缓存和消息代理。在 Spring Boot 中集成 Redis 可以显著提升应用性能。以下是完整的集成指南。

  1. 添加依赖

pom.xml 中添加 Redis 相关依赖:

<!-- Spring Data Redis 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 连接池依赖(推荐) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- 如果需要使用JSON序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
  1. 基本配置

application.propertiesapplication.yml 中配置 Redis:

# Redis基本配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourpassword
spring.redis.database=0

# 连接池配置
spring.redis.lettuce.pool.enabled=true
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.shutdown-timeout=100ms

YAML 格式:

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword
    database: 0
    lettuce:
      pool:
        enabled: true
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
      shutdown-timeout: 100ms
  1. Redis 配置类

创建自定义配置类来配置 RedisTemplate 和序列化方式:

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;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // 使用GenericJackson2JsonRedisSerializer来序列化和反序列化redis的value值
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        template.afterPropertiesSet();
        return template;
    }
}
  1. 基本操作示例

4.1 注入 RedisTemplate

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 设置缓存
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 设置缓存并设置过期时间
    public void setWithExpire(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 获取缓存
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 删除缓存
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    // 判断key是否存在
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    // 设置过期时间
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    // 获取剩余过期时间
    public Long getExpire(String key, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }
}

4.2 使用示例

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/redis")
public class RedisController {

    @Autowired
    private RedisService redisService;

    @PostMapping("/set")
    public String set(@RequestParam String key, @RequestParam String value) {
        redisService.set(key, value);
        return "设置成功";
    }

    @GetMapping("/get")
    public Object get(@RequestParam String key) {
        return redisService.get(key);
    }

    @PostMapping("/setWithExpire")
    public String setWithExpire(@RequestParam String key, 
                               @RequestParam String value,
                               @RequestParam long timeout) {
        redisService.setWithExpire(key, value, timeout, TimeUnit.SECONDS);
        return "设置成功,过期时间:" + timeout + "秒";
    }

    @DeleteMapping("/delete")
    public Boolean delete(@RequestParam String key) {
        return redisService.delete(key);
    }
}
  1. 高级功能

5.1 使用 Hash 结构

// 在RedisService中添加方法

// 设置Hash结构
public void hSet(String key, String hashKey, Object value) {
    redisTemplate.opsForHash().put(key, hashKey, value);
}

// 获取Hash结构
public Object hGet(String key, String hashKey) {
    return redisTemplate.opsForHash().get(key, hashKey);
}

// 获取整个Hash
public Map<Object, Object> hGetAll(String key) {
    return redisTemplate.opsForHash().entries(key);
}

// 删除Hash中的某个键
public Long hDelete(String key, Object... hashKeys) {
    return redisTemplate.opsForHash().delete(key, hashKeys);
}

5.2 使用 List 结构

// 在RedisService中添加方法

// 左推入List
public Long lLeftPush(String key, Object value) {
    return redisTemplate.opsForList().leftPush(key, value);
}

// 右推入List
public Long lRightPush(String key, Object value) {
    return redisTemplate.opsForList().rightPush(key, value);
}

// 获取List范围
public List<Object> lRange(String key, long start, long end) {
    return redisTemplate.opsForList().range(key, start, end);
}

// 获取List长度
public Long lSize(String key) {
    return redisTemplate.opsForList().size(key);
}

5.3 使用 Set 结构

// 在RedisService中添加方法

// 添加Set元素
public Long sAdd(String key, Object... values) {
    return redisTemplate.opsForSet().add(key, values);
}

// 获取Set所有成员
public Set<Object> sMembers(String key) {
    return redisTemplate.opsForSet().members(key);
}

// 判断是否是Set成员
public Boolean sIsMember(String key, Object value) {
    return redisTemplate.opsForSet().isMember(key, value);
}

// 获取Set大小
public Long sSize(String key) {
    return redisTemplate.opsForSet().size(key);
}

5.4 使用 ZSet (有序集合)

// 在RedisService中添加方法

// 添加ZSet元素
public Boolean zAdd(String key, Object value, double score) {
    return redisTemplate.opsForZSet().add(key, value, score);
}

// 获取ZSet范围
public Set<Object> zRange(String key, long start, long end) {
    return redisTemplate.opsForZSet().range(key, start, end);
}

// 获取ZSet范围(带分数)
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
    return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}

// 获取ZSet中元素的排名
public Long zRank(String key, Object value) {
    return redisTemplate.opsForZSet().rank(key, value);
}

// 获取ZSet中元素的分数
public Double zScore(String key, Object value) {
    return redisTemplate.opsForZSet().score(key, value);
}
  1. 发布/订阅模式

6.1 配置消息监听器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@Configuration
public class RedisPubSubConfig {

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            RedisConnectionFactory connectionFactory,
            MessageListenerAdapter listenerAdapter) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new ChannelTopic("news"));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(RedisMessageReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }
}

6.2 创建消息接收器

import org.springframework.stereotype.Component;

@Component
public class RedisMessageReceiver {

    public void receiveMessage(String message) {
        System.out.println("收到消息: " + message);
    }
}

6.3 发布消息

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class RedisPubService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void sendMessage(String channel, String message) {
        redisTemplate.convertAndSend(channel, message);
    }
}
  1. 分布式锁实现
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 30; // 默认锁过期时间30秒

    /**
     * 获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(可使用UUID)
     * @param expireTime 锁的过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryGetLock(String lockKey, String requestId, long expireTime) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(
                LOCK_PREFIX + lockKey, 
                requestId, 
                expireTime, 
                TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String currentValue = redisTemplate.opsForValue().get(LOCK_PREFIX + lockKey);
        if (currentValue != null && currentValue.equals(requestId)) {
            redisTemplate.delete(LOCK_PREFIX + lockKey);
            return true;
        }
        return false;
    }

    /**
     * 尝试获取锁,带重试机制
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @param expireTime 锁的过期时间(秒)
     * @param retryTimes 重试次数
     * @param sleepMillis 每次重试间隔(毫秒)
     * @return 是否获取成功
     * @throws InterruptedException
     */
    public boolean tryGetLockWithRetry(String lockKey, String requestId, 
                                      long expireTime, int retryTimes, 
                                      long sleepMillis) throws InterruptedException {
        for (int i = 0; i < retryTimes; i++) {
            if (tryGetLock(lockKey, requestId, expireTime)) {
                return true;
            }
            Thread.sleep(sleepMillis);
        }
        return false;
    }
}
  1. 缓存注解使用

Spring Boot 提供了方便的缓存注解:

8.1 启用缓存

在主类上添加 @EnableCaching 注解:

@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

8.2 常用缓存注解

import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 使用缓存,方法执行前先检查缓存
    @Cacheable(value = "user", key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库查询
        return userRepository.findById(id).orElse(null);
    }

    // 更新缓存
    @CachePut(value = "user", key = "#user.id")
    public User updateUser(User user) {
        // 更新数据库
        return userRepository.save(user);
    }

    // 清除缓存
    @CacheEvict(value = "user", key = "#id")
    public void deleteUser(Long id) {
        // 删除数据库记录
        userRepository.deleteById(id);
    }

    // 复杂缓存配置
    @Caching(
        cacheable = {
            @Cacheable(value = "user", key = "#name")
        },
        put = {
            @CachePut(value = "user", key = "#result.id", condition = "#result != null")
        }
    )
    public User getUserByName(String name) {
        // 根据名称查询用户
        return userRepository.findByName(name);
    }
}
  1. 性能优化建议

  2. 合理使用连接池:配置适当的连接池大小

  3. 批量操作:使用 multipipeline 减少网络往返

  4. 序列化优化:选择合适的序列化方式

  5. 键设计:使用合理的键命名规则,避免大键

  6. 过期时间:为缓存设置合理的过期时间

  7. 避免大查询:使用 SCAN 替代 KEYS 命令

  8. Lua脚本:复杂操作用Lua脚本减少网络开销

  9. 常见问题解决

10.1 连接超时

检查网络连接和Redis服务器状态,调整超时配置:

spring.redis.timeout=3000ms

10.2 序列化错误

确保存储和读取使用相同的序列化方式,或使用JSON序列化。

10.3 内存不足

监控Redis内存使用,设置合理的淘汰策略:

# 在Redis服务器配置中设置
maxmemory-policy allkeys-lru

10.4 缓存穿透

使用空值缓存或布隆过滤器:

@Cacheable(value = "user", key = "#id", unless = "#result == null")
public User getUserById(Long id) {
    User user = userRepository.findById(id).orElse(null);
    if (user == null) {
        // 缓存空值,设置较短过期时间
        redisTemplate.opsForValue().set("user:null:" + id, "", 5, TimeUnit.MINUTES);
    }
    return user;
}
  1. 监控与维护

11.1 健康检查

Spring Boot Actuator 提供Redis健康检查:

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

配置 application.properties:

management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always

访问 /actuator/health 查看Redis健康状态。

11.2 监控指标

Spring Boot Micrometer 提供Redis监控指标:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

配置 application.properties:

management.endpoints.web.exposure.include=health,info,metrics,prometheus

访问 /actuator/metrics/redis 查看Redis相关指标。

  1. 集群与哨兵模式配置

12.1 Redis集群配置

spring.redis.cluster.nodes=192.168.1.1:6379,192.168.1.2:6379,192.168.1.3:6379
spring.redis.cluster.max-redirects=3

12.2 Redis哨兵配置

spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.1.1:26379,192.168.1.2:26379,192.168.1.3:26379
spring.redis.sentinel.password=sentinelpassword
  1. 测试

13.1 单元测试

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class RedisTest {

    @Autowired
    private RedisService redisService;

    @Test
    public void testSetAndGet() {
        String key = "test:key";
        String value = "test value";

        redisService.set(key, value);
        String result = (String) redisService.get(key);

        assertEquals(value, result);
    }

    @Test
    public void testExpire() throws InterruptedException {
        String key = "test:expire";
        String value = "will expire";
        long timeout = 2; // 2秒

        redisService.setWithExpire(key, value, timeout, TimeUnit.SECONDS);

        assertTrue(redisService.hasKey(key));
        Thread.sleep(timeout * 1000 + 500); // 等待过期
        assertFalse(redisService.hasKey(key));
    }
}
  1. 最佳实践

  2. 键命名规范:使用冒号分隔,如 user:123:profile

  3. 避免大键:单个键值不宜过大

  4. 合理设置TTL:所有缓存都应设置过期时间

  5. 读写分离:读多写少的数据适合缓存

  6. 缓存雪崩防护:分散过期时间

  7. 本地缓存配合:高频访问数据可配合Caffeine等本地缓存

  8. 监控告警:设置内存和性能监控

通过以上完整指南,您可以在Spring Boot应用中高效地集成和使用Redis,提升应用性能和可扩展性。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
唐章明
讨论数量: 0
发起讨论 查看所有版本


暂无话题~