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

Spark 源码 | 脚本分析总结

前言

最初是想学习一下Spark提交流程的源码,比如 Spark On Yarn 、Standalone。之前只是通过网上总结的文章大概了解整体的提交流程,但是每个文章描述的又不太一样,弄不清楚到底哪个说的准确,比如Client 和 CLuster 模式的区别,Driver到底是干啥的,是如何定义的,为了彻底弄清楚这些疑问,所以决定学习一下相关的源码。因为不管是服务启动还是应用程序启动,都是通过脚本提交的,所以我们先从分析脚本开始。

版本

Spark 3.2.3

Spark 脚本

先看一下Spark 主要的脚本有哪些:spark-submit、spark-sql、spark-shell、spark-class、start-all.sh、stop-all.sh、start-master.sh、start-workers.sh 等。

spark-sql

if [ -z "${SPARK_HOME}" ]; thensource "$(dirname "$0")"/find-spark-home
fiexport _SPARK_CMD_USAGE="Usage: ./bin/spark-sql [options] [cli option]"
exec "${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver "$@"

通过 spark-submit 提交类 org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver

spark-shell

# Shell script for starting the Spark Shell REPLcygwin=false
case "$(uname)" inCYGWIN*) cygwin=true;;
esac# Enter posix mode for bash
set -o posixif [ -z "${SPARK_HOME}" ]; thensource "$(dirname "$0")"/find-spark-home
fiexport _SPARK_CMD_USAGE="Usage: ./bin/spark-shell [options]Scala REPL options:-I <file>                   preload <file>, enforcing line-by-line interpretation"# SPARK-4161: scala does not assume use of the java classpath,
# so we need to add the "-Dscala.usejavacp=true" flag manually. We
# do this specifically for the Spark shell because the scala REPL
# has its own class loader, and any additional classpath specified
# through spark.driver.extraClassPath is not automatically propagated.
SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Dscala.usejavacp=true"function main() {if $cygwin; then# Workaround for issue involving JLine and Cygwin# (see http://sourceforge.net/p/jline/bugs/40/).# If you're using the Mintty terminal emulator in Cygwin, may need to set the# "Backspace sends ^H" setting in "Keys" section of the Mintty options# (see https://github.com/sbt/sbt/issues/562).stty -icanon min 1 -echo > /dev/null 2>&1export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Djline.terminal=unix""${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"stty icanon echo > /dev/null 2>&1elseexport SPARK_SUBMIT_OPTS"${SPARK_HOME}"/bin/spark-submit --class org.apache.spark.repl.Main --name "Spark shell" "$@"fi
}# Copy restore-TTY-on-exit functions from Scala script so spark-shell exits properly even in
# binary distribution of Spark where Scala is not installed
exit_status=127
saved_stty=""# restore stty settings (echo in particular)
function restoreSttySettings() {stty $saved_sttysaved_stty=""
}function onExit() {if [[ "$saved_stty" != "" ]]; thenrestoreSttySettingsfiexit $exit_status
}# to reenable echo if we are interrupted before completing.
trap onExit INT# save terminal settings
saved_stty=$(stty -g 2>/dev/null)
# clear on error so we don't later try to restore them
if [[ ! $? ]]; thensaved_stty=""
fimain "$@"# record the exit status lest it be overwritten:
# then reenable echo and propagate the code.
exit_status=$?
onExit

这里的主要逻辑也是用 spark-submit 提交类 org.apache.spark.repl.Main

spark-submit

根据上面的分析:spark-sql 和 spark-shell 两个交互式命令行脚本都是通过 spark-submit --class ClassName 来实现的。

if [ -z "${SPARK_HOME}" ]; thensource "$(dirname "$0")"/find-spark-home
fi# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

逻辑比较清晰:通过 spark-class 提交 org.apache.spark.deploy.SparkSubmit
具体到 spark-sql 和 spark-shell 分别为:

/opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.SparkSubmit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver
/opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name Spark shell

start-all.sh

功能:启动 standalone 所有服务。相关配置可参考 Spark Standalone 集群配置

# Start all spark daemons.
# Starts the master on this node.
# Starts a worker on each node specified in conf/workersif [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi# Load the Spark configuration
. "${SPARK_HOME}/sbin/spark-config.sh"# Start Master
"${SPARK_HOME}/sbin"/start-master.sh# Start Workers
"${SPARK_HOME}/sbin"/start-workers.sh

主要逻辑:start-master.sh 启动 master、start-workers.sh 启动所有worker

start-master.sh

# Starts the master on the machine this script is executed on.if [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi# NOTE: This exact class name is matched downstream by SparkSubmit.
# Any changes need to be reflected there.
CLASS="org.apache.spark.deploy.master.Master"if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; thenecho "Usage: ./sbin/start-master.sh [options]"pattern="Usage:"pattern+="\|Using Spark's default log4j profile:"pattern+="\|Started daemon with process name"pattern+="\|Registered signal handler for""${SPARK_HOME}"/bin/spark-class $CLASS --help 2>&1 | grep -v "$pattern" 1>&2exit 1
fiORIGINAL_ARGS="$@". "${SPARK_HOME}/sbin/spark-config.sh". "${SPARK_HOME}/bin/load-spark-env.sh"if [ "$SPARK_MASTER_PORT" = "" ]; thenSPARK_MASTER_PORT=7077
fiif [ "$SPARK_MASTER_HOST" = "" ]; thencase `uname` in(SunOS)SPARK_MASTER_HOST="`/usr/sbin/check-hostname | awk '{print $NF}'`";;(*)SPARK_MASTER_HOST="`hostname -f`";;esac
fiif [ "$SPARK_MASTER_WEBUI_PORT" = "" ]; thenSPARK_MASTER_WEBUI_PORT=8080
fi"${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS 1 \--host $SPARK_MASTER_HOST --port $SPARK_MASTER_PORT --webui-port $SPARK_MASTER_WEBUI_PORT \$ORIGINAL_ARGS

主要逻辑:由 spark-daemon.sh 启动类 org.apache.spark.deploy.master.Master

具体的参数:/opt/dkl/spark-3.2.3-bin-hadoop3.2/sbin/spark-daemon.sh start org.apache.spark.deploy.master.Master 1 --host indata-10-110-8-199.indata.com --port 7077 --webui-port 8080

start-workers.sh

if [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi. "${SPARK_HOME}/sbin/spark-config.sh"
. "${SPARK_HOME}/bin/load-spark-env.sh"# Find the port number for the master
if [ "$SPARK_MASTER_PORT" = "" ]; thenSPARK_MASTER_PORT=7077
fiif [ "$SPARK_MASTER_HOST" = "" ]; thencase `uname` in(SunOS)SPARK_MASTER_HOST="`/usr/sbin/check-hostname | awk '{print $NF}'`";;(*)SPARK_MASTER_HOST="`hostname -f`";;esac
fi# Launch the workers
"${SPARK_HOME}/sbin/workers.sh" cd "${SPARK_HOME}" \; "${SPARK_HOME}/sbin/start-worker.sh" "spark://$SPARK_MASTER_HOST:$SPARK_MASTER_PORT"

配置host和端口,然后调用 workers.sh 参数是 cd "${SPARK_HOME}" ; "${SPARK_HOME}/sbin/start-worker.sh" “spark://$SPARK_MASTER_HOST:$SPARK_MASTER_PORT
具体的参数:cd /opt/dkl/spark-3.2.3-bin-hadoop3.2 ; /opt/dkl/spark-3.2.3-bin-hadoop3.2/sbin/start-worker.sh spark://indata-10-110-8-199.indata.com:7077

workers.sh

# Run a shell command on all worker hosts.
#
# Environment Variables
#
#   SPARK_WORKERS    File naming remote hosts.
#     Default is ${SPARK_CONF_DIR}/workers.
#   SPARK_CONF_DIR  Alternate conf dir. Default is ${SPARK_HOME}/conf.
#   SPARK_WORKER_SLEEP Seconds to sleep between spawning remote commands.
#   SPARK_SSH_OPTS Options passed to ssh when running remote commands.
##usage="Usage: workers.sh [--config <conf-dir>] command..."# if no args specified, show usage
if [ $# -le 0 ]; thenecho $usageexit 1
fiif [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi. "${SPARK_HOME}/sbin/spark-config.sh"# If the workers file is specified in the command line,
# then it takes precedence over the definition in
# spark-env.sh. Save it here.
if [ -f "$SPARK_WORKERS" ]; thenHOSTLIST=`cat "$SPARK_WORKERS"`
fi
if [ -f "$SPARK_SLAVES" ]; then>&2 echo "SPARK_SLAVES is deprecated, use SPARK_WORKERS"HOSTLIST=`cat "$SPARK_SLAVES"`
fi# Check if --config is passed as an argument. It is an optional parameter.
# Exit if the argument is not a directory.
if [ "$1" == "--config" ]
thenshiftconf_dir="$1"if [ ! -d "$conf_dir" ]thenecho "ERROR : $conf_dir is not a directory"echo $usageexit 1elseexport SPARK_CONF_DIR="$conf_dir"fishift
fi. "${SPARK_HOME}/bin/load-spark-env.sh"if [ "$HOSTLIST" = "" ]; thenif [ "$SPARK_SLAVES" = "" ] && [ "$SPARK_WORKERS" = "" ]; thenif [ -f "${SPARK_CONF_DIR}/workers" ]; thenHOSTLIST=`cat "${SPARK_CONF_DIR}/workers"`elif [ -f "${SPARK_CONF_DIR}/slaves" ]; thenHOSTLIST=`cat "${SPARK_CONF_DIR}/slaves"`elseHOSTLIST=localhostfielseif [ -f "$SPARK_WORKERS" ]; thenHOSTLIST=`cat "$SPARK_WORKERS"`fiif [ -f "$SPARK_SLAVES" ]; then>&2 echo "SPARK_SLAVES is deprecated, use SPARK_WORKERS"HOSTLIST=`cat "$SPARK_SLAVES"`fifi
fi# By default disable strict host key checking
if [ "$SPARK_SSH_OPTS" = "" ]; thenSPARK_SSH_OPTS="-o StrictHostKeyChecking=no"
fi
# 这里通过sed 将host # 后面的删除
# 遍历 HOSTLIST ,ssh 到每个host节点,执行 start-workers.sh 中的参数
# 备注:$@:传递给脚本或函数的所有参数
for host in `echo "$HOSTLIST"|sed  "s/#.*$//;/^$/d"`; doif [ -n "${SPARK_SSH_FOREGROUND}" ]; thenssh $SPARK_SSH_OPTS "$host" $"${@// /\\ }" \2>&1 | sed "s/^/$host: /"elsessh $SPARK_SSH_OPTS "$host" $"${@// /\\ }" \2>&1 | sed "s/^/$host: /" &fiif [ "$SPARK_WORKER_SLEEP" != "" ]; thensleep $SPARK_WORKER_SLEEPfiif [ "$SPARK_SLAVE_SLEEP" != "" ]; then>&2 echo "SPARK_SLAVE_SLEEP is deprecated, use SPARK_WORKER_SLEEP"sleep $SPARK_SLAVE_SLEEPfi
donewait

主要逻辑:

  1. 先获取 HOSTLIST,优先级 $SPARK_WORKERS$SPARK_SLAVES${SPARK_CONF_DIR}/workers、${SPARK_CONF_DIR}/slaves,一般我们在 conf/workers (Spark3 默认) 或者 conf/slaves (Spark2 默认) 里配置 worker的 ip 或者hostname,如果没有配置,则默认 localhost
  2. 获取 SPARK_SSH_OPTS ,默认 “-o StrictHostKeyChecking=no” ,如果有特殊需求,如端口号不是默认的 22,则可以在 spark-env.sh 中添加 export SPARK_SSH_OPTS=“-p 6233 -o StrictHostKeyChecking=no”
  3. 遍历 HOSTLIST , ssh 到每个host节点,执行上面 start-workers.sh 中的参数 cd “${SPARK_HOME}” ; “${SPARK_HOME}/sbin/start-worker.sh” “spark://$SPARK_MASTER_HOST:$SPARK_MASTER_PORT”。备注:$@:传递给脚本或函数的所有参数

start-worker.sh

# Starts a worker on the machine this script is executed on.
#
# Environment Variables
#
#   SPARK_WORKER_INSTANCES  The number of worker instances to run on this
#                           worker.  Default is 1. Note it has been deprecate since Spark 3.0.
#   SPARK_WORKER_PORT       The base port number for the first worker. If set,
#                           subsequent workers will increment this number.  If
#                           unset, Spark will find a valid port number, but
#                           with no guarantee of a predictable pattern.
#   SPARK_WORKER_WEBUI_PORT The base port for the web interface of the first
#                           worker.  Subsequent workers will increment this
#                           number.  Default is 8081.if [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi# NOTE: This exact class name is matched downstream by SparkSubmit.
# Any changes need to be reflected there.
CLASS="org.apache.spark.deploy.worker.Worker"if [[ $# -lt 1 ]] || [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; thenecho "Usage: ./sbin/start-worker.sh <master> [options]"pattern="Usage:"pattern+="\|Using Spark's default log4j profile:"pattern+="\|Started daemon with process name"pattern+="\|Registered signal handler for""${SPARK_HOME}"/bin/spark-class $CLASS --help 2>&1 | grep -v "$pattern" 1>&2exit 1
fi. "${SPARK_HOME}/sbin/spark-config.sh". "${SPARK_HOME}/bin/load-spark-env.sh"# First argument should be the master; we need to store it aside because we may
# need to insert arguments between it and the other arguments
MASTER=$1
shift# Determine desired worker port
if [ "$SPARK_WORKER_WEBUI_PORT" = "" ]; thenSPARK_WORKER_WEBUI_PORT=8081
fi# Start up the appropriate number of workers on this machine.
# quick local function to start a worker
function start_instance {WORKER_NUM=$1shiftif [ "$SPARK_WORKER_PORT" = "" ]; thenPORT_FLAG=PORT_NUM=elsePORT_FLAG="--port"PORT_NUM=$(( $SPARK_WORKER_PORT + $WORKER_NUM - 1 ))fiWEBUI_PORT=$(( $SPARK_WORKER_WEBUI_PORT + $WORKER_NUM - 1 ))"${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS $WORKER_NUM \--webui-port "$WEBUI_PORT" $PORT_FLAG $PORT_NUM $MASTER "$@"
}if [ "$SPARK_WORKER_INSTANCES" = "" ]; thenstart_instance 1 "$@"
elsefor ((i=0; i<$SPARK_WORKER_INSTANCES; i++)); dostart_instance $(( 1 + $i )) "$@"done
fi

主要逻辑:

  1. 获取 MASTER ,这里为 spark://indata-10-110-8-199.indata.com:7077
  2. 判断 SPARK_WORKER_INSTANCES 是否为空,默认为空,也就是默认一个 Worker 实例
  3. 调用 start_instance 1 “$@” ,主要逻辑计算每个示例的 PORT_NUM 和 WEBUI_PORT ,最后执行 “${SPARK_HOME}/sbin”/spark-daemon.sh start $CLASS $WORKER_NUM --webui-port “$WEBUI_PORT$PORT_FLAG $PORT_NUM $MASTER$@

具体的参数:/opt/dkl/spark-3.2.3-bin-hadoop3.2/sbin/spark-daemon.sh start org.apache.spark.deploy.worker.Worker 1 --webui-port 8081 spark://indata-10-110-8-199.indata.com:7077

spark-daemon.sh

根据上面的分析:master 和 worker 都是通过 spark-daemon.sh 来启动的。

# Runs a Spark command as a daemon.
#
# Environment Variables
#
#   SPARK_CONF_DIR  Alternate conf dir. Default is ${SPARK_HOME}/conf.
#   SPARK_LOG_DIR   Where log files are stored. ${SPARK_HOME}/logs by default.
#   SPARK_LOG_MAX_FILES Max log files of Spark daemons can rotate to. Default is 5.
#   SPARK_MASTER    host:path where spark code should be rsync'd from
#   SPARK_PID_DIR   The pid files are stored. /tmp by default.
#   SPARK_IDENT_STRING   A string representing this instance of spark. $USER by default
#   SPARK_NICENESS The scheduling priority for daemons. Defaults to 0.
#   SPARK_NO_DAEMONIZE   If set, will run the proposed command in the foreground. It will not output a PID file.
##usage="Usage: spark-daemon.sh [--config <conf-dir>] (start|stop|submit|status) <spark-command> <spark-instance-number> <args...>"# if no args specified, show usage
if [ $# -le 1 ]; thenecho $usageexit 1
fiif [ -z "${SPARK_HOME}" ]; thenexport SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi. "${SPARK_HOME}/sbin/spark-config.sh"# get arguments# Check if --config is passed as an argument. It is an optional parameter.
# Exit if the argument is not a directory.
# 判断有没有参数 --config ,如果有,则配置 SPARK_CONF_DIR 等于参数值,默认 ${SPARK_HOME}/conf
if [ "$1" == "--config" ]
thenshiftconf_dir="$1"if [ ! -d "$conf_dir" ]thenecho "ERROR : $conf_dir is not a directory"echo $usageexit 1elseexport SPARK_CONF_DIR="$conf_dir"fishift
fi
# 获取 option ,可选值 start|stop|submit|status ,start-master 和 start-worker 对应的都为 start
option=$1
shift
# 获取 command , 这里的值对应具体的 class , start-master 对应 org.apache.spark.deploy.master.Master
# start-worker 对应  org.apache.spark.deploy.worker.Worker
command=$1
shift
# 获取 instance , 这里的值均为 1 , 注意这里的 instance 是前面传过来的参数,实例数,为了给后面的 log、pid用。
instance=$1
shiftspark_rotate_log ()
{log=$1;if [[ -z ${SPARK_LOG_MAX_FILES} ]]; thennum=5elif [[ ${SPARK_LOG_MAX_FILES} -gt 0 ]]; thennum=${SPARK_LOG_MAX_FILES}elseecho "Error: SPARK_LOG_MAX_FILES must be a positive number, but got ${SPARK_LOG_MAX_FILES}"exit -1fiif [ -f "$log" ]; then # rotate logswhile [ $num -gt 1 ]; doprev=`expr $num - 1`[ -f "$log.$prev" ] && mv "$log.$prev" "$log.$num"num=$prevdonemv "$log" "$log.$num";fi
}. "${SPARK_HOME}/bin/load-spark-env.sh"if [ "$SPARK_IDENT_STRING" = "" ]; thenexport SPARK_IDENT_STRING="$USER"
fiexport SPARK_PRINT_LAUNCH_COMMAND="1"# get log directory
if [ "$SPARK_LOG_DIR" = "" ]; thenexport SPARK_LOG_DIR="${SPARK_HOME}/logs"
fi
mkdir -p "$SPARK_LOG_DIR"
touch "$SPARK_LOG_DIR"/.spark_test > /dev/null 2>&1
TEST_LOG_DIR=$?
if [ "${TEST_LOG_DIR}" = "0" ]; thenrm -f "$SPARK_LOG_DIR"/.spark_test
elsechown "$SPARK_IDENT_STRING" "$SPARK_LOG_DIR"
fiif [ "$SPARK_PID_DIR" = "" ]; thenSPARK_PID_DIR=/tmp
fi# some variables
# 配置 log、pid 的文件路径和文件名 。log 默认路径 ${SPARK_HOME}/logs , pid 默认路径 /tmp
log="$SPARK_LOG_DIR/spark-$SPARK_IDENT_STRING-$command-$instance-$HOSTNAME.out"
pid="$SPARK_PID_DIR/spark-$SPARK_IDENT_STRING-$command-$instance.pid"# Set default scheduling priority
# 设置 SPARK_NICENESS :守护进程的调度优先级,如果 SPARK_NICENESS 为空,则设置默认值为0
# 注意这里的 SPARK_NICENESS 与前面的 instance 不同
if [ "$SPARK_NICENESS" = "" ]; thenexport SPARK_NICENESS=0
fiexecute_command() {# -z 判断 ${SPARK_NO_DAEMONIZE+set} 是否为空,这里为空if [ -z ${SPARK_NO_DAEMONIZE+set} ]; then# 这里主要逻辑是执行 $@ 并将日志输出到对应的日志文件中, $@ : 所有脚本参数的内容# 实际是通过 nice -n 0 设置进程优先级,然后通过 spark-class 启动对应的 Master 和 Worker 类nohup -- "$@" >> $log 2>&1 < /dev/null &# 将上面返回的进程号赋给 newpid($! :Shell最后运行的后台Process的PID)newpid="$!"# 然后将 newpid 写到对应的 pid 文件中。echo "$newpid" > "$pid"# Poll for up to 5 seconds for the java process to start# for 循环 1到10,轮询最多5秒,以启动java进程for i in {1..10}do# 每次判断 newpid 对应的java 进程是否启动成功if [[ $(ps -p "$newpid" -o comm=) =~ "java" ]]; then# 如果启动成功则终止循环breakfi# 否则sleep 0.5 ,继续下次循环sleep 0.5done# 启动成功后,sleep 2秒sleep 2# Check if the process has died; in that case we'll tail the log so the user can see# 判断对应的java进程是否还存在,如果不存在,则提示启动失败,并打印对应的日志if [[ ! $(ps -p "$newpid" -o comm=) =~ "java" ]]; thenecho "failed to launch: $@"tail -10 "$log" | sed 's/^/  /'echo "full log in $log"fielse"$@"fi
}run_command() {# 先获取 mode ,这里 mode 为 class ,mode="$1"shift# 创建 pid 文件夹mkdir -p "$SPARK_PID_DIR"# 判断 pid 文件是否存在if [ -f "$pid" ]; then# 如果存在,则获取pid值TARGET_ID="$(cat "$pid")"# 判断是否存在对应的java进程if [[ $(ps -p "$TARGET_ID" -o comm=) =~ "java" ]]; then# 如果存在,则提示服务已经运行,先停止它echo "$command running as process $TARGET_ID.  Stop it first."exit 1fifiif [ "$SPARK_MASTER" != "" ]; thenecho rsync from "$SPARK_MASTER"rsync -a -e ssh --delete --exclude=.svn --exclude='logs/*' --exclude='contrib/hod/logs/*' "$SPARK_MASTER/" "${SPARK_HOME}"fispark_rotate_log "$log"echo "starting $command, logging to $log"# 匹配 mode 值case "$mode" in# 如果是class(class)#  nice -n "$SPARK_NICENESS" 是设置进程优先级,范围通常从 -20(最高优先级)到 +19(最低优先级)。默认的 nice 值是 0。#  可参考 https://www.cnblogs.com/yinguojin/p/18600924execute_command nice -n "$SPARK_NICENESS" "${SPARK_HOME}"/bin/spark-class "$command" "$@";;(submit)execute_command nice -n "$SPARK_NICENESS" bash "${SPARK_HOME}"/bin/spark-submit --class "$command" "$@";;(*)echo "unknown mode: $mode"exit 1;;esac}
# 匹配 option ,
case $option in# 如果为 submit ,执行 run_command submit "$@"(submit)run_command submit "$@";;# 如果为 start, 执行 run_command class "$@"(start)run_command class "$@";;(stop)if [ -f $pid ]; thenTARGET_ID="$(cat "$pid")"if [[ $(ps -p "$TARGET_ID" -o comm=) =~ "java" ]]; thenecho "stopping $command"kill "$TARGET_ID" && rm -f "$pid"elseecho "no $command to stop"fielseecho "no $command to stop"fi;;(decommission)if [ -f $pid ]; thenTARGET_ID="$(cat "$pid")"if [[ $(ps -p "$TARGET_ID" -o comm=) =~ "java" ]]; thenecho "decommissioning $command"kill -s SIGPWR "$TARGET_ID"elseecho "no $command to decommission"fielseecho "no $command to decommission"fi;;(status)if [ -f $pid ]; thenTARGET_ID="$(cat "$pid")"if [[ $(ps -p "$TARGET_ID" -o comm=) =~ "java" ]]; thenecho $command is running.exit 0elseecho $pid file is present but $command not runningexit 1fielseecho $command not running.exit 2fi;;(*)echo $usageexit 1;;esac

主要逻辑:

  • 判断有没有参数 --config ,如果有,则配置 SPARK_CONF_DIR 等于参数值,默认 ${SPARK_HOME}/conf
  • 获取 option ,可选值 start|stop|submit|status ,start-master 和 start-worker 对应的都为 start
  • 获取 command , 这里的值对应具体的 class , start-master 对应 org.apache.spark.deploy.master.Master ,start-worker 对应 org.apache.spark.deploy.worker.Worker
  • 获取 instance , 这里的值均为 1 , 实例数,为了给后面的 log、pid用。
  • 配置 log、pid 的文件路径和文件名 。log 默认路径 ${SPARK_HOME}/logs , pid 默认路径 /tmp
  • 设置 SPARK_NICENESS :守护进程的调度优先级,如果 SPARK_NICENESS 为空,则设置默认值为0 。
  • 匹配 option ,如果为 submit ,执行 run_command submit “$@” ;如果为 start, 执行 run_command class “$@” ; …… ,这里只看 start
  • run_command 逻辑:
    • 先获取 mode ,这里 mode 为 class ,
    • 创建 pid 文件夹, 判断 pid 文件是否存在,如果存在,则获取pid值并判断是否存在对应的java进程,如果存在,则提示服务已经运行,先停止它
    • 如果不存在,则匹配 mode 值,如果是class ,则执行 execute_command nice -n “$SPARK_NICENESS” “${SPARK_HOME}”/bin/spark-class “$command” “$@
    • execute_command 是这里自定义的函数, nice -n “$SPARK_NICENESS” 是设置进程优先级,范围通常从 -20(最高优先级)到 +19(最低优先级)。默认的 nice 值是 0。可参考 https://www.cnblogs.com/yinguojin/p/18600924
  • execute_command 逻辑 :
    • -z 判断 ${SPARK_NO_DAEMONIZE+set} 是否为空,这里为空
    • 执行 nohup – “$@” >> $log 2>&1 < /dev/null & ,这里主要逻辑是执行 $@ 并将日志输出到对应的日志文件中, $@ : 所有脚本参数的内容, 这里为 : nice -n 0 /opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.master.Master --host indata-10-110-8-199.indata.com --port 7077 --webui-port 8080 和 nice -n 0 /opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://indata-10-110-8-199.indata.com:7077
      实际是通过 nice -n 0 设置进程优先级,然后通过 spark-class 启动对应的 Master 和 Worker 类
    • 将上面返回的进程号赋给 newpid ,然后将 newpid 写到对应的 pid 文件中。($! :Shell最后运行的后台Process的PID)
    • for 循环 1到10,每次判断 newpid 对应的java 进程是否启动成功,如果启动成功则终止循环,否则sleep 0.5 ,继续下次循环,也就是轮询最多5秒,以启动java进程
    • 启动成功后,sleep 2秒,然后判断对应的java进程是否还存在,如果不存在,则提示启动失败,并打印对应的日志

spark-class

通过上面的分析可知:spark-sql、spark-shell、Master 和 Worker 的启动最终都是通过 spark-class 启动的,具体分别为:

/opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.SparkSubmit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver
/opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name Spark shell
nice -n 0 /opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.master.Master --host indata-10-110-8-199.indata.com --port 7077 --webui-port 8080
nice -n 0 /opt/dkl/spark-3.2.3-bin-hadoop3.2/bin/spark-class org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://indata-10-110-8-199.indata.com:7077
if [ -z "${SPARK_HOME}" ]; thensource "$(dirname "$0")"/find-spark-home
fi. "${SPARK_HOME}"/bin/load-spark-env.sh# Find the java binary
# 找Java环境变量,如果有,则拼接 ${JAVA_HOME}/bin/java
if [ -n "${JAVA_HOME}" ]; thenRUNNER="${JAVA_HOME}/bin/java"
elseif [ "$(command -v java)" ]; thenRUNNER="java"elseecho "JAVA_HOME is not set" >&2exit 1fi
fi# Find Spark jars.
# 找 Spark jars
if [ -d "${SPARK_HOME}/jars" ]; thenSPARK_JARS_DIR="${SPARK_HOME}/jars"
elseSPARK_JARS_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION/jars"
fiif [ ! -d "$SPARK_JARS_DIR" ] && [ -z "$SPARK_TESTING$SPARK_SQL_TESTING" ]; thenecho "Failed to find Spark jars directory ($SPARK_JARS_DIR)." 1>&2echo "You need to build Spark with the target \"package\" before running this program." 1>&2exit 1
elseLAUNCH_CLASSPATH="$SPARK_JARS_DIR/*"
fi# Add the launcher build dir to the classpath if requested.
if [ -n "$SPARK_PREPEND_CLASSES" ]; thenLAUNCH_CLASSPATH="${SPARK_HOME}/launcher/target/scala-$SPARK_SCALA_VERSION/classes:$LAUNCH_CLASSPATH"
fi# For tests
if [[ -n "$SPARK_TESTING" ]]; thenunset YARN_CONF_DIRunset HADOOP_CONF_DIR
fi# The launcher library will print arguments separated by a NULL character, to allow arguments with
# characters that would be otherwise interpreted by the shell. Read that in a while loop, populating
# an array that will be used to exec the final command.
#
# The exit code of the launcher is appended to the output, so the parent shell removes it from the
# command array and checks the value to see if the launcher succeeded.
build_command() {# 通过java -cp 提交 org.apache.spark.launcher.Main "$@" , 该类会打印对应的命令# 具体打印 : 先打印'\0'并换行,然后打印拼接的命令,每个命令后跟'\0',不换行。"$RUNNER" -Xmx128m $SPARK_LAUNCHER_OPTS -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"# 打印上面的命令的退出状态码并后跟一个\0,没有换行printf "%d\0" $?
}# Turn off posix mode since it does not allow process substitution
set +o posix
# 定义CMD数组
CMD=()
# -d : 输入结束符,分隔符
DELIM=$'\n'
# CMD 开始标志,默认false
CMD_START_FLAG="false"
# -r : 反斜杠转义不会生效,意味着行末的’\’成为有效的字符,例如使 \n 成为有效字符而不是换行
# 执行 build_command "$@",并解析其输出结果
# Bash read 命令可以参考 https://blog.csdn.net/lingeio/article/details/96587362
# 先解析完第一行,然后将 CMD_START_FLAG 设置为 true,开始拼接 CMD ,后以 '' 为分隔符分割具体命令,放到CMD数组中。
while IFS= read -d "$DELIM" -r ARG; do# 当 CMD_START_FLAG = true , 拼接 cmd 命令if [ "$CMD_START_FLAG" == "true" ]; thenCMD+=("$ARG")else# 开始以 $'\n' 为结束符, ARG = $'\0'if [ "$ARG" == $'\0' ]; then# After NULL character is consumed, change the delimiter and consume command string.# 修改 DELIM='' ,并将CMD_START_FLAG设置为true,意味着开始拼接 cmd 命令DELIM=''CMD_START_FLAG="true"elif [ "$ARG" != "" ]; thenecho "$ARG"fifi
done < <(build_command "$@")
# CMD数组长度
COUNT=${#CMD[@]}
LAST=$((COUNT - 1))
# 获取CMD最后一个值作为 LAUNCHER_EXIT_CODE
LAUNCHER_EXIT_CODE=${CMD[$LAST]}# Certain JVM failures result in errors being printed to stdout (instead of stderr), which causes
# the code that parses the output of the launcher to get confused. In those cases, check if the
# exit code is an integer, and if it's not, handle it as a special error case.
# 检查 LAUNCHER_EXIT_CODE 是否正常,如果不正常,则进行相应处理并退出
if ! [[ $LAUNCHER_EXIT_CODE =~ ^[0-9]+$ ]]; thenecho "${CMD[@]}" | head -n-1 1>&2exit 1
fiif [ $LAUNCHER_EXIT_CODE != 0 ]; thenexit $LAUNCHER_EXIT_CODE
fi
# 代表从下标0开始往后取 LAST 个值
# 数组用法可参考 https://blog.csdn.net/trayvontang/article/details/143440654
CMD=("${CMD[@]:0:$LAST}")
# 如果正常,则执行  org.apache.spark.launcher.Main 返回的命令
exec "${CMD[@]}"

主要逻辑:

  • 找Java环境变量,如果有,则拼接 ${JAVA_HOME}/bin/java
  • 执行 build_command “$@” ,并打印输出结果。对应命令:“$RUNNER” -Xmx128m $SPARK_LAUNCHER_OPTS -cp “$LAUNCH_CLASSPATH” org.apache.spark.launcher.Main “$@” 具体为:
    • spark-sql 对应命令 :/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -Xmx128m -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* org.apache.spark.launcher.Main org.apache.spark.deploy.SparkSubmit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver
    • spark-shell 对应命令 :/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -Xmx128m -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* org.apache.spark.launcher.Main org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name Spark shell
    • start-master 对应命令 :/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -Xmx128m -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* org.apache.spark.launcher.Main org.apache.spark.deploy.master.Master --host indata-10-110-8-199.indata.com --port 7077 --webui-port 8080
    • start-worer 对应命令 : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -Xmx128m -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* org.apache.spark.launcher.Main org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://indata-10-110-8-199.indata.com:7077
    • org.apache.spark.launcher.Main 主要逻辑 : 根据传入的参数,拼接命令并打印
    System.out.println('\0');
    List<String> bashCmd = prepareBashCommand(cmd, env);
    for (String c : bashCmd) {System.out.print(c);System.out.print('\0');
    }
    
    最后打印代码如上,先打印’\0’并换行,然后打印拼接的命令,每个命令后跟’\0’,不换行。最后打印的命令分别为:
    • spark-sql :

      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java-cp/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/*-Xmx1gorg.apache.spark.deploy.SparkSubmit–classorg.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriverspark-internal
      一共两行,第一行是空字符串 \0’表示字符串结束符,第二行是具体拼接的命令,每一个命令后跟 ‘\0’ ,因为是空所以没有空格分隔开,第二行最后没有跟换行。后面的命令第一行一样,所以只记录第二行

    • spark-shell :
      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java-cp/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/*-Dscala.usejavacp=true-Xmx1gorg.apache.spark.deploy.SparkSubmit–classorg.apache.spark.repl.Main–nameSpark shellspark-shell

    • start-master :
      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java-cp/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/*-Xmx1gorg.apache.spark.deploy.master.Master–hostindata-10-110-8-199.indata.com–port7077–webui-port8080

    • start-worker :
      /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java-cp/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/*-Xmx1gorg.apache.spark.deploy.worker.Worker–webui-port8081spark://indata-10-110-8-199.indata.com:7077

    • build_command 最后执行 printf “%d\0” $? ,这和脚本含义是打印 $? 后面再跟 \0 ,$? 是一个特殊的变量,用于获取上一个命令的退出状态码

      • 0:命令成功执行
      • 0以外的数字:命令执行失败。
      • 1:通用错误(General error), 发生了一个通用的错误,但没有提供具体的错误信息。
      • 2:误用shell内置命令(Misuse of shell built-ins)
      • 126:命令不可执行(Command invoked cannot execute)
      • 127:未找到命令(Command not found)
        所以build_command最终输出:以spark-sql为例:
        /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java-cp/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/-Xmx1gorg.apache.spark.deploy.SparkSubmit–classorg.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriverspark-internal0
        \0 输出到日志文件中,以vi命令看,会显示^@,下面是在日志文件中vi查看的效果:
        ^@
        /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java^@-cp^@/opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/
        ^@-Xmx1g^@org.apache.spark.deploy.SparkSubmit^@–class^@org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver^@spark-internal^@0^@
        这样更能方便的理解 build_command 的输出是啥样的,方便后面的脚本分析,然后我们将 ^@ 换成空格:
        /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Xmx1g org.apache.spark.deploy.SparkSubmit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver spark-internal 0
    • 最后解释一下 printf “%d\0” $? %d 代表数字,该行脚本的含义是将上一个命令的退出状态码打印并后跟一个\0,该命令没有换行,另外测试发现 %d 如果后面没有跟具体数字则默认值为0,可以通过 printf “\0” > test.log , 然后 vi test.log 查看 \0 在文件中会显示 ^@ ,但是cat test.log 则会显示空字符串。

  • 后面 while IFS= read -d “$DELIM” -r ARG; 是通过 read 命令读取 build_command “$@” 输出的结果,Bash read 命令可以参考 https://blog.csdn.net/lingeio/article/details/96587362 。 主要逻辑是读取第2步中输出的结果,解析对应的命令并放到 CMD 数组中,首先解析完第一行,然后将 CMD_START_FLAG 设置为 true开始拼接 CMD ,后以 ‘’ 为分隔符分割具体命令,放到CMD数组中。
  • 组装好CMD数组后,先取CMD数组长度,获取CMD最后一个值作为 LAUNCHER_EXIT_CODE,即为在 build_command 中 执行 “$RUNNER” -Xmx128m $SPARK_LAUNCHER_OPTS -cp “$LAUNCH_CLASSPATH” org.apache.spark.launcher.Main “$@” 的命令退出状态码。
  • 检查 LAUNCHER_EXIT_CODE 是否正常,如果不正常,则进行相应处理并退出,如果正常,则执行 org.apache.spark.launcher.Main 返回的命令 。

小结

通过本文上面的简单分析可知,无论是 spark-sql 、 spark-shell 这种交互式命令行,还是 Master 和 Worker 等Stanalone服务的启动,最终都是通过 spark-class 启动的。而 spark-class 的逻辑则是先通过 java -cp 执行类 org.apache.spark.launcher.Main,然后将拼接好的启动命令打印输出,最终在 spark-class 中解析输出的命令并执行,最终也都是通过 java -cp 执行具体的类的,分别如下:

  • spark-sql : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Xmx1g org.apache.spark.deploy.SparkSubmit --class org.apache.spark.sql.hive.thriftserver.SparkSQLCLIDriver spark-internal
  • spark-shell : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Dscala.usejavacp=true -Xmx1g org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main --name Spark shell spark-shell
  • start-master : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Xmx1g org.apache.spark.deploy.master.Master --host indata-10-110-8-199.indata.com --port 7077 --webui-port 8080
  • start-worker : /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Xmx1g org.apache.spark.deploy.worker.Worker --webui-port 8081 spark://indata-10-110-8-199.indata.com:7077
    当然我们提交程序代码jar也是一样的,比如 spark-submit --master local --class org.apache.spark.examples.SparkPi examples/jars/spark-examples_2.12-3.2.3.jar , 对应到 spark-class 的提交命令为 :
    /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/bin/java -cp /opt/dkl/spark-3.2.3-bin-hadoop3.2/conf/:/opt/dkl/spark-3.2.3-bin-hadoop3.2/jars/* -Xmx1g org.apache.spark.deploy.SparkSubmit --master local --class org.apache.spark.examples.SparkPi examples/jars/spark-examples_2.12-3.2.3.jar

进一步总结发现,关于服务类的启动都是直接通过 java -cp 提交具体的类,其他的交互式命令行、jar 则是先通过 java -cp 提交 org.apache.spark.deploy.SparkSubmit ,最终具体执行的类则通过 --class 作为参数提交。那么下次我们先分析 org.apache.spark.deploy.SparkSubmit,看看最终真正的 class 是怎么提交运行的。

相关文章:

Spark 源码 | 脚本分析总结

前言 最初是想学习一下Spark提交流程的源码&#xff0c;比如 Spark On Yarn 、Standalone。之前只是通过网上总结的文章大概了解整体的提交流程&#xff0c;但是每个文章描述的又不太一样&#xff0c;弄不清楚到底哪个说的准确&#xff0c;比如Client 和 CLuster 模式的区别&a…...

2025.2.9 每日学习记录2:技术报告写了一半+一点点读后感

0.近期主任务线 1.完成小论文准备 目标是3月份完成实验点1的全部实验和论文。 2.准备教资笔试 打算留个十多天左右&#xff0c;一次性备考笔试的三个科目 1.实习申请技术准备&#xff1a;微调、Agent、RAG 1.今日完成任务 1.电子斗蛐蛐&#xff08;文本书写领域&am…...

6、使用one-api管理统一管理大模型,并开始使用本地大模型

文章目录 本节内容介绍集中接入&#xff1a;将大模型统一管理起来当使用了大模型代理大模型代理示例 开源模型&#xff1a;如何使用Hugging Face上的模型modelscope使用 pipeline 调用模型用底层实现调用模型流式输出 如何在项目中使用开源模型使用 LangChain使用集中接入开始使…...

DFS+回溯+剪枝(深度优先搜索)——搜索算法

DFS也就是深度优先搜索&#xff0c;比如二叉树的前&#xff0c;中&#xff0c;后序遍历都属于DFS。其本质是递归&#xff0c;要学好DFS首先需要掌握递归。接下来咱们就一起来学习DFS涉及的算法。 一、递归 1.什么是递归&#xff1f; 递归可以这样理解把它拆分出来&#xff0…...

【数据结构】_堆的实现

目录 1. 堆的实现 1.1 Heap.h 1.2 Heap.c 1.3 Test_Heap.c 专栏前文中&#xff0c;已经介绍了入堆及向上调整算法&#xff0c;出堆及向下调整算法&#xff0c;详情见下文&#xff1a; 【数据结构】_堆的结构及向上、向下调整算法-CSDN博客文章浏览阅读352次&#xff0c;点…...

读书笔记《左耳听风》

读书笔记《左耳听风》 从今年开始&#xff0c;打算给自己定一下在看完书后整理成博客的计划。以往很多看完的书仅仅停留在看完&#xff0c;再回顾的时候总感觉已经不甚清晰了&#xff0c;希望能坚持下去。 《左耳听风》是今年我看完的第一本书&#xff0c;内容针对的是程序员…...

Axure原型图怎么通过链接共享

一、进入Axure 二、点击共享 三、弹出下面弹框&#xff0c;点击发布就可以了 发布成功后&#xff0c;会展示链接&#xff0c;复制即可共享给他人 四、发布失败可能的原因 Axure未更新&#xff0c;首页菜单栏点击帮助选择Axure更新&#xff0c;完成更新重复以上步骤即可...

本地部署DeepSeek,并使用UI界面进行快速交互

一.需要本地部署的原因 1.我们在deepseek的官网界面进行交互时&#xff0c;经常会出现如下问题&#xff0c;不能正常交互&#xff0c;很是困扰&#xff1a; 2.本地部署的好处 就是能够很流畅的与deepseek进行交互&#xff1b;也有缺点&#xff0c;现在官网交互的版本更高一点…...

ESP32S3读取数字麦克风INMP441的音频数据

ESP32S3 与 INMP441 麦克风模块的集成通常涉及使用 I2S 接口进行数字音频数据的传输。INMP441 是一款高性能的数字麦克风&#xff0c;它通过 I2S 接口输出音频数据。在 Arduino 环境中&#xff0c;ESP32S3 的开发通常使用 ESP-IDF&#xff08;Espressif IoT Development Framew…...

移动(新)魔百盒刷机教程[M301A_YS]

刚刚成功刷了一个坏的魔百盒&#xff0c;简单记录一下。 刷电视盒子有两种&#xff1a;卡刷和线刷。 线刷 一、线刷准备 1.刷机工具 Amlogic USB Burning Tool 晶晨线刷烧录工具 2.固件 根据盒子的型号、代工等找到对应的固件 二、线刷步骤 电脑打开下好的 Amlogic US…...

15 大 AWS 服务

在不断发展的云计算世界中&#xff0c;Amazon Web Services (AWS) 已成为一股主导力量&#xff0c;提供许多服务以满足各种应用程序开发、部署和管理方面的需求。本文将探讨 15 项 AWS 服务。这些服务对于构建可扩展、可靠且高效的系统至关重要。 1.Amazon EC2&#xff08;弹性…...

【C++】命名空间

&#x1f31f; Hello&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; 目录 背景知识 命名空间(namespace) 为何引入namespace namespace的定义 namespace的使用 背景知识 C的起源要追溯到1979年&#xff0…...

项目实战(11)-双通道气体压力计V1.0

一. 产品简介&#xff1a; 1、项目背景是在实际应用中需要监控通道内气体的压力&#xff0c;压力计分为两个通道&#xff1b;通道一时实时监控&#xff1b;通道二是保压&#xff0c;设定保压值得上下限后通道内得气体压力值会一直保持在这个范围内。 二. 应用场景&#xff1a…...

python+unity落地方案实现AI 换脸融合

先上效果再说技术结论&#xff0c;使用的是自行搭建的AI人脸融合库&#xff0c;可以离线不受限制无限次生成&#xff0c;有需要的可以后台私信python ai换脸融合。 TODO 未来的方向&#xff1a;3D人脸融合和AI数据训练 这个技术使用的是openvcinsighface&#xff0c;openvc…...

开启对话式智能分析新纪元——Wyn商业智能 BI 携手Deepseek 驱动数据分析变革

2月18号&#xff0c;Wyn 商业智能 V8.0Update1 版本将重磅推出对话式智能分析&#xff0c;集成Deepseek R1大模型&#xff0c;通过AI技术的深度融合&#xff0c;致力于打造"会思考的BI系统"&#xff0c;让数据价值触手可及&#xff0c;助力企业实现从数据洞察到决策执…...

数据结构——【二叉树模版】

#思路 1、二叉树不同于数的构建&#xff0c;在树节点类中&#xff0c;有数据&#xff0c;左子结点&#xff0c;右子节点三个属性&#xff0c;在树类的构造函数中&#xff0c;添加了变量maxNodes&#xff0c;用于后续列表索引的判断 2.GetTreeNode()函数是常用方法&#xff0c;…...

DeepSeek之于心理学的一点思考

模型和硬件参数对应关系参考 模型参数规模 典型用途 CPU建议 GPU建议 最小内存建议 磁盘空间建议 适用场景 1.5b(15亿) 小型推理、轻量级任务 4核以上(Intel i5/AMD Ryzen5) 可选&#xff0c;入门级GPU(如NVIDIA GTX1650 4GB显存) 8GB 10GB以上SSD 小型NLP任务、文…...

mysql 存储过程和自定义函数 详解

首先创建存储过程或者自定义函数时&#xff0c;都要使用use database 切换到目标数据库&#xff0c;因为存储过程和自定义函数都是属于某个数据库的。 存储过程是一种预编译的 SQL 代码集合&#xff0c;封装在数据库对象中。以下是一些常见的存储过程的关键字&#xff1a; 存…...

数据结构:单链表

1.概念&#xff1a; 单链表&#xff08;Singly Linked List&#xff09;是一种常见的数据结构&#xff0c;它由一系列节点&#xff08;Node&#xff09;组成&#xff0c;每个节点包含两个部分&#xff1a; 数据域&#xff08;Data&#xff09;&#xff1a;存储节点的值或数据。…...

部署项目(ubantu服务器,配置jdk,启动项目,及测试)

目录 1、ubantu安装jdk 2、部署项目 ​ 解决 java -jar 报错&#xff1a;xxx.jar 中没有主清单属性 ​ 3、测试 4、查看系统部署的应用 1、ubantu安装jdk #压缩文件jdk文件&#xff1a;tar -czvf jdk17.tar.gz jdk17 #解压jdk文件&#xff1a;tar -xzvf jdk17.tar.gz 参…...

deepseek本地部署教程

第一步&#xff1a;进入Ollama官网 &#xff08;Download Ollama on macOS&#xff09;&#xff0c;下载ollama(注意需要Window10或更高的版本&#xff09;&#xff0c;安装&#xff08;OllamaSetup.exe&#xff09;&#xff0c;默认在c盘 第二步&#xff1a;点击Models,再点击…...

MySQL主从同步+binlog

一、简介 MySQL内建的复制功能是构建大型&#xff0c;高性能应用程序的基础 通过将MySQL的某一台主机&#xff08;master&#xff09;的数据复制到其他主机&#xff08;slaves&#xff09;上&#xff0c;并重新执行一遍来执行 复制过程中一台服务器充当主服务器&#xff0c;而…...

防火墙术语大全( Firewalld Glossary of Terms)

防火墙术语大全 防火墙作为网络安全中不可或缺的设备&#xff0c;在各种网络架构中扮演着至关重要的角色。无论是企业级防火墙、云防火墙还是家用路由器内置的防火墙&#xff0c;它们的工作原理和配置策略都离不开一系列专业术语的支撑。对于网络工程师来说&#xff0c;掌握这…...

LeetCode刷题---数组---697

数组的度 697. 数组的度 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定一个非空且只包含非负数的整数数组 nums&#xff0c;数组的 度 的定义是指数组里任一元素出现频数的最大值。 你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组&am…...

C语言基础08:运算符+流程控制总结

运算符 算术运算符 结果&#xff1a;数值 、-、*、\、%、&#xff08;正&#xff09;、-&#xff08;负&#xff09;、、-- i和i 相同点&#xff1a;i自身都会增1 不同点&#xff1a;它们运算的最终结果是不同的。i&#xff1a;先使用&#xff0c;后计算&#xff1b;i&am…...

[安装FlashAttention] CUDA版本 和 Nvidia驱动版本

nvidia-smi 查看driver api 的CUDA版本 听说这个是本机能装到的最高版本 那这样看来我最高能装到12.4。 nvcc -V 查看当前runtime api的CUDA版本 还是古老的11.5版本&#xff0c;没办法啊&#xff0c;FlashAttention老是说不支持&#xff1f; 安装Torch时选择的CUDA版本 p…...

Android图片加载框架Coil,Kotlin

Android图片加载框架Coil&#xff0c;Kotlin implementation("io.coil-kt:coil:1.4.0") import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import coil.Coil i…...

Win11下搭建Kafka环境

目录 一、环境准备 二、安装JDK 1、下载JDK 2、配置环境变量 3、验证 三、安装zookeeper 1、下载Zookeeper安装包 2、配置环境变量 3、修改配置文件zoo.cfg 4、启动Zookeeper服务 4.1 启动Zookeeper客户端验证 4.2 启动客户端 四、安装Kafka 1、下载Kafka安装包…...

从大规模恶意攻击 DeepSeek 事件看 AI 创新隐忧:安全可观测体系建设刻不容缓

作者&#xff1a;羿莉&#xff08;萧羿&#xff09; 全球出圈的中国大模型 DeepSeek 作为一款革命性的大型语言模型&#xff0c;以其卓越的自然语言处理能力和创新性成本控制引领行业前沿。该模型不仅在性能上媲美 OpenAI-o1&#xff0c;而且在推理模型的成本优化上实现了突破…...

模拟(典型算法思想)—— OJ例题算法解析思路

目录 一、1576. 替换所有的问号 - 力扣(LeetCode) 运行代码: 1. 输入和输出 2. 变量初始化 3. 遍历字符串 4. 替换逻辑 5. 返回结果 整体分析 1. 思路总结 2. 为什么要这样设计 3. 时间复杂度与空间复杂度 4. 边界情况 二、495. 提莫攻击 - 力扣(LeetCode) …...

pgsql最快的数据导入BeginBinaryImport

PostgreSQL 的 BeginBinaryImport 是 libpq&#xff08;PostgreSQL 的 C 语言客户端库&#xff09; 中的一个函数&#xff0c;用于高效实现二进制数据的大批量导入。以下是详细介绍及适用语言说明&#xff1a; BeginBinaryImport 的作用 功能 它是 PostgreSQL C 接口库&#xf…...

【进程与线程】如何编写一个守护进程

如何编写一个守护进程。我们首先需要理解守护进程是什么。守护进程是在后台运行的进程&#xff0c;通常没有控制终端&#xff0c;用于执行系统任务&#xff0c;比如服务器或者定时任务。 用户可能想创建一个长期运行的服务&#xff0c;比如Web服务器或者日志监控程序。 首先&a…...

Docker容器访问外网:启动时的网络参数配置指南

在启动Docker镜像时,可以通过设置网络参数来确保容器能够访问外网。以下是几种常见的方法: 1. 使用默认的bridge网络 Docker的默认网络模式是bridge,它会创建一个虚拟网桥,将容器连接到宿主机的网络上。在大多数情况下,使用默认的bridge网络配置即可使容器访问外网。 启动…...

大数据-259 离线数仓 - Griffin架构 修改配置 pom.xml sparkProperties 编译启动

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…...

DeepSeek神经网络:技术架构与实现原理探析

以下是一篇关于DeepSeek神经网络的研究及实现原理的综述性文章&#xff0c;结合其技术架构、训练范式及创新点展开分析&#xff1a; 1. 核心架构设计 DeepSeek的神经网络架构以**混合专家模型&#xff08;Mixture of Experts, MOE&#xff09;**为基础&#xff0c;结合轻量化…...

KTOR:高效的Linux横向移动与无文件落地HTTP服务扫描工具

地址:https://github.com/MartinxMax/KTOR 简介 KTOR 是一款专为 Linux 横向渗透设计的工具。通过该工具&#xff0c;您可以快速扫描内部 HTTP 服务&#xff0c;以便进一步进行网络渗透&#xff0c;且实现无文件落地扫描。 在CTF中通常需要利用本地其他端口HTTP服务或其他主…...

C++ Primer 类型转换

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

2025新的一年网络营销推广该怎么干?

2025年&#xff0c;全球网民数量预计突破60亿&#xff0c;但流量红利消退、用户注意力稀缺、技术迭代加速……企业网络营销正面临前所未有的“生存战”。如何在竞争中突围&#xff1f;小马识途营销机构基于十五年实战经验&#xff0c;总结出2025年企业必须抢占的五大核心战场&a…...

java实现Http请求方式的几种常见方式

背景 在实际开发过程中&#xff0c;我们经常需要调用对方提供的接口或测试自己写的接口是否合适。很多项目都会封装规定好本身项目的接口规范&#xff0c;所以大多数需要去调用对方提供的接口或第三方接口&#xff08;短信、天气等&#xff09;。若是普通java工程推荐使用OkHt…...

安卓开发,底部导航栏

1、创建导航栏图标 使用系统自带的矢量图库文件&#xff0c;鼠标右键点击res->New->Vector Asset 修改 Name , Clip art 和 Color 再创建一个 同样的方法再创建四个按钮 2、添加百分比布局依赖 app\build.gradle.kts 中添加百分比布局依赖&#xff0c;并点击Sync Now …...

Spring Boot中实现多租户架构

文章目录 Spring Boot中实现多租户架构多租户架构概述核心思想多租户的三种模式优势挑战租户识别机制1. 租户标识(Tenant Identifier)2. 常见的租户识别方式3. 实现租户识别的关键点4. 租户识别示例代码5. 租户识别机制的挑战数据库隔离的实现1. 数据库隔离的核心目标2. 数据…...

SpringBoot源码解析(十):应用上下文AnnotationConfigServletWebServerApplicationContext构造方法

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args Sp…...

vue3+vite全局loading

vue3vite全局loading j-loading.vue组件 <template><transition enter-active-class"animate__animated animate__fadeIn"leave-active-class"animate__animated animate__fadeOut"><div class"root-box" v-if"show"…...

比亚迪发布智能化战略,天神之眼开创全民智驾

2月10日&#xff0c;比亚迪在深圳隆重召开智能化战略发布会&#xff0c;正式向全球发布了其最新的智驾技术——“天神之眼”。这一技术的发布&#xff0c;标志着比亚迪在智能驾驶领域迈出了坚实的一步&#xff0c;稳居行业第一梯队&#xff0c;真正实现了端到端的智能驾驶体验&…...

在 MySQL 中,通过存储过程结合条件判断来实现添加表字段时,如果字段已存在则不再重复添加

-- 创建存储过程 DELIMITER $$ CREATE PROCEDURE add_column(IN db_name VARCHAR(255),IN table_name VARCHAR(255),IN column_name VARCHAR(255),IN column_definition VARCHAR(255),IN column_comment VARCHAR(255) ) BEGINDECLARE column_exists INT;-- 检查字段是否存在SEL…...

UP-VLA:具身智体的统一理解与预测模型

25年1月来自清华大学和上海姚期智研究院的论文“UP-VLA: A Unified Understanding and Prediction Model for Embodied Agent”。 视觉-语言-动作 (VLA) 模型的最新进展&#xff0c;利用预训练的视觉语言模型 (VLM) 来提高泛化能力。VLM 通常经过视觉语言理解任务的预训练&…...

后端开发ThreadLocal简介

ThreadLocal是线程的局部变量&#xff0c;为每个线程单独提供一份存储空间&#xff0c;具有线程隔离的效果&#xff0c;只有线程内能获取到对应的值 客户端发起的每次请求都对应一个单独的线程 常用方法 public void set(T value) 设置当前线程局部变量值public T get() 返回…...

AI分支知识之机器学习,深度学习,强化学习的关系

机器学习&#xff0c;深度学习&#xff0c;强化学习的关系 这一篇文章我们来探讨下AI领域中机器学习&#xff08;ML&#xff09;、深度学习&#xff08;DL&#xff09;和强化学习&#xff08;RL&#xff09;的关系。 一、机器学习&#xff08;ML&#xff09;&#xff1a;从数…...

微信小程序案例2——天气微信小程序(学会绑定数据)

文章目录 一、项目步骤1 创建一个weather项目2 进入index.wxml、index.js、index.wxss文件,清空所有内容,进入App.json,修改导航栏标题为“中国天气网”。3进入index.wxml,进行当天天气情况的界面布局,包括温度、最低温、最高温、天气情况、城市、星期、风行情况,代码如下…...

CPLD实现SPI通信

在 CPLD 中编写 SPI 程序时,需根据具体需求(主/从设备、时钟极性、数据位宽等)设计逻辑。以下提供一个 SPI 主控制器的 Verilog 实现示例,支持 模式 0(CPOL=0, CPHA=0),适用于控制外设(如 ADC、DAC、存储器等)。 SPI 主控制器模块设计(Verilog) 模块功能 支持 8/16…...