控制Bean初始化顺序的方法

330 阅读5分钟

控制Bean初始化顺序的方法

  • 构造方法依赖

    通过构造函数依赖,被依赖的bean先初始化

  • @DependOn 注解

    @DepondOn("Bean2")在初始化Bean1之前,会先去初始化Bean2

  • BeanPostProcessor 扩展

    实现BeanPostProcessor接口,会在所有Bean之前初始化

    @Order只能控制注解的优先级

工作亮点

使用redis完成大量数据缓存,提高数据查询效率

使用mybati优化数据查询效率

使用xx解决安装速度慢问题

一次优化n个微服务的启动时间

背景

5个微服务,需要在服务启动脚本中,完成配置文件(5G典型)上传Hadoop的动作,耗时3分钟,串行执行,耗时15分钟,随着配置文件的增大,时间逐渐劣化

问题

上传过程在安装流程里,所以是串行,无优化空间,将上传动作迁移到服务启动过程中,5个服务并发完成

解决

上传时机:Hadoop服务启动成功后,微服务运行前

底层服务(sdk)提供@EnableAutoUploadConfig注解,需要上传的服务打上注解,需要原始配置文件

注解中完成@Import(UpLoadImportSelector.class)

解决问题一:什么时候做:时机确认

新增2个bean,HadoopEnvWatcher,ServiceWatcher 实现BeanPostProcessor接口,保证第一个初始化

实现InitializingBean接口,在afterPropertiesSet方法实现上传逻辑

ServiceWatcher 中autoWired了HadoopenvWatcher

解决问题二:谁来做:多实例服务

从环境中解析配置文件,单实例服务直接做 SingleUploader -> upload

多实例任务只有一个节点做 ClusterUploader -> chooseMaster -> upload

实现ImortSelector接口,不同场景放入不同的bean实例

选主:curator通过zk选主

创建zkClient(ip,retryPolicy,errorPolicy)

创建interProcessMutex(client,path)

interProcessMutex.acquire(); 获取锁 通过多个进程同时在路径下写顺序节点,谁的id最小,谁获取锁
 interProcessMutex.release(); 释放锁
 完成上传后设置flag

解决问题三:如何做

解析配置文件到对象

上传内容:文件、路径

上传场景:安装、升级 策略模式:

上传策略:覆盖、跳过 模板方法:支持替换文件、创建目录、保留

优化导入性能40w - 2分钟

流程

导入-校验-写审计日志-入库-分发

40w - 10分钟 2G

问题

1.数据校验 每次从数据库获取校验逻辑,耗时长

2.每次数据是逐行写入,写入磁盘效率低

3.写审计日志耗时长

优化点

1.校验逻辑变更少,缓存校验逻辑到Hashmap,空间换时间

2.使用values批量插入,对应mybatis的foreach

一次批量插入的数据 2000行一次,效率最高

2.结合stream的并行流, streamList.parallelStream().forEach(consumer);多线程导入

<insert id="insertHistory">
    INSERT INTO ^{tableName}
        <foreach collection="list" item="item" index="index" open="(" separator=", " close=")">^{item}               </foreach>
    VALUES
        <foreach collection="array" item="item" index="index" open="(" separator=", " close=")">#{item}             </foreach>
</insert>

3.后台线程写入审计日志

4.POI 三方包性能低,使用EasyExcel提高性能

5.GC使用并发GC 代替CMS,减少线程占用,提升吞吐量

使用gstat查看gc状况,发现多次fullgc,fullgc占用时间长

jmap查看对象:大量的数据对象和poi对象

6.其他不要在循环中打印日志

CMS

对比

如果系统追求低延迟,那么可以选择CMS垃圾收集器,只是STW的时间缩短了,但是整个GC的时间相对更长了; 如果系统追求高吞吐,那么可以选择并行Parallel GC,虽然STW的时间长,但是可以保证非GC时间,整个系统的资源全部被应用线程占用。

设置并发线程数:默认为 核心数的 5/8 + 3 最终设置为4个

作用范围: 老年代 算法: 并发标记清除算法。 启用参数: -XX:+UseConMarkSweepGC 默认回收线程数: (处理器核心数量 + 3)/4

CMS GC的几个大阶段

  • 1、初始标记(CMS initial mark) stw - 标记gcroot关联对象
  • 2、并发标记(CMS concurrent mark) 遍历老年代,标记存活对象
  • 3、并发标记预清理 并发标记垃圾对象,卡片标记来记录“脏区” (新加入老年代的对象)
  • 3、重新标记(CMS remark) stw 扫描范围: 新生代对象+GC Roots+被标记为“脏”区的对象
  • 4、并发清除(CMS concurrent sweep) 回收空间

java -XX:+PrintFlagsFinal -version

kill -3 打印进程堆栈

kill - 9 强制中断

kill -15 告诉进程自动中断

G1

G1最主要的设计目标:STW停顿时间和比例可预期,可配置

物理上不分区,逻辑分区

G1划分逻辑区 - 2048个Region 每个1MB

可以分为Eden区(5%-60%),存活区,老年代,大对象四种区域

每个区,垃圾占用到一定比例80%,标记为回收集,CollectionSet

垃圾最多的块优先收集

每次回收一部分垃圾(10%)

参数:MaxGcPauseMill:执行gc操作的暂停时间,默认为200毫秒,不一定做得到

巨型对象:52W长度的数组 大于region一半的对象

解决办法:调整RegionSize

吞吐量:并行

低延迟:CMS

内存较大:G1 - 16G以上G1

32G以上ZGC

ZGC

ZGC JDK 11 (实验性开关) JDK 15天然支持

原理:着色指针和读屏障,几乎全部并发执行

gc时间短 不超过10ms 进一步缩短STW时间

堆大小支持广:100M-16T

吞吐量低于G1 15%

只支持linus

Epsilon实验性GC,用于分析GC情况

ShennandoahGC

并行GC,不设置并行线程数,虚拟机有8个核

线程过度,频繁抢占核心, 切换线程,实际执行的GC线程48个,jvm拿到的是72个和 72 * 5/8 + 3 = 48

解决:显示的配置线程个数