前言:
在軟件開發過程中,經常會遇到需要預警,或者做埋點等和業務無關但又需要做的事,像釘釘預警,需要在特定場景執行,又不能影響業務。如果採用同步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);
}
}
這樣一個完整的異步線程池的構建和調用就完成了,後續如果有其他的異步操作,參照預警的做法就可以了~