Update(Stage4):Spark Streaming原理_运行过程_高级特性
Spark Streaming
介绍
入门
原理
操作
1. Spark Streaming 介绍
流式计算的场景
流式计算框架
Spark Streaming的特点
- 新的场景
-
通过对现阶段一些常见的需求进行整理, 我们要问自己一个问题, 这些需求如何解决?
场景 解释 商品推荐


京东和淘宝这样的商城在购物车, 商品详情等地方都有商品推荐的模块
商品推荐的要求
快速的处理, 加入购物车以后就需要迅速的进行推荐
数据量大
需要使用一些推荐算法
工业大数据

现在的工场中, 设备是可以联网的, 汇报自己的运行状态, 在应用层可以针对这些数据来分析运行状况和稳健程度, 展示工件完成情况, 运行情况等
工业大数据的需求
快速响应, 及时预测问题
数据是以事件的形式动态的产品和汇报
因为是运行状态信息, 而且一般都是几十上百台机器, 所以汇报的数据量很大
监控

一般的大型集群和平台, 都需要对其进行监控
监控的需求
要针对各种数据库, 包括
MySQL,HBase等进行监控要针对应用进行监控, 例如
Tomcat,Nginx,Node.js等要针对硬件的一些指标进行监控, 例如
CPU, 内存, 磁盘 等这些工具的日志输出是非常多的, 往往一个用户的访问行为会带来几百条日志, 这些都要汇报, 所以数据量比较大
要从这些日志中, 聚合系统运行状况
这样的需求, 可以通过传统的批处理来完成吗? - 流计算
-
批量计算

数据已经存在, 一次性读取所有的数据进行批量处理
流计算

数据源源不断的进来, 经过处理后落地
- 流和批的架构组合
-
流和批都是有意义的, 有自己的应用场景, 那么如何结合流和批呢? 如何在同一个系统中使用这两种不同的解决方案呢?
- 混合架构
-

混合架构说明
混合架构的名字叫做
Lambda 架构, 混合架构最大的特点就是将流式计算和批处理结合起来后在进行查询的时候分别查询流系统和批系统, 最后将结果合并在一起

一般情况下 Lambda 架构分三层
批处理层: 批量写入, 批量读取
服务层: 分为两个部分, 一部分对应批处理层, 一部分对应速度层
速度层: 随机读取, 随即写入, 增量计算
优点
兼顾优点, 在批处理层可以全量查询和分析, 在速度层可以查询最新的数据
速度很快, 在大数据系统中, 想要快速的获取结果是非常困难的, 因为高吞吐量和快速返回结果往往很难兼得, 例如
Impala和Hive,Hive能进行非常大规模的数据量的处理,Impala能够快速的查询返回结果, 但是很少有一个系统能够兼得两点,Lambda使用多种融合的手段从而实现
缺点
Lambda是一个非常反人类的设计, 因为我们需要在系统中不仅维护多套数据层, 还需要维护批处理和流式处理两套框架, 这非常困难, 一套都很难搞定, 两套带来的运维问题是是指数级提升的
- 流式架构
-

流式架构说明
流式架构常见的叫做
Kappa 结构, 是Lambda 架构的一个变种, 其实本质上就是删掉了批处理优点
非常简单
效率很高, 在存储系统的发展下, 很多存储系统已经即能快速查询又能批量查询了, 所以
Kappa 架构在新时代还是非常够用的
问题
丧失了一些
Lambda的优秀特点
关于架构的问题, 很多时候往往是无解的, 在合适的地方使用合适的架构, 在项目课程中, 还会进行更细致的讨论
Spark Streaming的特点-
特点 说明 Spark Streaming是Spark Core API的扩展Spark Streaming具有类似RDD的API, 易于使用, 并可和现有系统共用相似代码一个非常重要的特点是,
Spark Streaming可以在流上使用基于Spark的机器学习和流计算, 是一个一站式的平台
Spark Streaming具有很好的整合性Spark Streaming可以从Kafka,Flume,TCP等流和队列中获取数据Spark Streaming可以将处理过的数据写入文件系统, 常见数据库中
Spark Streaming是微批次处理模型微批次处理的方式不会有长时间运行的
Operator, 所以更易于容错设计微批次模型能够避免运行过慢的服务, 实行推测执行
2. Spark Streaming 入门
环境准备
工程搭建
代码编写
总结
Netcat的使用-
Step 1:Socket回顾-

Socket是Java中为了支持基于TCP / UDP协议的通信所提供的编程模型Socket分为Socket server和Socket clientSocket server-
监听某个端口, 接收
Socket client发过来的连接请求建立连接, 连接建立后可以向Socket client发送TCP packet交互 (被动) Socket client-
向某个端口发起连接, 并在连接建立后, 向
Socket server发送TCP packet实现交互 (主动)
TCP三次握手建立连接Step 1-
Client向Server发送SYN(j), 进入SYN_SEND状态等待Server响应 Step 2-
Server收到Client的SYN(j)并发送确认包ACK(j + 1), 同时自己也发送一个请求连接的SYN(k)给Client, 进入SYN_RECV状态等待Client确认 Step 3-
Client收到Server的ACK + SYN, 向Server发送连接确认ACK(k + 1), 此时,Client和Server都进入ESTABLISHED状态, 准备数据发送
Step 2:Netcat-

Netcat简写nc, 命令行中使用nc命令调用Netcat是一个非常常见的Socket工具, 可以使用nc建立Socket server也可以建立Socket clientnc -l建立Socket server,l是listen监听的意思nc host port建立Socket client, 并连接到某个Socket server
- 创建工程
-
- 目标
-
使用
Spark Streaming程序和Socket server进行交互, 从Server处获取实时传输过来的字符串, 拆开单词并统计单词数量, 最后打印出来每一个小批次的单词数量
Step 1:创建工程-
创建
IDEA Maven工程, 步骤省略, 参考Spark第一天工程建立方式导入
Maven依赖, 省略, 参考Step 2创建
main/scala文件夹和test/scala文件夹创建包
cn.itcast.streaming创建对象
StreamingWordCount
Step 2:Maven依赖-
如果使用
Spark Streaming, 需要使用如下Spark的依赖Spark Core:Spark的核心包, 因为Spark Streaming要用到Spark Streaming
Step 3:编码-
object StreamingWordCount { def main(args: Array[String]): Unit = {
if (args.length < 2) {
System.err.println("Usage: NetworkWordCount <hostname> <port>")
System.exit(1)
} val sparkConf = new SparkConf().setAppName("NetworkWordCount")
val ssc = new StreamingContext(sparkConf, Seconds(1)) val lines = ssc.socketTextStream(
hostname = args(0),
port = args(1).toInt,
storageLevel = StorageLevel.MEMORY_AND_DISK_SER) val words = lines.flatMap(_.split(" "))
val wordCounts = words.map(x => (x, 1)).reduceByKey(_ + _) wordCounts.print() ssc.start()
ssc.awaitTermination()
}
}在 Spark中, 一般使用XXContext来作为入口,Streaming也不例外, 所以创建StreamingContext就是创建入口开启 Socket的Receiver, 连接到某个TCP端口, 作为Socket client, 去获取数据选择 Receiver获取到数据后的保存方式, 此处是内存和磁盘都有, 并且序列化后保存类似 RDD中的Action, 执行最后的数据输出和收集启动流和 JobGenerator, 开始流式处理数据阻塞主线程, 后台线程开始不断获取数据并处理 Step 4:部署和上线-
使用 Maven 命令 package 打包

将打好的包上传到
node01
在
node02上使用nc开启一个Socket server, 接受Streaming程序的连接请求, 从而建立连接发送消息给Streaming程序实时处理nc -lk 9999
在
node01执行如下命令运行程序spark-submit --class cn.itcast.streaming.StreamingWordCount --master local[6] original-streaming-0.0.1.jar node02 9999
Step 5:总结和知识落地-
- 注意点
-
Spark Streaming并不是真正的来一条数据处理一条
Spark Streaming的处理机制叫做小批量, 英文叫做mini-batch, 是收集了一定时间的数据后生成RDD, 后针对RDD进行各种转换操作, 这个原理提现在如下两个地方控制台中打印的结果是一个批次一个批次的, 统计单词数量也是按照一个批次一个批次的统计
多长时间生成一个
RDD去统计呢? 由new StreamingContext(sparkConf, Seconds(1))这段代码中的第二个参数指定批次生成的时间
Spark Streaming中至少要有两个线程在使用
spark-submit启动程序的时候, 不能指定一个线程主线程被阻塞了, 等待程序运行
需要开启后台线程获取数据
- 创建
StreamingContext -
val conf = new SparkConf().setAppName(appName).setMaster(master)
val ssc = new StreamingContext(conf, Seconds(1))StreamingContext是Spark Streaming程序的入口在创建
StreamingContext的时候, 必须要指定两个参数, 一个是SparkConf, 一个是流中生成RDD的时间间隔StreamingContext提供了如下功能创建
DStream, 可以通过读取Kafka, 读取Socket消息, 读取本地文件等创建一个流, 并且作为整个DAG中的InputDStreamRDD遇到Action才会执行, 但是DStream不是,DStream只有在StreamingContext.start()后才会开始接收数据并处理数据使用
StreamingContext.awaitTermination()等待处理被终止使用
StreamingContext.stop()来手动的停止处理
在使用的时候有如下注意点
同一个
Streaming程序中, 只能有一个StreamingContext一旦一个
Context已经启动 (start), 则不能添加新的数据源 **
- 各种算子
-

这些算子类似
RDD, 也会生成新的DStream这些算子操作最终会落到每一个
DStream生成的RDD中
算子 释义 flatMaplines.flatMap(_.split(" "))将一个数据一对多的转换为另外的形式, 规则通过传入函数指定
mapwords.map(x => (x, 1))一对一的转换数据
reduceByKeywords.reduceByKey(_ + _)这个算子需要特别注意, 这个聚合并不是针对于整个流, 而是针对于某个批次的数据
2. 原理
总章
静态
DAG动态切分
数据流入
容错机制
- 总章
-
Spark Streaming的特点-
Spark Streaming会源源不断的处理数据, 称之为流计算Spark Streaming并不是实时流, 而是按照时间切分小批量, 一个一个的小批量处理Spark Streaming是流计算, 所以可以理解为数据会源源不断的来, 需要长时间运行
Spark Streaming是按照时间切分小批量-
如何小批量?
Spark Streaming中的编程模型叫做DStream, 所有的API都从DStream开始, 其作用就类似于RDD之于Spark Core
可以理解为
DStream是一个管道, 数据源源不断的从这个管道进去, 被处理, 再出去
但是需要注意的是,
DStream并不是严格意义上的实时流, 事实上,DStream并不处理数据, 而是处理RDD
以上, 可以整理出如下道理
Spark Streaming是小批量处理数据, 并不是实时流Spark Streaming对数据的处理是按照时间切分为一个又一个小的RDD, 然后针对RDD进行处理
所以针对以上的解读, 可能会产生一种疑惑
如何切分
RDD?
如何处理数据?
如下代码
val lines: DStream[String] = ssc.socketTextStream(
hostname = args(0),
port = args(1).toInt,
storageLevel = StorageLevel.MEMORY_AND_DISK_SER) val words: DStream[String] = lines
.flatMap(_.split(" "))
.map(x => (x, 1))
.reduceByKey(_ + _)可以看到
RDD中针对数据的处理是使用算子, 在DStream中针对数据的操作也是算子DStream的算子似乎和RDD没什么区别
有一个疑惑
难道
DStream会把算子的操作交给RDD去处理? 如何交?
Spark Streaming是流计算, 流计算的数据是无限的-
什么系统可以产生无限的数据?

无限的数据一般指的是数据不断的产生, 比如说运行中的系统, 无法判定什么时候公司会倒闭, 所以也无法断定数据什么时候会不再产生数据
- 那就会产生一个问题
-
如何不简单的读取数据, 如何应对数据量时大时小?
如何数据是无限的, 意味着可能要一直运行下去
- 那就会又产生一个问题
-
Spark Streaming不会出错吗? 数据出错了怎么办?
- 总结
-
总结下来, 有四个问题
DStream如何对应RDD?如何切分
RDD?如何读取数据?
如何容错?
DAG的定义-
RDD和DStream的DAG-
如果是
RDD的WordCount, 代码大致如下val textRDD = sc.textFile(...)
val splitRDD = textRDD.flatMap(_.split(" "))
val tupleRDD = splitRDD.map((_, 1))
val reduceRDD = tupleRDD.reduceByKey(_ + _)用图形表示如下

同样,
DStream的代码大致如下val lines: DStream[String] = ssc.socketTextStream(...)
val words: DStream[String] = lines.flatMap(_.split(" "))
val wordCounts: DStream[(String, Int)] = words.map(x => (x, 1)).reduceByKey(_ + _)同理,
DStream也可以形成DAG如下
看起来
DStream和RDD好像哟, 确实如此 RDD和DStream的区别-

DStream的数据是不断进入的,RDD是针对一个数据的操作像
RDD一样,DStream也有不同的子类, 通过不同的算子生成一个
DStream代表一个数据集, 其中包含了针对于上一个数据的操作DStream根据时间切片, 划分为多个RDD, 针对DStream的计算函数, 会作用于每一个DStream中的RDD
DStream如何形式DAG-

每个
DStream都有一个关联的DStreamGraph对象DStreamGraph负责表示DStream之间的的依赖关系和运行步骤DStreamGraph中会单独记录InputDStream和OutputDStream
- 切分流, 生成小批量
-
- 静态和动态
-
根据前面的学习, 可以总结一下规律
DStream对应RDDDStreamGraph表示DStream之间的依赖关系和运行流程, 相当于RDD通过DAGScheduler所生成的RDD DAG
但是回顾前面的内容,
RDD的运行分为逻辑计划和物理计划逻辑计划就是
RDD之间依赖关系所构成的一张有向无环图后根据这张
DAG生成对应的TaskSet调度到集群中运行, 如下

但是在
DStream中则不能这么简单的划分, 因为DStream中有一个非常重要的逻辑, 需要按照时间片划分小批量在
Streaming中,DStream类似RDD, 生成的是静态的数据处理过程, 例如一个DStream中的数据经过map转为其它模样在
Streaming中,DStreamGraph类似DAG, 保存了这种数据处理的过程
上述两点, 其实描述的是静态的一张
DAG, 数据处理过程, 但是Streaming是动态的, 数据是源源不断的来的
所以, 在
DStream中, 静态和动态是两个概念, 有不同的流程
DStreamGraph将DStream联合起来, 生成DStream之间的DAG, 这些DStream之间的关系是相互依赖的关系, 例如一个DStream经过map转为另外一个DStream但是把视角移动到
DStream中来看,DStream代表了源源不断的RDD的生成和处理, 按照时间切片, 所以一个DStream DAG又对应了随着时间的推进所产生的无限个RDD DAG
- 动态生成
RDD DAG的过程 -
RDD DAG的生成是按照时间来切片的,Streaming会维护一个Timer, 固定的时间到达后通过如下五个步骤生成一个RDD DAG后调度执行通知
Receiver将收到的数据暂存, 并汇报存储的元信息, 例如存在哪, 存了什么通过
DStreamGraph复制出一套新的RDD DAG将数据暂存的元信息和
RDD DAG一同交由JobScheduler去调度执行提交结束后, 对系统当前的状态
Checkpoint
- 数据的产生和导入
-
Receiver-
在
Spark Streaming中一个非常大的挑战是, 很多外部的队列和存储系统都是分块的,RDD是分区的, 在读取外部数据源的时候, 会用不同的分区对照外部系统的分片, 例如
不仅
RDD,DStream中也面临这种挑战
那么此处就有一个小问题
DStream中是RDD流, 只是RDD的分区对应了Kafka的分区就可以了吗?
答案是不行, 因为需要一套单独的机制来保证并行的读取外部数据源, 这套机制叫做
Receiver Receiver的结构-

为了保证并行获取数据, 对应每一个外部数据源的分区, 所以
Receiver也要是分布式的, 主要分为三个部分Receiver是一个对象, 是可以有用户自定义的获取逻辑对象, 表示了如何获取数据Receiver Tracker是Receiver的协调和调度者, 其运行在Driver上Receiver Supervisor被Receiver Tracker调度到不同的几点上分布式运行, 其会拿到用户自定义的Receiver对象, 使用这个对象来获取外部数据
Receiver的执行过程-

在
Spark Streaming程序开启时候,Receiver Tracker使用JobScheduler分发Job到不同的节点, 每个Job包含一个Task, 这个Task就是Receiver Supervisor, 这个部分的源码还挺精彩的, 其实是复用了通用的调度逻辑ReceiverSupervisor启动后运行Receiver实例Receiver启动后, 就将持续不断地接收外界数据, 并持续交给ReceiverSupervisor进行数据存储ReceiverSupervisor持续不断地接收到Receiver转来的数据, 并通过BlockManager来存储数据获取的数据存储完成后发送元数据给
Driver端的ReceiverTracker, 包含数据块的id, 位置, 数量, 大小 等信息
- 容错
-
因为要非常长时间的运行, 对于任何一个流计算系统来说, 容错都是非常致命也非常重要的一环, 在
Spark Streaming中, 大致提供了如下的容错手段- 热备
-
还记得这行代码吗

这行代码中的
StorageLevel.MEMORY_AND_DISK_SER的作用是什么? 其实就是热备份当 Receiver 获取到数据要存储的时候, 是交给 BlockManager 存储的
如果设置了
StorageLevel.MEMORY_AND_DISK_SER, 则意味着BlockManager不仅会在本机存储, 也会发往其它的主机进行存储, 本质就是冗余备份如果某一个计算失败了, 通过冗余的备份, 再次进行计算即可

这是默认的容错手段
- 冷备
-
冷备在
Spark Streaming中的手段叫做WAL(预写日志)当
Receiver获取到数据后, 会交给BlockManager存储在存储之前先写到
WAL中,WAL中保存了Redo Log, 其实就是记录了数据怎么产生的, 以便于恢复的时候通过Log恢复当出错的时候, 通过
Redo Log去重放数据
- 重放
-
有一些上游的外部系统是支持重放的, 比如说
KafkaKafka可以根据Offset来获取数据当
SparkStreaming处理过程中出错了, 只需要通过Kafka再次读取即可
3. 操作
这一小节主要目的是为了了解 Spark Streaming 一些特别特殊和重要的操作, 一些基本操作基本类似 RDD
updateStateByKey-
需求: 统计整个流中, 所有出现的单词数量, 而不是一个批中的数量 - 状态
-
统计总数
入门案例中, 只能统计某个时间段内的单词数量, 因为
reduceByKey只能作用于某一个RDD, 不能作用于整个流如果想要求单词总数该怎么办?
状态
可以使用状态来记录中间结果, 从而每次来一批数据, 计算后和中间状态求和, 于是就完成了总数的统计

- 实现
-
使用
updateStateByKey可以做到这件事updateStateByKey会将中间状态存入CheckPoint中
val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[6]")
val sc = new SparkContext(sparkConf)
sc.setLogLevel("ERROR")
val ssc = new StreamingContext(sc, Seconds(1)) val lines: DStream[String] = ssc.socketTextStream(
hostname = "localhost",
port = "9999".toInt,
storageLevel = StorageLevel.MEMORY_AND_DISK_SER) val words = lines.flatMap(_.split(" ")).map(x => (x, 1)) // 使用 updateStateByKey 必须设置 Checkpoint 目录
ssc.checkpoint("checkpoint") // updateStateByKey 的函数
def updateFunc(newValue: Seq[Int], runningValue: Option[Int]) = {
// newValue 之所以是一个 Seq, 是因为它是某一个 Batch 的某个 Key 的全部 Value
val currentBatchSum = newValue.sum
val state = runningValue.getOrElse(0)
// 返回的这个 Some(count) 会再次进入 Checkpoint 中当作状态存储
Some(currentBatchSum + state)
} // 调用
val wordCounts = words.updateStateByKey[Int](updateFunc) wordCounts.print() ssc.start()
ssc.awaitTermination()
window操作-
需求: 计算过 30s的单词总数, 每10s更新一次- 实现
-
使用
window即可实现按照窗口组织 RDD
val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[6]")
val sc = new SparkContext(sparkConf)
sc.setLogLevel("ERROR")
val ssc = new StreamingContext(sc, Seconds(1)) val lines: DStream[String] = ssc.socketTextStream(
hostname = "localhost",
port = 9999,
storageLevel = StorageLevel.MEMORY_AND_DISK_SER) val words = lines.flatMap(_.split(" ")).map(x => (x, 1)) // 通过 window 操作, 会将流分为多个窗口
val wordsWindow = words.window(Seconds(30), Seconds(10))
// 此时是针对于窗口求聚合
val wordCounts = wordsWindow.reduceByKey((newValue, runningValue) => newValue + runningValue) wordCounts.print() ssc.start()
ssc.awaitTermination()既然
window操作经常配合reduce这种聚合, 所以Spark Streaming提供了较为方便的方法
val sparkConf = new SparkConf().setAppName("NetworkWordCount").setMaster("local[6]")
val sc = new SparkContext(sparkConf)
sc.setLogLevel("ERROR")
val ssc = new StreamingContext(sc, Seconds(1)) val lines: DStream[String] = ssc.socketTextStream(
hostname = "localhost",
port = 9999,
storageLevel = StorageLevel.MEMORY_AND_DISK_SER) val words = lines.flatMap(_.split(" ")).map(x => (x, 1)) // 开启窗口并自动进行 reduceByKey 的聚合
val wordCounts = words.reduceByKeyAndWindow(
reduceFunc = (n, r) => n + r,
windowDuration = Seconds(30),
slideDuration = Seconds(10)) wordCounts.print() ssc.start()
ssc.awaitTermination() - 窗口时间
-

在
window函数中, 接收两个参数windowDuration窗口长度,window函数会将多个DStream中的RDD按照时间合并为一个, 那么窗口长度配置的就是将多长时间内的RDD合并为一个slideDuration滑动间隔, 比较好理解的情况是直接按照某个时间来均匀的划分为多个window, 但是往往需求可能是统计最近xx分内的所有数据, 一秒刷新一次, 那么就需要设置滑动窗口的时间间隔了, 每隔多久生成一个window
滑动时间的问题
如果
windowDuration > slideDuration, 则在每一个不同的窗口中, 可能计算了重复的数据如果
windowDuration < slideDuration, 则在每一个不同的窗口之间, 有一些数据为能计算进去
但是其实无论谁比谁大, 都不能算错, 例如, 我的需求有可能就是统计一小时内的数据, 一天刷新两次
Update(Stage4):Spark Streaming原理_运行过程_高级特性的更多相关文章
- Update(Stage4):Spark原理_运行过程_高级特性
如何判断宽窄依赖: =================================== 6. Spark 底层逻辑 导读 从部署图了解 Spark 部署了什么, 有什么组件运行在集群中 通过对 W ...
- .Spark Streaming(上)--实时流计算Spark Streaming原理介
Spark入门实战系列--7.Spark Streaming(上)--实时流计算Spark Streaming原理介绍 http://www.cnblogs.com/shishanyuan/p/474 ...
- Spark入门实战系列--7.Spark Streaming(上)--实时流计算Spark Streaming原理介绍
[注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .Spark Streaming简介 1.1 概述 Spark Streaming 是Spa ...
- 实时流计算Spark Streaming原理介绍
1.Spark Streaming简介 1.1 概述 Spark Streaming 是Spark核心API的一个扩展,可以实现高吞吐量的.具备容错机制的实时流数据的处理.支持从多种数据源获取数据,包 ...
- Spark Streaming 原理剖析
通过源码呈现 Spark Streaming 的底层机制. 1. 初始化与接收数据 Spark Streaming 通过分布在各个节点上的接收器,缓存接收到的流数据,并将流数 据 包 装 成 Spar ...
- 大数据技术之_19_Spark学习_04_Spark Streaming 应用解析 + Spark Streaming 概述、运行、解析 + DStream 的输入、转换、输出 + 优化
第1章 Spark Streaming 概述1.1 什么是 Spark Streaming1.2 为什么要学习 Spark Streaming1.3 Spark 与 Storm 的对比第2章 运行 S ...
- Spark Streaming原理简析
执行流程 数据的接收 StreamingContext实例化的时候,需要传入一个SparkContext,然后指定要连接的spark matser url,即连接一个spark engine,用于获得 ...
- Python笔记_第一篇_面向过程_第一部分_2.内存详解
Python的很多教材中并没有讲内存方面的知识,但是内存的知识非常重要,对于计算机工作原理和方便理解编程语言是非常重要的,尤其是小白,因此需要把这一方面加上,能够更加深入的理解编程语言.这里引用了C语 ...
- jsp运行原理及运行过程
JSP的执行过程主要可以分为以下几点: 1)客户端发出请求. 2)Web容器将JSP转译成Servlet源代码. 3)Web容器将产生的源代码进行编译. 4)Web容器加载编译后的代码并执行. 5)把 ...
随机推荐
- Python学习(学习视频b站小甲鱼)
001讲 0. Python 是什么类型的语言? Python是脚本语言以简单的方式快速完成某些复杂的事情通常是创造脚本语言的重要原则. 特性: 语法和结构通常比较简单 学习和使用通常比较简单 通常以 ...
- Linux - Linux中线程为何有PID?
重现 用htop的Tree view(按F5)之后查看线程 参考 https://segmentfault.com/q/1010000003586656 mousycoder的回答 https://u ...
- Qt那点事儿(一)
原文http://www.cnblogs.com/andreitang/archive/2011/08/03/2125815.html 第一回 Signal和Slot是同步的还是异步的? 我们知道Qt ...
- DataGridView 更改Header样式
'必须先设置 EnableHeadersVisualStyles 属性 才能设置Header颜色dgv.EnableHeadersVisualStyles = Falsedgv.ColumnHeade ...
- 左偏树(p4431)
难得不是左偏树,而是思维: 这道题在做得时候,有两个性质 1.如果a是一个不下降序列,那么b[i]==a[i]时取得最优解. 2.如果a是一个严格递减序列,则取a序列的中位数x,令b[1]=b[2]= ...
- [转]使用HttpOnly提升Cookie安全性
原文:https://www.cnblogs.com/zlhff/p/5477943.html 在介绍HttpOnly之前,我想跟大家聊聊Cookie及XSS. 随着B/S的普及,我们平时上网都是依赖 ...
- angular 页面中引入静态 PDF 文件
在web开发时我们有时会需要在线预览PDF内容,在线嵌入pdf文件 常用的几种PDF预览代码片段如下: 方法一: <object type="application/pdf" ...
- python接口自动化测试 - unittest框架基本使用
unittest简单介绍 单元测试框架 还可以适用WEB自动化测试用例的开发与执行 提供丰富的断言方法 官方文档:https://docs.python.org/zh-cn/3/library/uni ...
- laravel搭建博客实战的教程实例
这篇文章主要给大家介绍了关于利用laravel搭建一个迷你博客的相关资料,文中将一步步的实现步骤通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着来一起学习学习吧. 本文主 ...
- acm数论之旅--欧拉函数的证明
随笔 - 20 文章 - 0 评论 - 73 ACM数论之旅7---欧拉函数的证明及代码实现(我会证明都是骗人的╮( ̄▽ ̄)╭) https://blog.csdn.net/chen_ze_hua ...