当前位置: 代码迷 >> Java相关 >> 项目中应用eventbus解决的有关问题
  详细解决方案

项目中应用eventbus解决的有关问题

热度:9   发布时间:2016-04-22 19:16:28.0
项目中应用eventbus解决的问题

  在项目开发过程中,往往有些功能表面看起来简单,但实际开发的结果非常复杂,仔细分析下原因发现很多都是因为附加了许多的额外功能。

  真的简单吗?

  比如我们对一个电商平台的商品数据做修改的功能来讲,其实非常简单,无非就是运营人员在管理平台中对商品进行修改数据,然后点击提交,核心功能的确很简单,但可能有人会要求对商品的修改都需要增加操作日志,还有人提出需要在商品数据修改后自动去更新检索系统中的数据,有人提要在商品数据修改后需要经过审核人的审核才能生效,还有人提需要给运营人员发邮件通知等等,如此一来这个商品修改的功能就不是那么简单了,它除了完成自己的使命,还需要去调用其它的服务来完成,活动类似如下:

 横向的主流程,上下四个小方框是附加功能,复杂的原因包含如下两点:

  •   附加功能导致功能开点变多,工作量加大
  •   附加功能导致程序逻辑复杂,在程序中需要去访问其它的服务,还需要考虑数据完整性,性能,服务依赖等各种问题。

  刚开始我们在项目中开发时,使用的就是最简单的强耦合去直接调用服务,比如在调用完数据保存的方法后,去调用logService的log方法记录日志,调用esService的方法去更新检索系统,调用mailService的方法去发邮件,这样会导致我们的productService强耦合这些与商品保存逻辑没有直接关联的服务,这样看起来商品保存的功能变得不那么单纯,也就是我们文前提到的复杂了,会出现类似代码:

@Autowiredprivate SearchService searchService;@Autowiredprivate MailService mailService;@Autowiredprivate ProductLogService productLogService;//如下下保存商品的代码片段itemDao.save(product);searchService.update(product.getId());mailService.post(product.getId());logService.log(product.getId());

  问题如下:

  • 对无业务直接关联的服务强耦合
  • 可能存在性能问题,比如你需要关心邮件发送是否会影响主流程
  • 代码可读性变差,过多的逻辑容易导致分不清主体核心功能

  如果解决呢?有一个设计模式可以解决,那就是观察者模式,之前学习.net时专门写过一篇(老生常谈:观察者模式),里面提到有传统的实现方式以及事件机制,事件机制的实现比较简单一些,这里我们在解决这个问题时引用了guava组件中提供的eventbus,它与之前那篇观察者模式的实现很相似,看下面这张图,功能之间没有错综复杂的依赖。

  注:guava的eventbus是个进程内级别的,无法跨进程,后面我抽时间再整理下基于消息队列的分布式事件总结。

 

  这里我并不介绍如何使用EventBus,而是重点来说明我们项目中对它的应用,哪些是做的不好的地方。

  第一:要有观察者,这里我们根据不同的业务封装不同的观察者,下面是更新检索系统数据的类

@Service  public class SearchEventListener {    @Autowired()    ProductUpdateMgr productSearchUpdateMgr;    private final static Logger logger = LoggerFactory.getLogger(SearchEventListener.class);    @Subscribe    public void listen(String itemLegacyId) {        try        {            productSearchUpdateMgr.markProductDirty(itemLegacyId);        }        catch(Exception ex)        {            logger.error("更新检索异常:" + ex.getMessage() + ex.getStackTrace());        }    }    }


  第二:需要有将观察者注册到eventbus中去,我们专门写了一个类来做这件事情,完成两件事情:

  • 注册观察者到eventbus中
  • 进一步包装post方法以便调用者以服务形式调用,让productService依赖eventbus而不是依赖实际的检索服务,邮件服务等
@Servicepublic class EventListenerManager {    EventBus mmsProductEventBus;    EventBus mmsItemEventBus;    EventBus mmsSearchEventBus;    EventBus mmsRebuildAllSearchEventBus;    EventBus mmsCommonLogEventBus;    @Autowired    ItemEventListener itemListener;    @Autowired    ProductEventListener productListener;    @Autowired    SearchEventListener searchListener;    @Autowired    RebuildAllSearchEventListener rebuildAllSearchListener;    @Autowired    MmsCommonLogEventListener commonLogListener;    @PostConstruct    private void init() {        mmsProductEventBus = new EventBus();        mmsItemEventBus = new EventBus();        mmsSearchEventBus = new EventBus();        mmsRebuildAllSearchEventBus=new EventBus();        mmsCommonLogEventBus=new EventBus();        mmsItemEventBus.register(itemListener);        mmsProductEventBus.register(productListener);        mmsSearchEventBus.register(searchListener);        mmsRebuildAllSearchEventBus.register(rebuildAllSearchListener);        mmsCommonLogEventBus.register(commonLogListener);    }    public void notifyItemLog(Long itemId) {        mmsItemEventBus.post(itemId);    }    public void notifyProductLog(Long productId) {        mmsProductEventBus.post(productId);    }    public void notifyUpdateSearch(String itemLegacyId) {        mmsSearchEventBus.post(itemLegacyId);    }    public void notifyRebuildAllSearch() {        mmsRebuildAllSearchEventBus.post("");    }    public void notifyAddCommonLog(MmsCommonLogModel log) {        mmsCommonLogEventBus.post(log);    }} 

 

  上面的实现以及我们在刚学习使用guava eventbugs时遇到哪些问题呢?
  问题一:为什么上面的代码有这么多的eventbus而不是一个呢?注意下enventbus的post方法,我们再看下它的源码:它是根据参数的类型来找观察者注册的方法的,而我们写的观察者类中的方法中的参数都是一些primitive类型的,总共有10个左右方法,要想根据参数类型来正确的在一个eventbus中识别调用哪个方法,是比较困难的。
 

public void post(Object event) {    Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());    boolean dispatched = false;    for (Class<?> eventType : dispatchTypes) {      subscribersByTypeLock.readLock().lock();      try {        Set<EventSubscriber> wrappers = subscribersByType.get(eventType);        if (!wrappers.isEmpty()) {          dispatched = true;          for (EventSubscriber wrapper : wrappers) {            enqueueEvent(event, wrapper);          }        }      } finally {        subscribersByTypeLock.readLock().unlock();      }    }    if (!dispatched && !(event instanceof DeadEvent)) {      post(new DeadEvent(this, event));    }    dispatchQueuedEvents();  }


  如何解决?可以针对每个方法的参数封装一个类,比如更新检索的方法参数叫SearchChangeEvent,发送审核邮件的参数叫ApprovalChangeEvent等等,这样我们就可以将所有的观察者注册到一个eventbus中,调用post方法时就不会出现问题了,最后简化后的结果如下:

@AutowiredEventListenerManager eventManager;//如下下保存商品的代码片段itemDao.save(product);eventManager.post(new SearchChangeEvent(product.getId));eventManager.post(new MailChangeEvent(product.getId));eventManager.post(new LogChangeEvent(product.getId));


  问题二:性能问题,之前有提到过附加的功能会导致原本单纯的事物不单纯,比如调用某些服务时可能影响整体性能,当时我们想当然的认为使用了enventbus本身就是异步的,其实如果用eventbus它是同步的,要想使用异步需要使用这个类来完成AsyncEventBus。

  问题三:强耦合问题,由于有了EventListenerManager,我们在具体业务中就不需要依赖不直接相关的服务了,只需要依赖EventListenerManager这个看起来与任务业务都无关的管理类就可以了。


  通过实际项目中对eventbus的应用来分析它能解决的问题以及当初应用有待提高的地方。很显示eventbus应用得当可以简化程序复杂性,提高代码可读性,降低开发维护成本。


2楼大道无情
应用 了领域事件很好,但是我觉得你对事件应用反了,商品的修改不应该产生那么多事件,只需要产生一个 商品修改事件就OK了, 应该让日志记录器,邮件发送器等来订阅修改事件,这样才能解耦合,这样修改功能就简单了,修改就产生一个修改事件,至于修改后要做什么 那交给其它的领域去处理
Re: min.jiang
@大道无情,容我琢磨琢磨
Re: philip502
@大道无情,引用应用 了领域事件很好,但是我觉得你对事件应用反了,商品的修改不应该产生那么多事件,只需要产生一个 商品修改事件就OK了, 应该让日志记录器,邮件发送器等来订阅修改事件,这样才能解耦合,这样修改功能就简单了,修改就产生一个修改事件,至于修改后要做什么 那交给其它的领域去处理,,,应该是只有一个事件,这样消息更明确,,另外可以通过注解+反射自动订阅事件,对于复杂系统这样省事多了
1楼玻璃鱼儿
这不就是领域事件吗?
Re: JRoger
@玻璃鱼儿,是的