Redis环境
参考之前文章《CentOS 7 + Redis 实践哨兵模式一主两从》
Spring Boot集成
创建项目
通过Spring Initializr 创建一个集成项目框架
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kindey</groupId>
<artifactId>redissen</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redissen</name>
<description>redissen</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
data:
redis:
sentinel:
database: 0
password: Redis!23
master: mymaster
nodes: 192.168.88.96:26379,192.168.88.98:26379,192.168.88.156:26379
lettuce:
pool:
max-idle: 10
max-active: 20
min-idle: 0
max-wait: 10000
RedisConfig
package com.kindey.redissen.conf;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import cn.hutool.core.lang.Validator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.CollectionUtils;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
/**
* 作用描述
*
* @author Kindey.S [kindey123@163.com]
* @version 1.0
* @date 2023/9/6
*/
@Configuration
@Slf4j
public class RedisConfig {
@Value("${spring.data.redis.sentinel.database}")
int database;
@Value("${spring.data.redis.sentinel.password}")
String password;
@Value("${spring.data.redis.sentinel.master}")
String master;
@Value("${spring.data.redis.sentinel.nodes}")
List<String> nodes;
@Value("${spring.data.redis.lettuce.pool.max-idle}")
int maxIdle;
@Value("${spring.data.redis.lettuce.pool.max-active}")
int maxActive;
@Value("${spring.data.redis.lettuce.pool.min-idle}")
int minIdle;
@Value("${spring.data.redis.lettuce.pool.max-wait}")
long maxWait;
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
if (StringUtils.isEmpty(master)) {
master = "mymaster";
}
sentinelConfig.setMaster(master);
if (CollectionUtils.isEmpty(nodes)) {
RedisNode redisNode = new RedisNode("127.0.0.1", 26379);
sentinelConfig.addSentinel(redisNode);
}
for (String node : nodes) {
String[] hostInfo = node.split(":");
if (CollectionUtils.isEmpty(Arrays.asList(hostInfo))) {
throw new RuntimeException("哨兵地址不能为空");
} else if (hostInfo.length == 1) {
if(!Validator.isIpv4(hostInfo[0])){
throw new RuntimeException("无效的哨兵IP:"+node);
} else {
RedisNode redisNode = new RedisNode(hostInfo[0], 26379);
sentinelConfig.addSentinel(redisNode);
}
} else if (hostInfo.length == 2){
RedisNode redisNode = new RedisNode(hostInfo[0], Integer.parseInt(hostInfo[1]));
sentinelConfig.addSentinel(redisNode);
} else {
throw new RuntimeException("错误的哨兵连接地址:" + node);
}
}
sentinelConfig.setDatabase(database);
if (StringUtils.isNotEmpty(password)) {
sentinelConfig.setPassword(password);
}
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
if (maxIdle == 0) {
maxIdle = 10;
}
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(minIdle);
if (maxActive == 0) {
maxActive = 20;
}
genericObjectPoolConfig.setMaxTotal(maxActive);
if (maxWait == 0) {
maxWait = 10 * 1000;
}
genericObjectPoolConfig.setMaxWaitMillis(maxWait);
//redis客户端配置
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder =
LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(maxWait));
builder.shutdownTimeout(Duration.ofMillis(maxWait));
builder.poolConfig(genericObjectPoolConfig);
LettuceClientConfiguration clientConfig = builder.build();
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 将template 泛型设置为 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
// 连接工厂,不必修改
template.setConnectionFactory(redisConnectionFactory);
// key、hash的key 采用 String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
TestService
package com.kindey.redissen.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
/**
* 作用描述
*
* @author Kindey.S [kindey123@163.com]
* @version 1.0
* @date 2023/9/6
*/
@Service
@Slf4j
public class TestService {
@Autowired
private RedisTemplate redisTemplate;
@Scheduled(cron = "*/3 * * * * ?")
public void getRedisData() {
redisTemplate.opsForValue().set("bb","22");
Object aa = redisTemplate.opsForValue().get("aa");
log.info("aa:{}", null == aa ? null : aa.toString());
}
}
测试结果
启动项目,可以看到,定时任务每3S访问一次redis。
2023-09-07 09:26:41.390 INFO 12284 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1577 ms
2023-09-07 09:26:42.247 INFO 12284 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-09-07 09:26:42.263 INFO 12284 --- [ main] com.kindey.redissen.RedissenApplication : Started RedissenApplication in 3.199 seconds (JVM running for 4.29)
2023-09-07 09:26:45.829 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
2023-09-07 09:26:48.013 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
2023-09-07 09:26:51.010 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
模拟master挂了,2023-09-07 09:27:57.947断连,2023-09-07 09:28:07.973恢复,中间间隔10s,原因是配置设置的命令超时时间为10s,如果想缩短此时间可以对配置信息spring.data.redis.lettuce.pool.max-wait进行调整,同时此时间还会受到sentinel的配置项sentinel down-after-milliseconds影响。
2023-09-07 09:27:57.947 INFO 12284 --- [xecutorLoop-1-3] i.l.core.protocol.ConnectionWatchdog : Reconnecting, last destination was /192.168.88.96:6379
2023-09-07 09:28:07.021 ERROR 12284 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task
org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 second(s)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:278) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceConnection.await(LettuceConnection.java:1086) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceConnection.lambda$doInvoke$4(LettuceConnection.java:939) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceInvoker$Synchronizer.invoke(LettuceInvoker.java:673) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceInvoker$DefaultSingleInvocationSpec.get(LettuceInvoker.java:589) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.set(LettuceStringCommands.java:123) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.connection.DefaultedRedisConnection.set(DefaultedRedisConnection.java:314) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.DefaultValueOperations$7.inRedis(DefaultValueOperations.java:309) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:61) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:191) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:97) ~[spring-data-redis-2.7.6.jar:2.7.6]
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:305) ~[spring-data-redis-2.7.6.jar:2.7.6]
at com.kindey.redissen.service.TestService.getRedisData(TestService.java:24) ~[classes/:na]
at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-5.3.24.jar:5.3.24]
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.3.24.jar:5.3.24]
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95) [spring-context-5.3.24.jar:5.3.24]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_191]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_191]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_191]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_191]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_191]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 second(s)
at io.lettuce.core.internal.ExceptionFactory.createTimeoutException(ExceptionFactory.java:59) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
at io.lettuce.core.protocol.CommandExpiryWriter.lambda$potentiallyExpire$0(CommandExpiryWriter.java:176) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:153) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.AbstractEventExecutor.runTask$$$capture(AbstractEventExecutor.java:174) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:66) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.85.Final.jar:4.1.85.Final]
... 1 common frames omitted
2023-09-07 09:28:07.956 WARN 12284 --- [ioEventLoop-6-3] io.lettuce.core.RedisClient : Cannot connect Redis Sentinel at redis://192.168.88.96:26379?timeout=10s: java.util.concurrent.CompletionException: io.netty.channel.ConnectTimeoutException: connection timed out: /192.168.88.96:26379
2023-09-07 09:28:07.973 INFO 12284 --- [ioEventLoop-6-5] i.l.core.protocol.ReconnectionHandler : Reconnected to 192.168.88.156:6379
2023-09-07 09:28:09.009 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
2023-09-07 09:28:12.004 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
2023-09-07 09:28:15.004 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11
2023-09-07 09:28:18.006 INFO 12284 --- [ scheduling-1] com.kindey.redissen.service.TestService : aa:11