Spring循环依赖以及三个级别缓存

Spring循环依赖以及三个级别缓存

什么是循环依赖?

循环依赖,顾名思义,就是指两个或多个 Spring Bean 之间相互依赖,形成一个闭环。

image-20250725163349432

最常见也是 Spring 能够“解决”的循环依赖是构造器注入setter 注入 混合或单独使用时,发生在 单例(Singleton) Bean 上的情况。


什么是三级缓存?

image-20250725165502311

第一级缓存:singletonObjects (一级缓存 / 成品 Bean 缓存)

  • 类型: ConcurrentHashMap<String, Object>

  • 作用: 存放已经完全初始化好,并且可供使用的单例 Bean。当一个 Bean 在这里被找到时,它就是“成品”了,可以直接返回给请求者。

第二级缓存:earlySingletonObjects (二级缓存 / 早期暴露对象缓存)

  • 类型: ConcurrentHashMap<String, Object>

  • 作用: 存放已经实例化但尚未完成属性填充和初始化的 Bean。这些 Bean 是“半成品”,但它们被提前暴露出来,以便解决循环依赖。

第三级缓存:singletonFactories (三级缓存 / 早期单例工厂缓存)

  • 类型: HashMap<String, ObjectFactory<?>>
  • 作用: 存放创建 Bean 的 ObjectFactory。这个工厂负责生产“半成品”的 Bean 实例(可能经过 AOP 代理)。它是解决循环依赖的关键所在,尤其是在涉及到 AOP 代理的场景。
  • 特点: 这里的不是 Bean 实例本身,而是一个能够获取早期 Bean 引用(可能是原始对象,也可能是代理对象)的工厂

我们可以看到三级缓存singletonFactories的类型是HashMap,并且map的value值为ObjectFactory,不同于其他两级缓存。

💠为什么 valueObjectFactory 而不是 Object

  • 这就是第三级缓存的精妙之处,也是它能够解决循环依赖与 AOP 代理同时存在问题的关键:

    1. 延迟生成早期引用:
      • 在 Bean A 实例化后,它会立即将一个 ObjectFactory 放入第三级缓存。
      • 这个工厂只有在另一个 Bean B 发生循环依赖,并且需要提前获取 Bean A 的引用时,才会被调用(singletonFactory.getObject())。
      • 这种延迟机制使得 Spring 可以在真正需要 Bean A 的早期引用时,才决定并生成它。
    2. 处理 AOP 代理:
      • 如果 Bean A 需要进行 AOP 代理(例如,因为它上面有 @Transactional 注解,或者被某个切面匹配到),那么在 ObjectFactorygetObject() 方法被调用时,Spring 的 AOP 逻辑会被触发。
      • 此时,getObject() 方法将不会简单地返回 Bean A 的原始实例,而是会返回 Bean A 的代理实例
      • 这个代理实例随后会被放入第二级缓存 earlySingletonObjects,供依赖方使用。

    总结来说:

    • 一级和二级缓存直接存放Bean 实例(成品或半成品)。
    • 第三级缓存存放的是一个**“生产 Bean 实例的工厂”**。这个工厂在被调用时,能根据 Bean 的特性(特别是是否需要 AOP 代理),决定是返回原始实例还是其代理实例。

    正是因为 singletonFactories 存储的是一个能够“生产”早期 Bean 实例的工厂,而不是直接的 Bean 实例,Spring 才能够在循环依赖的场景下,灵活地提供经过 AOP 代理的早期 Bean 引用,从而保证了 Bean 引用的一致性,解决了复杂场景下的循环依赖问题。

💠为什么 singletonFactories 使用 HashMap?

  • singletonObjectsearlySingletonObjects 需要 ConcurrentHashMap 是因为它们是并发访问的热点,需要内部的并发控制来保证性能和线程安全。

  • singletonFactories 使用 HashMap 是因为它的所有相关操作都已经被外部的 synchronized (this.singletonObjects) 锁保护起来,本身不需要内部的并发机制。 在这个全局锁的保护下,使用 HashMap 既满足了线程安全,又因为其操作频率相对较低而没有性能瓶颈。


循环依赖流程?

image-20250725173258369

这五个是单例 Bean 的创建和缓存紧密相关的核心环节,清晰描述了 Spring 单例 Bean 的生命周期

  1. 缓存查询

​ 这是获取 Bean 的第一步,也是最快、最高效的方式。

  • 目标:检查 Spring IoC 容器中是否已经存在一个完全初始化好的 Bean 实例。

  • 实现:Spring 会首先去它的**一级缓存(singletonObjects)**中查找。

  • 结果

    • 如果找到了,直接返回这个 Bean 实例。这是最理想的情况,省去了后续所有创建和初始化步骤。

    • 如果没找到,并且这个 Bean 当前正在创建中(表示可能存在循环依赖),它会进一步尝试从**二级缓存(earlySingletonObjects三级缓存(singletonFactories)**获取一个早期引用。

  1. 创建对象 (实例化)

​ 如果缓存中没有找到可用的 Bean,那么 Spring 就会开始创建新的 Bean 实例。

  • 目标:根据 Bean 定义(BeanDefinition),通过反射等方式,生成 Bean 的原始实例。
  • 实现
    • 对于普通 Bean,通常是调用其构造函数来创建对象。
    • 在实例化完成后,Bean 的原始实例就存在了,但此时它只是一个“空壳”,没有任何属性被填充,也没有进行任何初始化操作。
  • 关键时机:这个阶段是 Bean 生命周期中首次出现具体对象的地方。也是在这个阶段之后,如果存在循环依赖且允许早期引用,Spring 会将 Bean 的一个早期引用工厂ObjectFactory)放入三级缓存
  1. 填充属性

​ 对象创建后,它需要被注入所依赖的其他 Bean 和配置。

  • 目标:将 Bean 定义中声明的属性(通过 @Autowired@Resource 等注解或 XML 配置)注入到刚创建的 Bean 实例中。
  • 实现
    • Spring IoC 容器会解析 Bean 的依赖关系。
    • 如果是通过 Setter 方法注入字段注入,Spring 会查找对应的依赖 Bean。
    • 如果在这个过程中遇到循环依赖(例如,BeanA 依赖 BeanB,而 BeanB 此时需要 BeanA),Spring 会利用之前放入三级缓存中的 ObjectFactory 来获取 BeanA 的早期引用(可能是代理对象),从而打破循环。
  • 关键时机:循环依赖问题主要发生在这个阶段。
  1. 初始化

​ 属性填充完成后,Bean 实例就具备了它所有的依赖,但可能还需要进行一些自定义的初始化工作。

  • 目标:执行 Bean 的自定义初始化逻辑和生命周期回调。

  • 实现

    • Aware 接口回调:如果 Bean 实现了 BeanNameAwareBeanFactoryAwareApplicationContextAware 等接口,Spring 会调用相应的方法注入名称、工厂或上下文。
    • BeanPostProcessor 前置处理:调用所有注册的 BeanPostProcessorpostProcessBeforeInitialization 方法。
    • @PostConstruct 方法:执行 Bean 中被 @PostConstruct 注解标记的方法。
    • InitializingBean 接口 afterPropertiesSet() 方法:如果 Bean 实现了 InitializingBean 接口,调用其 afterPropertiesSet() 方法。
    • 自定义 init-method:执行 Bean 定义中指定的自定义初始化方法(如 XML 配置中的 init-method)。
    • BeanPostProcessor 后置处理:调用所有注册的 BeanPostProcessorpostProcessAfterInitialization 方法。AOP 代理通常在这个阶段发生postProcessAfterInitialization 方法会返回 Bean 的代理对象。
  • 关键时机:Bean 的最终形态(包含所有代理)通常在这个阶段确定。

  1. 缓存转移 (放入一级缓存)

​ 所有步骤都完成后,这个 Bean 就“大功告成”了。

  • 目标:将完全初始化并可用的 Bean 实例,从所有临时缓存中移除,并放入最终的成品缓存。
  • 实现
    • Spring 会将这个 Bean 实例放入一级缓存(singletonObjects
    • 同时,从**二级缓存(earlySingletonObjects三级缓存(singletonFactories)**中移除该 Bean 的相关条目,因为它现在已经是“成品”了,不再是早期引用或工厂。
  • 结果:此后,任何对该 Bean 的请求都将直接从一级缓存中获取,高效且快速。

来看BeanA和BeanB这个循环依赖流程,在三级缓存中是怎么作用的?

流程参考视频[彻底拿捏Spring循环依赖以及三个级别缓存哔哩哔哩bilibili]

首先来看BeanA的创建过程

1.缓存查询

查询三级缓存,都没有,回到主流程,进行创建对象。

image-20250725211931540

2.创建对象

我们通过反射创建对象,包装成ObjectFactory类型,放到三级缓存singletonFactories中,再回到主流程,进行填充属性。

image-20250725212203302

✨3.填充属性

因为填充的是BeanB,所以要开始创建BeanB,同样走的是BeanA走的那一套流程 。

​ 🎯3.1 进行缓存查询,查询不到,进行下一步创建对象。

image-20250725211931540

​ 🎯3.2 进行通过反射创建对象,此时三级缓存singletonFactories中已经有BeanA了,将BeanB放进去

image-20250725212940589

​ 🎯3.3填充属性,要填充BeanA,又要开始创建BeanA的那一套流程。

​ 3.3.1 进行缓存查询,此时三级缓存中是有的,执行一些额外操作

image-20250725213345428

​ 来看源码,重点看2.3-2.6 ,通过getObject获得对象,放到二级缓存中,并在三级缓存中移除它。

​ 这样BeanB的填充属性就完成了。回到BeanB的主流程中,进行初始化

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 首先尝试从一级缓存中获取成品 BeanObject singletonObject = this.singletonObjects.get(beanName);// 2. 如果一级缓存中没有,并且该 Bean 正在创建中 (解决循环依赖的关键入口)if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {// 2.1. 尝试从二级缓存中获取早期暴露的 BeansingletonObject = this.earlySingletonObjects.get(beanName);// 2.2. 如果二级缓存中也没有,并且允许早期引用 (allowEarlyReference通常为true,表示允许循环依赖)if (singletonObject == null && allowEarlyReference) {// 对 singletonObjects 进行同步锁,保证线程安全,防止多个线程同时处理同一个Bean的早期引用synchronized(this.singletonObjects) {// 再次检查一级缓存,因为在同步块外可能被其他线程放入singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) { // 再次确认一级缓存没有// 再次检查二级缓存,确保同步块内没有被其他线程放入singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) { // 再次确认二级缓存没有// 2.3. 从三级缓存中获取 ObjectFactoryObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);if (singletonFactory != null) {// 2.4. 调用 ObjectFactory 的 getObject() 方法获取早期 Bean 实例//      这可能是原始对象,也可能是代理对象(如果AOP触发)singletonObject = singletonFactory.getObject();// 2.5. 将获取到的早期 Bean 实例放入二级缓存this.earlySingletonObjects.put(beanName, singletonObject);// 2.6. 从三级缓存中移除对应的 ObjectFactory,因为已经使用了this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

​ 🎯3.4 初始化,执行 Bean 的自定义初始化逻辑和生命周期回调。

​ 🎯3.5 缓存转移,因为BeanB已经完全创建完毕了,所有要将缓存里面的对象进行转移

image-20250725214714531

​ 看源码,将BeanB放入一级缓存中,从三级缓存中移除,从二级缓存中移除。

protected void addSingleton(String beanName, Object singletonObject) {synchronized(this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject); // 放入一级缓存this.singletonFactories.remove(beanName); // 从三级缓存移除,因为已经不是早期引用了this.earlySingletonObjects.remove(beanName); // 从二级缓存移除,因为已经不是早期引用了this.registeredSingletons.add(beanName); // 记录为已注册的单例}
}
image-20250725215830859

​ 到这里整个BeanB的创建过程就完成了,但这只是BeanA填充属性,我们还要跳回BeanA的创建过程。

image-20250725220101752

✨4.初始化

✨5.缓存转移

同上类似,移除二级和三级缓存中的BeanA,添加一级缓存中

image-20250725220224994 image-20250725220414321

为什么需要第三级缓存,有两个不就行了

简单来说,第三级缓存是为了解决当 Bean 存在循环依赖,并且还需要进行 AOP 代理(或者其他后置处理器处理)时的问题。

如果没有第三级缓存,仅靠两级缓存,Spring 无法在需要 AOP 代理时正确处理循环依赖。

为什么两级缓存不够?

我们来想象一下只有两级缓存(singletonObjectsearlySingletonObjects)的场景:

假设有两个 Bean:BeanABeanB

  • BeanA 依赖 BeanB
  • BeanB 依赖 BeanA
  • 最重要的是:BeanA 需要被 AOP 代理(例如,它上面有 @Transactional 注解,或者自定义的切面)。

流程推演(只有两级缓存):

  1. 创建 BeanA
    • Spring 开始创建 BeanA
    • 实例化 BeanA 的原始对象 a
    • a 放入二级缓存 earlySingletonObjects
  2. BeanA 填充属性,需要 BeanB
    • BeanA 的属性填充过程中,发现需要 BeanB
    • Spring 暂停 BeanA 的创建,转而创建 BeanB
  3. 创建 BeanB
    • Spring 开始创建 BeanB
    • 实例化 BeanB 的原始对象 b
    • b 放入二级缓存 earlySingletonObjects
  4. BeanB 填充属性,需要 BeanA
    • BeanB 的属性填充过程中,发现需要 BeanA
    • Spring 到缓存中查找 BeanA
      • 在一级缓存 singletonObjects 中找不到 BeanA(因为它还没初始化完)。
      • 二级缓存 earlySingletonObjects 中找到了 BeanA 的原始对象 a
      • a 注射到 BeanB 中。
  5. BeanB 完成初始化:
    • BeanB 完成属性填充和所有初始化步骤。
    • BeanB 被放入一级缓存 singletonObjects,完成创建。
  6. BeanA 继续完成初始化:
    • BeanA 现在得到了完整的 BeanB
    • BeanA 继续进行初始化步骤,包括执行 AOP 代理逻辑
    • 此时,问题来了:当 BeanA 执行 AOP 代理时,它会生成一个代理对象 a_proxy。这个 a_proxy 才是最终应该被其他 Bean 引用和使用的对象。
    • 但问题是,BeanB 在第4步中已经获取并使用了 BeanA原始对象 a,而不是 a_proxy

结论: 在这种情况下,BeanB 引用的是未被 AOP 代理的 BeanA 原始对象,而其他后来的 Bean 引用的是 BeanA 的代理对象。这就导致了 Bean 实例的不一致性,后续对 BeanA 的方法调用可能无法触发 AOP 逻辑,从而导致功能异常。

第三级缓存 singletonFactories 的作用

第三级缓存 singletonFactories 存放的不是 Bean 实例,而是一个 ObjectFactory(一个工厂)。这个工厂的 getObject() 方法,会在需要时,动态地生成并返回 Bean 的早期引用

关键在于:这个工厂在生成早期引用时,会判断当前 Bean 是否需要进行 AOP 代理。如果需要,它会直接返回代理对象;如果不需要,它就返回原始对象。

引入第三级缓存后的流程推演:

  1. 创建 BeanA
    • Spring 开始创建 BeanA
    • 实例化 BeanA 的原始对象 a
    • Spring 将一个 ObjectFactory 放入第三级缓存 singletonFactories 这个工厂在被调用时,会负责生成 BeanA 的早期引用(可能是原始对象或代理对象)。
    • 同时,将 BeanAsingletonFactories 中移除,将原始对象 a 放入 earlySingletonObjects (二级缓存)
  2. BeanA 填充属性,需要 BeanB
    • BeanA 的属性填充过程中,发现需要 BeanB
    • Spring 暂停 BeanA 的创建,转而创建 BeanB
  3. 创建 BeanB(步骤同前,不再赘述)。
  4. BeanB 填充属性,需要 BeanA
    • BeanB 的属性填充过程中,发现需要 BeanA
    • Spring 到缓存中查找 BeanA
      • 在一级缓存 singletonObjects 中找不到。
      • 在二级缓存 earlySingletonObjects 中找到了 BeanA 的原始对象 a
      • 如果此时 BeanA 需要被 AOP 代理,并且 AOP 后置处理器已经准备好,那么 earlySingletonObjects 中的 a 将不会直接返回给 BeanB
      • 而是会通过 singletonFactories 中对应的 ObjectFactory 来获取 BeanA 的早期引用。 这个工厂被调用时,会触发 BeanA 的 AOP 代理逻辑,生成 a_proxy
      • a_proxy 会被放入二级缓存 earlySingletonObjects,替换掉原始对象 a(重要:这里原始对象被替换为代理对象)
      • 然后,a_proxy 被注射到 BeanB 中。
  5. BeanB 完成初始化:
    • BeanB 完成属性填充和所有初始化步骤,并被放入一级缓存 singletonObjects
  6. BeanA 继续完成初始化:
    • BeanA 现在得到了完整的 BeanB
    • BeanA 继续进行剩余的初始化步骤。
    • BeanA 的最终代理对象(如果之前生成过,就是那个 a_proxy;如果没有,则在此处生成并最终确定)被放入一级缓存 singletonObjects

核心优势:

通过三级缓存,当一个 Bean(比如 BeanA)在创建过程中被另一个 Bean(比如 BeanB)提前引用时,并且这个 BeanA 需要被 AOP 代理,三级缓存中的 ObjectFactory 能够保证所有获取到的早期引用都是经过 AOP 代理后的对象。这样就确保了 Bean 实例的一致性,无论是在循环依赖中被提前引用,还是在后续正常流程中被引用,都将得到的是同一个 AOP 代理后的实例。

总结: 两级缓存无法处理 AOP 代理的场景,因为在 Bean 完全初始化前无法确定最终是返回原始对象还是代理对象。第三级缓存通过存储一个工厂(ObjectFactory),使得 Spring 能够在需要时才决定并生成最终的早期引用(可以是原始对象,也可以是代理对象),从而保证了在循环依赖和 AOP 代理同时存在时的正确性和一致性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《零基础入门AI:OpenCV图像预处理进一步学习》

本文全面讲解OpenCV图像预处理的七大核心技术&#xff08;插值方法、边缘填充、图像矫正&#xff08;透视变换&#xff09;、图像掩膜、ROI切割、图像添加水印、图像噪点消除&#xff09;&#xff0c;每个知识点都配有详细解释和实用代码示例&#xff0c;帮助初学者建立系统的图…

MongoDB的内存和核心数对于运行效率的影响

在 MongoDB 线上生产环境中&#xff0c;CPU&#xff08;核心&#xff09; 和 内存 是两大关键硬件资源&#xff0c;它们在不同的操作场景下发挥着核心作用&#xff0c;共同影响着数据库的性能、稳定性和扩展性。理解它们的作用场景至关重要&#xff0c;是容量规划、性能优化和故…

自己的SAPGUI尝试

为满足用户需求&#xff0c;博主做了一个台账管理程序&#xff0c;尝试用自己的程序做GUI&#xff0c;用SAP 系统做数据库。 运行了半年&#xff0c;程序很nice,用户每天都在高效的使用&#xff0c;已经有十几万的数据。 总结一下这次自己的GUI尝试&#xff0c;好处是C# WINFOR…

高效处理 JSON 数据:JsonUtil 工具类全方位解析与实战

在现代软件开发中,JSON(JavaScript Object Notation)已成为数据交换的“通用语言”——从前后端接口通信到微服务数据交互,从配置文件解析到日志格式化,几乎所有场景都离不开JSON的处理。然而,原生JSON框架(如FastJSON、Jackson)的API往往需要大量重复代码,且空指针、…

Python 库手册:xmlrpc.client 与 xmlrpc.server 模块

xmlrpc.client 和 xmlrpc.server 是 Python 标准库中用于构建基于 XML-RPC 协议的远程过程调用&#xff08;RPC&#xff09;通信模块。xmlrpc.client 用于编写客户端程序&#xff0c;向远程服务器发起方法调用。xmlrpc.server 用于编写服务器端&#xff0c;暴露本地方法供远程客…

渲染篇(一):从零实现一个“微型React”:Virtual DOM的真面目

渲染篇(一)&#xff1a;从零实现一个“微型React”&#xff1a;Virtual DOM的真面目 引子&#xff1a;前端性能的“永恒之问” 在前面两章中&#xff0c;我们已经奠定了坚实的架构基础。我们用“任务调度器”建立了声明式和模块化的编程范式&#xff0c;并通过对比MVC等模式论…

SWC 深入全面讲解

一、核心功能与原理 1. 高性能编译 Rust 架构优势&#xff1a;SWC 基于 Rust 编写&#xff0c;利用 Rust 的性能和并发性优势&#xff0c;编译速度比 Babel 快约 20 倍&#xff0c;比 TypeScript 编译器更快。并行编译&#xff1a;支持多线程并行处理&#xff0c;在四核基准测试…

XML Expat Parser:深入解析与高效应用

XML Expat Parser:深入解析与高效应用 引言 XML(可扩展标记语言)作为一种广泛使用的标记语言,在数据交换、存储和表示中扮演着重要角色。XML Expat Parser 是一个高性能、可扩展的XML解析库,广泛应用于各种编程语言中。本文将深入探讨XML Expat Parser 的原理、特性以及…

【Python】自动化GIT提交

在日常开发中&#xff0c;我们经常需要频繁地向 Git 仓库提交代码。虽然 git add、git commit、git push 这几个命令并不复杂&#xff0c;但重复操作容易出错&#xff0c;也浪费时间。本文将介绍如何使用 Python 脚本自动化完成 Git 提交流程&#xff0c;让开发更高效&#xff…

基于Qlearning强化学习的水下无人航行器路径规划与避障系统matlab性能仿真

目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.算法涉及理论知识概要 5.参考文献 6.完整算法代码文件获得 1.引言 水下无人航行器 (Autonomous Underwater Vehicle, AUV) 的路径规划与避障是海洋探索、资源开发和军事应用中的关键技术。传统的路径规划方…

模块自由拼装!Python重构DSSAT作物模块教程(以杂交水稻为例)

基于过程的作物生长模型&#xff08;Process-based Crop Growth Simulation Model&#xff09;在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农业碳中和、农田固碳减排等领域扮演着越来越重要的作用。Decision Support Systems for Agrotechnology Tr…

Java项目接口权限校验的灵活实现

引言 在Java Web开发中&#xff0c;接口权限校验是保护系统资源安全的关键机制。本文将介绍一种灵活、可配置的接口权限校验方案&#xff0c;通过注解驱动和拦截器实现&#xff0c;既能保证安全性&#xff0c;又能灵活控制哪些接口需要校验。 设计思路 实现方案的核心设计要点&…

瀚高DB兼容MySQL if函数

文章目录环境症状问题原因解决方案环境 系统平台&#xff1a;Linux x86-64 Red Hat Enterprise Linux 7 版本&#xff1a;4.5 症状 MySQL if函数在瀚高DB当中没有&#xff0c;源应用在用到if函数时&#xff0c;就会报if函数不存在的错误信息。为此&#xff0c;我们需要根据业…

基于深度学习的胸部 X 光图像肺炎分类系统(六)

目录 结果指标解读 一、为什么选择这些指标&#xff1f; 二、各指标的定义和解读 1. 准确率&#xff08;Accuracy&#xff09; 2. 损失&#xff08;Loss&#xff09; 3. 精确率&#xff08;Precision&#xff09; 4. 召回率&#xff08;Recall&#xff09; 三、这些指标…

区块链性能优化策略:从理论到实践

目录 区块链性能优化策略:从理论到实践 1. 引言:区块链性能的挑战 2. 性能评估指标 2.1 核心性能指标 2.2 性能瓶颈分析 3. 分层优化策略 3.1 网络层优化 3.1.1 Gossip协议改进 3.1.2 网络分片 3.2 共识层优化 3.2.1 PBFT优化 3.3 数据层优化 3.3.1 状态树优化 3.3.2 区块数据…

【VLLM】open-webui部署模型全流程

目录 前言 一、租用服务器到服务器连接VScode全流程(可选) 二、下载模型到本地服务器 2.1 进入魔塔社区官网 2.2 选择下载模型 2.3 执行下载 三、部署VLLM 3.1 参考vllm官网文档 3.2 查看硬件要求 3.3 安装vLLM框架 3.4 启动模型服务 方法1:直接启动下载的本地模…

办公自动化入门:如何高效将图片整合为PDF文档

将多张图片合并到一个PDF文件中可以帮助保持特定的顺序和布局&#xff0c;同时确保图像的质量不会因为格式转换而下降。它是免费&#xff0c;不限次数&#xff0c;批量导入也毫无压力。操作堪比发朋友圈&#xff1a;拖图进来 → 选个纸张尺寸 → 点击转换 → 指定保存路径&…

使用宝塔面板搭建 PHP 环境开发一个简单的 PHP 例子

目录一、引言二、准备工作2.1 服务器选择2.2 下载安装宝塔面板三、使用宝塔面板搭建 PHP 环境3.1 登录宝塔面板3.2 选择 Web Server3.3 安装 PHP3.4 安装 MySQL 数据库四、开发一个简单的 PHP 例子4.1 创建 PHP 文件4.2 编写 PHP 代码4.3 设置站点4.4 访问 PHP 页面五、常见问题…

AWS WebRTC:我们的业务模式

拉流、卡录基本流程 设备端&#xff08;摄像机&#xff09; 与 App端 是通过 AWS KVS WebRTC 信令服务进行“点对点连接”的&#xff0c;真正的媒体数据&#xff08;音视频&#xff09;是通过 WebRTC 的 ICE 通道&#xff08;P2P 或 TURN&#xff09;直接传输的&#xff0c;而不…

使用Python,OpenCV,K-Means聚类查找图像中最主要的颜色

使用Python&#xff0c;OpenCV&#xff0c;K-Means聚类查找图像中最主要的颜色 分别把跑图聚类选取1, 2, 3&#xff0c;4, 5, 6, 7&#xff0c;8, 9种主要颜色并绘制colormap颜色图; 效果图 分别把跑图聚类选取3&#xff0c;4, 5&#xff0c;7&#xff0c;9种主要颜色并绘制…