如何查系统日志组件(日志系统开发总结之Guava)

已经连续有10天没有更新头条号了,原因是最近比较忙。最近和同事一起搞日志系统的开发,涉及到基础架构和具体的业务日志记录,难倒不难,但量比较大,而且这期间还有其他需求和BUG需要我们来维护,所以我也就没有了时间去更新头条号,明天我要请一天假有点事处理,今天下午就写写我们这个日志系统怎么做的吧。

有人会问我,你的日志为什么不用AOP来做呢,对一些方法例如save,update方法做方法级别的拦截,在执行完成后像数据库插入一条记录不就可以了。这种方案我们考虑过,也许是我对AOP的理解不到位吧,我们并没有采用这个方案,原因是因为我们要记录的日志并不是固定的、模版式的,而是具有很强的业务属性,要记录每个操作的人、以及具体做了那些操作,操作的对象都是什么属性等,而AOP适合一些公共的,统一的,业务性弱但行为性强的操作。所以我们就没有考虑使用spring的切面。

首先说一下我们这个日志记录的技术要求有哪些:

(1)日志的保存要求在本次事务提交之后的另外一个新事务异步提交;当然也要支持在同一个事务里同步提交。

(2)支持日志的持久化,即若系统宕机后要记录的日志不能丢并保证日志可以在某个时间点统一保存到日志库(表)中。

针对第一个问题,在以前我记录日志的设计中,通常我会在service层(事务层)执行完后成,将日志信息返回到前一层也就是controller(控制层),在控制层中提交日志。优点是可以保证事务已经提交,不过缺点是改造比较大,我需要改造每一个接口,修改返回值等内容,还要在控制层判断业务是否成功。其次,假设日志保存比较慢,日志的保存应当不能影响主线程的进度,所以要开启一个新的线程完成这项工作。

针对第二个问题,我想可以引入MQ,将日志先保存到MQ中,通过监听来读取mq消息,然后逐一保存到日志库中。即使服务宕机,MQ可以靠其集群特性保证日志不丢失。优点是方案比较成熟,缺点是增加了维护成本。

综上考虑,我们最后决定使用eventbus这个组件,暂时也不引入MQ,因为业务量没有那么大。而是创建一个message的表,用以保存日志或者其他消息记录。那首先我们解释下什么是eventbus.

如何查系统日志组件(日志系统开发总结之Guava)(1)

我们在百度搜索eventbus,竟然没有百度百科,很多人都不知道这是什么,如果让我为其总结一个概念我可能也说不好,根据我个人的理解,eventbus汉语意思就是事件服务总线,事件代表着我们系统中各种各样可能被触发的事件、任务。由于服务中的这些事件可能会越来越多,所以我们需要一套机制去管理这些事件,提供订阅、发布等模式,最终形成一整套的事件调度和执行,适应于多系统集成的方案。当然这都是我自己的体会不一定对。简单来说,eventbus的一个比较好的实现就如mq,mq可以作为事件中心,我们将所有的事件全部注册到mq,mq帮助我们来分发消化我们的消息。这里,我们使用的eventbus的实现方案是google的guava的eventbus库。MQ就是传统的事件订阅与发布的模式,eventbus是进程内的模式,下面我们先来看一下具体的术语概念:

如何查系统日志组件(日志系统开发总结之Guava)(2)

那我们如何将日志抽象成事件呢?

(1)当我们保存日志的时候其实就是要注册一个事件,我们可以称之为LogEvent。

(2)而这个事件我们的目的是要保存它,我们肯定会有一个事件监听者我们称之为LogEventListener。

(3)将来我们系统中还会有其他的事件,不同的事件监听器针对不同的事件进行订阅,会走各自的实现类,但接口应该都是一样的,即IEventListener,提供一个listener方法,LogEventListener实现该接口,重写listener方法。将来比如有发短信的事件处理,我们就在定义一个MsgEventListener即可。

(4)IEventListener的listener方法就是统一的事件处理方法

(5)最后我们要做的就是将该事件注册到总线中,让各位监听者可以监听到。

发现讲了很多,可能大家都不会理解,因为文章涉及的内容比较多,我觉得还是应该拆看来讲,我们今天就主要将guava的eventbus库,让大家理解什么是eventbus,如何使用eventbus。

guava-eventbus为我们提供了同步和异步的事件解决方案,即我们在进程中希望我们这个事件可以同步完成然后返回,或者可以异步完成不影响主线程。各自的实现方式如下图:

如何查系统日志组件(日志系统开发总结之Guava)(3)

为了让大家更清晰的了解EventBus所提供的同步和异步机制,下面我们来做一个DEMO,假设我们现在系统内有A事件与B事件分别发送给事件中心EventBus处理,AB事件分别有各自的事件处理者,首先看事件处理接口定义:

如何查系统日志组件(日志系统开发总结之Guava)(4)

实现类如下:

如何查系统日志组件(日志系统开发总结之Guava)(5)

如何查系统日志组件(日志系统开发总结之Guava)(6)

在主线程中,将这些事件处理者在服务启动的时候注册到eventBus中心来管理,如图:

如何查系统日志组件(日志系统开发总结之Guava)(7)

其中eventBus实例为同步实例,而asyncEventBus为异步实例。

假设主线程可以生存5000毫秒,主线程启动后,启动一个新的线程(这个线程1000毫秒就结束了)发送A事件和B事件,在A事件由于业务简单,基本不需要耗时;B事件需要耗时2000毫秒才能执行完成,如果上述场景存在,那结论应该是当我们分别想eventBus提交我们的事件后,A事件会被处理,但B事件不会别处理,因为在同步情况下,B事件所在的子线程1000毫秒已经死亡,B事件由于耗时太长2000毫秒被终止。我们看结论是否正确:

如何查系统日志组件(日志系统开发总结之Guava)(8)

执行main方法,我们看下输出:

1、子线程处理业务开始

2、子线程发送消息给EventBus

3、AEventListener.listener:A事件执行完成

5、子线程终止

6、主线程....

7、主线程结束....

我们发现(4)也就是B事件压根没有输出,也就是说在同步模式下,由于子线程死亡,B事件没有处理成功。

如果同样的问题,我们使用异步的方式结果是什么呢?只需要将eventBus修改为asyncEventBus实例调用post方法即可。

如何查系统日志组件(日志系统开发总结之Guava)(9)

运行main方法,看下输出:

1、子线程处理业务开始

2、子线程发送消息给EventBus

3、AEventListener.listener:A事件执行完成

5、子线程终止

6、主线程....

4、BEventListener.listener:B事件执行完成

7、主线程结束....

结论是(3)(4)事件都会执行了,由于我们使用的是异步方式,即任务已经在其他子线程中执行了,即使其所在的子线程已经死亡,也不会影响任务的执行。

在这里有同学可能会疑问,你是通过什么让AEventListener监听到A任务的?只调用了一个post方法就可以吗? 因为guava已经帮助我们将这个机制给封装了,我们会在之后的续篇讲解其源代码,这里我们可以看一下源代码,当我们调用eventbus.post(T)方法的时候,实际上guava会在底层解析T对象的类型,将其作为key去找map中的value,而这个map是其维护在内部的,key是参数class,而value就是处理类,我们在定义AEventListener的时候它重写listener方法所接受的参数就是AEvent,同理,BEventListener的参数是BEvent,在服务启动时,eventbus中心根据这些类型将listener放在map中,在运行时,根据post的参数类型找到对应的listener。源码如下:

如何查系统日志组件(日志系统开发总结之Guava)(10)

getAnnotatedMethods是找哪些被注解@Subscribe修饰的方法。即如果你想让这个listener监听到你的事件,就要在其方法上加上注解@Subscribe,这样在服务启动时,它就可以以此监听到你的事件了。

其实有了这个机制,不管是做日志还是做其他的什么,只要涉及到异步执行的例如发短信,发邮件等我们都可以基于这个机制来做,如果说性能肯定不如mq,如果从易用性维护性来讲,对于中小型系统足够了。在我们的日志系统中,我们要选择的就是这种异步的处理方式,当主线程的业务处理完成后,构造的日志内容也完成了,此时我们就要将这个日志post到事件中心。而日志的保存就异步执行了,不会阻塞主线程。不过这里还有一个问题我们没有解决就是事务。我们如何保证让主线程的事务提交后,再去post日志数据呢?之前我们说放在controller层,这样显然不好,改动太大,我们要的就是在service层post日志,但需要在service提交事务后做?这里的确可以使用aop做,不过Spring4.3.2给我们提供了更好更简单的方式ApplicationEventPublisher,这个我们将放在下一篇再讲了。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页