Spring Boot 集成 Redis 完整指南

Redis 是一个开源的内存数据结构存储系统，常用作数据库、缓存和消息代理。在 Spring Boot 中集成 Redis 可以显著提升应用性能。以下是完整的集成指南。

1. 添加依赖

在 `pom.xml` 中添加 Redis 相关依赖：

```xml
<!-- 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>
```

2. 基本配置

在 `application.properties` 或 `application.yml` 中配置 Redis：

```properties
# 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 格式：

```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
```

3. Redis 配置类

创建自定义配置类来配置 RedisTemplate 和序列化方式：

```java
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;
    }
}
```

4. 基本操作示例

4.1 注入 RedisTemplate

```java
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 使用示例

```java
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);
    }
}
```

5. 高级功能

5.1 使用 Hash 结构

```java
// 在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 结构

```java
// 在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 结构

```java
// 在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 (有序集合)

```java
// 在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);
}
```

6. 发布/订阅模式

6.1 配置消息监听器

```java
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 创建消息接收器

```java
import org.springframework.stereotype.Component;

@Component
public class RedisMessageReceiver {

    public void receiveMessage(String message) {
        System.out.println("收到消息: " + message);
    }
}
```

6.3 发布消息

```java
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);
    }
}
```

7. 分布式锁实现

```java
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;
    }
}
```

8. 缓存注解使用

Spring Boot 提供了方便的缓存注解：

8.1 启用缓存

在主类上添加 `@EnableCaching` 注解：

```java
@SpringBootApplication
@EnableCaching
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
```

8.2 常用缓存注解

```java
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);
    }
}
```

9. 性能优化建议

1. 合理使用连接池：配置适当的连接池大小
2. 批量操作：使用 `multi` 或 `pipeline` 减少网络往返
3. 序列化优化：选择合适的序列化方式
4. 键设计：使用合理的键命名规则，避免大键
5. 过期时间：为缓存设置合理的过期时间
6. 避免大查询：使用 `SCAN` 替代 `KEYS` 命令
7. Lua脚本：复杂操作用Lua脚本减少网络开销

10. 常见问题解决

10.1 连接超时

检查网络连接和Redis服务器状态，调整超时配置：

```properties
spring.redis.timeout=3000ms
```

10.2 序列化错误

确保存储和读取使用相同的序列化方式，或使用JSON序列化。

10.3 内存不足

监控Redis内存使用，设置合理的淘汰策略：

```properties
# 在Redis服务器配置中设置
maxmemory-policy allkeys-lru
```

10.4 缓存穿透

使用空值缓存或布隆过滤器：

```java
@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;
}
```

11. 监控与维护

11.1 健康检查

Spring Boot Actuator 提供Redis健康检查：

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```

配置 `application.properties`:

```properties
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always
```

访问 `/actuator/health` 查看Redis健康状态。

11.2 监控指标

Spring Boot Micrometer 提供Redis监控指标：

```xml
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
```

配置 `application.properties`:

```properties
management.endpoints.web.exposure.include=health,info,metrics,prometheus
```

访问 `/actuator/metrics/redis` 查看Redis相关指标。

12. 集群与哨兵模式配置

12.1 Redis集群配置

```properties
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哨兵配置

```properties
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
```

13. 测试

13.1 单元测试

```java
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));
    }
}
```

14. 最佳实践

1. 键命名规范：使用冒号分隔，如 `user:123:profile`
2. 避免大键：单个键值不宜过大
3. 合理设置TTL：所有缓存都应设置过期时间
4. 读写分离：读多写少的数据适合缓存
5. 缓存雪崩防护：分散过期时间
6. 本地缓存配合：高频访问数据可配合Caffeine等本地缓存
7. 监控告警：设置内存和性能监控

通过以上完整指南，您可以在Spring Boot应用中高效地集成和使用Redis，提升应用性能和可扩展性。