背景
在某些特定的場景下,某些服務總有獨立於主鏈路,而又必須去做的事(簡稱爲下游鏈路):比如
- 場景A:往數據庫中插入一條數據之後,下游鏈路需要同步的去處理消費(在同一應用中,就不考慮kafka等MQ了);
- 場景B:系統中需要打印大量的log,log打印操作比較耗時但是在主鏈路中又高頻次;
場景A和場景B中,顯然當前主鏈路是不關注下游鏈路的處理結果的,需要立即返回。好處主要有:
- 降低系統RT值;
- 業務解耦合:避免下游鏈路影響上游
在線上環境,顯然不能採用隨時創建一個線程的方式來執行,從而需要一個全局線程池來操作,故而AsyncEventBus應運而生。
關鍵類,代碼舉例
- Event:事件主體,提交和消費的數據載體
@Data
@AllArgsConstructor
public class MyEvent {
private String name;
private Integer age;
}
- Listener:消費者,實際消費Event的地方
- 實現EventListener接口
- 接收到消息時,被調用的方法用@Subscribe註解
@Slf4j
public class MyEventListener implements EventListener {
/**
* 收到每條消息,實際上執行的方法:注意,需要這兩個註解
*
* @param event
*/
@Subscribe
@AllowConcurrentEvents
public void listen(MyEvent event) {
try {
Thread.sleep(100);
log.info(" deal event " + " " + JSON.toJSONString(event));
} catch (Exception e) {
log.error(" listen error " , e);
}
}
}
- EventBus:控制中心
public class MyAsyncEventBus extends AsyncEventBus {
/**
* 在xml中實例化bean時,會調用此構造方法,將自定義的線程池交由AsyncEventBus使用
*
* @param executor
*/
public MyAsyncEventBus(Executor executor) {
super(executor);
}
/**
* 在xml中實例化bean時,會調用此方法將自定義的Listener註冊進AsyncEventBus
*
* @param eventListeners
*/
public synchronized void setEventListeners(Set<EventListener> eventListeners) {
if (eventListeners != null) {
for (EventListener eventListener : eventListeners) {
// 調用父類AsyncEventBus的register方法,將Listeners註冊進EventBus中
this.register(eventListener);
}
}
}
/**
* 業務方調用此方法來提交事件,由AsyncEventBus分發至各個Listener,執行業務代碼
*
* @param event
*/
@Override
public void post(Object event) {
try {
super.post(event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- XML 方式配置bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName">
<!-- 將自定義的MyEventListener 交由Spring託管 -->
<bean id="myEventListener" class="com.jszhao.demo.bus.MyEventListener"/>
<!-- 註冊一個線程池,供Bus使用 -->
<bean id="eventExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10"/>
<property name="maxPoolSize" value="20"/>
<property name="queueCapacity" value="10000"/>
</bean>
<!-- 將自定義的EventBus 交由Spring託管 -->
<!-- 實例化時,採用包含eventExecutor的構造方法,將前面生成的線程池注入 -->
<!-- 實例化時,將前面註冊的Listener向EventBus註冊 -->
<bean id="myEventBus" class="com.jszhao.demo.bus.MyAsyncEventBus">
<constructor-arg ref="eventExecutor"/>
<property name="eventListeners">
<set>
<ref bean="myEventListener"/>
</set>
</property>
</bean>
</beans>
- controller 層
@Slf4j
@RestController("bus")
public class BusController {
@Autowired
private MyAsyncEventBus eventBus;
@GetMapping("/add")
public void addEvent(String name, Integer age, int threadNum) {
for (int i = 0; i < threadNum; i++) {
MyEvent event = new MyEvent(name, age);
eventBus.post(event);
}
log.info("main method is end ");
}
}
- 運行結果
入參:name= “jszhao” , age= 18, threadNum = 5
2019-10-10 22:05:40.689 INFO 25039 --- [nio-8080-exec-2] c.jszhao.demo.Controller.BusController : main method is end
2019-10-10 22:05:40.787 INFO 25039 --- [eventExecutor-6] com.jszhao.demo.bus.MyEventListener : deal event MyEvent(name=jszhao, age=18)
2019-10-10 22:05:40.787 INFO 25039 --- [eventExecutor-7] com.jszhao.demo.bus.MyEventListener : deal event MyEvent(name=jszhao, age=18)
2019-10-10 22:05:40.788 INFO 25039 --- [eventExecutor-8] com.jszhao.demo.bus.MyEventListener : deal event MyEvent(name=jszhao, age=18)
2019-10-10 22:05:40.793 INFO 25039 --- [eventExecutor-9] com.jszhao.demo.bus.MyEventListener : deal event MyEvent(name=jszhao, age=18)
2019-10-10 22:05:40.793 INFO 25039 --- [ventExecutor-10] com.jszhao.demo.bus.MyEventListener : deal event MyEvent(name=jszhao, age=18)
顯然,從結果來看實現了我們的目的:主鏈路直接返回,下游消費event。
到了這一步,一個簡單的單機異步隊列就已經實現了。
接下來是源碼解析時刻。
核心代碼解析
我們直接看post方法,從這裏進去大致可以分爲這麼幾個步驟
- 獲取當前event註冊的Listener列表(在生產者線程中)
- 由 dispatcher分發Listener,將Listener添加到當前的隊列中(在生產者線程中)
- 開始消費當前隊列,直至隊列爲空。消費的代碼爲將event提交至線程池中執行(在生產者線程中)
- 線程池中線程開始執行Listener (線程池中的線程執行)
從上述流程中可以看吹,雖然1、2、3步都在生產者線程中運行,但這三步並沒有任何耗時的操作(I/O磁盤操作、網絡傳輸等可能造成阻塞的行爲),真正可能耗時的操作是執行Listener的時候,這步是在應用統一管控的線程池中執行,是在我們可控範圍內的。所以總體來說效果是不錯的。
-
- 獲取當前event註冊的Listener列表(在生產者線程中)
@Override
public void post(Object event) {
try {
super.post(event);
} catch (Exception e) {
e.printStackTrace();
}
}
public void post(Object event) {
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
深入subscribers.getSubscribers(event)
Iterator<Subscriber> getSubscribers(Object event) {
ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());
List<Iterator<Subscriber>> subscriberIterators =
Lists.newArrayListWithCapacity(eventTypes.size());
for (Class<?> eventType : eventTypes) {
CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
if (eventSubscribers != null) {
// eager no-copy snapshot
subscriberIterators.add(eventSubscribers.iterator());
}
}
return Iterators.concat(subscriberIterators.iterator());
}
這裏subscribers是個Map,其定義爲
private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers =
Maps.newConcurrentMap();
其中Key爲Event類型,value爲一個線程安全的List。從這裏可以看出:
同一個Event可以有多個下游依賴,AsyncEventBus可根據Listener的入參Event類型來判定,將所有相同Event類型的listener放入一個List中。 具體實現這裏不再贅述,讀者感興趣可以自行查看其實現。
到了這一步,我們就拿到了當前Event對應的Listener列表。
-
- 由 dispatcher分發Listener,將Listener添加到當前隊列中(在生產者線程中)
public void post(Object event) {
Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
if (eventSubscribers.hasNext()) {
dispatcher.dispatch(event, eventSubscribers);
} else if (!(event instanceof DeadEvent)) {
// the event had no subscribers and was not itself a DeadEvent
post(new DeadEvent(this, event));
}
}
其中dispatcher接口有三個實現,在這裏構造函數默認選用的是LegacyAsyncDispatcher,我們進入到dispatcher.dispatch方法內部來:
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
queue.add(new EventWithSubscriber(event, subscribers.next()));
}
EventWithSubscriber e;
while ((e = queue.poll()) != null) {
e.subscriber.dispatchEvent(e.event);
}
}
在這裏發佈,其將本次Event對應的Listener列表信息全部加入了一個線程安全的隊列queue
而queue的定義如下:
private final ConcurrentLinkedQueue<EventWithSubscriber> queue = Queues.newConcurrentLinkedQueue();
在這一環節,event被提交到線程池中執行
-
- 消費的代碼爲將event提交至線程池中執行(在生產者線程中)
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
-
- 線程池中線程開始執行Listener (線程池中的線程執行)