Расскажите о EventBus, который вы не можете использовать

интервью задняя часть

Недавно я обнаружил такой бизнес-сценарий во время Code Review.После завершения бизнес-процесса необходимо уведомить рецензентов.Способ уведомления включает SMS и электронную почту, поэтому код примерно такой:

//业务校验
validate();
//处理业务逻辑
doBusiness();
//发送邮件或者发送其他类型消息
sendMsg(); 

Это правильно?

Основываясь на этом общем бизнес-сценарии, обычно мы сначала рассмотримСинхронизироватьилиасинхронныйпроблема с отправкой.

Синхронизация влияет на интерфейс RT и завязана на бизнес-логику, такой подход однозначно не годится.

В обычных условиях мы сделаем это асинхронным, используяMQОтправьте собственное потребление или пул потоков для этого, чтобы это не влияло на основную бизнес-логику, могло повысить производительность, а код можно было развязать.

Затем возникает проблема согласованности данных: должно ли электронное письмо быть успешно отправлено?

На самом деле, в большинстве случаев мы не требуем, чтобы 100 % писем были успешно отправлены. Если это не удастся, то это не удастся. Хорошо, что частота отказов не должна превышать пороговое значение при мониторинге и оповещении. Кроме того , как только служба сообщений получит запрос, она должна убедиться, что сообщение может быть доставлено самостоятельно.

В общем, используйте MQ для отправки сообщений, чтобы потреблять и обрабатывать их самостоятельно, или пулы потоков для их асинхронной обработки, и, наконец, создайте собственную компенсационную логику для решения таких проблем.

Итак, сегодня я хочу поговорить о методе обработки, отличном от этих двух решений.Для этого сценария мы действительно можем использоватьEventBusрешать.

Использование EventBus

Как видно из названия, EventBus означает шину событий, это инструмент библиотеки Google Guava, это можно сделать на основе режима наблюдателя.в процессеразвязка кода.

Если взять приведенный выше пример в качестве примера, то внедрение MQ слишком тяжело. На самом деле в этом нет необходимости. EventBus также может достичь этого эффекта. По сравнению с MQ, он может только обеспечивать внутрипроцессную доставку событий сообщения, что очень важно для нашего бизнеса.Достаточно для сцены, не так ли?

Давайте сначала посмотрим, как используется EventBus.Как правило, сначала создается экземпляр EventBus.

//1.创建EventBus
private static EventBus eventBus = new EventBus();

Второй шаг — создать подписчика на сообщение о событии.Метод обработки очень прост, если мы добавим метод, который хотим обрабатывать событие.@SubscribeПросто аннотируйте.

Формальный параметр может быть только один.Если определено 0 или более, операция сообщит об ошибке.

public class EmailMsgHandler {

    @Subscribe
    public void handle(Long businessId) {
        System.out.println("send email msg" + businessId);
    }
}

Третий шаг — регистрация события.

eventBus.register(new EmailMsgHandler());

Четвертый шаг — отправить событие.

eventBus.post(1L);

Это самый простой пример использования EventBus Давайте посмотрим, как поступить с примером, упомянутым в начале.

Совместить с реальностью

Например, во всех случаях, упомянутых выше, таких как сценарии регистрации и заказа пользователей, необходимо отправлять сообщения и электронные письма пользователям.

EventBus не принуждает нас использовать одноэлементный режим, потому что стоимость его создания и уничтожения относительно низка, поэтому его лучше выбирать в соответствии с нашими бизнес-сценариями и контекстом.

public class UserService {
    private static EventBus eventBus = new EventBus();

    public void regist(){
        Long userId = 1L;
        eventBus.register(new EmailMsgHandler());
        eventBus.register(new SmsMsgHandler());
        eventBus.post(userId);
    }
}

public class BookingService {
    private static EventBus eventBus = new EventBus();

    public void booking(){
        //业务逻辑
        Long bookingId = 2L;
        eventBus.register(new EmailMsgHandler());
        eventBus.register(new SmsMsgHandler());
        eventBus.post(bookingId);
    }
}

Затем, после завершения обработки бизнес-логики, регистрируются два подписчика событий для электронной почты и SMS соответственно.

public class EmailMsgHandler {

    @Subscribe
    public void handle(Long businessId) {
        System.out.println("send email msg" + businessId);
    }
}

public class SmsMsgHandler {

    @Subscribe
    public void handle(Long businessId) {
        System.out.println("send sms msg" + businessId);
    }
}

Наконец, мы отправляем событие, пользователь регистрируется, и мы отправляем идентификатор пользователя, и мы отправляем идентификатор заказа, если заказ выполнен успешно.

Напишите еще один тестовый класс, чтобы протестировать его, и создайте дваservice, а затем вызывать методы по отдельности.

public class EventBusTest {

    public static void main(String[] args) {
        UserService userService = new UserService();
        userService.regist();

        BookingService bookingService = new BookingService();
        bookingService.booking();

    }
}

Выполнив тестовый класс, мы можем увидеть вывод, соответственно, для выполнения нашего метода подписки на событие.

send email msg1
send sms msg1
send email msg2
send sms msg2

Вы найдете его очень простым в использовании для тех, кто хочет быть легким и простым в использовании развязкой.EventBusочень подходит.

Будьте осторожны, чтобы не наступить на яму

Во-первых, обратите внимание, что параметры в примереLongТип, если параметры события другого типа, то сообщение не может быть получено, например, меняем идентификатор заказа, отправляемого в заказе, наStringЗатем тип обнаружит, что потребления нет, потому что мы не определили тип параметра, которыйStringМетоды.

public class BookingService {
    private static AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(3));

    public void booking(){
        //业务逻辑
        String bookingId = "2";
        eventBus.register(new EmailMsgHandler());
        eventBus.register(new SmsMsgHandler());
        eventBus.post(bookingId);
    }
}
//输出
send email msg1
send sms msg1

идти сEmailMsgHandlerа такжеSmsMsgHandlerдобавить получениеStringВведите метод подписки, чтобы его можно было получить.

@Subscribe
public void handle(String businessId) {
 System.out.println("send email msg for string" + businessId);
}

@Subscribe
public void handle(String businessId) {
 System.out.println("send sms msg for string" + businessId);
}

//输出
send sms msg1
send email msg1
send email msg for string2
send sms msg for string2

Кроме того, мы можем фактически определитьDeadEventЧтобы справиться с этой ситуацией, он эквивалентен методу обработки по умолчанию, когда нет соответствующего параметра типа события, он отправляет метод по умолчанию.DeadEventмероприятие.

Определите обработчик по умолчанию.

public class DefaultEventHandler {

    @Subscribe
    public void handle(DeadEvent event) {
        System.out.println("no subscriber," + event);
    }

}

даватьBookingServiceдобавить одинpay()Способ оплаты, перейдите к оплате после размещения заказа, зарегистрируйте наше событие по умолчанию.

public void pay(){
  //业务逻辑
  eventBus.register(new DefaultEventHandler());
  eventBus.post(new Payment(UUID.randomUUID().toString()));
}

@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment {
    private String paymentId;
}

выполнить тестbookingService.pay()Смотрите вывод:

no subscriber,DeadEvent{source=AsyncEventBus{default}, event=Payment(paymentId=255da942-7128-4bd1-baca-f0a8e569ed88)}

Анализ исходного кода

Итак, это краткое введение. На самом деле, то, что мы сказали до сих пор,Синхронный вызовДа, это не совсем соответствует нашим требованиям, конечно лучше использовать асинхронную обработку.

Затем посмотрите исходный код, как это реализовано.

@Beta
public class EventBus {

  private static final Logger logger = Logger.getLogger(EventBus.class.getName());

  private final String identifier;
  private final Executor executor;
  private final SubscriberExceptionHandler exceptionHandler;

  private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
  private final Dispatcher dispatcher;
  
  public EventBus() {
    this("default");
  }

  public EventBus(String identifier) {
    this(
        identifier,
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        LoggingHandler.INSTANCE);
  }
}

identifierэто имя, тег, по умолчаниюdefault.

executorисполнитель, один создается по умолчаниюMoreExecutors.directExecutor(), подписчики событий на основе предоставленных вамиexecutorЧтобы определить, как выполнять подписку на события.

exceptionHandlerЭто обработчик исключений, и созданием по умолчанию является журнал управления.

subscribersЭто наши потребители, наши подписчики.

dispatcherИспользуется для распространения событий.

создан по умолчаниюexecutorЯвляетсяMoreExecutors.directExecutor(),Видетьcommand.run()Вы обнаружите, что он не является синхронным исполнением.

public static Executor directExecutor() {
 return DirectExecutor.INSTANCE;
}

private enum DirectExecutor implements Executor {
 INSTANCE;

@Override
public void execute(Runnable command) {
 command.run();
}

@Override
public String toString() {
 return "MoreExecutors.directExecutor()";
}

Синхронное выполнение не очень хорошо, мы надеемся не только развязать нас, но и выполнять асинхронно, EventBus предоставляет намAsyncEventBus,ExecutorМы просто передаем это в себя.

public class AsyncEventBus extends EventBus {

  public AsyncEventBus(String identifier, Executor executor) {
    super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }
 
  public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
    super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
  }

  public AsyncEventBus(Executor executor) {
    super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
  }

Мы изменили приведенный выше код на асинхронный, чтобы он был лучше.В этом случае мы действительно можем работать с ним в сочетании с нашим собственным пулом потоков.

private static AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(3));

Хорошо, это понятно, кстати, мы можем взглянуть на обработку распределения событий, см.DeadEventНу нет подписчиков на текущее событие, и один будет отправленDeadEventСобытия, бинго!

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));
  }
}

Суммировать

Хорошо, это использование и исходный код относительно просты, и заинтересованные студенты могут взглянуть на них самостоятельно, это не требует особых усилий.

В целом,EventBusЭто должно предоставить нам более элегантный способ разделения кода, и вы определенно можете использовать его в реальной работе бизнеса!