文章目录

  • 七、调度与线程模型
    • 7.1 概述
    • 7.2 Scheduler: Reactor 的线程调度器
    • 7.3 两大核心操作符:subscribeOn vs publishOn
    • 7.4 示例详解
      • 7.4.1 subscribeOn()的全局影响
      • 7.4.2 publishOn() 的局部切换
      • 7.4.3 多个publishOn切换
      • 7.4.4 线程切换时序图
    • 7.5 核心调度器
      • 7.5.1 BoundedElastic:IO 密集型任务首选
      • 7.5.2 Parallel:CPU 密集型任务首选
      • 7.5.3 Single:串行任务专用
      • 7.5.4 Schedulers.immediate()
      • 7.5.5 Schedulers.elastic()
      • 7.5.6 Schedulers.fromExecutorService(ExecutorService)
      • 7.5.7 Schedulers.new() 工厂方法
      • 7.5.8 调度器使用最佳实践
    • 7.6 线程模型实战: 典型场景
      • 7.6.1 I/O密集型任务
      • 7.6.2 场景 2:CPU 密集型任务
      • 7.6.3 混合任务(I/O + CPU)
    • 7.7 综合示例
    • 7.8 高级特性
      • 7.8.1 调用器生命周期管理
      • 7.8.2 自定义线程命名
      • 7.8.3 在操作符中使用调度器
    • 7.9 最佳实践与陷阱

七、调度与线程模型

核心作用

  • 线程抽象:将底层线程管理与响应式流解耦,提供统一的 API 控制执行上下文。
  • 异步执行:支持非阻塞操作,避免阻塞主线程,提升系统吞吐量。
  • 并发控制:通过不同类型的调度器,适配不同的并发场景(如 IO 密集型、CPU 密集型)。

🌺 关键概念

  • 调度器(Scheduler):负责提供执行任务的线程,是 Reactor 中线程池的抽象。
  • 调度器工作线程(Worker):Scheduler 创建的轻量级工作单元,负责执行具体任务。
  • publishOn () 与 subscribeOn ():用于切换执行上下文的操作符。
    • subscribeOn():指定订阅操作(包括上游数据生成)的执行线程。
    • publishOn():指定下游操作符链的执行线程

7.1 概述

Reactor 与 RxJava 类似,可以被认为是并发无关的 。也就是说,它不强制执行并发模型。相反,它把控制权交给开发者自己。然而,这并不妨碍该库帮助你处理并发问题。

获得 FluxMono 并不一定意味着它在专用的 Thread ,大多数操作符会在前一个操作符执行的 Thread 中继续工作。除非另有说明,最顶层的操作符(源操作符)本身会在调用 subscribe() Thread 中运行。以下示例在新线程中运行 Mono

public static void main(String[] args) throws InterruptedException {final Mono<String> mono = Mono.just("hello "); // 🥇 Mono<String> 在线程 main 中组装。Thread t = new Thread(() -> mono.map(msg -> msg + "thread ").subscribe(v -> // 🥈 它是在线程 Thread-0 中订阅的。System.out.println(v + Thread.currentThread().getName()) // map 和 onNext 回调实际上都在 Thread-0 中运行));t.start();t.join();}

7.2 Scheduler: Reactor 的线程调度器

Scheduler 是 Reactor 的线程抽象,类似于 Java 的 ExecutorService,但专为响应式流设计。

核心作用:控制 Publisher 在哪个线程上执行。

Reactor 提供了多种内置 Scheduler

Scheduler用途线程模型
Schedulers.immediate()当前线程执行❌ 不推荐用于生产
Schedulers.single()共享的单线程1 个线程,复用
Schedulers.parallel()CPU 密集型任务固定线程数(CPU 核数)
Schedulers.boundedElastic()I/O 阻塞任务弹性线程池(默认 10万线程上限)
Schedulers.newXXX()自定义线程池newParallel()

7.3 两大核心操作符:subscribeOn vs publishOn

这是理解 Reactor 线程模型的重中之重

🔑 核心区别

操作符作用影响范围
subscribeOn()指定 Publisher 的创建和上游执行线程影响整个链的上游(从源头到当前位置)
publishOn()指定下游操作的执行线程只影响其后的下游操作(当前位置到 subscribe

🎯 记忆口诀:

  • subscribeOn:从哪里开始(影响源头)
  • publishOn:从哪里切换(影响后续)

7.4 示例详解

7.4.1 subscribeOn()的全局影响

package cn.tcmeta.scheduler;import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;import java.util.concurrent.TimeUnit;/*** @author: laoren* @description: subscribeOn的全局影响* @version: 1.0.0*/
public class SubscribeOnExample {public static void main(String[] args) {Flux.just("A", "B", "C").map(data -> {System.out.println("1️⃣ Map1 线程: " + Thread.currentThread().getName());return data + "-1";}).subscribeOn(Schedulers.parallel()).map(data -> {System.out.println("2️⃣ Map2 线程: " + Thread.currentThread().getName());return data + "-2";}).subscribe(data -> {System.out.println("📩 订阅线程: " + Thread.currentThread().getName() + ", 数据: " + data);});try {TimeUnit.MILLISECONDS.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}
}
1️⃣ Map1 线程: parallel-1
2️⃣ Map2 线程: parallel-1
📩 订阅线程: parallel-1, 数据: A-1-2
1️⃣ Map1 线程: parallel-1
2️⃣ Map2 线程: parallel-1
📩 订阅线程: parallel-1, 数据: B-1-2
1️⃣ Map1 线程: parallel-1
2️⃣ Map2 线程: parallel-1
📩 订阅线程: parallel-1, 数据: C-1-2

结论subscribeOn(Schedulers.parallel()) 即使放在中间,也使 just() 和两个 map() 都在 parallel 线程执行。

subscribeOn影响范围:

subscribeOn 影响区域
Flux.just
map1
map2
subscribeOn(parallel)
subscribe

7.4.2 publishOn() 的局部切换

package cn.tcmeta.scheduler;import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;/*** @author: laoren* @description: publishOn()局部切换* @version: 1.0.0*/
public class PublishOnExample {public static void main(String[] args) throws InterruptedException {Flux.just("A", "B").map(data -> {System.out.println("📍 上游 Map 线程: " + Thread.currentThread().getName());return data + "-up";})// ✅ publishOn 切换下游线程.publishOn(Schedulers.boundedElastic()).map(data -> {System.out.println("📍 下游 Map 线程: " + Thread.currentThread().getName());return data + "-down";}).subscribe(data ->System.out.println("📩 订阅线程: " + Thread.currentThread().getName() + ", 数据: " + data));Thread.sleep(1000);}
}
📍 上游 Map 线程: main
📍 上游 Map 线程: main
📍 下游 Map 线程: boundedElastic-1
📩 订阅线程: boundedElastic-1, 数据: A-up-down
📍 下游 Map 线程: boundedElastic-1
📩 订阅线程: boundedElastic-1, 数据: B-up-down

结论publishOn 之后的所有操作(包括 subscribe)都在 boundedElastic 线程执行。

publishOn() 影响范围:

publishOn 影响区域
map2
subscribe
Flux.just
map1
publishOn(elastic)

🔴 红色部分(下游)在 elastic 线程执行,justmap1 在主线程。

7.4.3 多个publishOn切换

package cn.tcmeta.scheduler;import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;public class MultiPublishOnExample {public static void main(String[] args) throws InterruptedException {Flux.just("Hello").publishOn(Schedulers.parallel())           // 切到 parallel.map(s -> {System.out.println("ParallelGroup: " + Thread.currentThread().getName());return s + "-1";}).publishOn(Schedulers.boundedElastic())    // 再   切到 boundedElastic.map(s -> {System.out.println("ElasticGroup: " + Thread.currentThread().getName());return s + "-2";}).subscribe(data ->System.out.println("Final: " + Thread.currentThread().getName() + " => " + data));Thread.sleep(1000);}
}

✅ 每个 publishOn 都会切换其后操作的执行线程。

7.4.4 线程切换时序图

MainParallelElasticsubscribeOn(parallel) 后,上游执行publishOn(elastic) 切换下游操作和 subscribe 执行MainParallelElastic

7.5 核心调度器

7.5.1 BoundedElastic:IO 密集型任务首选

  • 设计背景:替代已过时的ElasticScheduler(无界线程池,可能导致 OOM),通过有界缓冲队列动态线程数(空闲线程会回收)避免资源耗尽。
  • 适用场景:数据库查询、HTTP 请求、文件 IO 等阻塞且耗时的操作(允许线程阻塞,通过动态扩缩容应对并发)

7.5.2 Parallel:CPU 密集型任务首选

  • 线程特性:线程数固定为 CPU 核心数(Runtime.getRuntime().availableProcessors()),无空闲线程回收(保持计算能力)。
  • 适用场景:数据计算、序列化 / 反序列化、复杂集合处理等非阻塞但耗 CPU的操作(充分利用多核性能)。

7.5.3 Single:串行任务专用

  • 线程特性:全局唯一单线程(所有Schedulers.single()调用共享),任务按提交顺序执行。
  • 注意:若需多个独立串行线程,使用Schedulers.newSingle()创建私有单线程调度器。

7.5.4 Schedulers.immediate()

  • 特性:在当前线程直接执行,不开启新线程。
  • 适用场景:测试或不需要异步执行的场景。

7.5.5 Schedulers.elastic()

  • 特性:弹性线程池,按需创建线程,空闲线程会在 60s 后回收。
  • 适用场景:IO 密集型任务(如网络调用、文件操作)。
  • 注意:已被弃用,推荐使用 boundedElastic

7.5.6 Schedulers.fromExecutorService(ExecutorService)

  • 特性:适配自定义的 ExecutorService,灵活集成现有线程池。

7.5.7 Schedulers.new() 工厂方法

  • 特性:创建独立的新调度器实例(如 newSingle()newParallel()),避免共享资源。

7.5.8 调度器使用最佳实践

按任务类型选择调度器

  • IO 密集型(数据库、网络、文件)→ boundedElastic(允许阻塞,动态扩缩容);
  • CPU 密集型(计算、排序、序列化)→ parallel(固定线程数,避免线程切换开销);
  • 串行任务(状态依赖操作)→ singlenewSingle()(保证顺序执行);
  • 同步操作(无阻塞)→ immediate(无需线程切换,减少开销)。

避免线程阻塞滥用

  • 禁止在parallel线程中执行阻塞操作(会浪费 CPU 核心,降低计算效率);
  • 阻塞操作必须放在boundedElastic线程(其线程设计允许阻塞);
// 错误:在parallel线程执行阻塞操作
Flux.range(1, 10).publishOn(Schedulers.parallel()).doOnNext(num -> {Thread.sleep(1000); // 阻塞CPU线程,浪费计算资源});// 正确:阻塞操作放在boundedElastic
Flux.range(1, 10).publishOn(Schedulers.boundedElastic()).doOnNext(num -> Thread.sleep(1000)); // 安全

控制boundedElastic的资源上限

默认配置可能不适合高并发场景,可通过系统属性调整:

// JVM启动参数:调整boundedElastic的线程和队列上限
-Dreactor.schedulers.boundedElastic.maxThreads=100 
-Dreactor.schedulers.boundedElastic.queuesize=1024

减少不必要的线程切换

// 优化前:多次不必要的线程切换
flux.publishOn(A).map(...).publishOn(B).filter(...).publishOn(C)// 优化后:合并操作,减少切换
flux.map(...).filter(...).publishOn(C); // 一次切换即可

7.6 线程模型实战: 典型场景

7.6.1 I/O密集型任务

如数据库、HTTP 调用

// 假设这是调用外部 HTTP 服务
Mono<String> callExternalApi() {return Mono.fromCallable(() -> {// 模拟阻塞调用Thread.sleep(1000);return "API Result";}).subscribeOn(Schedulers.boundedElastic()); // ✅ 使用弹性线程池
}// 使用
callExternalApi().map(result -> processResult(result)) // 可在主线程或其他线程处理.subscribe(System.out::println);

原则:I/O 操作必须用 boundedElastic(),防止阻塞 CPU线程。

7.6.2 场景 2:CPU 密集型任务

Flux.range(1, 1000).publishOn(Schedulers.parallel()) // ✅ 切到并行线程池.map(i -> heavyComputation(i))    // 耗时计算.subscribe(System.out::println);

原则:CPU 密集型用 parallel(),避免创建过多线程。

7.6.3 混合任务(I/O + CPU)

externalServiceCall()           // I/O: boundedElastic.publishOn(Schedulers.parallel()) // 切到 CPU 线程池.map(data -> compute(data)) // CPU 密集型计算.publishOn(Schedulers.boundedElastic()) // 再切回 I/O 线程.flatMap(result -> saveToDB(result)) // 再次 I/O 操作.subscribe();

原则:根据操作类型动态切换线程池。

7.7 综合示例

package cn.tcmeta.scheduler;import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;import java.time.Duration;
import java.util.concurrent.TimeUnit;public class SchedulerExamples {public static void main(String[] args) {SchedulerExamples examples = new SchedulerExamples();examples.schedulerTypes();System.out.println("-------------------------------------");examples.publishOnVsSubscribeOn();System.out.println("-------------------------------------");examples.parallelProcessing();System.out.println("-------------------------------------");examples.timeoutWithScheduler();}public void schedulerTypes() {// 1. 立即调度 (当前线程)Flux.just("A", "B", "C").subscribeOn(Schedulers.immediate()).subscribe(System.out::println);// 2. 单一线程调度Flux.range(1, 3).subscribeOn(Schedulers.single()).subscribe(i -> System.out.println(Thread.currentThread().getName() + ": " + i));// 3. 弹性线程池 (适合IO密集型任务)Flux.range(1, 3).subscribeOn(Schedulers.boundedElastic()).subscribe(i -> System.out.println(Thread.currentThread().getName() + ": " + i));// 4. 并行调度 (适合CPU密集型任务)Flux.range(1, 3).subscribeOn(Schedulers.parallel()).subscribe(i -> System.out.println(Thread.currentThread().getName() + ": " + i));}public void publishOnVsSubscribeOn() {// subscribeOn - 影响整个链的订阅上下文Mono.fromCallable(() -> {System.out.println("Callable on: " + Thread.currentThread().getName());return "Result";}).subscribeOn(Schedulers.boundedElastic()).subscribe(result ->System.out.println("Subscribe on: " + Thread.currentThread().getName()));// publishOn - 影响后续操作的执行上下文Flux.range(1, 3).map(i -> {System.out.println("Map1 on: " + Thread.currentThread().getName());return i * 2;}).publishOn(Schedulers.parallel()).map(i -> {System.out.println("Map2 on: " + Thread.currentThread().getName());return i + 1;}).subscribe();}public void parallelProcessing() {// 并行处理流Flux.range(1, 10).parallel(4) // 分成4个并行流.runOn(Schedulers.parallel()).map(i -> i * i).sequential() // 合并回顺序流.subscribe(System.out::println);}public void timeoutWithScheduler() {// 使用调度器实现超时Mono.delay(Duration.ofSeconds(3)).timeout(Duration.ofSeconds(1), Schedulers.parallel()).subscribe(System.out::println,error -> System.out.println("Timeout: " + error));try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

7.8 高级特性

7.8.1 调用器生命周期管理

// 创建独立的调度器实例
Scheduler customScheduler = Schedulers.newBoundedElastic(10, 100, "custom");// 使用自定义调度器
Flux.just(1, 2, 3).subscribeOn(customScheduler).subscribe();// 使用完毕后关闭调度器(重要!避免资源泄漏)
customScheduler.dispose();

7.8.2 自定义线程命名

Scheduler namedScheduler = Schedulers.newParallel("my-thread", 4);
Flux.just("A", "B").subscribeOn(namedScheduler).subscribe(value -> {System.out.println("Running on: " + Thread.currentThread().getName());});// 输出:Running on: my-thread-1

7.8.3 在操作符中使用调度器

// 使用 subscribeOn 在 flatMap 中为每个内部流指定调度器
Flux.just(1, 2, 3).flatMap(num -> Mono.just(num * 2).subscribeOn(Schedulers.parallel()) // 为每个元素创建独立的执行上下文).subscribe();

7.9 最佳实践与陷阱

✅ 最佳实践

  1. I/O 操作Schedulers.boundedElastic()
  2. CPU 计算Schedulers.parallel()
  3. 避免在 map() 中阻塞
  4. 合理使用 publishOn 切换线程
  5. subscribeOn 通常放在链的开头或中间,效果相同

❌ 常见陷阱

// ❌ 错误:在 parallel 线程中执行阻塞 I/O
Flux.range(1, 10).publishOn(Schedulers.parallel()).map(i -> blockingIoCall(i)) // 阻塞调用!会耗尽 parallel 线程池.subscribe();// ✅ 正确:使用 boundedElastic
Flux.range(1, 10).flatMap(i -> Mono.fromCallable(() -> blockingIoCall(i)).subscribeOn(Schedulers.boundedElastic())).subscribe();
概念关键点
Scheduler线程执行的“容器”,选择合适的类型至关重要
subscribeOn()影响上游,决定 Publisher 在哪个线程启动
publishOn()影响下游,用于在链中切换执行线程
线程选择I/O → boundedElastic,CPU → parallel
背压与线程背压控制数据流,线程控制执行位置,二者协同工作

🚀 掌握调度,就掌握了 Reactor 的“方向盘”。合理使用 subscribeOnpublishOn,结合正确的 Scheduler,你就能构建出高效、稳定、可扩展的响应式系统。

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

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

相关文章

第21节:环境贴图与PBR材质升级——构建电影级真实感渲染

第21节&#xff1a;环境贴图与PBR材质升级——构建电影级真实感渲染 概述 基于物理的渲染&#xff08;Physically Based Rendering, PBR&#xff09;是当代计算机图形学中最重要的技术进步之一&#xff0c;它彻底改变了实时渲染的质量标准。在本节中&#xff0c;我们将深入探索…

【ROS2】ROS2 基础学习教程 、movelt学习

主要博主 参考资料&#xff1a; ROS系列&#xff1a; b站荔枝橙 b战哈萨克斯坦x 《ROS 2机器人开发从入门到实践》6.2.2 在RViz中显示机器人_哔哩哔哩_bilibili 动手学ROS2–鱼香肉丝 ​​​​​​​ 古月居ros2教程 北京华清智能科技 ros教程 moveit系列&#xff1a; 爱喝青…

Java类加载与JVM详解:从基础到双亲委托机制

在Java开发中&#xff0c;理解JVM&#xff08;Java虚拟机&#xff09;和类加载机制是掌握高级特性的关键。本文将从JDK、JRE、JVM的关系入手&#xff0c;深入讲解JVM的内存结构&#xff0c;并详细剖析类加载的全过程&#xff0c;包括加载时机、流程以及核心机制——双亲委托模型…

准备机试--图【y总版】[重要]【最短路】

常用代码模板3——搜索与图论 - AcWing 一般&#xff0c;稀疏图&#xff08;m约等于n&#xff09;:堆优化版本的dj&#xff1b;稠密图&#xff08;mn^2&#xff09;&#xff1a;朴素dj 最短路的难点在于建图【抽象出点和边】 朴素dj

Python API接口实战指南:从入门到精通

&#x1f31f; Hello&#xff0c;我是蒋星熠Jaxonic&#xff01; &#x1f308; 在浩瀚无垠的技术宇宙中&#xff0c;我是一名执着的星际旅人&#xff0c;用代码绘制探索的轨迹。 &#x1f680; 每一个算法都是我点燃的推进器&#xff0c;每一行代码都是我航行的星图。 &#x…

Spring和mybatis整合后事务拦截器TransactionInterceptor开启提交事务流程

目录一、说明二、TransactionInterceptor开启事务&#xff08;1&#xff09;、拦截方法&#xff08;2&#xff09;、开启事务绑定数据库连接&#xff08;3&#xff09;、mybatis中sql执行数据库连接获取&#xff08;4&#xff09;、事务提交和当前线程ThreadLocal清理&#xff…

05.《ARP协议基础知识探秘》

ARP协议基本介绍与实践 文章目录**ARP协议基本介绍与实践**ARP概述ARP报文类型ARP工作过程解析ARP工作原理示意图无故ARP/免费ARP实验案例**实验目标**实验环境实验步骤ARP概述 作用&#xff1a;ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff…

互联网大厂面试:大模型应用开发岗位核心技术点解析

互联网大厂面试&#xff1a;大模型应用开发岗位核心技术点解析 第一轮&#xff1a;大模型基础与上下文工程 问题 1&#xff1a;你能简单介绍 Transformer 架构的工作原理吗&#xff1f; 小C&#xff1a;嗯&#xff0c;我理解是 Transformer 主要依赖自注意力机制&#xff08;Se…

【深度学习新浪潮】有没有什么方法可以将照片变成线描稿,比如日式漫画的那种?

一、技术原理与研究进展 1. 线描生成的核心技术路径 传统方法:基于边缘检测(如Canny算子)和形态学操作,但难以处理复杂纹理和艺术风格。 深度学习方法: 端到端生成:使用U-Net架构(如ArtLine项目)直接学习照片到线描的映射,结合自注意力机制和感知损失提升细节保留能力…

NV032NV037美光固态闪存NV043NV045

NV032NV037美光固态闪存NV043NV045在数字化浪潮席卷全球的当下&#xff0c;存储技术的每一次突破都深刻影响着从个人消费到企业级应用的各个领域。美光科技作为行业领军者&#xff0c;其NV系列固态闪存产品始终以技术创新为核心驱动力。本文将聚焦NV032、NV037、NV043、NV045四…

天硕G40工业固态硬盘破解轨道存储难题

在高铁与轨道交通高速发展的今天&#xff0c;轨道检测探伤是保障列车安全运行的核心环节。据统计&#xff0c;我国铁路总里程已突破16万公里&#xff0c;日均检测数据量超10TB。加固平板一体机作为轨道探伤领域的“移动工作站”&#xff0c;需要在跨越大江南北的极端环境中实时…

基于Velero + 阿里云 OSS的Kubernetes 集群的备份与恢复

在 Kubernetes&#xff08;K8s&#xff09;中&#xff0c;备份和恢复是保障数据安全与业务连续性的关键环节&#xff0c;主要方式包括 ETCD 备份恢复 和 Velero 备份恢复&#xff0c;两者在备份粒度、恢复影响范围、存储位置等方面存在以下差异&#xff1a; 1、ETCD 备份恢复&…

解构与重构:“真人不露相,露相非真人” 的存在论新解 —— 论 “真在” 的行为表达本质

解构与重构&#xff1a;“真人不露相&#xff0c;露相非真人” 的存在论新解 —— 论 “真在” 的行为表达本质绪论&#xff1a;传统解释的突围 —— 从 “藏才” 到 “存真”“真人不露相&#xff0c;露相非真人” 这句谚语&#xff0c;自明代《西游记》以降&#xff0c;便长期…

数据结构:哈希表、排序和查找

一、哈希算法1.将数据通过哈希算法映射成一个健值&#xff0c;存取都在同一个位置&#xff0c;实现数据的高效存储和查找&#xff0c;时间复杂度由O(n)->O(1)2.哈希碰撞&#xff1a;多个数据通过哈希算法得到的键值相同二、哈希表1.构建哈希表存放0-100之间的数据2.哈希算法…

【Java基础】Java I/O模型解析:BIO、NIO、AIO的区别与联系(Netty入门必备基础)

Java I/O模型深度解析&#xff1a;BIO、NIO、AIO的区别与联系 引言 在Java的网络编程与文件操作中&#xff0c;I/O&#xff08;输入/输出&#xff09;模型是绕不开的核心话题。从早期的BIO&#xff08;Blocking I/O&#xff09;到Java 1.4引入的NIO&#xff08;Non-blocking I/…

windows PowerToys之无界鼠标:一套键鼠控制多台设备

&#x1f4bb;简介 在多设备协作的工作场景中&#xff0c;如何实现一套键鼠控制多台设备了&#xff1f;微软推出的 PowerToys 工具集中的 Mouse Without Borders&#xff08;无界鼠标&#xff09;&#xff0c;通过软件层实现跨设备的键鼠共享与数据同步功能&#xff0c;为多台…

一道比较难的sql题,筛选出重复字段的行数

select * from 导入数据表; id city_column 1 北京,上海,广州 2 上海,上海,深圳 3 北京,杭州,北京 4 上海,广州,深圳select substring_index(khmc,,,1), * from 导入数据表 truncate table 导入数据表 select count(distinct khmc) from 导入数据表; …

【K8s】整体认识K8s之与集群外部访问--service

这一篇文章主要是对service发现新的理解 为什么要使用service服务发现&#xff1f; 首先pod的IP&#xff0c;是动态的&#xff0c;当我们重启一个pod的时候&#xff0c;它会给它分配一个新的IP&#xff0c;但是如果微服务a想要去调用微服务b&#xff0c;他是需要知道微服务b所有…

k8s(自写)

kubernetes k8s是什么&#xff1f;Kubernetes是什么&#xff1f;架构是怎么样的&#xff1f;6分钟快速入门_哔哩哔哩_bilibili kubernetes是google开源神器&#xff0c;介于应用服务和服务器之间&#xff0c;能够通过策略协调和管理多个应用服务&#xff0c;只需要一个yaml文…

实现微信小程序的UniApp相机组件:拍照、录像与双指缩放

在微信小程序开发中&#xff0c;相机功能已成为许多应用的核心组成部分。本文将介绍如何使用UniApp框架实现一个功能丰富的相机组件&#xff0c;支持拍照、录像、前后摄像头切换以及双指缩放等功能。功能概述这个相机组件具备以下核心功能&#xff1a;拍照功能&#xff1a;支持…