Spring Boot利用dag加速Spring beans初始化

1.什么是Dag?

有向无环图(Directed Acyclic Graph),简称DAG,是一种有向图,其中没有从节点出发经过若干条边后再回到该节点的路径。换句话说,DAG中不存在环路。这种数据结构常用于表示并解决具有依赖关系的问题。

20201229164751920

DAG的特性

  • 首先,DAG中的节点可以有入度和出度。节点的入度是指指向该节点的边的数量,而节点的出度是指由该节点指向其他节点的边的数量。在DAG中,节点的入度可以是0或正整数,而出度可以是0或正整数,但不能同时为负数。
  • DAG的另一个重要性质是存在一个或多个拓扑排序。拓扑排序是DAG中节点的线性排列,满足任意一条有向边的起点在排序中都位于终点之前。可以使用深度优先搜索(DFS)或宽度优先搜索(BFS)算法来生成拓扑排序。

 

DAG的应用

  1. 任务调度
  2. 编译器优化
  3. 数据流分析
  4. 电路设计

2.如何加速Spring Bean初始化?

在Spring框架中进行DAG(有向无环图)分析以实现并行初始化,可以有效提升应用程序启动的性能。通常情况下,Spring应用程序的Bean是按依赖顺序初始化的,而通过DAG分析可以识别出哪些Bean之间没有依赖关系,并行初始化这些Bean可以减少启动时间。以下是实现思路:

1. 识别依赖关系,构建DAG

首先需要识别Spring Bean之间的依赖关系。可以通过@DependsOn注解、构造器注入或@Autowired等方式获取Bean依赖。具体步骤:

  • 遍历Spring上下文中的所有Bean定义。
  • 根据Bean的依赖关系构建DAG,节点代表Bean,边表示依赖关系。

Spring的ApplicationContext提供了getBeanDefinitionNames()方法可以列出所有的Bean,通过BeanDefinition可以分析出依赖。

2. 拓扑排序

通过拓扑排序(Topological Sorting)对DAG进行排序,以确保Bean按依赖顺序初始化。拓扑排序可以确定哪些Bean可以并行初始化,哪些Bean必须在某些Bean之后初始化。 使用算法如Kahn’s Algorithm或DFS找到所有没有依赖的Bean(入度为0的节点),这些节点可以并行初始化。

3. 并行初始化Bean

在完成拓扑排序后,使用多线程来并行初始化可以同时启动的Bean。可以使用Java的ExecutorService或类似的线程池机制来管理并发的Bean初始化过程。 步骤:

  • 针对所有入度为0的节点,启动一个线程来初始化它们。
  • 当某个Bean初始化完成后,减少它所依赖的其他Bean的入度值。
  • 当某个Bean的入度为0时,可以在另一个线程中启动它的初始化。

4. Spring Integration

可以通过BeanFactoryPostProcessorApplicationContextInitializer来挂钩到Spring的初始化流程中,分析Bean之间的依赖关系,并将并行化初始化逻辑集成到Spring容器的启动过程中。 具体方法:

  • 实现BeanFactoryPostProcessor,在Bean初始化之前分析Bean的依赖并构建DAG。
  • 在Bean初始化阶段,使用多线程并行处理独立的Bean。

3.代码工程

整体的依赖关系如下:

layer

实验目标

实现自定义Bean并行化加载

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.1</version></parent><modelVersion>4.0.0</modelVersion><artifactId>dag</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.jgrapht</groupId><artifactId>jgrapht-core</artifactId><version>1.5.1</version></dependency></dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><fork>true</fork><failOnError>false</failOnError></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><forkCount>0</forkCount><failIfNoTests>false</failIfNoTests></configuration></plugin></plugins></pluginManagement></build>
</project>

Bean初始化

package com.et.config;import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedAcyclicGraph;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;@Component
public class DAGBeanInitializer implements BeanFactoryPostProcessor {private final ExecutorService executorService = Executors.newFixedThreadPool(10);@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);beanDefinitionMap.put(beanName, beanDefinition);}// build DAGDirectedAcyclicGraph<String, DefaultEdge> dag = buildDAG(beanDefinitionMap,beanFactory);// bean layersList<Set<String>> layers = getBeansByLayer(dag);System.out.println("layers:"+layers);// init bean by layersinitializeBeansInLayers(layers, beanFactory);}// DAG Beanprivate DirectedAcyclicGraph<String, DefaultEdge> buildDAG(Map<String, BeanDefinition> beanDefinitionMap, ConfigurableListableBeanFactory beanFactory) {DependencyResolver resolver = new DependencyResolver(beanFactory);DirectedAcyclicGraph<String, DefaultEdge> dag = new DirectedAcyclicGraph<>(DefaultEdge.class);for (String beanName : beanDefinitionMap.keySet()) {if(shouldLoadBean(beanName)) {dag.addVertex(beanName);String[] dependencies = beanDefinitionMap.get(beanName).getDependsOn();if (dependencies != null) {for (String dependency : dependencies) {dag.addEdge(dependency, beanName); }}// get @Autowired dependenciesSet<String> autowireDependencies = resolver.getAllDependencies(beanName);for (String autowireDependency : autowireDependencies) {// convert beanNameString autowireBeanName = convertToBeanName(autowireDependency);dag.addVertex(autowireBeanName);dag.addEdge(autowireBeanName, beanName);}}}return dag;}private String convertToBeanName(String className) {String simpleName = className.substring(className.lastIndexOf('.') + 1);return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);}private List<Set<String>> getBeansByLayer(DirectedAcyclicGraph<String,DefaultEdge> dag) {List<Set<String>> layers = new ArrayList<>();Map<String, Integer> inDegree = new HashMap<>();Queue<String> queue = new LinkedList<>();// init all nodes degreefor (String vertex : dag) {int degree = dag.inDegreeOf(vertex);inDegree.put(vertex, degree);if (degree == 0) {queue.offer(vertex);  //zero degree as the first layer}}// BFS process everyLayerswhile (!queue.isEmpty()) {Set<String> currentLayer = new HashSet<>();int size = queue.size();for (int i = 0; i < size; i++) {String currentBean = queue.poll();currentLayer.add(currentBean);// iterator layersfor (String successor : getSuccessors(dag,currentBean)) {inDegree.put(successor, inDegree.get(successor) - 1);if (inDegree.get(successor) == 0) {queue.offer(successor);  // add next layer when the degress is zero}}}layers.add(currentLayer);}return layers;}// get next nodeprivate Set<String> getSuccessors(DirectedAcyclicGraph<String, DefaultEdge> dag, String vertex) {// get outgoingEdgesSet<DefaultEdge> outgoingEdges = dag.outgoingEdgesOf(vertex);// find the next nodereturn outgoingEdges.stream().map(edge -> dag.getEdgeTarget(edge)).collect(Collectors.toSet());}// init beans by layerprivate void initializeBeansInLayers(List<Set<String>> layers, ConfigurableListableBeanFactory beanFactory) {for (Set<String> layer : layers) {// Beans of the same layer can be initialized in parallelList<CompletableFuture<Void>> futures = new ArrayList<>();for (String beanName : layer) {// only load beans that  wrote by yourselfif (shouldLoadBean(beanName)) {CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {beanFactory.getBean(beanName);  // init Bean} catch (Exception e) {System.err.println("Failed to initialize bean: " + beanName);e.printStackTrace();}}, executorService);futures.add(future);}}//Wait for all beans in the current layer to be initialized before initializing the next layer.CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));allOf.join();  // make sure to be done on current layer}}private boolean shouldLoadBean(String beanName) {return beanName.startsWith("helloWorldController")||beanName.startsWith("serviceOne")||beanName.startsWith("serviceTwo")||beanName.startsWith("serviceThree");}
}

获取bean@Autowired依赖

package com.et.config;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;@Component
public class DependencyResolver {private final ConfigurableListableBeanFactory beanFactory;@Autowiredpublic DependencyResolver(ConfigurableListableBeanFactory beanFactory) {this.beanFactory = beanFactory;}public Set<String> getAllDependencies(String beanName) {Set<String> dependencies = new HashSet<>();// get Bean definiteBeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);// reflecttry {Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());Field[] fields = beanClass.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {dependencies.add(field.getType().getName()); }}} catch (ClassNotFoundException e) {e.printStackTrace();}return dependencies;}
}

controller

package com.et.controller;import com.et.service.ServiceTwo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.et.service.*;
import java.util.HashMap;
import java.util.Map;@RestController
public class HelloWorldController {@AutowiredServiceOne ServiceOne;@AutowiredServiceTwo ServiceTwo;@RequestMapping("/hello")public Map<String, Object> showHelloWorld(){Map<String, Object> map = new HashMap<>();map.put("msg", "HelloWorld");return map;}
}

service

package com.et.service;import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceOne {private   void  sayhi(){System.out.println("this is service one sayhi");}
}
package com.et.service;import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceThree {private   void  sayhi(){System.out.println("this is service three sayhi");}
}
package com.et.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @author liuhaihua* @version 1.0* @ClassName ServiceOne* @Description todo* @date 2024/09/20/ 14:01*/
@Service
public class ServiceTwo {@AutowiredServiceThree serviceThree;private   void  sayhi(){System.out.println("this is service two sayhi");}
}

只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • https://github.com/Harries/springboot-demo(dag)

4.测试

启动Spring Boot工程,查看bean加载顺序如下

2024-09-20T15:51:27.081+08:00 INFO 33188 --- [ main] com.et.DemoApplication : Starting DemoApplication using Java 17.0.9 with PID 33188 (D:\IdeaProjects\ETFramework\dag\target\classes started by Dell in D:\IdeaProjects\ETFramework)
2024-09-20T15:51:27.085+08:00 INFO 33188 --- [ main] com.et.DemoApplication : No active profile set, falling back to 1 default profile: "default"
layers:[[serviceOne, serviceThree], [serviceTwo], [helloWorldController]]
2024-09-20T15:51:28.286+08:00 INFO 33188 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8088 (http)
2024-09-20T15:51:28.297+08:00 INFO 33188 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-09-20T15:51:28.297+08:00 INFO 33188 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17]
2024-09-20T15:51:28.373+08:00 INFO 33188 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-09-20T15:51:28.374+08:00 INFO 33188 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1198 ms
2024-09-20T15:51:28.725+08:00 INFO 33188 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8088 (http) with context path ''
2024-09-20T15:51:28.732+08:00 INFO 33188 --- [ main] com.et.DemoApplication

5.引用

  • JGraphX User Manual
  • Spring Boot利用dag加速Spring beans初始化 | Harries Blog™

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1540407.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

什么是损失函数?常见的损失函数有哪些?

损失函数 什么是损失函数&#xff1f;损失函数作用如何设计损失函数常见的损失函数有哪些&#xff1f; 什么是损失函数&#xff1f; 损失函数&#xff08;Loss Function&#xff09;&#xff0c;也称为误差函数&#xff0c;是机器学习和深度学习中的一个重要概念。它用于衡量模…

python怎么打开编辑器

1、在电脑开始菜单中点击所有程序&#xff0c;找到Python程序&#xff0c;点击其中idle。 2、然后点击左上角的“File”&#xff0c;打开菜单&#xff0c;在下拉菜单中选择“New File”选项&#xff0c;就可打开python编辑器了。 3、在打开的python编辑器中就可以输入自己想写的…

105.游戏安全项目-基址的技术原理-分析技巧

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信…

如何衡量企业品牌力?判断指标有哪些?

企业品牌力是指品牌在市场中的竞争力和影响力&#xff0c;它反映了品牌的价值、知名度、忠诚度、感知质量、差异化以及市场表现等方面。要去衡量一个企业的品牌力&#xff0c;大多从品牌的知名度、忠诚度、所占市场份额、顾客口碑、社媒影响力、品牌资产价值等多方面去判断。我…

sqoop的安装与简单使用

文章目录 一、安装1、上传&#xff0c;解压&#xff0c;重命名2、修改环境变量3、修改配置文件4、上传驱动包5、拷贝jar包 二、import命令1、将mysql的数据导入到hdfs上2、将mysql的数据导入到hive上3、增量导入数据 三、export命令1、从hdfs导出到mysql中2、从hive导出到mysql…

企业微信oauth2提示应用无法使用

问题描述&#xff1a; 生成oauth2之后&#xff0c;我a公司是服务商&#xff0c;我给b公司的人去点授权链接会提示这个 应用服务商还没有在企业微信为你开通接口调用许可」&#xff0c;导致无法使用此应用&#xff0c;请联系服务商开通 正文 你先要知道一件事&#xff01;&…

Jenkins私有化部署

最终目标 与GitLab配合&#xff0c;实践前端自动化&#xff0c;详细内容移步基于Jenkins和GitLab的前端自动化实践 前置条件 一台云服务器云服务器上已安装Docker了解Docker基础 使用Docker安装Jenkins 参考github文档安装 docker run --name docker_jenkins --privilege…

操作系统 --- 进程的同步和互斥问题以及进程互斥实现方法(软件、硬件实现)、同步机制遵循的四条准则

目录 一、进程同步 二、进程互斥 三、进程互斥的实现方法 3.1 软件实现 3.1.1 单标志法&#xff08;存在的主要问题&#xff1a;违背“空闲让进”原则&#xff09; 3.1.1.1 基本思想 3.1.1.2 单标志法的基本概念及执行流程 3.1.1.3 特点 3.1.2 双标志先检查法&#…

进程间的通信 2 消息队列

system V IPC IPC : Inter-Process Communication (进程间通讯) System V IPC 对象共有三种&#xff1a; 消息队列共享内存信号量 System V IPC 是由内核维护的若干个对象&#xff0c;通过ipcs命名查询 每个 IPC 对象都有一个唯一的 ID&#xff0c;可以通过ftok()函数生成 …

使用SoapUI、Postman工具调用Webservice方法

SoapUI工具更适合调用Webservice使用。 1.使用SoapUI工具调用Webservice 创建“New SOAP Project” 自行定义一个项目名称&#xff0c;输入wsdl地址&#xff1a; 在左侧列表找到方法名&#xff0c;双击“Request 1”, 在请求数据中&#xff0c;添加对应的参数&#xff0c;然…

Linux--禁止root用户通过ssh直接登录

原文网址&#xff1a;Linux--禁止root用户通过ssh直接登录_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Linux服务器怎样禁止root用户通过ssh直接登录。 为什么要禁止&#xff1f; 因为root用户是每个Linux系统都有的&#xff0c;黑客可以使用root用户名尝试不同的密码来暴力破…

【笔记】自动驾驶预测与决策规划_Part3_路径与轨迹规划

文章目录 0. 前言1. 基于搜索的路径规划1.1 A* 算法1.2 Hybrid A* 算法 2. 基于采样的路径规划2.1 Frent Frame方法2.2 Cartesian →Frent 1D ( x , y ) (x, y) (x,y) —> ( s , l ) (s, l) (s,l)2.3 Cartesian →Frent 3D2.4 贝尔曼Bellman最优性原理2.5 高速轨迹采样——…

ETHERCAT转PROFIENT网关—迅捷伺服驱动器数据交互

在当前的生产现场中&#xff0c;PLC 控制器与迅捷伺服驱动器之间通过通讯方式进行连接的情况愈发频繁。有些现场中&#xff0c;控制器和伺服驱动器采用统一的通讯协议&#xff0c;然而在另一些现场&#xff0c;会出现伺服驱动器 站使用不同协议的情况&#xff0c;这主要是由于不…

小阿轩yx-通过state模块定义主机状态

小阿轩yx-通过state模块定义主机状态 前言 前面学习了远程执行模块&#xff0c;这些模块的执行类似语段 shell 脚本&#xff0c;每次执行都会触发一次相同的功能&#xff0c;在大量的 minion 上运行远程命令当然是重要的&#xff0c;但是对于 minion 的环境控制&#xff0c;使…

利用 ARMxy边缘计算网关和 BLiotlink 软网关,实现工业智能化升级

在当今数字化、智能化的时代浪潮中&#xff0c;工业领域也在不断寻求创新与突破&#xff0c;以提高生产效率、降低成本并提升竞争力。ARM 工业计算机与 BLiotlink 协议转换软件的结合&#xff0c;为工业智能化带来了新的机遇和解决方案。 一、ARM 工业计算机的优势 ARM 工业计…

怎么找到抖音爆款内容,进行扩散传播?

企业如果想做好抖音平台的品牌营销&#xff0c;需要时刻监测抖音爆款内容并进行加热放大&#xff0c;据此快速创新和改进内容&#xff0c;才能短期提高品牌相关内容的曝光量&#xff0c;快速拉升品牌声量。怎么去找到抖音的爆款内容或者是值得品牌关注的优质内容&#xff0c;主…

Active Directory 实验室设置第一部分- AD林安装

在之前的文章中&#xff0c;已经讨论了活动目录的基本知识。在这篇文章中&#xff0c;我们将讨论如何设置和配置环境&#xff0c;以便我们可以使用它来执行各种攻击方案和检测。我们将讨论如何通过GUI和CLI方式完成。 # 1、Active Directory 设置 让我们从活动目录实验室设置…

统计项目代码行数工具—cloc

目录 引言一、cloc简介二、cloc安装三、cloc使用四、参考博客 引言 项目开发完成&#xff0c;想要查看自己项目的代码行数&#xff0c;强烈推荐一款非常好用的命令行工具-cloc。 一、cloc简介 只需要通过命令行的方式运行cloc&#xff0c;就可以得知指定文件代码行数、注释函…

AI大模型之旅-langchain结合glm4,faiss构建本地知识库

所需依赖如下&#xff1a; _libgcc_mutex0.1main _openmp_mutex5.11_gnu accelerate0.34.2pypi_0 aiofiles23.2.1pypi_0 aiohappyeyeballs2.4.0pypi_0 aiohttp3.10.5pypi_0 aiosignal1.3.1pypi_0 annotated-types0.7.0pypi_0 anyio4.4.0pypi_0 attrs24.2.0pypi_0 bitsandbytes…

Leetcode面试经典150题-172.阶乘后的零

给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * ... * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0示例 2&#xff1a; 输入&#xff1a;n 5 输出&a…