事件驅動,Do you know?
**個人理解:**事件驅動(even-driven),字面理解即:由事件去觸發某個或者一系列動作。
百度百科: 從事件角度說,事件驅動程式的基本結構是由一個事件收集器、一個事件傳送器和一個事件處理器組成。 事件收集器專門負責收集所有事件,包括來自使用者的(如滑鼠、鍵盤事件等)、來自硬體的(如時鐘事件等)和來自軟體的(如作業系統、應用程式本身等)。 事件傳送器負責將收集器收集到的事件分發到目標物件中。 事件處理器做具體的事件響應工作。
舉個栗子:起床鬧鐘響了驅動我們該起床了,上課鈴聲響了驅動我們進教室上課、放學鈴聲響了驅動我們上課結束了可以愛幹嘛幹嘛去了。
或許這個栗子符合你,但是對我來說根本不可能?,鬧鐘響了壓根不會起床、不起床聽不到上課鈴、不去上課也聽不到下課鈴,GG
轉入正題,事件驅動的應用無處不在比如:spring、netty、zookeeper、mq,而且事件【event】和監聽器【listener】都是成對存在的,單個存在是沒有意義的,下面手動實現一個事件驅動模型。
那麼事件驅動模型的組成是怎樣的呢?
一、事件驅動模型
先去百度了一張模型圖,噹噹噹當~
說實話,圖不是自己畫的就是不滿意,但是 MAC
上畫圖工具都找不到好用的,就懶得畫了,把事件收集器 ==> **事件中心 **,事件和監聽器的關係可以是1:1、1:N、N:M
,取決於自己的需求。
說下流程:
1. 系統啟動,監聽器把自己註冊到事件中心,與某種事件進行繫結
2. 事件傳送器傳送事件到事件中心,事件中心去查詢與處理該事件的監聽器
複製程式碼
過程非常簡單,為什麼要用事件驅動呢?剛好百度百科有個栗子,看完相信優秀的你就明白了:
通常,我們寫伺服器處理模型的程式時,有以下幾種模型:
(1)每收到一個請求,建立一個新的程式,來處理該請求;
(2)每收到一個請求,建立一個新的執行緒,來處理該請求;
(3)每收到一個請求,放入一個事件列表,讓主程式通過非阻塞I/O方式來處理請求
上面的幾種方式,各有千秋,
第(1)種方法,由於建立新的程式的開銷比較大,所以,會導致伺服器效能比較差,但實現比較簡單。
第(2)種方式,由於要涉及到執行緒的同步,有可能會面臨死鎖等問題。
第(3)種方式,在寫應用程式程式碼時,邏輯比前面兩種都複雜。
綜合考慮各方面因素,一般普遍認為第(3)種方式是大多數網路伺服器採用的方式
實際上,事件驅動模型的核心就是 執行緒池 !!! 來實現非同步非阻塞。
二、Simple實現
OK,到這裡就是動手實踐的過程,好記性不如爛筆頭,讀百遍不如敲一遍。看下工程結構
理論部分說到事件和監聽器的關係可以是1:1、1:N、N:M
,這裡基於 spring boot 編寫一個事件:監聽器=1:N
的實現,老套路,跟著上面的流程分析走:
- 系統啟動,監聽器把自己註冊到事件中心,與某種事件進行繫結
- 事件傳送器傳送事件到事件中心,事件中心去查詢與處理該事件的監聽器
寫一個監聽器介面EventListener
和兩個實現類OrderCancelListener
、OrderCreateListener
package com.glmapper.event.driven.listener;
/**
* 監聽器介面
* @author: Jerry
* @date: 2018/7/1
*/
public interface EventListener{
/**
* 事件觸發時呼叫
*
* @param event
*/
void trigger(OrderEvent event);
}
複製程式碼
package com.glmapper.event.driven.listener;
/**
* 訂單取消事件監聽
* @author: Jerry
* @date: 2018/7/1
*/
@Slf4j
@Component
public class OrderCancelListener implements EventListener {
@Autowired
private EventCenter eventCenter;
@PostConstruct
private void registry() {
eventCenter.registry(this, OrderCancelEvent.class);
}
@Override
public void trigger(OrderEvent event) {
log.info("取消訂單,訂單id={}", event.getOrderId());
}
}
複製程式碼
package com.glmapper.event.driven.listener;
/**
* 訂單建立事件監聽
* @author: Jerry
* @date: 2018/7/1
*/
@Slf4j
@Component
public class OrderCreateListener implements EventListener {
@Autowired
private EventCenter eventCenter;
@PostConstruct
private void registry() {
eventCenter.registry(this, OrderCreateEvent.class);
}
@Override
public void trigger(OrderEvent event) {
log.info("建立訂單,訂單id={}", event.getOrderId());
}
}
複製程式碼
@PostConstruct
相當於org.springframework.beans.factory.InitializingBean#afterPropertiesSet
的功能,在構造方法完成後會呼叫@PostConstruct
註解的 registry()
方法把自己註冊到 EventCenter
事件中心。
重點類 EventCenter
事件中心
package com.glmapper.event.driven;
/**
* @author: Jerry
* @date: 2018/7/1
*/
public class EventCenter {
/**
* 事件型別和監聽器的繫結對映
*/
private final ConcurrentHashMap<Class<?>, List<EventListener>> subscribers = new ConcurrentHashMap<>();
private final Executor executor;
public EventCenter(Executor executor) {
this.executor = executor;
}
/**
* 繫結 監聽器與事件型別
*
* @param eventListener
* @param clazz
*/
public void registry(EventListener eventListener, Class<?> clazz) {
List<EventListener> listeners = subscribers.get(clazz);
if (listeners == null) {
listeners = new ArrayList<>();
}
listeners.add(eventListener);
subscribers.put(clazz, listeners);
}
/**
* 向事件中心傳送訊息
*
* @param orderEvent
*/
public void post(OrderEvent orderEvent) {
List<EventListener> listeners = subscribers.get(orderEvent.getClass());
if (listeners == null || listeners.size() == 0) {
throw new EventException("找不到該事件的監聽器");
}
for (EventListener listener : listeners) {
//執行緒池非同步處理
executor.execute(() -> listener.trigger(orderEvent));
}
}
}
複製程式碼
他的例項化在配置類裡面
package com.glmapper.event.driven;
/**
* @author: Jerry
* @date: 2018/7/1
*/
@Slf4j
@SpringBootApplication
public class EventDrivenApplication {
public static void main(String[] args) throws InterruptedException {
ApplicationContext applicationContext = SpringApplication.run(EventDrivenApplication.class, args);
EventSender eventSender = applicationContext.getBean(EventSender.class);
while (true) {
long orderId = ThreadLocalRandom.current().nextLong();
eventSender.post(new OrderCreateEvent(orderId));
log.info("有一個新訂單,訂單id={}", orderId);
orderId = ThreadLocalRandom.current().nextLong();
eventSender.post(new OrderCancelEvent(orderId));
log.info("有一個訂單取消,訂單id={}", orderId);
Thread.sleep(ThreadLocalRandom.current().nextLong(1000, 10000));
}
}
@Bean
public EventCenter eventCenter() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("event-bus-%d").build();
int corePoolSize = Runtime.getRuntime().availableProcessors();
int maximumPoolSize = corePoolSize * 2;
// 建立一個執行緒池
Executor pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 10L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024),
namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
return new EventCenter(pool);
}
}
複製程式碼
系統啟動後,事件傳送器不停的向訊息中心傳送事件,事件中心再把事件委派給對應的監聽器處理
其他類
剩下的就是事件傳送器EventSender
、一個事件介面、兩個具體的事件類
package com.glmapper.event.driven.sender;
/**
* @author: Jerry
* @date: 2018/7/1
*/
@Slf4j
@Component
public class EventSender {
@Autowired
private EventCenter eventCenter;
public void post(OrderEvent event) {
eventCenter.post(event);
}
}
===========================================分割線=================================================
package com.glmapper.event.driven.event;
/**
* @author: Jerry
* @date: 2018/7/1
*/
public interface OrderEvent {
Long getOrderId();
OrderEventType getEventType();
}
===========================================分割線=================================================
package com.glmapper.event.driven.event;
/**
* @author: Jerry
* @date: 2018/7/1
*/
public class OrderCreateEvent implements OrderEvent {
private Long orderId;
public OrderCreateEvent(Long orderId) {
this.orderId = orderId;
}
@Override
public Long getOrderId() {
return this.orderId;
}
@Override
public OrderEventType getEventType() {
return OrderEventType.CREATE;
}
}
===========================================分割線=================================================
package com.glmapper.event.driven.event;
/**
* @author: Jerry
* @date: 2018/7/1
*/
public class OrderCancelEvent implements OrderEvent {
private Long orderId;
public OrderCancelEvent(Long orderId) {
this.orderId = orderId;
}
@Override
public Long getOrderId() {
return this.orderId;
}
@Override
public OrderEventType getEventType() {
return OrderEventType.CANCEL;
}
}
複製程式碼
剩下的這些類就很簡單了,不解釋。最後看下結果
當然了,這是一個簡易的事件驅動實現,如果要在框架中實現必然還要考慮更多的因素如:事件中心定義異常處理器,用於消費方處理事件發生的異常等,本來準備考慮實現這些場景的,出於時間限制就。。。或許是太懶了?,那麼推薦一個好用的事件驅動類 google guava
的 EventBus
,這是一個考慮非常完善的事件驅動實現了。
可能有人說了現在都用 MQ
了沒人會用這個了,那麼它存在必然有他存在的道理,說實話一些小型專案壓根都不用 MQ
,就用這個 EventBus
就能夠解決節約成本,話雖如此,為了便於擴充套件還是推薦 MQ
。MQ
是在應用外進行解耦、通過網路傳輸,EventBus
在應用內實現解耦、直接在同一個虛擬機器中完成,不必考慮網路不可用的問題。EventBus
還是非常有學習參考意義的