背景
在看 gitee的一个开源项目 => gitee-base-admin 的时候,发现其中的项目日志通过websocket推送到客户端挺有意义的.个人项目可以加上这个小功能。
但是 其实现是每个用户都新建线程去推日志, 线程数和用户数 1:1. 并且无法对集群日志监听.遂想实现更通用(优雅)的方法实现
改写思路
- 将原本的读取本地日志文件 => logging的append机制 => append 多份 => 追加一份到自定义的 Appender
- Appender将消息写入到Dispatcher中进行分发处理
- 将Dispatcher抽象为接口,做多份实现 以满足 单机基于内存的,集群基于mq中间件的情况
- 将用户的websocket上下线推送 => 缓存n行(CircularFifoQueue)做首次推送,其他的消息做轮询用户推送,可以异步化防止单条消息阻塞时间长的问题。 新用户推送需要注意防止消息丢失问题
- coding
坑点出现了
在项目启动的时候,启动没有报错,就是没有新消息过来,但是append也写入消息,但是就是取不到消息可以进行消费,后发现两个Dispatcher不是一个实例,饿汉式的单例失效了? 竟然不是一个类加载器?
原因如下:
spring-boot-devtools
对项目的代码是 RestartClassLoader,jar包的代码则是App,又因为我们是通过logback构造的append,所以就算是项目的代码,构造方式,或者流程不一,导致类加载器不一样,最终体现在单例不是单例了。
怎么解决呢? 不用devtools即可 !!! 或者百度dev-tools 配置 or jrebl不香吗
编写过程中的注意点
因为logging的优先级问题,所以不能采用bean的方式构造,我采用的是java spi机制实现多环境切换
ClassLoader
- jar包隔离
- 动态debug
加载类实例的几种方式 ?
- Class.forName
- new
- ClassLoader.loadClass
- other 一些序列化什么的
- spi => ServiceLoader
ClassLoader 一些点
- ClassLoader.loadClass 类加载器和当前加载的Class不一致时 会导致
com.boommanpro.Parent cannot be cast to com.boommanpro.Parent
-
new 方式使用的是当前代码所在类加载器加载,而不是调用当前代码的代码所在类加载器加载
-
Class.forName运作时,通过Reflection.getCallerClass(),能够获取是谁调用了Class.forName,最终还是和new 一致性
-
代码 Class有类加载器,线程有类加载器 ServiceLoader用线程的
什么时候类会被加载
参照 java类在何时被加载
说明了以下几种方式
- main类
- 创建类的实例 new创建子类的实例
- 访问类的静态方法
- 访问类的静态变量
- 反射
补充两点:
- cast -> Object o = new Object(); Parent o2 = (Parent) o; 本质是 Parent.class.cast(o);
- instanceof =>
java.lang.Class#isInstance