3.11.2是老版本,本文主要讲3.11.2升级到最新的3.17.4中遇到的问题,如果想直接使用3.17,可以直接跳过3.11看3.17即可。
Caused by: java.lang.IllegalArgumentException: Can't parse config
at org.redisson.spring.starter.RedissonAutoConfiguration.redisson(RedissonAutoConfiguration.java:138)
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$25ac00c4.CGLIB$redisson$5()
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$25ac00c4$$FastClassBySpringCGLIB$$b30f0a32.invoke()
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$25ac00c4.redisson()
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 61 common frames omitted
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'classpath': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
at [Source: (String)"classpath:singleServerConfig.yaml"; line: 1, column: 10]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:745)
根据断点,系统是在类“com.fasterxml.jackson.databind.ObjectMapper”中的“readValue(String content, JavaType valueType)”中的代码“return (T) _readMapAndClose(_jsonFactory.createParser(content), valueType);”抛出的异常,异常信息如下:
如上:3.17*版本系统会抛出:“Unrecognized field "pingTimeout" (class org.redisson.config.SingleServerConfig), not marked as ignorable”,但是在我们的console输出的信息如下:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'redisson' threw exception; nested exception is java.lang.IllegalArgumentException: Can't parse config
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 33 common frames omitted
Caused by: java.lang.IllegalArgumentException: Can't parse config
at org.redisson.spring.starter.RedissonAutoConfiguration.redisson(RedissonAutoConfiguration.java:151)
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$ce6efe30.CGLIB$redisson$5()
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$ce6efe30$$FastClassBySpringCGLIB$$3728d689.invoke()
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at org.redisson.spring.starter.RedissonAutoConfiguration$$EnhancerBySpringCGLIB$$ce6efe30.redisson()
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 34 common frames omitted
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('-' (code 45)) in numeric value: expected digit (0-9) to follow minus sign, for valid numeric value
at [Source: (String)"---
singleServerConfig:
最终查询官方手册可知,是没有“pingTimeout”属性。其他排查方法一致。
新版本的redis中不需要单独配置singleServerConfig.yaml。删除spring.redis.redisson.config配置即可
### 删除此配置
spring:
redis:
redisson:
config: classpath:singleServerConfig.yaml
将application.yml里面的config改为“file”,修改后的application.yml参数:
spring:
redis:
redisson:
file: classpath:singleServerConfig.yml
修改singleServerConfig.yaml内容,在3.17版本中是没有:“pingTimeout”、“reconnectionTimeout”、“failedAttempts”三个参数。修改后singleServerConfig.yaml内容见3.17.4配置。
org.springframework.boot
spring-boot-starter-web
2.1.6.RELEASE
org.redisson
redisson-spring-boot-starter
3.11.2
org.springframework.boot
spring-boot-starter-test
2.1.6.RELEASE
org.projectlombok
lombok
1.18.8
junit
junit
4.12
#spring相关配置
spring:
redis:
redisson:
config: classpath:singleServerConfig.yml
---
singleServerConfig:
#如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,
#那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
#默认值:10000
idleConnectionTimeout: 10000
pingTimeout: 1000
#同任何节点建立连接时的等待超时。时间单位是毫秒。
#默认值:10000
connectTimeout: 10000
#等待节点回复命令的时间。该时间从命令发送成功时开始计时。
#默认值:3000
timeout: 3000
#如果尝试达到 retryAttempts(命令失败重试次数)
#仍然不能将命令发送至某个指定的节点时,将抛出错误。如果尝试在此限制之内发送成功,
#则开始启用 timeout(命令等待超时) 计时。
#默认值:3
retryAttempts: 3
#在某个节点执行相同或不同命令时,连续失败failedAttempts(执行失败最大次数)时,
#该节点将被从可用节点列表里清除,直到 reconnectionTimeout(重新连接时间间隔) 超时以后再次尝试。
#默认值:1500
retryInterval: 1500
#重新连接时间间隔
reconnectionTimeout: 3000
#执行失败最大次数
failedAttempts: 3
#密码
password: pass
#每个连接的最大订阅数量。
#默认值:5
subscriptionsPerConnection: 5
#在Redis节点里显示的客户端名称。
clientName: null
#在Redis节点
address: "redis://node2:6379"
#从节点发布和订阅连接的最小空闲连接数
#默认值:1
subscriptionConnectionMinimumIdleSize: 1
#用于发布和订阅连接的连接池最大容量。连接池的连接数量自动弹性伸缩。
#默认值:50
subscriptionConnectionPoolSize: 50
#节点最小空闲连接数
#默认值:32
connectionMinimumIdleSize: 32
#节点连接池大小
#默认值:64
connectionPoolSize: 64
#这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。
#默认值: 当前处理核数量 * 2
threads: 8
#这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,
#以及底层客户端所一同共享的线程池里保存的线程数量。
#默认值: 当前处理核数量 * 2
nettyThreads: 8
#Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。
#默认值: org.redisson.codec.JsonJacksonCodec
codec: ! {}
#传输模式
#默认值:TransportMode.NIO
transportMode: "NIO"
org.springframework.boot
spring-boot-starter-web
2.6.0
org.redisson
redisson-spring-boot-starter
3.17.4
org.springframework.boot
spring-boot-starter-test
2.6.0
org.projectlombok
lombok
1.18.8
junit
junit
4.12
spring:
redis:
redisson:
file: classpath:singleServerConfig.yml
# config:|
# singleServerConfig:
# idleConnectionTimeout: 10000
# connectTimeout: 10000
# timeout: 3000
# retryAttempts: 3
# retryInterval: 1500
# password: pass
# subscriptionsPerConnection: 5
# clientName: null
# address: "redis://node2:6379"
# subscriptionConnectionMinimumIdleSize: 1
# subscriptionConnectionPoolSize: 50
# connectionMinimumIdleSize: 32
# connectionPoolSize: 64
# threads: 8
# nettyThreads: 8
# codec: ! {}
# transportMode: "NIO"
####file和config二选一即可
---
singleServerConfig:
#如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,
#那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
#默认值:10000
idleConnectionTimeout: 10000
#同任何节点建立连接时的等待超时。时间单位是毫秒。
#默认值:10000
connectTimeout: 10000
#等待节点回复命令的时间。该时间从命令发送成功时开始计时。
#默认值:3000
timeout: 3000
#如果尝试达到 retryAttempts(命令失败重试次数)
#仍然不能将命令发送至某个指定的节点时,将抛出错误。如果尝试在此限制之内发送成功,
#则开始启用 timeout(命令等待超时) 计时。
#默认值:3
retryAttempts: 3
#在某个节点执行相同或不同命令时,连续失败failedAttempts(执行失败最大次数)时,
#该节点将被从可用节点列表里清除,直到 reconnectionTimeout(重新连接时间间隔) 超时以后再次尝试。
#默认值:1500
retryInterval: 1500
#密码
password: pass
#每个连接的最大订阅数量。
#默认值:5
subscriptionsPerConnection: 5
#在Redis节点里显示的客户端名称。
clientName: null
#在Redis节点
address: "redis://node2:6379"
#从节点发布和订阅连接的最小空闲连接数
#默认值:1
subscriptionConnectionMinimumIdleSize: 1
#用于发布和订阅连接的连接池最大容量。连接池的连接数量自动弹性伸缩。
#默认值:50
subscriptionConnectionPoolSize: 50
#节点最小空闲连接数
#默认值:32
connectionMinimumIdleSize: 32
#节点连接池大小
#默认值:64
connectionPoolSize: 64
#这个线程池数量被所有RTopic对象监听器,RRemoteService调用者和RExecutorService任务共同共享。
#默认值: 当前处理核数量 * 2
threads: 8
#这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,
#以及底层客户端所一同共享的线程池里保存的线程数量。
#默认值: 当前处理核数量 * 2
nettyThreads: 8
#Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储。
#默认值: org.redisson.codec.JsonJacksonCodec
codec: ! {}
#传输模式
#默认值:TransportMode.NIO
transportMode: "NIO"
@SpringBootApplication
public class RedissionStart {
public static void main(String[] args) {
SpringApplication.run(RedissionStart.class);
System.out.println("redission,启动成功...");
}
}
@Slf4j
@Component
public class RLockOpertions {
@Autowired
RedissonClient redissonClient;
public void lock() throws InterruptedException{
log.info("线程:{},进入方法",Thread.currentThread().getName());
RLock rLock = redissonClient.getLock("lock");
//加锁:锁的有效期默认30秒
rLock.lock();
long timeToLive = rLock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S",Thread.currentThread().getName(),timeToLive/1000);
//休眠一下
Thread.sleep(2000);
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
rLock.unlock();
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
public void lockLaseTime() throws InterruptedException{
log.info("线程:{},进入方法",Thread.currentThread().getName());
RLock rLock = redissonClient.getLock("lockLaseTime");
//加锁 上面是默认30秒,
//这里可以手动设置锁的有效时间,锁到期后会自动释放的
rLock.lock(10,TimeUnit.SECONDS);
long timeToLive = rLock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S",Thread.currentThread().getName(),timeToLive/1000);
//休眠一下
Thread.sleep(2000);
//如果主线程未释放,且当前锁未调用unlock方法,则锁到期后会自动释放的
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
rLock.unlock();
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
public void tryLock() throws InterruptedException {
log.info("线程:{},进入方法",Thread.currentThread().getName());
RLock rLock = redissonClient.getLock("tryLock");
//tryLock()方法是有返回值的,它表示用来尝试获取锁,
//如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false .
boolean flag = rLock.tryLock();
if (flag){
long timeToLive = rLock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S,加锁状态:{}",Thread.currentThread().getName(),timeToLive/1000,flag);
//休眠一下
Thread.sleep(2000);
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
rLock.unlock();
log.info("线程:{},释放锁",Thread.currentThread().getName());
}else {
log.info("线程:{},获得锁失败",Thread.currentThread().getName());
}
}
public void tryLockWaitTime() throws InterruptedException {
log.info("线程:{},进入方法",Thread.currentThread().getName());
RLock rLock = redissonClient.getLock("tryLockWaitTime");
//tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,
//只不过区别在于这个方法在拿不到锁时会等待一定的时间,
//在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
boolean flag = rLock.tryLock(6, TimeUnit.SECONDS);
if (flag){
long timeToLive = rLock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S,加锁状态:{}",Thread.currentThread().getName(),timeToLive/1000,flag);
//休眠一下
Thread.sleep(7000);
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
rLock.unlock();
log.info("线程:{},释放锁",Thread.currentThread().getName());
}else {
log.info("线程:{},获得锁失败",Thread.currentThread().getName());
}
}
public void tryLockleasTime() throws InterruptedException {
log.info("线程:{},进入方法",Thread.currentThread().getName());
RLock rLock = redissonClient.getLock("tryLockleasTime");
//比上面多一个参数,多添加一个锁的有效时间
boolean flag = false;
flag = rLock.tryLock(12,10, TimeUnit.SECONDS);
if (flag){
long timeToLive = rLock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S,加锁状态:{}",Thread.currentThread().getName(),timeToLive/1000,flag);
//休眠一下
Thread.sleep(3000);
log.info("线程:{},释放锁",Thread.currentThread().getName());
rLock.unlock();
}else {
log.info("线程:{},获得锁失败",Thread.currentThread().getName());
}
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class RLockOpertionsTest {
@Autowired
RLockOpertions rLockOpertions;
@Test
public void lockTest() throws InterruptedException{
//模拟并发,2个线程同时强同一对象
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread("name-A"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.lock();
}
}.start();
new Thread("name-B"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.lock();
}
}.start();
countDownLatch.await();
Thread.sleep(600000);
}
@Test
public void lockLaseTimeTest() throws InterruptedException{
//模拟并发,2个线程同时强同一对象
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread("name-A"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.lockLaseTime();
}
}.start();
new Thread("name-B"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.lockLaseTime();
}
}.start();
countDownLatch.await();
Thread.sleep(600000);
}
@Test
public void tryLockTest() throws InterruptedException {
//模拟并发,2个线程同时强同一对象
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread("name-A"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLock();
}
}.start();
new Thread("name-B"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLock();
}
}.start();
countDownLatch.await();
Thread.sleep(600000);
}
@Test
public void tryLockWaitTimeTest() throws InterruptedException {
//模拟并发,2个线程同时强同一对象
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread("name-A"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockWaitTime();
}
}.start();
new Thread("name-B"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockWaitTime();
}
}.start();
countDownLatch.await();
Thread.sleep(600000);
}
@Test
public void tryLockleasTimeTimeTest() throws InterruptedException {
//模拟并发,2个线程同时强同一对象
CountDownLatch countDownLatch = new CountDownLatch(5);
new Thread("name-A"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockleasTime();
}
}.start();
new Thread("name-B"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockleasTime();
}
}.start();
new Thread("name-C"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockleasTime();
}
}.start();
new Thread("name-D"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockleasTime();
}
}.start();
new Thread("name-E"){
@SneakyThrows
public void run(){
countDownLatch.countDown();
rLockOpertions.tryLockleasTime();
}
}.start();
countDownLatch.await();
Thread.sleep(600000);
}
}