Spring Boot 3 配置 Redis 兼容单例和集群
配置项
Spring Boot 3.x 的 redis 配置和 Spring Boot 2.x 是不一样的, 路径多了一个data
spring:
...
data:
redis:
host: @redis.host@
port: @redis.port@
password: @redis.password@
database: @redis.database@
兼容单例和集群的配置
开发时一般用一个Redis单例就足够, 测试和生产环境再换成集群, 但是在application.yml中默认的 Redis 单例和集群配置格式是不同的, 如果要用同一套格式兼容两种配置, 需要自定义 RedisConnectionFactory 这个bean的初始化.
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
public String host;
@Value("${spring.data.redis.port}")
public int port;
@Value("${spring.data.redis.password}")
public String password;
@Value("${spring.data.redis.database}")
public int database;
@Bean
public RedisTemplate<String, String> redisStringTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public RedisTemplate<String, byte[]> redisBytesTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer<String> redisKeySerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisKeySerializer);
redisTemplate.setHashKeySerializer(redisKeySerializer);
redisTemplate.setValueSerializer(RedisSerializer.byteArray());
redisTemplate.setHashValueSerializer(RedisSerializer.byteArray());
return redisTemplate;
}
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
if (host.contains(",")) {
RedisClusterConfiguration config = new RedisClusterConfiguration(Arrays.asList(host.split(",")));
config.setMaxRedirects(3);
if (password != null && !password.isEmpty()) {
config.setPassword(RedisPassword.of(password));
}
LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
factory.afterPropertiesSet();
return factory;
} else {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPort(port);
config.setDatabase(database);
if (password != null && !password.isEmpty()) {
config.setPassword(RedisPassword.of(password));
}
LettuceConnectionFactory factory = new LettuceConnectionFactory(config);
factory.afterPropertiesSet();
return factory;
}
}
}
这样, 当配置改为集群时, 只需要修改 spring.data.redis.host 的内容为 1.1.1.1:6379,1.1.1.2:6379,1.1.1.3:6379
这样的格式就可以了.
使用 Byte 作为值存储
ByteUtil.java
public class ByteUtil {
public static byte[] toByte(String str) {
if (str == null) return null;
return str.getBytes();
}
public static byte[][] toByte(String[] strs) {
if (strs == null) return null;
byte[][] arr = new byte[strs.length][];
for (int i = 0; i < strs.length; i++) {
arr[i] = strs[i].getBytes();
}
return arr;
}
public static String toString(byte[] bytes) {
return bytes == null ? null : new String(bytes);
}
public static Set<String> toString(Set<byte[]> byteset) {
if (byteset == null) return null;
return byteset.stream()
.map(String::new)
.collect(Collectors.toSet());
}
public static List<String> toStrings(List<byte[]> byteslist) {
if (byteslist == null) return null;
return byteslist.stream()
.map(String::new)
.collect(Collectors.toList());
}
public static byte[] toByte(int x) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(x);
return buffer.array();
}
public static int toInteger(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.put(bytes);
buffer.flip();//need flip
return buffer.getInt();
}
public static byte[] toByte(long x) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(x);
return buffer.array();
}
public static long toLong(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.put(bytes);
buffer.flip();//need flip
return buffer.getLong();
}
public static byte[] toByte(Object object) {
if (object == null) return null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> List<T> toObjs(List<byte[]> byteslist) {
if (byteslist == null) return null;
List<T> list = new ArrayList<>();
for (byte[] bytes : byteslist) {
T t = toObj(bytes);
list.add(t);
}
return list;
}
@SuppressWarnings("unchecked")
public static <T> T toObj(byte[] bytes) {
if (bytes == null || bytes.length < 8) return null;
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return (T)ois.readObject();
} catch (IOException|ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
在服务中的调用方式
@Autowired
private RedisTemplate<String, byte[]> redisBytesTemplate;
@Override
public Boolean hasKey(String key) {
return redisBytesTemplate.hasKey(key);
}
@Override
public Boolean hashHasKey(String key, String field) {
return redisBytesTemplate.opsForHash().hasKey(key,field);
}
@Override
public Integer hashGetInt(String key, String field) {
HashOperations<String, String, byte[]> opsForHash = redisBytesTemplate.opsForHash();
byte[] bytes = opsForHash.get(key, field);
return bytes == null? null : ByteUtil.toInteger(bytes);
}
@Override
public void hashSetInt(String key, String field, int value) {
HashOperations<String, String, byte[]> opsForHash = redisBytesTemplate.opsForHash();
opsForHash.put(key, field, ByteUtil.toByte(value));
}
@Override
public <T> T hashGetObj(String key, String field) {
HashOperations<String, String, byte[]> opsForHash = redisBytesTemplate.opsForHash();
return ByteUtil.toObj(opsForHash.get(key, field));
}
@Override
public <T> void hashSetObj(String key, String field, T value) {
HashOperations<String, String, byte[]> opsForHash = redisBytesTemplate.opsForHash();
opsForHash.put(key, field, ByteUtil.toByte(value));
}
/**
* @param timeout seconds to block
*/
@Override
public <T> T bLPopObj(int timeout, String key) {
ListOperations<String, byte[]> opsForList = redisBytesTemplate.opsForList();
byte[] bytes = opsForList.leftPop(key, timeout, TimeUnit.SECONDS);
return ByteUtil.toObj(bytes);
}
@Override
public <T> Long rPush(String key, T value) {
ListOperations<String, byte[]> opsForList = redisBytesTemplate.opsForList();
return opsForList.rightPush(key, ByteUtil.toByte(value));
}
参考
- https://vincentbogousslavsky.com/post/configuration-for-spring-data-redis-reactive-for-connecting
创建 RedisClusterConfiguration - https://blog.csdn.net/weixin_67601403/article/details/129706748
创建 RedisConnectionFactory lettuceConnectionFactory - https://cloud.tencent.com/developer/article/2371793
默认的级联配置方式