事件驅動,Do you know?

J_Queue發表於2018-07-15

個人理解: 事件驅動(even-driven),字面理解即:由事件去觸發某個或者一系列動作。

百度百科: 從事件角度說,事件驅動程式的基本結構是由一個事件收集器、一個事件傳送器和一個事件處理器組成。  事件收集器專門負責收集所有事件,包括來自使用者的(如滑鼠、鍵盤事件等)、來自硬體的(如時鐘事件等)和來自軟體的(如作業系統、應用程式本身等)。  事件傳送器負責將收集器收集到的事件分發到目標物件中。  事件處理器做具體的事件響應工作。

舉個栗子:起床鬧鐘響了驅動我們該起床了,上課鈴聲響了驅動我們進教室上課、放學鈴聲響了驅動我們上課結束了可以愛幹嘛幹嘛去了。

或許這個栗子符合你,但是對我來說根本不可能?,鬧鐘響了壓根不會起床、不起床聽不到上課鈴、不去上課也聽不到下課鈴,GG

轉入正題,事件驅動的應用無處不在比如:spring、netty、zookeeper、mq,而且事件【event】和監聽器【listener】都是成對存在的,單個存在是沒有意義的,下面手動實現一個事件驅動模型。

那麼事件驅動模型的組成是怎樣的呢?

一、事件驅動模型

先去百度了一張模型圖,噹噹噹當~

事件驅動,Do you know?

說實話,圖不是自己畫的就是不滿意,但是 MAC 上畫圖工具都找不到好用的,就懶得畫了,把事件收集器 ==> **事件中心 **,事件和監聽器的關係可以是1:1、1:N、N:M,取決於自己的需求。

說下流程:

1. 系統啟動,監聽器把自己註冊到事件中心,與某種事件進行繫結
2. 事件傳送器傳送事件到事件中心,事件中心去查詢與處理該事件的監聽器
複製程式碼

過程非常簡單,為什麼要用事件驅動呢?剛好百度百科有個栗子,看完相信優秀的你就明白了:

通常,我們寫伺服器處理模型的程式時,有以下幾種模型:

(1)每收到一個請求,建立一個新的程式,來處理該請求;

(2)每收到一個請求,建立一個新的執行緒,來處理該請求;

(3)每收到一個請求,放入一個事件列表,讓主程式通過非阻塞I/O方式來處理請求

上面的幾種方式,各有千秋,

第(1)種方法,由於建立新的程式的開銷比較大,所以,會導致伺服器效能比較差,但實現比較簡單。

第(2)種方式,由於要涉及到執行緒的同步,有可能會面臨死鎖等問題。

第(3)種方式,在寫應用程式程式碼時,邏輯比前面兩種都複雜。

綜合考慮各方面因素,一般普遍認為第(3)種方式是大多數網路伺服器採用的方式

實際上,事件驅動模型的核心就是 執行緒池 !!! 來實現非同步非阻塞。

二、Simple實現

OK,到這裡就是動手實踐的過程,好記性不如爛筆頭,讀百遍不如敲一遍。看下工程結構

事件驅動,Do you know?
專案地址忘了放 event-driven

理論部分說到事件和監聽器的關係可以是1:1、1:N、N:M,這裡基於 spring boot 編寫一個事件:監聽器=1:N的實現,老套路,跟著上面的流程分析走:

  1. 系統啟動,監聽器把自己註冊到事件中心,與某種事件進行繫結
  2. 事件傳送器傳送事件到事件中心,事件中心去查詢與處理該事件的監聽器

寫一個監聽器介面EventListener 和兩個實現類OrderCancelListenerOrderCreateListener

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;
    }
}
複製程式碼

剩下的這些類就很簡單了,不解釋。最後看下結果

事件驅動,Do you know?

當然了,這是一個簡易的事件驅動實現,如果要在框架中實現必然還要考慮更多的因素如:事件中心定義異常處理器,用於消費方處理事件發生的異常等,本來準備考慮實現這些場景的,出於時間限制就。。。或許是太懶了?,那麼推薦一個好用的事件驅動類 google guavaEventBus ,這是一個考慮非常完善的事件驅動實現了。

可能有人說了現在都用 MQ 了沒人會用這個了,那麼它存在必然有他存在的道理,說實話一些小型專案壓根都不用 MQ,就用這個 EventBus 就能夠解決節約成本,話雖如此,為了便於擴充套件還是推薦 MQMQ 是在應用外進行解耦、通過網路傳輸,EventBus在應用內實現解耦、直接在同一個虛擬機器中完成,不必考慮網路不可用的問題。EventBus還是非常有學習參考意義的

終於寫完了,感覺得睡到下午兩三點了。。。。

相關文章