shedlock 重启系统报错问题_springBoot框架shedlock+websocket功能冲突的解决方案

在SpringBoot项目中,同时使用Shedlock实现分布式锁和WebSocket时遇到冲突问题,表现为启动报错。问题出在两者的底层配置冲突。为解决这个问题,文章介绍了通过引入Redis来实现分布式锁,并详细展示了配置Redis、创建相关配置类以及使用WebSocket的步骤,从而在分布式环境中兼容定时任务和WebSocket功能。
摘要由CSDN通过智能技术生成

背景描述:

项目开发中首先遇到在集群模式部署服务端时,多个节点的定时任务在各自的节点处均会执行一遍的问题,针对这个单一问题,上一篇文章

https://www.cnblogs.com/Iris1998/p/11413099.html给出了springboot集成shedlock的解决方案,但一段时间后项目中有了新的需求,想要实现消息主动推到客户端的功能

,单从功能上来看可以采用springboot自带的websocket实现,但开发过程遇到很奇怪的报错,报错信息如下

{"time":"2019-09-27 11:07:22 626", "level":"WARN", "classname":"org.apache.juli.logging.DirectJDKLog", "method":"log", "line":"173", "msg":"The web application [ROOT] appears to have started a thread named [RxIoScheduler-1 (Evictor)] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

sun.misc.Unsafe.park(Native Method)

java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)

java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)

java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)

java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)

java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)

java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)

java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

java.lang.Thread.run(Thread.java:748)"}

{"time":"2019-09-27 11:07:22 666", "level":"ERROR", "classname":"org.springframework.boot.SpringApplication", "method":"reportFailure", "line":"858", "msg":"Application run failed"}

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'stompWebSocketHandlerMapping' defined in class path resource [org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'stompWebSocketHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: @Bean method AbstractMessageBrokerConfiguration.messageBrokerTaskScheduler called as bean reference for type [org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy220]. Overriding bean of same name declared in: class path resource [org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.class]

at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627)

at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:456)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1288)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)

at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)

at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)

at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)

at com.hundunyun.wechat.App.main(App.java:19)

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'stompWebSocketHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: @Bean method AbstractMessageBrokerConfiguration.messageBrokerTaskScheduler called as bean reference for type [org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy220]. Overriding bean of same name declared in: class path resource [org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.class]

at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)

at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622)

... 19 common frames omitted

Caused by: java.lang.IllegalStateException: @Bean method AbstractMessageBrokerConfiguration.messageBrokerTaskScheduler called as bean reference for type [org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy220]. Overriding bean of same name declared in: class path resource [org/springframework/web/socket/config/annotation/DelegatingWebSocketMessageBrokerConfiguration.class]

at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.resolveBeanReference(ConfigurationClassEnhancer.java:418)

at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:366)

at org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration$$EnhancerBySpringCGLIB$$748fbfa6.messageBrokerTaskScheduler()

at org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurationSupport.stompWebSocketHandlerMapping(WebSocketMessageBrokerConfigurationSupport.java:76)

at org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration$$EnhancerBySpringCGLIB$$748fbfa6.CGLIB$stompWebSocketHandlerMapping$14()

at org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration$$EnhancerBySpringCGLIB$$748fbfa6$$FastClassBySpringCGLIB$$19a6d0cf.invoke()

at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)

at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)

at org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration$$EnhancerBySpringCGLIB$$748fbfa6.stompWebSocketHandlerMapping()

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)

... 20 common frames omitted

经过实验,独自使用shedlock没有问题,独自使用websocket也没有问题,但两个放在一起就有问题,大胆猜测问题出在springBoot的shedlock和websocket的底层,尝试解决无果后决定换一种实现方式,springboot的websocket+redis分布式锁实现分布式环境中两个功能的兼容。

第一步:引入相关包

org.springframework.boot

spring-boot-starter-websocket

2.2.1

org.springframework.boot

spring-boot-starter-data-redis

2.2.1

org.springframework.boot

spring-boot-starter-test

test

io.projectreactor

reactor-test

test

net.javacrumbs.shedlock

shedlock-spring

2.2.1

net.javacrumbs.shedlock

shedlock-provider-redis-jedis

2.2.1

com.alibaba

fastjson

1.2.41

第二步:添加redis分布式锁功能相关的文件

1.添加自定义锁注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface CacheLock {

String lockedPrefix() default ""; //redis 锁key的前缀

int expireTime() default 10; //key在redis里存在的时间,1000S

}

2.添加aop切面类

import com.iris.websocket.annotation.CacheLock;

import com.iris.websocket.springwebsocket.redis.RedisClient;

import lombok.Synchronized;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/*

* @Description TODO

* @Author muruan.lt

* @Date 2019/9/26 11:36

*/

@Aspect

@Slf4j

@Component

public class CacheLockAspect {

private static final String LOCK_VALUE = "locked";

@Autowired

RedisClient redisClient;

/*

* 1.有springboot-websocket情况下不用@Synchronized这个注解,可能多线程导致的分布式锁不生效

* 2.没有springboot-websocket情况下可以不用@Synchronized这个注解

*/

@Around("@annotation(com.iris.websocket.annotation.CacheLock)")

@Synchronized

public void cacheLockPoint(ProceedingJoinPoint pjp) {

String name = pjp.getSignature().getName();

Method[] methods = pjp.getTarget().getClass().getMethods();

for (Method cacheMethod : methods) {

if (null != cacheMethod.getAnnotation(CacheLock.class)

&& name.equals(cacheMethod.getName())){

try {

// String lockKey = pjp.getTarget().getClass().getName()+cacheMethod.getAnnotation(CacheLock.class).lockedPrefix();

String lockKey = cacheMethod.getAnnotation(CacheLock.class).lockedPrefix();

int timeOut = cacheMethod.getAnnotation(CacheLock.class).expireTime();

if(null == lockKey){

return;

}

if (redisClient.setnx(lockKey, LOCK_VALUE)) {

redisClient.expire(lockKey, timeOut);

// log.info("method:{}获取锁:{},开始运行!",cacheMethod,lockKey);

pjp.proceed();

return;

}

// log.info("method:{}未获取锁:{},运行失败!",cacheMethod,lockKey);

} catch (Throwable e) {

log.error("method:{},运行错误!",cacheMethod,e);

return;

}

break;

}

}

}

}

3.添加application.properties配置文件

server.port: 8800spring.application.name: sun-websocket

eureka.instance.hostname: localhost

eureka.client.registerWithEureka:falseeureka.client.fetchRegistry:falseserviceUrl.defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

####################################### redis 配置信息开始 ###########################################

redis.host=127.0.0.1redis.password=123456redis.port=6379#最大能够保持idel状态的对象数

redis.maxIdle=300#最大分配的对象数

redis.maxActive=600#当池内没有返回对象时,最大等待时间

redis.maxWaitMillis=1000redis.maxTotal=1000#当调用borrow Object方法时,是否进行有效性检查

redis.testOnBorrow=true#当调用return Object方法时,是否进行有效性检查

redis.testOnReturn=trueredis.timeout=3600####################################### redis 配置信息结束 ###########################################

4.添加redis配置相关文件

4.1 添加 RedisPool.java 文件

import org.springframework.beans.factory.annotation.Value;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.JedisPoolConfig;

/*

* @Description: JedisPool配置类,配置信息在默认的"application.properties"文件中

* @Author: muruan.lt

* @CreateDate: 2019/9/27 9:50

* @Version: 1.0

*/

public abstract class RedisPool {

public JedisPool jedisPool;

@Value("${redis.host}")

private String host;

@Value("${redis.port}")

private Integer port;

@Value("${redis.password}")

private String password;

@Value("${redis.maxIdle}")

private Integer maxIdle;

@Value("${redis.maxTotal}")

private Integer maxTotal;

@Value("${redis.maxActive}")

private Integer maxActive;

@Value("${redis.maxWaitMillis}")

private Integer maxWaitMillis;

@Value("${redis.timeout}")

private Integer timeout;

@Value("${redis.testOnBorrow}")

private Boolean testOnBorrow;

@Value("${redis.testOnReturn}")

private Boolean testOnReturn;

protected RedisPool() {

JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

jedisPoolConfig.setMaxIdle(maxIdle);

jedisPoolConfig.setMaxTotal(maxTotal);

jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);

jedisPoolConfig.setTestOnBorrow(testOnBorrow);

jedisPoolConfig.setTestOnReturn(testOnReturn);

jedisPool =

new JedisPool(jedisPoolConfig, host, port, timeout, password);

}

}

4.2 添加 RedisClient.java 文件

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import redis.clients.jedis.*;

import redis.clients.jedis.exceptions.JedisConnectionException;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Set;

@Component

public class RedisClient {

private static final Logger loggger = LoggerFactory.getLogger(RedisClient.class);

@Autowired

private JedisPool jedisPool;

private Jedis getJedis() {

Jedis jedis = jedisPool.getResource();

// jedis.select(database);

return jedis;

}

public void set(String key, String value) {

Jedis jedis = null;

try {

jedis = getJedis();

jedis.set(key, value);

} finally {

jedis.close();

}

}

public void set(String key, String value, int expTime) {

Jedis jedis = null;

try {

jedis = getJedis();

jedis.setex(key, expTime, value);

} finally {

jedis.close();

}

}

public String get(String key) {

Jedis jedis = null;

try {

jedis = getJedis();

return jedis.get(key);

} finally {

jedis.close();

}

}

public void expire(String key, int expTime) {

Jedis jedis = null;

try {

jedis = getJedis();

jedis.expire(key, expTime);

} finally {

jedis.close();

}

}

public boolean setnx(String lockKey, String expires) {

Jedis jedis = null;

try {

jedis = getJedis();

Long result = jedis.setnx(lockKey, expires);

return result == 1;

} finally {

jedis.close();

}

}

}

4.3 添加RedisConfig.java文件

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import com.fasterxml.jackson.annotation.PropertyAccessor;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.cache.annotation.CachingConfigurerSupport;

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.cache.interceptor.KeyGenerator;

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.core.StringRedisTemplate;

import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import redis.clients.jedis.JedisPool;

import java.lang.reflect.Method;

/*

* @Description 服务于shedlock-redis

* @Author muruan.lt

* @Date 2019/9/26 10:19

*/

@Configuration

@EnableCaching

public class RedisConfig extends CachingConfigurerSupport {

@Value("${redis.host}")

private String host;

@Value("${redis.port}")

private Integer port;

@Bean

@Override

public KeyGenerator keyGenerator() {

return new KeyGenerator() {

@Override

public Object generate(Object target, Method method, Object... params) {

StringBuilder sb = new StringBuilder();

sb.append(target.getClass().getName());

sb.append(method.getName());

for (Object o : params) {

if (o != null) {

sb.append(o.toString());

}

}

return sb.toString();

}

};

}

@Bean

public RedisTemplate myRedisTemplate(RedisConnectionFactory factory) {

StringRedisTemplate srt = new StringRedisTemplate(factory);

Jackson2JsonRedisSerializer j2j = new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper om = new ObjectMapper();

om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

j2j.setObjectMapper(om);

srt.setValueSerializer(j2j);

srt.afterPropertiesSet();

return srt;

}

@Bean

public JedisPool jedisPool() {

return new JedisPool(this.host, this.port);

}

}

以上就完成了所有redis分布式锁功能相关的文件添加,下面就是websocket需要添加的文件

第三步:添加websocket功能相关的文件

1. 添加websocket配置文件

import org.springframework.context.annotation.Configuration;

import org.springframework.messaging.simp.config.MessageBrokerRegistry;

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/*

* @Description springWebsoket配置文件

* @Author muruan.lt

* @Date 2019/9/24 09:23

*/

@Configuration

@EnableWebSocketMessageBroker

public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override

public void configureMessageBroker(MessageBrokerRegistry config) {

config.enableSimpleBroker("/topic");

config.setApplicationDestinationPrefixes("/app");

}

@Override

public void registerStompEndpoints(StompEndpointRegistry registry) {

registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS();

}

}

至此就完成了两个功能相关的全部文件,下面就是功能的使用和检测。

第四步:功能的使用和检测

import com.alibaba.fastjson.JSONObject;

import com.iris.websocket.annotation.CacheLock;

import lombok.extern.slf4j.Slf4j;

import net.javacrumbs.shedlock.core.SchedulerLock;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.messaging.simp.SimpMessageSendingOperations;

import org.springframework.scheduling.annotation.Scheduled;

import org.springframework.stereotype.Component;

import java.util.Date;

/*

* @Description 定时任务测试类

* @Author muruan.lt

* @Date 2019/9/2 14:27

*/

@Component

@Slf4j

public class TaskTest {

@Autowired

private SimpMessageSendingOperations messageTemplate;

@Scheduled(cron = "0/10 * * * * ?")

@CacheLock(lockedPrefix = "test",expireTime=9)

public void test1(){

System.out.println(new Date() + "hello1!");

JSONObject jsonObject = new JSONObject();

jsonObject.put("code", 200);

jsonObject.put("content", "hello1!");

String resStr = jsonObject.toJSONString();

log.info("send message to browser,message content [{}]", resStr);

messageTemplate.convertAndSend("/topic/greetings", resStr);

}

@Scheduled(cron = "0/10 * * * * ?")

@CacheLock(lockedPrefix = "test",expireTime=9)

public void test2(){

System.out.println(new Date() + "hello2!");

}

/**

* 执行定时任务

**/

@Scheduled(cron = "0/10 * * * * ?")

@CacheLock(lockedPrefix = "TimeTaskService",expireTime=9)

public void executeTask() {

System.out.println(new Date() +"hello3!");

}

/**

* 执行定时任务

**/

@Scheduled(cron = "0/10 * * * * ?")

@CacheLock(lockedPrefix = "TimeTaskService",expireTime=9)

public void executeTask1() {

System.out.println(new Date() +"hello4!");

}

}

websocket的使用:在需要向前端推送消息的地方添加

@Autowired

private SimpMessageSendingOperations messageTemplate;

即可,向特定的topic发送相关,客户端就可以通过这个topic接收到新消息

@CacheLock(lockedPrefix = "TimeTaskService",expireTime=9)

这个注解就是实现分布式redis锁的关键,在需要解决分布式部署重复执行问题的方法上添加此注解就可以解决分布式部署重复执行的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值