128-152-spark-核心编程-源码

news2024/10/6 22:21:01

128-spark-核心编程-源码(主要以了解基本原理和流程为主):

总体相关

​ 1.环境准备(Yarn 集群)
​ (1) Driver, Executor
​ 2.组件通信
​ (1) Driver => Executor
​ (2) Executor => Driver
​ (3) Executor => Executor
​ 3.应用程序的执行
​ (1) RDD 依赖
​ (2)阶段的划分
​ (3) 任务的切分
​ (4)任务的调度
​ (5)任务的执行

​ 4.Shuffle
​ (1) Shuffle 的原理和执行过程
​ (2) Shuffle 写磁盘
​ (3) Shuffle 读取磁盘
​ 5.内存的管理
​ (1)内存的分类
​ (2)内存的配置

起点:org/apache/spark/deploy/SparkSubmit.scala main

java org.apache.spark.deploy.SparkSubmit
java HelloWorld
JVM => Process ( SparkSubmit)
SparkSubmit.main
jps

结合尚硅谷视频讲解图片理解。

#提交命令
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10

main->doSubmit->parseArguments->parse(args.asJava)->SparkSubmitArguments.handle(--master --class)
给action赋值action = Option(action).getOrElse(SUBMIT) org.apache.spark.deploy.SparkSubmit#submit ->doRunMain()-org.apache.spark.deploy.SparkSubmit#runMain->prepareSubmitEnvironment(准备提交的环境)

#准备提交的环境
val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)

#根据环境找到childMainClass
if (isYarnCluster) {childMainClass = YARN_CLUSTER_SUBMIT_CLASS。。。} (YARN_CLUSTER_SUBMIT_CLASS:	org.apache.spark.deploy.yarn.YarnClusterApplication)

#yarnclient创建了资源调度器rmclient
YarnClient.createYarnClient->ApplicationClientProtocol rmClient;->org.apache.spark.deploy.yarn.Client#run
#提交应用程序,返回appid
org.apache.spark.deploy.yarn.Client#submitApplication
#客户端启动      yarnClient.init(hadoopConf)->yarnClient.start()->val newApp = yarnClient.createApplication()创建应用	->	createContainerLaunchContext(创建容器环境)->createApplicationSubmissionContext(创建提交环境)
#连接和提交,yarnClient连接,submitApplication提交
yarnClient.submitApplication(appContext)->Seq(Environment.JAVA_HOME.$$() + "/bin/java", "-server") ++
      javaOpts ++ amArgs ++  ->amArgs->amClass="org.apache.spark.deploy.yarn.ApplicationMaster"(启动ApplicationMaster)
      


 #启动ApplicationMaster
 org.apache.spark.deploy.yarn.ApplicationMaster#main-》org.apache.spark.deploy.yarn.YarnRMClient#amClient属性。applicationmaster和resourcemaster的链接-》org.apache.spark.deploy.yarn.ApplicationMaster#runDriver-》
 org.apache.spark.deploy.yarn.ApplicationMaster#startUserApplication启动用户应用程序-》startUserApplication.start(driver驱动线程初始化sparkcontext以及run mian方法)-》org.apache.spark.deploy.yarn.ApplicationMaster#registerAM(注册到rm,申请资源)-》org.apache.spark.deploy.yarn.YarnRMClient#createAllocator(创建分配器)-》org.apache.spark.deploy.yarn.YarnAllocator#allocateResources(返回可用资源列表)-》
org.apache.spark.deploy.yarn.YarnAllocator#handleAllocatedContainers(处理可用的容器)-》
org.apache.spark.deploy.yarn.YarnAllocator#runAllocatedContainers(运行已分配的容器)-》
org.apache.spark.deploy.yarn.ExecutorRunnable#prepareCommand(准备指令)-》
nmClient.startContainer(container.get, ctx)(向指定的NM启动容器)-》
/bin/java org.apache.spark.executor.YarnCoarseGrainedExecutorBackend(启动Executor)


#启动Executor
org.apache.spark.executor.YarnCoarseGrainedExecutorBackend#main-》  run  ->
org.apache.spark.SparkEnv#createExecutorEnv(创建executorenv环境)-》
org.apache.spark.rpc.netty.Dispatcher#registerRpcEndpoint(注册rpc通讯终端)-》
org.apache.spark.rpc.netty.Inbox#Inbox(收件箱,自己给自己发消息constructor -> onStart -> receive* -> onStop)-》
org.apache.spark.executor.CoarseGrainedExecutorBackend#onStart-》
org.apache.spark.rpc.RpcEndpointRef#ask(向driver注册executor)-》
org.apache.spark.scheduler.SchedulerBackend-》
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend-》
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend.DriverEndpoint#receiveAndReply(接收和应答)-》
org.apache.spark.rpc.RpcCallContext#reply( context.reply(true)注册成功)-》
org.apache.spark.executor.CoarseGrainedExecutorBackend#receive(executor = new Executor(executorId, hostname, env, userClassPath, isLocal = false,resources = _resources)创建Executor计算对象)-》
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend.DriverEndpoint#receive(makeOffers(executorId)注册成功)-》

通讯环境:

Netty:通讯框架,AIO异步非阻塞IO,BIO阻塞式IO,NIO非阻塞式IO

Linux对AIO支持不够好,Windows支持,Linux采用Epoll方式模仿AIO操作

org.apache.spark.SparkContext#createSparkEnv-》
org.apache.spark.rpc.RpcEnv#create(rpcenv创建)-》
org.apache.spark.rpc.netty.NettyRpcEnv#NettyRpcEnv-》
org.apache.spark.util.Utils$#startServiceOnPort-》
org.apache.spark.rpc.netty.NettyRpcEnv#startServer(创建服务)-》
org.apache.spark.network.server.TransportServer#TransportServer(创建服务)-》
org.apache.spark.network.server.TransportServer#init(初始化)-》
org.apache.spark.network.util.NettyUtils#getServerChannelClass(nio,EPOLL方式模仿异步)-》
org.apache.spark.rpc.netty.Dispatcher#registerRpcEndpoint(注册通讯终端,receive接受数据,收件箱inbox)-》
org.apache.spark.rpc.RpcEndpointRef#ask(发送数据,终端引用,)-》
org.apache.spark.rpc.netty.NettyRpcEnv#outboxes属性,发件箱

应用程序的执行:

应用SparkContext对象重要相关字段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EjehU7jt-1670772034910)(png/image-20211023100428603.png)]

(1) RDD 依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCRroNC7-1670772034911)(png/image-20211023101856923.png)]

(2)阶段的划分

org.apache.spark.rdd.RDD#collect(行动算子的触发)-》
org.apache.spark.SparkContext#runJob(运行任务)-》
org.apache.spark.scheduler.DAGScheduler#runJob(有向无环图)-》
org.apache.spark.util.EventLoop#post(将JobSubmitted事件放入事件队列中)-》
org.apache.spark.scheduler.DAGSchedulerEventProcessLoop#doOnReceive(事件队列中取出作业提交)-》
org.apache.spark.scheduler.DAGScheduler#handleJobSubmitted(进行阶段的划分)-》
org.apache.spark.scheduler.DAGScheduler#createResultStage(进行阶段的划分)-》
org.apache.spark.scheduler.DAGScheduler#getOrCreateParentStages(获取上级阶段)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RePkKYud-1670772034912)(png/image-20211023101920351.png)]

(3) 任务的切分

每个阶段最后分区的数量即n*2个任务,例如最后一个阶段分为3个分区,最后shufflerdd也会有三个分区

org.apache.spark.scheduler.DAGScheduler#submitStage-》
org.apache.spark.scheduler.DAGScheduler#submitMissingTasks(没上一节阶段提交任务,有上一级阶段提交上一级阶段)-》

(4)任务的调度

org.apache.spark.scheduler.TaskScheduler#submitTasks(任务调度器提交任务)-》
org.apache.spark.scheduler.TaskSchedulerImpl#submitTasks(实现)-》
org.apache.spark.scheduler.TaskSetManager(任务tasksset的管理者)-》
org.apache.spark.scheduler.SchedulableBuilder(调度器)-》
org.apache.spark.scheduler.TaskSchedulerImpl#initialize(初始化调度器,默认FIFO)-》
org.apache.spark.scheduler.Pool#addSchedulable(任务池添加调度)-》
org.apache.spark.scheduler.SchedulerBackend#reviveOffers(取任务)-》org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend#reviveOffers(集群模式取)-》
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend.DriverEndpoint#makeOffers()-》
org.apache.spark.scheduler.TaskSchedulerImpl#resourceOffers(获取资源调度信息)-》
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend.DriverEndpoint#launchTasks(调度任务)-》
executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))(任务池取出发送终端执行)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJ6oNHxS-1670772034912)(png/image-20211023104940587.png)]

本地化级别
#补充,本地化级别,首选位置。task任务发送到哪里,数据和节点位置等。效率考虑,移动数据不如移动计算
for(currentMaxLocality<-taskSet.myLocalityLevels) 
移动数据不如移动计算
计算和数据的位置存在不同的级别,这个级别称之为本地化级别
进程本地化:数据和计算在同一个进程中
节点本地化:数据和计算在同一个节点中
机架本地化:数据和计算在同一个机架中
任意

(5)任务的执行

org.apache.spark.executor.CoarseGrainedExecutorBackend#receive(executor接收到消息)-》
org.apache.spark.executor.Executor#launchTask(启动Task)-》
java.util.concurrent.ThreadPoolExecutor#execute(每个线程执行每个task)-》
org.apache.spark.executor.Executor.TaskRunner#run-》
org.apache.spark.scheduler.Task#run

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YeoNG4rn-1670772034914)(png/image-20211023111151468.png)]

Shuffle原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pnRL92cj-1670772034914)(png/image-20211023180336159.png)]

详解转变的过程

前提1:一核,一个task,落盘一个文件,三个任务读取数据,但是没法分辨需要的数据是文件中的那些数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ULeyvl0C-1670772034915)(png/image-20211023180544869.png)]

前提2:多核,多task,每个任务针对落盘三个文件,导致小文件过多

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTmqEXci-1670772034915)(png/image-20211023180730597.png)]

前提3:对前提2的优化,将同核任务的落盘,写相同的文件,但是真实环境task可能会很多,下游任务也可能人多,或者100核数。文件将还是很多。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DG26XExg-1670772034916)(png/image-20211023180910097.png)]

前提4:对前提3的优化,写到同一个文件,使用index索引文件记录下游任务读取数据的偏移量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V85FxisI-1670772034916)(png/image-20211023182401664.png)]

Shuffle实现过程:

org.apache.spark.scheduler.ShuffleMapTask-》
org.apache.spark.scheduler.ShuffleMapTask#runTask-》
org.apache.spark.shuffle.ShuffleWriteProcessor#write(写)-》
org.apache.spark.shuffle.ShuffleManager#getWriter(获取写入的对象)-》
org.apache.spark.shuffle.sort.SortShuffleWriter#write()-》
org.apache.spark.util.collection.ExternalSorter#writePartitionedMapOutput-》
org.apache.spark.shuffle.sort.io.LocalDiskShuffleMapOutputWriter#commitAllPartitions(提交)-》
org.apache.spark.shuffle.IndexShuffleBlockResolver#writeIndexFileAndCommit(索引文件和数据文件的提交)-》

#读
org.apache.spark.scheduler.ResultTask#runTask(结果任务)-》
org.apache.spark.rdd.RDD#getOrCompute(获取或计算)-》
org.apache.spark.rdd.ShuffledRDD#compute(shufflerdd的计算)-》
org.apache.spark.shuffle.BlockStoreShuffleReader#read(读取数据)

shuffle写:

org.apache.spark.shuffle.ShuffleWriteProcessor(shuffle写的处理器)-》write-》
org.apache.spark.shuffle.ShuffleManager(shuffle管理器,hash早期有,sort现版本)-》
org.apache.spark.shuffle.sort.SortShuffleManager#getWriter(获取到了下面的写对象的SortShuffleWriter)-》
org.apache.spark.shuffle.sort.SortShuffleWriter#write(写)-》
org.apache.spark.util.collection.ExternalSorter#writePartitionedMapOutput(写出)-》
org.apache.spark.shuffle.sort.io.LocalDiskShuffleMapOutputWriter#commitAllPartitions(提交)

补充writer写的类型,判断条件org.apache.spark.shuffle.sort.SortShuffleManager#registerShuffle

处理器写对象判断条件
SerializedShuffleHandleUnsafeShuffleWriter1.序列化规则支持重定位操作(java序列化不支持,kryo序列化支持) 2.不能使用预聚合功能 3.如果下游的分区数量小区大(16777215+1=16777216)PackedRecordPointer.MAXIMUM_PARTITION_ID + 1
BypassMergeSortShuffleHandleBypassMergeSortShuffleWriter1.不能使用预聚合 2、如果下游的分区数量小区等于200(可配)
BaseShuffleHandleSortShuffleWriter其他情况

Shuffle归并排序和读

org.apache.spark.util.collection.ExternalSorter#insertAll(插入)-》
org.apache.spark.util.collection.PartitionedAppendOnlyMap(支持预聚合的map结构)
org.apache.spark.util.collection.PartitionedPairBuffer(不支持预聚合的结构)
org.apache.spark.util.collection.SizeTrackingAppendOnlyMap#changeValue(预聚合)-》位置-》
org.apache.spark.util.collection.ExternalSorter#maybeSpillCollection(是否溢写磁盘)-》
org.apache.spark.util.collection.Spillable#maybeSpill-》
org.apache.spark.util.collection.ExternalSorter#spill(溢写)-》
org.apache.spark.util.collection.ExternalSorter#spillMemoryIteratorToDisk(写到磁盘)-》
org.apache.spark.util.collection.ExternalSorter#writePartitionedMapOutput-》
org.apache.spark.util.collection.ExternalSorter#merge(Merge spilled and in-memory data合并溢写和磁盘)-》
org.apache.spark.util.collection.ExternalSorter#mergeSort(归并排序)-》
org.apache.spark.shuffle.sort.io.LocalDiskShuffleMapOutputWriter#commitAllPartitions()-》
org.apache.spark.shuffle.IndexShuffleBlockResolver#writeIndexFileAndCommit(写索引文件和数据文件)->

5.内存的管理
(1)内存的分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ir6j3dQC-1670772034916)(png/image-20211024010705514.png)]

相关位置:

org.apache.spark.memory.UnifiedMemoryManager#apply	->
org.apache.spark.memory.UnifiedMemoryManager#getMaxMemory

#相关参数:org.apache.spark.memory.UnifiedMemoryManager#RESERVED_SYSTEM_MEMORY_BYTES预留内存300M

​ (2)内存的配置

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

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

相关文章

Unity异步加载AB包

Unity异步加载AB包写在前面效果关键讲解项目地址写在后面写在前面 最近项目需要在Unity中完成一个非常耗时的工作&#xff0c;所以学习了下异步加载的流程&#xff0c;这里做了一个demo&#xff0c;异步加载AB包&#xff0c;其实异步加载场景等&#xff0c;原理差不多。 效果…

Tomcat的Maven插件使用方法(在idea里面运行Tomcat)

目录 一、概述 二、下载和导入插件 三、测试使用方式 四、总结 一、概述 使用这个插件可以快速的运行Tomcat&#xff0c;比在本地配置快得多。 二、下载和导入插件 1.下载插件Maven Helper ps&#xff1a;已经有下载过这个插件的可以跳过此步骤 &#xff08;1&#xff…

一、导论——可解释性机器学习(DataWhale组队学习)

目录导言一、什么是可解释人工智能?二、学可解释机器学习有什么用?2.1学习可解释机器学习的原因2.2 Machine Teaching :人工智能教人类学习2.3 细粒度图像分类2.4前沿AI三、本身可解释性好的机器学习模型四、传统机器学习算法的可解释性分析五、卷积神经网络的可解释性分析5.…

前端基础(二)_HTML常用标签(块级标签、行级标签、行块级标签)

HTML常用标签 我们可以分为三类&#xff1a; 1.块级标签 2.行级标签 3.行块级标签 一、块级标签 1.1 h系类标签 标题标签 H1~h6 大到小 H1 在同一个页面中只能使用一次 其他标签可以重复 特点&#xff1a;默认宽度100% 高度自适应 独立成行 自带间距加粗 <body><…

java计算机毕业设计ssm制造型企业仓储管理系统i0180(附源码、数据库)

java计算机毕业设计ssm制造型企业仓储管理系统i0180&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&…

[附源码]计算机毕业设计的项目管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

基于小波变换的图像压缩解压缩的matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 图像压缩的类别 对于图像压缩&#xff0c;主要有两类方法&#xff1a;无损的图像压缩以及有损的图像压缩&#xff0c;分别称为lossless image compression and lossy image compression。 对于无…

[附源码]Node.js计算机毕业设计动漫网站Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

第37篇 网络(七)TCP(一)

导语 TCP即TransmissionControl Protocol&#xff0c;传输控制协议。与UDP不同&#xff0c;它是面向连接和数据流的可靠传输协议。也就是说&#xff0c;它能使一台计算机上的数据无差错的发往网络上的其他计算机&#xff0c;所以当要传输大量数据时&#xff0c;我们选用TCP协议…

ssm+Vue计算机毕业设计校园生活服务预约管理系统(程序+LW文档)

ssmVue计算机毕业设计校园生活服务预约管理系统&#xff08;程序LW文档&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;…

spring切入点表达式(一)

前面写到切入点表达式&#xff0c;如果把全部方法都作为切入点的话&#xff0c;用execution(* *(..))表达式&#xff0c;这个表达式代表什么意思呢&#xff1f; public void login (String name,String address){} * * ( . . ) * *(..)对应方法如上图 * -------->代表修…

【GRU回归预测】基于鲸鱼算法优化门控循环单元WOA-GRU神经网络实现多输入单输出回归预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

网络协议—应用层的HTTPS协议

用 HTTP 协议&#xff0c;看个新闻还没有问题&#xff0c;但是换到更加严肃的场景中&#xff0c;就存在很多的安全风险。例如&#xff0c;你要下单做一次支付&#xff0c;如果还是使用普通的 HTTP 协议&#xff0c;那你很可能会被黑客盯上。例如在点外卖的环境中&#xff0c;发…

面试题 —— 真实面试题分享

文章目录 一、对BFC的理解。二、CSS中”::“和”:”的区别&#xff1f;三、对vue生命周期的理解&#xff1f; 四、vue组件通信的方式 五、vue中给data中的对象添加一个新的属性会发生什么&#xff0c;如何解决? 六.微信小程序组件的生命周期 七、javascript原型与继承的理解…

【vue基础】关于组件之间的通信

目录 &#xff08;1&#xff09;父组件向子组件传递信息 1.props&#xff1a; 2.第二种是直接从子组件里面利用&#xffe5;parent和root引用&#xff0c;获取根组件和父组件中的数据 &#xff08;2&#xff09;子组件数据传入父组件 1.通过自定义事件 2.通过$refs引用集合…

Docker-自定义镜像上传阿里云

目录 一、Docker制作jdk镜像 jdkv.1.0的制作 步骤 二&#xff0c;alpine制作jdk镜像 2.1 alpine Linux简介 2.2 基于alpine制作JDK8镜像 前期准备 2.3步骤 2.3.1.下载镜像 2.3.2.创建并编辑dockerfile 2.3.3.执行dockerfile创建镜像 2.3.4.创建并启动容器(可略) 2.3.5.进…

考虑碳交易机制的园区综合能源系统电热协同运行优化研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

【加油站会员管理小程序】05 充值套餐功能

上一篇我们讲解了轮播图的开发,本篇我们讲解充值优惠功能的开发。 开发之前我们要思考如何展示信息,在我们的功能规划里,在首页部分是要展示三个充值的套餐信息。那这个套餐呢最好是存在数据源中,便于日后维护信息。 在应用的编辑器里,点击数据源的图标,我们创建一个数…

m最小二乘法自适应均衡误码率仿真,对比LS,DEF以及LMMSE三种均衡算法误码率

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 信道估计是通信系统接收机的重要功能模块&#xff0c;主要是用来估计信号所经历信道的冲击响应&#xff0c;并用于后续的信道均衡处理&#xff0c;以便消除多径信号混叠造成的ISI。 信道估…

[附源码]Node.js计算机毕业设计二手车交易平台设计Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…