Vert.x学习笔记-Vert.x的基本处理单元Verticle
Verticle介绍
Verticle是Vert.x的基本处理单元,Vert.x应用程序中存在着处理各种事件的处理单元,比如负责HTTP API响应请求的处理单元、负责数据库存取的处理单元、负责向第三方发送请求的处理单元。Verticle就是对这些功能单元的封装,Verticle可被部署,有自己的生命周期,Verticle是Vert.x中构建异步事件处理程序及相关业务逻辑的基础。
Verticle特点
Verticle是Vert.x框架的核心概念,它是一种单一组件,所有业务功能都使用这种组件来完成。
- 简单性(Simplicity) :Vert.x的设计采用了单一组件结构,即Verticle,这使得所有业务功能都使用同一种组件进行编程,从而让开发人员能够快速适应Vert.x编程,并加快项目的开发速度。
- 并发性(Concurrency) :在处理多用户并发连接请求时,Vert.x摒弃了传统的多线程、同步工作、阻塞模式,而采用简单的单线程、异步工作、非阻塞模式,通过单线程内的事件驱动实现并发处理。这种处理方式更高效地利用了系统资源,并能够更好地应对高并发场景。
- 线程隔离性(Thread Isolation) :Verticle的最大特色就是它的线程隔离性。在启动时,Verticle就被分配给了创建和start方法调用的Event Loop。当调用一个使用core API的handler的方法时,Vert.x保证这些handler将在同一个Event Loop上执行。这种线程隔离性可以保证在Verticle实例的代码是在同一个Event Loop执行,避免了多线程环境下的并发问题。
实现原理
Verticle的实现原理主要基于事件驱动和非阻塞I/O模型。在Vert.x中,应用程序将业务逻辑封装在事件驱动的处理程序中,称为Verticle。每个Verticle都是一个独立的运行单元,可以按照一定规则通过时间和状态变换来实现。
具体来说,当外部系统或客户端发起请求时,Vert.x会将请求转换为处理程序可以处理的标准格式,并将处理结果返回给客户端。在这个过程中,Vert.x采用了异步消息传递的方式,使得不同Verticle之间可以相互通信。
此外,Vert.x还提供了容器来为Verticle提供运行的环境和服务,并支持将多个Verticle部署在同一容器中。当一个Verticle需要访问网络服务器时,它可以通过容器来获取相应的服务,并且当多个Verticle实例被部署时,事件会按照轮询的方式分发到每个Verticle实例,这有助于在大量并发网络请求时最大化CPU使用率。
总之,Verticle的实现原理是基于事件驱动和非阻塞I/O模型,通过将业务逻辑封装在事件驱动的处理程序中,并借助容器提供运行环境和服务的支持,实现了高效、可扩展、易于使用的应用程序开发方式。
接口定义
public interface Verticle {/*** Get a reference to the Vert.x instance that deployed this verticle* 获取一个Vert.x实例的引用用来发布当前的verticle*/Vertx getVertx();/*** Initialise the verticle with the Vert.x instance and the context.* 通过Vert.x实例和上下文来初始化一个Verticle* <p>* This method is called by Vert.x when the instance is deployed. You do not call it yourself.** @param vertx Vert.x实例* @param context 上下文*/void init(Vertx vertx, Context context);/*** Start the verticle instance.* 启动当前的verticle实例* <p>* Vert.x calls this method when deploying the instance. You do not call it yourself.* <p>* A promise is passed into the method, and when deployment is complete the verticle should either call* {@link io.vertx.core.Promise#complete} or {@link io.vertx.core.Promise#fail} the future.** @param startPromise the future*/void start(Promise<Void> startPromise) throws Exception;/*** Stop the verticle instance.* 停止当前的verticle实例* <p>* Vert.x calls this method when un-deploying the instance. You do not call it yourself.* <p>* A promise is passed into the method, and when un-deployment is complete the verticle should either call* {@link io.vertx.core.Promise#complete} or {@link io.vertx.core.Promise#fail} the future.** @param stopPromise the future*/void stop(Promise<Void> stopPromise) throws Exception;
}
Verticle编写
在日常的开发中,我们通常会采用继承AbstractVerticle抽象类的方式来实现自定义的Verticle,理论上来说,我们也可以通过实现Verticle接口的方式来定义一个Verticle实现类,但是开发人员通常会通过继承AbstractVerticle的方式来实现,因为它提供了所有Vert.x用户都会用到的事件处理程序、配置信息、执行流水线等。
Vert.x是一个库,而不是一个框架,所以你既可以选择从main方法中创建Vert.x实例,也可以选择从任何其它类中来创建,先创建好Vert.x,再部署Vertcle
一个Verticle的生命周期由start和stop事件组成,AbstractVerticle类提供了start和stop方法,我们可以覆盖它们
-
start:设置初始化工作,初始化事件处理程序,启动Http服务端口监听等
-
stop: 执行一些清理任务,例如关闭数据库连接,停止Http端口监听等
默认情况,这两个方法什么都不做
代码实例
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class HelloVerticle extends AbstractVerticle {private final Logger logger = LoggerFactory.getLogger(HelloVerticle.class);//定义一个全局变量 private long counter = 1;@Overridepublic void start() {//定义一个每5秒执行一次的周期性任务vertx.setPeriodic(5000, id -> {logger.info("tick");});//定义一个HttpServer,监听8080端口vertx.createHttpServer().requestHandler(req -> {logger.info("Request #{} from {}", counter++, req.remoteAddress().host());req.response().end("Hello!");})//监听8080端口.listen(8080);logger.info("Open http://localhost:8080/");}public static void main(String[] args) {//实例化一个全局的Vert.x实例Vertx vertx = Vertx.vertx();//部署一个Verticlevertx.deployVerticle(new HelloVerticle());}
}
通过执行上面的实例代码,可以得出一个Verticle的重要属性:事件处理是在单个事件循环线程上进行的,从日志可以看出来,他们都是在 vert.x-eventloop-thread-0 这个线程上进行执行的,该设计的好处就在于在同一个线程上进行,减少了锁对性能的影响,同时减少了线程切换的开销,
代码实例交互过程
HelloVerticle调用Vert.x对象上的setPeriodic创建周期性任务处理程序,该对象又使用Vert.x定时器创建周期性任务,反过来,定时器定时回调HelloVerticle中的InternalTimerHandler处理程序
事件循环线程与Verticle的关系
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class HelloVerticle extends AbstractVerticle {private final Logger logger = LoggerFactory.getLogger(HelloVerticle.class);private long counter = 1;public long delay;public int port;HelloVerticle (long delay, int port) {this.delay = delay;this.port = port;}public void start() {start(delay, port);}public void start(long delay, int port) {vertx.setPeriodic(delay, id -> {logger.info("tick : " + delay);});vertx.createHttpServer().requestHandler(req -> {logger.info("Request #{} from {}", counter++, req.remoteAddress().host());req.response().end("Hello!");}).listen(port);logger.info("Open http://localhost:" + port);}public static void main(String[] args) {Vertx vertx = Vertx.vertx();vertx.deployVerticle(new HelloVerticle(5000, 8080));vertx.deployVerticle(new HelloVerticle(3000, 8081));}
}
通过运行上面的服务并查看对应的日志,就会发现每一个Verticle实例对应了一个独立的事件循环线程
阻塞和事件循环
事件处理程序运行再事件循环线程上,所以这里需要特别注意的一个点:在事件循环的线程中执行的代码所用时间要尽可能短,这样事件循环才能有更高的吞吐量处理大量的事件。所以程序员不应该在事件循环线程中执行耗时太长的任务或进行阻塞IO的操作
下面通过一个代码实例来展示一下当我们故意阻塞事件循环线程后的情形:
public class BlockEventLoop extends AbstractVerticle {@Overridepublic void start() {//定义一个1000毫秒延时的定时器vertx.setTimer(1000, id -> {//进入死循环,模拟阻塞while (true);});}public static void main(String[] args) {Vertx vertx = Vertx.vertx();vertx.deployVerticle(new BlockEventLoop());}
}
运行结果:
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 2592 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 3596 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 4600 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 5603 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blockedat app//chapter2.blocker.BlockEventLoop.lambda$start$0(BlockEventLoop.java:11)at app//chapter2.blocker.BlockEventLoop$$Lambda$78/0x00000008001a5040.handle(Unknown Source)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:951)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:918)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:52)at app//io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:294)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)at app//io.vertx.core.impl.AbstractContext.emit(AbstractContext.java:49)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.run(VertxImpl.java:941)at app//io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)at app//io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)at app//io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)at app//io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)at java.base@11.0.21/java.lang.Thread.run(Thread.java:829)
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 6605 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blockedat app//chapter2.blocker.BlockEventLoop.lambda$start$0(BlockEventLoop.java:11)at app//chapter2.blocker.BlockEventLoop$$Lambda$78/0x00000008001a5040.handle(Unknown Source)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:951)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:918)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:52)at app//io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:294)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)at app//io.vertx.core.impl.AbstractContext.emit(AbstractContext.java:49)at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.run(VertxImpl.java:941)at app//io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)at app//io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)at app//io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)at app//io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)at java.base@11.0.21/java.lang.Thread.run(Thread.java:829)
当事件处理程序进入阻塞状态后,日志开始输出告警信息,现在开始事件循环线程无法处理其它的事件啦,经过几轮告警(默认设置为5秒)后,就开始打印线程堆栈信息啦,但是需要注意的是,这些只是警告信息,事件循环线程的检查器并不能终止这些阻塞的操作
线程阻塞检查器配置
默认情况,事件循环线程阻塞器发出告警的时间2秒,打印线程堆栈信息的时间是5秒,根据不同设备的处理能力,该时间可以进行灵活的配置
- -Dvertx.options.blockedThreadCheckInterval = 1000 //检查器阈值设置为1秒
- -Dvertx.threadChecks=false //禁用线程阻塞器
生命周期事件异步通知
Verticle中的start和stop方法被称作生命周期方法,该方法在定义时是没有返回值的,根据约定,除非该方法在调用过程中发生了异常,否则则认为该调用是成功的,但是在start和stop方法中可能存在一些异步的操作,那我们如何来监听这些异步操作的成功与失败呢,下面我们就来介绍一下Promise
Vert.x的Promise是对Future-Promise模型的适配,这是一个用于处理异步结果的机制。Promise用来写入异步结果,而Future用来查看异步结果。可调用一个Promise对象的future方法以取得Vert.x中的Future类型的Future对象
package chapter2.future;import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;public class FutureVerticle extends AbstractVerticle {@Overridepublic void start(Promise<Void> promise) { // Promise的类型是Void,因为Vert.x只关心部署是否成功,不关心带了什么参数vertx.createHttpServer().requestHandler(req -> req.response().end("Ok")).listen(8080, ar -> {if (ar.succeeded()) { // 这个listen函数支持异步回调,通过它就知道了结果是成功还是失败promise.complete(); // complete函数用于将promise标记为以完成状态,如果Promise的对象类型不是Void,complete函数也可以传递值} else {promise.fail(ar.cause()); // 如果listen操作失败,将promise标记为失败,并传递一个异常信息}});System.out.println("start执行完成");}public static void main(String[] args) {Vertx vertx = Vertx.vertx();//定义一个异步的回调,来监测start方法的执行情况vertx.deployVerticle(new FutureVerticle(), h -> {if (h.succeeded()) {System.out.println("成功");} else {System.out.println("失败:" + h.cause().getMessage());}});}
}
日常开发中尽量使用有回调参数的异步方法,这样当发生错误的时候,我们就可以得到通知
Verticle配置
应用程序启动的时候经常需要传入不同的配置参数,我们通过Vert.x来部署Verticle的时候也可以进行参数传递和配置,配置数据需要以JSON格式来进行传递,次数使用Vert.x中JsonObject和JsonArray类中的具体API
配置传递示例代码
public class ConfigVerticle extends AbstractVerticle {private final Logger logger = LoggerFactory.getLogger(ConfigVerticle.class);@Overridepublic void start() {logger.info("n = {}", config().getInteger("n", -1));}public static void main(String[] args) {Vertx vertx = Vertx.vertx();for (int n = 0; n < 4; n++) {JsonObject conf = new JsonObject().put("n", n);DeploymentOptions opts = new DeploymentOptions() //该对象可以让我们更多的控制部署过程.setConfig(conf).setInstances(n); // 该配置表示一次部署了Verticle多个实例vertx.deployVerticle("xxx.ConfigVerticle", opts); //当我们一次性部署Verticle多个实例时,该处需要用类的全名;如果是部署单个实例,则可以用全名或者new}}
}
Verticle部署
上面的示例中,我们通过在Verticle内嵌main方法来完成Verticle的部署,部署都是通过Vert.x对象来进行的,一个由多个Verticle组成的应用程序的典型部署方式是这样的:
- 先部署一个主Verticle
- 主Verticle再部署其它的Verticle
- 被部署的Verticle可以继续部署更多的Verticle
这样的部署方式可能让我们感觉这些Verticle是存在层级关系的,实际上,这些Verticle在运行中不存在父子的概念
部署示例
待部署Verticle
public class BaseVerticle extends AbstractVerticle {private final Logger logger = LoggerFactory.getLogger(BaseVerticle.class);@Overridepublic void start() {logger.info("Start");}@Overridepublic void stop() {logger.info("Stop");}
专门用于部署Verticle的部署工具
public class Deployer extends AbstractVerticle {private final Logger logger = LoggerFactory.getLogger(Deployer.class);@Overridepublic void start() {long delay = 1000;for (int i = 0; i < 50; i++) {vertx.setTimer(delay, id -> deploy()); // 每隔1秒部署一个 BaseVerticle 实例delay = delay + 1000;}}private void deploy() {vertx.deployVerticle(new BaseVerticle(), ar -> { // vertx中的Vertcle的部署是一个异步的操作,所以这里使用带异步结果的异步方法来进行部署,部署成功会生成一个唯一的Verticle IDif (ar.succeeded()) {String id = ar.result();logger.info("Successfully deployed {}", id);vertx.setTimer(5000, tid -> undeployLater(id)); // Verticle部署成功5秒以后就卸载该实例} else {logger.error("Error while deploying", ar.cause());}});}private void undeployLater(String id) {vertx.undeploy(id, ar -> { //卸载的过程和部署的过程类似,也是一个异步的操作if (ar.succeeded()) {logger.info("{} was undeployed", id);} else {logger.error("{} could not be undeployed", id);}});}
}
主启动类
public class Main {public static void main(String[] args) {Vertx vertx = Vertx.vertx();vertx.deployVerticle(new Deployer());}
}
该方法也可以直接放在 Deployer 中,放在外面层次更分明一些
线程启动日志分析
线程ID | 操作行为 |
---|---|
vert.x-eventloop-thread-2 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed 7186663f-10a6-4cdc-985a-d3357fe240db |
vert.x-eventloop-thread-3 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed 88ba21cb-e8c1-403b-bf29-edde7c1f2ebc |
vert.x-eventloop-thread-4 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed e1ccd570-c631-4035-b7d2-7b06bbc3c326 |
vert.x-eventloop-thread-5 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed 8ab2b0f5-856f-498b-8a50-0622a65233cc |
vert.x-eventloop-thread-6 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed 1191576b-a3c1-49bb-a882-29cdc041a9c2 |
vert.x-eventloop-thread-7 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed 12512cf1-3ad7-41c6-82e0-bd1478524c73 |
vert.x-eventloop-thread-2 | EmptyVerticle - Stop |
vert.x-eventloop-thread-0 | Deployer - 7186663f-10a6-4cdc-985a-d3357fe240db was undeployed |
vert.x-eventloop-thread-1 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed cd6fbb87-10fb-46da-b1c5-710a4d0e305c |
vert.x-eventloop-thread-3 | EmptyVerticle - Stop |
vert.x-eventloop-thread-0 | Deployer - 88ba21cb-e8c1-403b-bf29-edde7c1f2ebc was undeployed |
vert.x-eventloop-thread-0 | EmptyVerticle - Start |
vert.x-eventloop-thread-0 | Deployer - Successfully deployed a574512e-509a-45cb-968c-4ae6c89f7ca2 |
vert.x-eventloop-thread-2 | EmptyVerticle - Start |
通过上面的日志,我们可以得到这样的一些结论:
- 针对于不同的Verticle,并不是有多少个Verticle就会有多少个线程,默认情况下,Vert.x创建的事件循环线程的数量是CPU核心数的2倍,比如说:CPU是4核,那么Vert.x默认会创建8个事件循环线程。Verticle会被轮流分配给各事件循环
- 一个Verticle总是运行在同一个事件循环线程上,而一个事件循环线程会被多个Verticle共享,这种设计使应用程序运行时的线程数量是可以预测的
开发人员可以调整事件循环的数量,但是无法手动指定一个Verticle应该分配到那个事件循环
Vert.x相关笔记
- 异步变成与响应式系统
- 什么是Vert.x
相关文章:

Vert.x学习笔记-Vert.x的基本处理单元Verticle
Verticle介绍 Verticle是Vert.x的基本处理单元,Vert.x应用程序中存在着处理各种事件的处理单元,比如负责HTTP API响应请求的处理单元、负责数据库存取的处理单元、负责向第三方发送请求的处理单元。Verticle就是对这些功能单元的封装,Vertic…...

干货分享:基于 LSTM 的广告库存预估算法
近年来,随着互联网的发展,在线广告营销成为一种非常重要的商业模式。出于广告流量商业化售卖和日常业务投放精细化运营的目的,需要对广告流量进行更精准的预估,从而更精细的进行广告库存管理。 因此,携程广告纵横平台…...
dataframe删除某一列
drop import pandas as pd data {‘A’: [1, 2, 3], ‘B’: [4, 5, 6], ‘C’: [7, 8, 9]} df pd.DataFrame(data) #使用drop方法删除列 df df.drop(‘B’, axis1) # 通过指定列名和axis1来删除列 del import pandas as pd data {‘A’: [1, 2, 3], ‘B’: [4, 5, 6]…...

提升ChatGPT答案质量和准确性的方法Prompt engineering
文章目录 怎么获得优质的答案设计一个优质prompt的步骤:Prompt公式:示例怎么获得优质的答案 影响模型回答精确度的因素 我们应该知道一个好的提示词,要具备一下要点: 清晰简洁,不要有歧义; 有明确的任务/问题,任务如果太复杂,需要拆分成子任务分步完成; 确保prompt中…...

SpringBoot + Vue2项目打包部署到服务器后,使用Nginx配置SSL证书,配置访问HTTP协议转HTTPS协议
配置nginx.conf文件,这个文件一般在/etc/nginx/...中,由于每个人的体质不一样,也有可能在别的路径里,自己找找... # 配置工作进程的最大连接数 events {worker_connections 1024; }# 配置HTTP服务 http {# 导入mime.types配置文件…...

HTML 表格
<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>表格标签</title>/* <style>.yun {widt…...
AIGC(生成式AI)试用 10 -- 安全性问题
上次遗留的问题:代码的安全性呢?下次找几个问题测试下看。 AI,你安全吗? AI生成的程序,安全吗? 也许这个世界最难做的事就是自己测试自己:测试什么?如何测? …...

STM32循迹小车原理介绍和代码示例
目录 1. 循迹模块介绍 2. 循迹小车原理 3. 循迹小车核心代码 4. 循迹小车解决转弯平滑问题 1. 循迹模块介绍 TCRT5000传感器的红外发射二极管不断发射红外线当发射出的红外线没有被反射回来或被反射回来但强度不够大时红外接收管一直处于关断状态,此时模块的输出…...

Nginx 配置详细讲解
Nginx.conf 配置文件分为三部分,分别为main块、events块、http块(http块又包含server块和location块),如下图。 第一部分:main块(全局块) main块主要是设置一些影响Nginx服务器整体运行的配置指令,主要包括…...
gdb 日志记录不显示到屏幕的方法(gdb13最新版)
tags: gdb categories: [Debug] 写在前面 gdb 的更新好快啊… 之前的选项都有改动了, 比如 logging… 需要屏幕重定向不能简单设置: set logging on set logging redirect on了, 而是要多开一个配置, 踩坑了 方法 在此之前先看一下我的 gdbinit 配置: set debuginfod e…...

JAVA智慧工地管理系统源码基于微服务
智慧工地是将互联网的理念和科技引入施工现场,从施工现场源头抓起,大程度的收集人员、安全、环境、质量等关键业务数据。通过结合物联网、大数据、互联网、云计算等技术建立云端大数据管理平台,形成端云大数据的体系与模式,这就是…...

学习笔记三十四:Ingress和 Ingress Controller概述
Ingress和 Ingress Controller概述 回顾service四层负载在k8s中为什么要做负载均衡Service不足之处四层负载和七层负载的区别OSI七层模型: Ingress介绍Ingress Controller介绍Ingress-controller 作用Ingress和Ingress Controller总结使用Ingress Controller代理k8s…...

Webpack的Tree Shaking。它的作用是什么?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

研发效能DevOps: Git安装
目录 一、理论 1.Git 2.Git 工具 二、实验 1.Git安装 2.配置Git 3. VS Code加载Git 一、理论 1.Git (1)简介 Git 是一个分布式版本控制及源代码管理工具;Git 可以为你的项目保存若干快照,以此来对整个项目进行版本管理。 Git 是一个…...

ZZ038 物联网应用与服务赛题第D套
2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 (D卷) 赛位号:______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等; 2.竞赛任务中所使用的各类软件工具、软件安装文件等,都…...

基于STM32设计的室内环境监测系统(华为云IOT)_2023
一、设计需求 基于STM32+华为云物联网平台设计一个室内环境监测系统,以STM32系列单片机为主控器件,采集室内温湿度、空气质量、光照强度等环境参数,将采集的数据结果在本地通过LCD屏幕显示,同时上传到华为云平台并将上传的数据在Android移动端能够实时显示、查看。 【1…...

UE5C++学习(一)--- 增强输入系统
一、关于增强输入系统的介绍 增强输入系统官方文档介绍 二、增强输入系统的具体使用 注:在使用方面,不会介绍如何创建项目等基础操作,如果还没有UE的使用基础,可以参考一下我之前UE4的文章,操作差别不会很大。 如上…...

好物周刊#29:项目管理软件
https://github.com/cunyu1943/JavaPark https://yuque.com/cunyu1943 村雨遥的好物周刊,记录每周看到的有价值的信息,主要针对计算机领域,每周五发布。 一、项目 1. HelloGithub 分享 GitHub 上有趣、入门级的开源项目。每月 28 号以月刊…...

玻色量子“天工量子大脑”亮相中关村论坛,大放异彩
2023年5月25日至30日,2023中关村论坛(科博会)在北京盛大召开。中关村论坛(科博会)是面向全球科技创新交流合作的国家级平台行业盛会,由科技部、国家发展改革委、工业和信息化部、国务院国资委、中国科学院、…...

使用Gorm进行高级查询
深入探讨GORM的高级查询功能,轻松实现Go中的数据检索 高效的数据检索是每个应用程序性能的核心。GORM,强大的Go对象关系映射库,不仅扩展到基本的CRUD操作,还提供了高级的查询功能。本文是您掌握使用GORM进行高级查询的综合指南。…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...

算法—栈系列
一:删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...