当前位置: 首页 > news >正文

【Spark源码分析】事件总线机制分析

Spark事件总线机制

采用Spark2.11源码,以下类或方法被@DeveloperApi注解额部分,可能出现不同版本不同实现的情况。

Spark中的事件总线用于接受事件并提交到对应的监听器中。事件总线在Spark应用启动时,会在SparkContext中激活spark运行的事件总线(LiveListenerBus)。

LiveListenerBus相关的部分类图如下:

由于Spark使用scala语言编写的,所以在类图上的接口代表的是Traits类的接口功能。

继承
实现
实现
聚合
聚合
继承
继承
继承
实现
SparkContext
«interface»
SparkListenerEvent
«interface»
SparkListenerInterface
«interface»
SparkListenerBus
«interface»
ListenerBus
LiveListenerBus
AsyncEventQueue
AppStatusListener
ExecutorAllocationListener
«Abstract»
SparkListener
SparkListener相关事件
EventLoggingListener

主体逻辑

启动应用的时候,在SparkConext中对LiveListenerBus进行实例化,除了内部的监听器,还将注册在 spark.extraListeners配置项中指定的监听器,然后启动监听器总线。

LiveListenerBus中使用AsyncEventQueue作为核心,实现将事件异步的分发给已经注册的SparkListener监听器们。其中AsyncEventQueue有4类:

LiveListenerBusAsyncEventQueue分为4类,不同的事件分发给各自独立的线程进行处理,防止在监听器和事件较多的时候造成积压问题。

  • eventLog:日志事件队列
  • executorManagement:执行器管理队列
  • appStatus:应用程序状态队列
  • shared:非内部监听器共享的队列

在AsyncEventQueue内部采用LinkedBlockingQueue来存储事件,并启动一个常住线程(dispatchThread)进行事件的转发。

LiveListenerBus
AsyncEventQueue-eventLog
AsyncEventQueue-executorManagement
AsyncEventQueue-appStatus
AsyncEventQueue-shared
addToQueue
addToQueue
addToQueue
addToQueue
start
stop
eventQueue
event4-1
event4-2
listeners
listener4类
listener8类
dispatchThread
eventQueue
event3-1
event3-2
listeners
listener3类
listener7类
dispatchThread
eventQueue
event2-1
event2-2
listeners
listener2类
listener6类
dispatchThread
eventQueue
event1-1
event1-2
listeners
listener1类
listener5类
dispatchThread
events发生源1
listener1
events发生源2
listener2
events发生源3
listener3
events发生源4
listener4

代码详解

org.apache.spark.util.ListenerBus Traits类

scala中的Traits类,类似Java中的接口类。与接口相同的部分是可以定义抽象的方法和成员,不用的部分是可以包含具体的方法可以成员。

package org.apache.spark.utilimport java.util.concurrent.CopyOnWriteArrayListimport scala.collection.JavaConverters._
import scala.reflect.ClassTag
import scala.util.control.NonFatalimport com.codahale.metrics.Timerimport org.apache.spark.internal.Logging/*** 事件总线的基类。用来转发事件到对应的事件监听器*/
// [ L<:AnyRef]指的是泛型,<:符号是泛型的上限。private[spark]代表作用域,只对spark目录下可见
private[spark] trait ListenerBus[L <: AnyRef, E] extends Logging {// (L, Option[Timer])采用的元组式集合private[this] val listenersPlusTimers = new CopyOnWriteArrayList[(L, Option[Timer])]// Marked `private[spark]` for access in tests.private[spark] def listeners = listenersPlusTimers.asScala.map(_._1).asJavaprotected def getTimer(listener: L): Option[Timer] = None/*** 添加监听器来监听事件。 该方法是线程安全的,可以在任何线程中调用。*/final def addListener(listener: L): Unit = {listenersPlusTimers.add((listener, getTimer(listener)))}/*** 移除监听器,它将不会接收任何事件。 该方法是线程安全的,可以在任何线程中调用。*/final def removeListener(listener: L): Unit = {listenersPlusTimers.asScala.find(_._1 eq listener).foreach { listenerAndTimer =>listenersPlusTimers.remove(listenerAndTimer)}}/*** 如果删除侦听器时需要进行任何额外的清理,则可以由子类覆盖它。 特别是AsyncEventQueue可以清理LiveListenerBus中的队列。*/def removeListenerOnError(listener: L): Unit = {removeListener(listener)}/*** 将事件转发给所有注册的侦听器。 `postToAll` 调用者应该保证在同一线程中为所有事件调用 `postToAll`。*/def postToAll(event: E): Unit = {val iter = listenersPlusTimers.iteratorwhile (iter.hasNext) {val listenerAndMaybeTimer = iter.next()val listener = listenerAndMaybeTimer._1val maybeTimer = listenerAndMaybeTimer._2val maybeTimerContext = if (maybeTimer.isDefined) {maybeTimer.get.time()} else {null}try {doPostEvent(listener, event)if (Thread.interrupted()) {throw new InterruptedException()}} catch {case ie: InterruptedException =>logError(s"Interrupted while posting to ${Utils.getFormattedClassName(listener)}.  " +s"Removing that listener.", ie)removeListenerOnError(listener)case NonFatal(e) =>logError(s"Listener ${Utils.getFormattedClassName(listener)} threw an exception", e)} finally {if (maybeTimerContext != null) {maybeTimerContext.stop()}}}}/*** 将事件发布到指定的侦听器。 保证所有侦听器在同一线程中调用“onPostEvent”。*/protected def doPostEvent(listener: L, event: E): Unitprivate[spark] def findListenersByClass[T <: L : ClassTag](): Seq[T] = {val c = implicitly[ClassTag[T]].runtimeClasslisteners.asScala.filter(_.getClass == c).map(_.asInstanceOf[T]).toSeq}}

org.apache.spark.util.ListenerBus.SparkListenerBus

package org.apache.spark.schedulerimport org.apache.spark.util.ListenerBus/*** SparkListenerEvent事件总线继承ListenerBus类,将SparkListenerEvent事件转发到SparkListenerInterface中。* SparkListenerInterface是一个trait接口类,里面定义了一些关于spark应用运行周期中的一些事件监听器。* SparkListenerEvent是定义了一个事件的通用接口类,其他关于Spark应用运行周期过程中的事件均以 case class实现这个接口*/
private[spark] trait SparkListenerBusextends ListenerBus[SparkListenerInterface, SparkListenerEvent] {// 监听器处理对不同的事件采用不用的处理protected override def doPostEvent(listener: SparkListenerInterface,event: SparkListenerEvent): Unit = {event match {case stageSubmitted: SparkListenerStageSubmitted =>listener.onStageSubmitted(stageSubmitted)case stageCompleted: SparkListenerStageCompleted =>listener.onStageCompleted(stageCompleted)case jobStart: SparkListenerJobStart =>listener.onJobStart(jobStart)case jobEnd: SparkListenerJobEnd =>listener.onJobEnd(jobEnd)case taskStart: SparkListenerTaskStart =>listener.onTaskStart(taskStart)case taskGettingResult: SparkListenerTaskGettingResult =>listener.onTaskGettingResult(taskGettingResult)case taskEnd: SparkListenerTaskEnd =>listener.onTaskEnd(taskEnd)case environmentUpdate: SparkListenerEnvironmentUpdate =>listener.onEnvironmentUpdate(environmentUpdate)case blockManagerAdded: SparkListenerBlockManagerAdded =>listener.onBlockManagerAdded(blockManagerAdded)case blockManagerRemoved: SparkListenerBlockManagerRemoved =>listener.onBlockManagerRemoved(blockManagerRemoved)case unpersistRDD: SparkListenerUnpersistRDD =>listener.onUnpersistRDD(unpersistRDD)case applicationStart: SparkListenerApplicationStart =>listener.onApplicationStart(applicationStart)case applicationEnd: SparkListenerApplicationEnd =>listener.onApplicationEnd(applicationEnd)case metricsUpdate: SparkListenerExecutorMetricsUpdate =>listener.onExecutorMetricsUpdate(metricsUpdate)case executorAdded: SparkListenerExecutorAdded =>listener.onExecutorAdded(executorAdded)case executorRemoved: SparkListenerExecutorRemoved =>listener.onExecutorRemoved(executorRemoved)case executorBlacklisted: SparkListenerExecutorBlacklisted =>listener.onExecutorBlacklisted(executorBlacklisted)case executorUnblacklisted: SparkListenerExecutorUnblacklisted =>listener.onExecutorUnblacklisted(executorUnblacklisted)case nodeBlacklisted: SparkListenerNodeBlacklisted =>listener.onNodeBlacklisted(nodeBlacklisted)case nodeUnblacklisted: SparkListenerNodeUnblacklisted =>listener.onNodeUnblacklisted(nodeUnblacklisted)case blockUpdated: SparkListenerBlockUpdated =>listener.onBlockUpdated(blockUpdated)case speculativeTaskSubmitted: SparkListenerSpeculativeTaskSubmitted =>listener.onSpeculativeTaskSubmitted(speculativeTaskSubmitted)case _ => listener.onOtherEvent(event)}}}

SparkListener实现了接口SparkListenerInterface,是它的默认实现类。主要对所有的事件回调做了无操作实现。

事件的存储与转发队列

org.apache.spark.scheduler.AsyncEventQueue

package org.apache.spark.schedulerimport java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong}import com.codahale.metrics.{Gauge, Timer}import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.internal.Logging
import org.apache.spark.internal.config._
import org.apache.spark.util.Utils/*** 事件的异步队列。 发布到此队列的所有事件都将传递到单独线程中的子侦听器。** 仅当调用 `start()` 方法时才会开始传递事件。 当不需要传递更多事件时,应该调用“stop()”方法。*/
private class AsyncEventQueue(val name: String,conf: SparkConf,metrics: LiveListenerBusMetrics,bus: LiveListenerBus)extends SparkListenerBuswith Logging {import AsyncEventQueue._// 维护了队列前文所述的继承自SparkListenerEvent的样例类事件,默认长度10000。private val eventQueue = new LinkedBlockingQueue[SparkListenerEvent](conf.get(LISTENER_BUS_EVENT_QUEUE_CAPACITY))// 代表未处理的事件个数,从eventQueue弹出的事件不保证处理结束了,所以采用一个单独的变量对事件进行计数private val eventCount = new AtomicLong()/**丢弃事件的计数器。 */private val droppedEventsCounter = new AtomicLong(0L)/** 上次记录“droppedEventsCounter”的时间(以毫秒为单位)。 */@volatile private var lastReportTimestamp = 0Lprivate val logDroppedEvent = new AtomicBoolean(false)private var sc: SparkContext = nullprivate val started = new AtomicBoolean(false)private val stopped = new AtomicBoolean(false)private val droppedEvents = metrics.metricRegistry.counter(s"queue.$name.numDroppedEvents")private val processingTime = metrics.metricRegistry.timer(s"queue.$name.listenerProcessingTime")// 首先删除队列大小计量器,以防它是由从侦听器总线中删除的该队列的先前版本创建的。metrics.metricRegistry.remove(s"queue.$name.size")metrics.metricRegistry.register(s"queue.$name.size", new Gauge[Int] {override def getValue: Int = eventQueue.size()})// 事件转发的常驻线程,不停的调用dispatch()进行事件转发private val dispatchThread = new Thread(s"spark-listener-group-$name") {setDaemon(true)override def run(): Unit = Utils.tryOrStopSparkContext(sc) {dispatch()}}private def dispatch(): Unit = LiveListenerBus.withinListenerThread.withValue(true) {var next: SparkListenerEvent = eventQueue.take()while (next != POISON_PILL) {val ctx = processingTime.time()try {// 通过事件总线将事件转发到所有的注册的监听器中。super.postToAll(next)} finally {ctx.stop()}eventCount.decrementAndGet()next = eventQueue.take()}eventCount.decrementAndGet()}override protected def getTimer(listener: SparkListenerInterface): Option[Timer] = {metrics.getTimerForListenerClass(listener.getClass.asSubclass(classOf[SparkListenerInterface]))}/*** 启动一个dispatchThread线程将事件分派给监听器。** @param sc Used to stop the SparkContext in case the async dispatcher fails.*/private[scheduler] def start(sc: SparkContext): Unit = {if (started.compareAndSet(false, true)) {this.sc = scdispatchThread.start()} else {throw new IllegalStateException(s"$name already started!")}}/*** 停止监听器总线。 它将等待,直到处理完排队的事件,但新事件将被丢弃。* 插入POISON_PILL,dispatchThread线程读取到POISON_PIL时就会停止事件的分发*/private[scheduler] def stop(): Unit = {if (!started.get()) {throw new IllegalStateException(s"Attempted to stop $name that has not yet started!")}if (stopped.compareAndSet(false, true)) {eventCount.incrementAndGet()eventQueue.put(POISON_PILL)}if (Thread.currentThread() != dispatchThread) {dispatchThread.join()}}// 向队列中添加事件,如果队列满了,丢弃当前事件并记录日志。这是个生产者消费者模型,当队列满时生产者丢弃事件,但队列为空时消费者等待生产者。def post(event: SparkListenerEvent): Unit = {if (stopped.get()) {return}eventCount.incrementAndGet()if (eventQueue.offer(event)) {return}// 向eventQueue添加事件失败后的逻辑eventCount.decrementAndGet()droppedEvents.inc()droppedEventsCounter.incrementAndGet()if (logDroppedEvent.compareAndSet(false, true)) {logError(s"Dropping event from queue $name. " +"This likely means one of the listeners is too slow and cannot keep up with " +"the rate at which tasks are being started by the scheduler.")}logTrace(s"Dropping event $event")val droppedCount = droppedEventsCounter.getif (droppedCount > 0) {// 为了控制日志的输出频率。采用1分钟输出一次。if (System.currentTimeMillis() - lastReportTimestamp >= 60 * 1000) {if (droppedEventsCounter.compareAndSet(droppedCount, 0)) {val prevLastReportTimestamp = lastReportTimestamplastReportTimestamp = System.currentTimeMillis()val previous = new java.util.Date(prevLastReportTimestamp)logWarning(s"Dropped $droppedCount events from $name since $previous.")}}}}/*** For testing only. Wait until there are no more events in the queue.*/def waitUntilEmpty(deadline: Long): Boolean = {while (eventCount.get() != 0) {if (System.currentTimeMillis > deadline) {return false}Thread.sleep(10)}true}override def removeListenerOnError(listener: SparkListenerInterface): Unit = {bus.removeListener(listener)}}private object AsyncEventQueue {val POISON_PILL = new SparkListenerEvent() { }}

spark运行事件总线

org.apache.spark.scheduler.LiveListenerBus

package org.apache.spark.schedulerimport java.util.{List => JList}
import java.util.concurrent._
import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong}import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.reflect.ClassTag
import scala.util.DynamicVariableimport com.codahale.metrics.{Counter, MetricRegistry, Timer}import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.internal.Logging
import org.apache.spark.internal.config._
import org.apache.spark.metrics.MetricsSystem
import org.apache.spark.metrics.source.Source/*** SparkListenerEvent事件管理器* 将 SparkListenerEvents 异步传递给已注册的 SparkListener。** 在调用`start()`之前,所有发布的事件都只会被缓冲。 只有在此侦听器总线启动后,事件才会实际传播到所有连接的侦听器。 当调用 stop() 时,该监听器总线将停止,停止后它将丢弃更多事件。*/
private[spark] class LiveListenerBus(conf: SparkConf) {import LiveListenerBus._private var sparkContext: SparkContext = _private[spark] val metrics = new LiveListenerBusMetrics(conf)// 表示是否调用了`start()`方法==>总线已启动private val started = new AtomicBoolean(false)// 表示是否调用了`stop()`方法==>总线已启动private val stopped = new AtomicBoolean(false)/** 事件放弃计数器 */private val droppedEventsCounter = new AtomicLong(0L)/** 上次记录“droppedEventsCounter”的时间(以毫秒为单位)。 */@volatile private var lastReportTimestamp = 0Lprivate val queues = new CopyOnWriteArrayList[AsyncEventQueue]()// Visible for testing.@volatile private[scheduler] var queuedEvents = new mutable.ListBuffer[SparkListenerEvent]()/**将侦听器添加到所有非内部侦听器共享的队列中。 */def addToSharedQueue(listener: SparkListenerInterface): Unit = {addToQueue(listener, SHARED_QUEUE)}/** 将监听器添加到执行器管理队列中。 */def addToManagementQueue(listener: SparkListenerInterface): Unit = {addToQueue(listener, EXECUTOR_MANAGEMENT_QUEUE)}/** 将侦听器添加到应用程序状态队列。*/def addToStatusQueue(listener: SparkListenerInterface): Unit = {addToQueue(listener, APP_STATUS_QUEUE)}/** 将监听器添加到事件日志队列. */def addToEventLogQueue(listener: SparkListenerInterface): Unit = {addToQueue(listener, EVENT_LOG_QUEUE)}/*** 将侦听器添加到特定队列,并根据需要创建新队列。 * 队列彼此独立(每个队列使用单独的线程来传递事件),允许较慢的侦听器在一定程度上与其他侦听器隔离。*/private[spark] def addToQueue(listener: SparkListenerInterface,queue: String): Unit = synchronized {if (stopped.get()) {throw new IllegalStateException("LiveListenerBus is stopped.")}// 先寻找队列是否存在,如果存在就注册,不存在就创建新队列并注册queues.asScala.find(_.name == queue) match {case Some(queue) =>queue.addListener(listener)case None =>val newQueue = new AsyncEventQueue(queue, conf, metrics, this)newQueue.addListener(listener)if (started.get()) {newQueue.start(sparkContext)}queues.add(newQueue)}}def removeListener(listener: SparkListenerInterface): Unit = synchronized {// 从添加到的所有队列中删除侦听器,并停止已变空的队列。queues.asScala.filter { queue =>queue.removeListener(listener)queue.listeners.isEmpty()}.foreach { toRemove =>if (started.get() && !stopped.get()) {toRemove.stop()}queues.remove(toRemove)}}/** 将事件转发到所有的队列中 */def post(event: SparkListenerEvent): Unit = {if (stopped.get()) {return}metrics.numEventsPosted.inc()// 如果事件缓冲区为空,则意味着总线已启动,我们可以避免同步并将事件直接发布到队列中。 这应该是事件总线生命周期中最常见的情况。if (queuedEvents == null) {postToQueues(event)return}// 否则,需要同步检查总线是否启动,以确保调用 start() 的线程拾取新事件。synchronized {if (!started.get()) {queuedEvents += eventreturn}}// 如果进行上述检查时总线已经启动,则直接发送到队列。postToQueues(event)}// 遍历所有队列进行事件分发private def postToQueues(event: SparkListenerEvent): Unit = {val it = queues.iterator()while (it.hasNext()) {it.next().post(event)}}/*** 启动每个队列,并发送queuedEvents中缓存的事件。每个队列就开始消费之前post的事件并调用postToAll()方法将事件发送给监视器。** 这首先发送在此侦听器总线启动之前发布的所有缓冲事件,然后在侦听器总线仍在运行时异步侦听任何其他事件。* 这应该只被调用一次。** @param sc Used to stop the SparkContext in case the listener thread dies.*/def start(sc: SparkContext, metricsSystem: MetricsSystem): Unit = synchronized {if (!started.compareAndSet(false, true)) {throw new IllegalStateException("LiveListenerBus already started.")}this.sparkContext = scqueues.asScala.foreach { q =>q.start(sc)queuedEvents.foreach(q.post)}queuedEvents = nullmetricsSystem.registerSource(metrics)}/*** Exposed for testing.*/@throws(classOf[TimeoutException])def waitUntilEmpty(timeoutMillis: Long): Unit = {val deadline = System.currentTimeMillis + timeoutMillisqueues.asScala.foreach { queue =>if (!queue.waitUntilEmpty(deadline)) {throw new TimeoutException(s"The event queue is not empty after $timeoutMillis ms.")}}}/*** 停止监听器总线。 它将等待,直到处理完排队的事件,但在停止后删除新事件。*/def stop(): Unit = {if (!started.get()) {throw new IllegalStateException(s"Attempted to stop bus that has not yet started!")}if (!stopped.compareAndSet(false, true)) {return}synchronized {queues.asScala.foreach(_.stop())queues.clear()}}// For testing only.private[spark] def findListenersByClass[T <: SparkListenerInterface : ClassTag](): Seq[T] = {queues.asScala.flatMap { queue => queue.findListenersByClass[T]() }}// For testing only.private[spark] def listeners: JList[SparkListenerInterface] = {queues.asScala.flatMap(_.listeners.asScala).asJava}// For testing only.private[scheduler] def activeQueues(): Set[String] = {queues.asScala.map(_.name).toSet}}private[spark] object LiveListenerBus {// Allows for Context to check whether stop() call is made within listener threadval withinListenerThread: DynamicVariable[Boolean] = new DynamicVariable[Boolean](false)private[scheduler] val SHARED_QUEUE = "shared"private[scheduler] val APP_STATUS_QUEUE = "appStatus"private[scheduler] val EXECUTOR_MANAGEMENT_QUEUE = "executorManagement"private[scheduler] val EVENT_LOG_QUEUE = "eventLog"
}private[spark] class LiveListenerBusMetrics(conf: SparkConf)extends Source with Logging {override val sourceName: String = "LiveListenerBus"override val metricRegistry: MetricRegistry = new MetricRegistryval numEventsPosted: Counter = metricRegistry.counter(MetricRegistry.name("numEventsPosted"))// Guarded by synchronization.private val perListenerClassTimers = mutable.Map[String, Timer]()def getTimerForListenerClass(cls: Class[_ <: SparkListenerInterface]): Option[Timer] = {synchronized {val className = cls.getNameval maxTimed = conf.get(LISTENER_BUS_METRICS_MAX_LISTENER_CLASSES_TIMED)perListenerClassTimers.get(className).orElse {if (perListenerClassTimers.size == maxTimed) {logError(s"Not measuring processing time for listener class $className because a " +s"maximum of $maxTimed listener classes are already timed.")None} else {perListenerClassTimers(className) =metricRegistry.timer(MetricRegistry.name("listenerProcessingTime", className))perListenerClassTimers.get(className)}}}}}

Spark任务启动时,会在SparkContext中启动spark运行的事件总线(LiveListenerBus)

  private def setupAndStartListenerBus(): Unit = {try {conf.get(EXTRA_LISTENERS).foreach { classNames =>val listeners = Utils.loadExtensions(classOf[SparkListenerInterface], classNames, conf)listeners.foreach { listener =>listenerBus.addToSharedQueue(listener)logInfo(s"Registered listener ${listener.getClass().getName()}")}}} catch {case e: Exception =>try {stop()} finally {throw new SparkException(s"Exception when registering SparkListener", e)}}// 启动应用的运行事件总线listenerBus.start(this, _env.metricsSystem)_listenerBusStarted = true}

相关文章:

【Spark源码分析】事件总线机制分析

Spark事件总线机制 采用Spark2.11源码&#xff0c;以下类或方法被DeveloperApi注解额部分&#xff0c;可能出现不同版本不同实现的情况。 Spark中的事件总线用于接受事件并提交到对应的监听器中。事件总线在Spark应用启动时&#xff0c;会在SparkContext中激活spark运行的事件总…...

c语言第七弹--扫雷小游戏!

今天做一个有趣的扫雷小游戏 现在正式开始设计。 思路&#xff1a;想要根本上实现必须拥有 实现函数的主体.c文件 头文件.h 及头文件实现.c。 头文件.h #pragma once #include <stdio.h> #include <stdlib.h> #include <time.h> #define EASY_COUNT 10 #d…...

浏览器是什么

浏览器是什么 本文简要介绍浏览器的功能和组成。 浏览器&#xff08;Web Browser&#xff09;是一种用于访问和浏览互联网上的网页和资源的软件应用程序。它是用户与互联网交互的主要工具之一。 浏览器通过使用网络协议&#xff08;如HTTP、HTTPS等&#xff09;与远程服务器通…...

一文彻底看懂Python切片,Python切片理解与操作

1.什么是切片 切片是Python中一种用于操作序列类型(如列表、字符串和元组)的方法。它通过指定起始索引和结束索引来截取出序列的一部分,形成一个新的序列。切片是访问特定范围内的元素,就是一个Area。 说个笑话:切片不是切片,而是切片,但是又是切片。大家理解下呢(末…...

聊聊tomcat的connection-timeout

序 本文主要研究一下tomcat的connection-timeout ServerProperties.Tomcat org/springframework/boot/autoconfigure/web/ServerProperties.java public static class Tomcat {/*** Access log configuration.*/private final Accesslog accesslog new Accesslog();/*** Th…...

HCIA-RS基础:动态路由协议基础

摘要&#xff1a;本文介绍动态路由协议的基本概念&#xff0c;为后续动态路由协议原理课程提供基础和引入。主要讲解常见的动态路由协议、动态路由协议的分类&#xff0c;以及路由协议的功能和自治系统的概念。文章旨在优化标题吸引力&#xff0c;并通过详细的内容夯实读者对动…...

jQuery 第十一章(表单验证插件推荐)

文章目录 前言jValidateZebra FormjQuery.validValValidityValidForm BuilderForm ValidatorProgressionformvalidationjQuery Validation PluginjQuery Validation EnginejQuery ValidateValidarium后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&…...

SSL握手失败的解决方案

一、SSL握手失败的原因&#xff1a; 1&#xff0c;证书过期&#xff1a;SSL证书有一个有效期限&#xff0c;如果证书过期&#xff0c;就会导致SSL握手失败。 2&#xff0c;证书不被信任&#xff1a;如果网站的SSL证书不被浏览器或操作系统信任&#xff0c;也会导致SSL握手失败…...

K8S客户端一 Rancher的安装

一 安装方式一 通过官网方式安装&#xff1a;官网 sudo docker run --privileged -d --restartunless-stopped -p 80:80 -p 443:443 rancher/rancher:stable访问服务器地址即可&#xff1a;http://192.168.52.128 修改语言 第一次安装会生成密码&#xff0c;查看密码步骤如下…...

websocket与node.js实现

什么是 websocket&#xff1f; websoket 是一种网络通信协议&#xff0c;基于 tcp 连接的全双工通信协议&#xff08;客户端和服务器可以同时收发信息&#xff09;&#xff0c;值得注意的是他不基于 http 协议&#xff0c;websocket 只有在建立连接的时候使用到 http 协议进行…...

postpresql 查询某张表的字段名和字段类型

postpresql 查询某张表的字段名和字段类型 工作中第一次接触postpresql&#xff0c;接触到这么个需求&#xff0c;只是对sql有点了解&#xff0c;于是就网上查阅资料。得知通过系统表可以查询&#xff0c;设计到几张系统表&#xff1a;pg_class、pg_attrubute、information_sc…...

jetson NX部署Yolov8

一,事情起因,由于需要对无人机机载识别算法进行更新,所以需要对yolov8算法进行部署到边缘端。 二,环境安装 安装虚拟环境管理工具,这个根据个人喜好。 我们需要选择能够在ARM架构上运行的conda,这里我们选择conda-forge 下载地址 安装即可 剩下的就是和conda 创建虚拟…...

【论文阅读笔记】Emu Edit: Precise Image Editing via Recognition and Generation Tasks

【论文阅读笔记】Emu Edit: Precise Image Editing via Recognition and Generation Tasks 论文阅读笔记论文信息摘要背景方法结果额外 关键发现作者动机相关工作1. 使用输入和编辑图像的对齐和详细描述来执行特定的编辑2. 另一类图像编辑模型采用输入掩码作为附加输入 。3. 为…...

python:列表的拷贝详解

python&#xff1a;列表的拷贝详解 文章目录 python&#xff1a;列表的拷贝详解方法1&#xff1a;直接赋值&#xff08;&#xff09;方法2&#xff1a;浅拷贝&#xff08;.copy方法&#xff09;格式原理注意 方法3&#xff1a;深拷贝&#xff08;.deepcopy方法&#xff09;格式…...

zip4j压缩使用总结

一、引入依赖 <dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>1.3.1</version></dependency>二、使用添加文件&#xff08;addFiles&#xff09;的方式生成压缩包 /*** Author wan…...

【第一部分:概述】ARM Realm Management Monitor specification

目录 概述机密计算系统软件组成MonitorRealmRealm Management Monitor (RMM)Virtual Machine (VM)HypervisorSecure Partition Manager (SPM)Trusted OS (TOS)Trusted Application (TA) Realm Management Monitor 参考文献 概述 RMM是一个软件组件&#xff0c;它构成了实现ARM…...

切换服务器上自己用户目录下的 conda 环境和一个外部的 Conda 环境

如果我们有自己的 Miniconda 安装和一个外部的 Conda 环境&#xff08;比如一个全局安装的 Anaconda&#xff09;&#xff0c;我们可以通过修改 shell 环境来切换使用它们。这通常涉及到更改 PATH 环境变量&#xff0c;以便指向你想要使用的 Conda 安装的可执行文件&#xff1a…...

移动端的自动化基于类实现启动一次应用跑全部用例

1.unittest框架 class TestStringMethods(unittest.TestCase): def setUp(self) -> None: # 每一条测试用例开始前执行 print("setup") def tearDown(self) -> None: # 每一条测试用例结束后执行 print("teardown") …...

Python与设计模式--抽象工厂模式

Python与设计模式–抽象工厂模式 一、快餐点餐系统 想必大家一定见过类似于麦当劳自助点餐台一类的点餐系统吧。在一个大的触摸显示屏上&#xff0c;有三类可以选择的上餐品&#xff1a;汉堡等主餐、小食、饮料。当我们选择好自己需要的食物&#xff0c;支付完成后&#xff0…...

JSP:MVC

一个好的Web应用&#xff1a; 功能完善、易于实现和维护易于扩展等的体系结构 一个Web应用通常分为两个部分&#xff1a; 1. 由界面设计人员完成的表示层&#xff08;主要做网页界面设计&#xff09; 2. 由程序设计人员实现的行为层&#xff08;主要完成本Web应用的各种功能…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...