springBoot 用異步線程池實現異步事件

前言:

        在軟件開發過程中,經常會遇到需要預警,或者做埋點等和業務無關但又需要做的事,像釘釘預警,需要在特定場景執行,又不能影響業務。如果採用同步http處理,如果預警接口響應慢超時,將直接影響業務,使用異步線程池就可以很好地解決。

例子:

        釘釘預警,用異步線程池處理。

一、代碼層次

        

        這是異步線程池的搭建處理

       

      這是釘釘預警的監聽器和請求參數

二、實現異步線程池

       1.先構建一個事件實體,繼承ApplicationEvent ,其中T是針對每個異步事件傳的入參,這個類可以包含事件名稱,處理參數等,對於所有事件通用,方便打印日誌等

public class DomainEvent<T extends Serializable> extends ApplicationEvent {

    private static final long serialVersionUID = 1L;

    private String eventName;

    private String eventHandler;

    private T eventData;

    public DomainEvent(T source) {
        super(source);
        this.eventData = source;
    }

    public String getEventName() {
        return eventName;
    }

    public void setEventName(String eventName) {
        this.eventName = eventName;
    }

    public String getEventHandler() {
        return eventHandler;
    }

    public void setEventHandler(String eventHandler) {
        this.eventHandler = eventHandler;
    }

    public T getEventData() {
        return eventData;
    }

    public void setEventData(T eventData) {
        this.eventData = eventData;
    }

}

        2.創建一個異步線程池處理配置

@Configuration
@EnableAsync
public class DomainEventAsyncConfig {

    /** Set the ThreadPoolExecutor's core pool size. */
    private int corePoolSize = 5;
    /** Set the ThreadPoolExecutor's maximum pool size. */
    private int maxPoolSize = 50;
    /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
    private int queueCapacity = 2000;

    public static final String DOMAIN_EXECUTOR = "domainExecutor";

    /** 監控預警記錄處理,程數和隊列已經達到上限使用直接丟棄任務的策略方式 */
    @Bean(DOMAIN_EXECUTOR)
    public Executor moniExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(DOMAIN_EXECUTOR);
        // rejection-policy:當pool已經達到max size的時候,如何處理新任務
        // ThreadPoolExecutor#DiscardPolicy:這個策略將會直接丟棄任務
        // ThreadPoolExecutor#CallerRunsPolicy:不在新線程中執行任務,而是有調用者所在的線程來執行策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

     3.領域事件發佈者,這裏使用springContext代理,也可以應用MQ或其他事件框架

@Component
public class DomainEventPublisher implements ApplicationContextAware {

    private static Logger log = LoggerFactory.getLogger(DomainEventPublisher.class);

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    /** 指定使用的異步執行線程池,如果不指定名字會使用缺省的asyncExecutor */
    @Async(DomainEventAsyncConfig.DOMAIN_EXECUTOR)
    public void publishEvent(DomainEvent<?> event) {
        context.publishEvent(event);
        if (log.isDebugEnabled()) {
            log.debug("DomainEventPublish,eventMsg:{}", JsonMapper.INSTANCE.toJson(event));
        }
    }

}

      到這裏一個異步線程池就好了,後續需要使用,直接調用 DomainEventPublisher.publishEvent() 就可以了,下面用釘釘預警舉例

三、釘釘預警用異步線程池處理

        1.先構建一個發送預警的實體

public final class DingAlarmEventData extends DomainEvent<DingAlarmEventDto> {

    public DingAlarmEventData(DingAlarmEventDto source) {
        super(source);
    }

    private static final long serialVersionUID = 1L;

}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class DingAlarmEventDto implements Serializable {

    private String title;

    private String subject;

    private String message;
}

       2.建立一個預警監聽器,監聽實體 DingAlarmEventData 

@Component
@Slf4j
public class DingAlarmEventListener implements ApplicationListener<DingAlarmEventData> {

    @Override
    public void onApplicationEvent(DingAlarmEventData event) {
        try {
            if (log.isDebugEnabled()) {
                log.debug("DomainEventListener,eventMsg:{}", JsonMapper.INSTANCE.toJson(event));
            }
            DingAlarmEventDto eventData = event.getEventData();
            if (null == event || null == eventData) {
                log.warn("domainEventData is null");
                return;
            }
            //處理釘釘預警邏輯
            
        } catch (Exception e) {
            log.error("", e);
        }
    }

}

       這樣就完成了預警的監聽器,接下來看看怎麼調用

四、調用預警實例

@Component
@Slf4j
public class AlarmTest {

    @Autowired
    private DomainEventPublisher domainEventPublisher;

    public void alarmTest() {
        DingAlarmEventDto dingAlarmEventDto = new DingAlarmEventDto();
        dingAlarmEventDto.setTitle("預警標題");
        dingAlarmEventDto.setSubject("預警科目");
        dingAlarmEventDto.setMessage("預警內容");
        DingAlarmEventData alarmEventData = new DingAlarmEventData(dingAlarmEventDto);
        domainEventPublisher.publishEvent(alarmEventData);
    }
}

          這樣一個完整的異步線程池的構建和調用就完成了,後續如果有其他的異步操作,參照預警的做法就可以了~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章