从spring的ApplicationListener的角度了解异步机制

背景

接触新的项目的几天时间,除了传统的CRUD业务之外,项目用到的集群锁,以及事件监听广播机制等使用,使人联系其java.util.concurrent.futureTask.的使用,本节将从项目里遇到的各种业务场景中使用的广播事件来对异步线程池的处理广播事件总结,并且对这种事件的广播机制延伸的处理方案的提出改良。

如何使用异步线程池处理任务事件

笔者从事电商项目有一段时间,对于传统的电商项目的了解就是局限于集群,使用统一的redis服务器实现事务锁,达到数据的一致,除此之外,电商项目没有使用到流行的队列,如rabbitMQ之类的成熟工具来处理消息的消费以及生产的业务。而是使用了spring的广播机制来处理,网上看到一遍非常详细介绍这个使用的文章,而笔者从事的电商项目里面同样也是使用applicationContext的applicationListener的监听以及applicationEvenPublisher来处理异步消息的消费。比如分享的消息,推送的消息,等等业务场景都使用使用这样的方式来实现event的监听以及消费。这里重点是事件的定义,定义好了事件需要的处理的属性。

@lombok
public class DemoApplicationEvent extends ApplicationEvent {

    private static final long serialVersionUID = -3452985719192365159L;
    
    private String eventName;
 }

定义好了需要处理的事件就是定义监听的listener,这里关键的点就是实现spring提供的ApplicationListener,上面定义好的event实现ContextStartedEvent这个接口。

public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
 
    @Override
    public void onApplicationEvent(ContextStartedEvent event) {
        DemoApplicationListener listener = event.getApplicationContext().getBean("listener",
                DemoApplicationListener.class);
 
        if (listener != null) {
            System.out.println("listener got.");
        } else {
            System.out.println("listener absent, please check!");
        }
 
    }
 
}

下面的代码来自连接的文章,获取applicationContext的方式是通过ClassPathXml的方式,但是真正的商用项目都是通过ApplicationContextAware这个类来支持获取ApplicationContext的实例对象,从而通过getBean()获取到到配置文件的bean,以及listener等。

public class DemoRunner {
 
    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath*:/context/spring-context-application-listener.xml");
 
        context.start();
 
        String targetObj = "i am the target object";
        DemoApplicationEvent event = new DemoApplicationEvent(targetObj);
        event.setEventName("event name");
        context.publishEvent(event);
 
        Thread.sleep(3000);
        context.close();
    }
 
}

这里就通过了创建event对象,从而使用applicationContext.publishEvent(),来对时间监听广播处理。这里还针对电商活动的主题来加锁处理某个监听活动事件的并发情况处理,下面简单的介绍ThreadPoolExcecutor。

java.util.concurrent.ThreadPoolExcecutor.

为了针对上面提到的事件的广播可以更好的支持并发的情况出现,项目里使用了异步线程池的方式来处理,这里对ThreadPoolExcecutor的详细介绍,针对了线程池的类图做了介绍,以及使用,以及futureTask的原理,这里也详细介绍了使用以及类图,有兴趣延伸知识的读者可以对AQS以及CAS等做进入了解。
项目的伪码如下

@Conponent
public class AsyPoolListener implement ApplicationListener<MsgEvent>{
	@AutoWited
	@Qualifier("")
	private Log logger;
	
	@AutoWited
	@Qualifier("")
	private ThreadPoolTaskExecutor threadPool;
	
	@AutoWired
	private RedisService redisService;
	
	@Override
	public void onApplication(MagEvent event){
		Msg msg = event.getMsg();
		try{
			if(redisService.autoLock("ASY_MSG_EVENT_LOCK",100)){
				//threadPool.execut();处理异步事务。	
			}
		}catch (Exception e){
			logger.error();
		}
	}
	

}

总结

在使用异步线程池处理并发事务,是电商项目常用的方法,但是也是存在一定的风险,比如applicationListener出现消费消息失败的情况,这里就没有成熟的MQ一样有ACK机制做事务补偿,所以后续的章节会重点的讲讲RabbitMQ的成熟的ACK机制,如何保证消息100%消费,以及分时间段的详细说说上面提到的CAS,以及AQS。推荐详细的文章

猜你喜欢

转载自blog.csdn.net/weixin_30947631/article/details/85855173