一、沉默王二-JVM
1、JVM 性能监控-命令行
1.1 JDK 性能监控工具
除了我们的老朋友 java 和 javac 命令,在 Java 的 bin 目录下,还有很多其他的命令行工具,比如说用于性能监控的 jps、jstat、jinfo、jmap、jstack、jcmd 等等。
1.1.1 jps:查看虚拟机进程
jps(Java Virtual Machine Process Status Tool)类似 Linux 下的 ps,用于快速查看哪些 Java 应用正在运行,以及它们的进程 ID,这对于进一步使用其他 JVM 工具进行诊断是必要的。
jps 命令格式:
jps [ options ] [ hostid ]
jps 命令示例:
①、注意看第三个进程正是我本地运行着的技术派实战项目。
②、pid 是什么?pid 是进程 ID,是操作系统分配给进程的唯一标识符,可以用来查看进程的详细信息。
通常情况下,我们关闭一个进程可以通过右上角的 X 号来完成,但有了 pid,我们可以直接在命令行通过 kill 命令来关闭进程,比如:
kill -9 pid
意思是强制关闭 pid 对应的进程,新手可千万别在生产环境下乱 kill 哈。
再来看一下 jps 的常用选项:
| 选项列表 | 描述 |
|---|---|
| -q | 只输出进程 ID,忽略主类信息 |
| -l | 输出主类全名,或者执行 JAR 包则输出路径 |
| -m | 输出虚拟机进程启动时传递给主类 main() 方法的参数 |
| -v | 输出虚拟机进程启动时的 JVM 参数 |
1.1.2 jstat:查看 JVM 运行时信息
jstat(Java Virtual Machine Statistics Monitoring Tool)用于监控 JVM 的各种运行时状态信息,提供有关垃圾回收、类加载、JIT 编译等运行数据。
jstat 命令格式为:
jstat [ option vmid [interval[s|ms] [count]] ]
选项 option 主要分为三类:类加载、垃圾收集、运行期编译状况。
①、-class:监视类装载、卸载数量、总空间以及类装载所耗费的时间。
如下命令 jstat -class -t 75952 1000 2 会输出进程 75952 的类装载信息,每秒统计一次,一共输出两次。
- Loaded:加载的类的数量。
- Bytes:所有加载类占用的空间大小。
- Unloaded:卸载的类的数量。
- Time:类加载器所花费的时间。
②、-gc:监视 Java 堆状况,包括 Eden 区、2 个 Survivor 区、老年代等容量、已用空间、GC 时间合计等信息。
如下命令 jstat -gc 75952 1000 2 会输出进程 75952 的 GC 信息,每秒统计一次,一共输出两次。结果比较多,我就截断折叠了一下,方便大家查看。
- S0C, S1C, S0U, S1U:Survivor 区的大小和使用情况,一个 From 一个 To,C 为当前大小(Current),U 为已使用大小(Used)。
- EC, EU:Eden 区的大小和使用情况。
- OC, OU:老年代(Old)的大小和使用情况。
- MC, MU:元空间(Metaspace)的大小和使用情况。
- GC,GCC:GC 表示垃圾回收器进行 Minor GC(年轻代垃圾回收)的累计次数和总时间;GCC 表示垃圾回收器进行 Major GC(老年代垃圾回收,也称为 Full GC)的累计次数和总时间。
③、-compiler:监视 JIT 编译器编译过的方法、耗时等信息。
- Compiled:编译的方法数量。
- Failed:编译失败的方法数量。
- Invalid:失效的编译方法数量。
- Time:编译所花费的时间。
如下命令 jstat -compiler 75952 1000 2 会输出进程 75952 的编译信息,每秒统计一次,一共输出两次。
好,我们再来总结一下 jstat 的主要选项,见下表:
| 选项列表 | 描述 |
|---|---|
| -class | 监视类加载、卸载数量、总空间以及类装载所耗费时长 |
| -gc | 监视 Java 堆情况,包括 Eden 区、2 个 Survivor 区、老年代、元空间等,容量、已用空间、垃圾收集时间合计等信息 |
| -gccapacity | 监视内容与-gc 基本一致,但输出主要关注 Java 堆各个区域使用到的最大、最小空间 |
| -gcutil | 监视内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比 |
| -gccause | 与 -gcutil 功能一样,但是会额外输出导致上一次垃圾收集产生的原因 |
| -gcnew | 监视新生代垃圾收集情况 |
| -gcnewcapacity | 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间 |
| -gcold | 监视老年代垃圾收集情况 |
| -gcoldcapacity | 监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间 |
| -compiler | 输出即时编译器编译过的方法、耗时等信息 |
| -printcompilation | 输出已经被即时编译的方法 |
1.1.3 jinfo:查看虚拟机配置
jinfo(Configuration Info for Java)用于在补重启应用的情况下,调整虚拟机的各项参数,或者输出 Java 进程的详细信息。
jinfo 命令格式:
jinfo [ option ] pid
如下命令 jinfo -flags 88952 会输出进程 88952 的 JVM 参数信息。
1.1.4 jmap:导出堆快照
jmap 命令用于生成堆转储快照(一般称为 heap dump 或 dump 文件)。堆转储包含了 JVM 堆中所有对象的信息,包括类、属性、引用等。这对于分析内存泄漏和优化内存使用非常有帮助。
当然了,jmap 的作用不局限于此,它还可以查看堆的空间使用率、当前用的是哪种垃圾收集器等。
jmap 命令格式:
jmap [ option ] vmid
如下命令 jmap -histo 10025 会输出进程 10025 的堆内存中所有对象的数量和占用内存大小的汇总信息,按照内存使用量排序。
如下命令 jmap -dump:format=b,file=heap.hprof 10025 会输出进程 10025 的堆快照信息,保存到文件 heap.hprof 中。
简单解释一下这条命令:
- format:文件格式,这里是 b,表示二进制格式。
- file:文件名。
那么,我们可以用什么工具来打开这个文件呢?后面会讲。我们先来看一下 jmap 的主要选项:
| 选项 | 描述 |
|---|---|
| -dump | 生成 Java 堆转储快照。 |
| -finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。Linux 平台 |
| -heap | 显示 Java 堆详细信息,比如:用了哪种回收器、参数配置、分代情况。Linux 平台 |
| -histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
| -F | 当虚拟机进程对 -dump 选项没有响应式,可以强制生成快照。Linux 平台 |
1.1.5 jstack:跟踪Java堆栈
jstack 用于打印出 JVM 中某个进程或远程调试服务的线程堆栈信息(一般称为 threaddump 或者 javacore 文件)。它常用于诊断应用程序中的线程问题,比如线程死锁、死循环或长时间等待。
jstack 命令格式:
jstack [ option ] vmid
如下 jstack -l 10025 会输出进程 10025 的线程堆栈信息,包括锁信息。
jstack 工具主要选项:
| 选项 | 描述 |
|---|---|
| -F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
| -l | 除了堆栈外,显示关于锁的附加信息 |
| -m | 如果调用的是本地方法的话,可以显示 c/c++的堆栈 |
1.1.6 jcmd:多功能命令
jcmd 是一个多功能命令,可以用于收集堆转储、生成 JVM 和 Java 应用程序的性能数据,以及动态更改某些 Java 运行时参数。jcmd 提供的功能比其他单一命令,如 jstack, jmap, jstat 都要强大。
例如,使用 jcmd -l 列出当前的所有 Java 应用,和 jps 类似:
例如,使用 jcmd 10025 help 查看进程 10025 支持的命令:
例如,使用 jcmd 10025 VM.flags 查看进程 10025 的 JVM 参数,相当于 jinfo -flags 10025:
例如,使用 jcmd 10025 Thread.print 查看进程 10025 的线程信息,相当于 jstack 10025:
jmcd 命令格式:
jcmd <pid | main class> <command ... | PerfCounter.print | -f file>
jmcd 的主要选项:
| 选项 | 描述 | 补充 |
|---|---|---|
| help | 打印帮助信息,示例:jcmd help [] | 无 |
| ManagementAgent.stop | 停止 JMX Agent | 无 |
| ManagementAgent.start_local | 开启本地 JMX Agent | 无 |
| ManagementAgent.start | 开启 JMX Agent | 无 |
| Thread.print | 参数-l 打印 java.util.concurrent 锁信息,相当于:jstack | 无 |
| PerfCounter.print | 相当于:jstat -J-Djstat.showUnsupported=true -snap | 无 |
| GC.class_histogram | 相当于:jmap -histo | 无 |
| GC.heap_dump | 相当于:jmap -dump:format=b,file=xxx.bin | 无 |
| GC.run_finalization | 相当于:System.runFinalization() | 无 |
| GC.run | 相当于:System.gc() | 无 |
| VM.uptime | 参数-date 打印当前时间,VM 启动到现在的时候,以秒为单位显示 | 无 |
| VM.flags | 参数-all 输出全部,相当于:jinfo -flags , jinfo -flag | 无 |
| VM.system_properties | 相当于:jinfo -sysprops | 无 |
| VM.command_line | 相当于:jinfo -sysprops | grep command |
| VM.version | 相当于:jinfo -sysprops | grep version |
1.2 操作系统工具
除了 JDK 自带的命令行,我们很多时候还要使用操作系统为我们提供的命令行工具,来完成性能监控的监测。
比如说 top、vmstat、iostat、netstat 等等。
1.2.1 top:显示系统整体资源使用情况
top 命令用于实时显示系统中各个进程的资源占用情况,如 CPU 和内存使用率。常用于快速查看哪些进程占用了较高的资源。
该命令的输出结果是实时变化的,可以使用 ctrl + c 来退出。下图是我的 macOS 上输出的结果:
top 命令的输出可以分为两个部分:前半部分是系统统计信息,后半部分是进程信息。
1.2.2 统计信息
统计信息是针对整个系统的,主要包括系统负载、CPU 使用率、内存使用情况、虚拟内存使用情况、网络和硬盘使用情况等。
- 第 1 行是进程和线程信息,分别表示总进程数、正在运行的进程数、睡眠的进程数、线程数。
- 第 2 行是负载均衡和 CPU 使用率信息,
Load Avg: 4.02, 3.89, 3.29:这表示过去 1 分钟、5 分钟和 15 分钟的平均系统负载。负载大于 3 意味着系统相对繁忙;CPU usage: 6.97% user, 3.54% sys, 89.47% idle:用户占用了 6.97% 的 CPU,系统占用了 3.54%,还有 89.47% 的 CPU 处于空闲。 - 第 3 行是共享库(Shared Libraries)内存使用的信息。这一行的数据主要涉及到操作系统加载的共享库(如动态链接库或共享对象文件)。
- 第 4 行是内存区域(Memory Regions)的使用信息。内存区域是指操作系统为应用程序和进程分配的内存块。每个内存区域都有特定的用途和属性,比如代码、数据、堆、栈等。这一行的数据提供了系统内存使用的更详细的视图。
- 第 5 行是内存使用情况,
PhysMem: 30G used (3018M wired), 1547M unused:内存总共使用了 30GB,还有大约 1547MB 的内存未使用; - 第 6 行是虚拟内存的信息,虚拟内存是计算机内存管理的一种技术,它为每个程序提供一种“虚拟”的地址空间,这些地址空间对于每个程序来说都是连续的,但实际上可能分散在物理内存和磁盘的交换空间(swap space)上。
- 第 7 行是网络和硬盘信息,
Networks: packets: 22655692/19G in, 19180791/11G out:网络接收了 19GB 的数据包;发送了约 11GB 的数据包;Disks: 14866544/288G read, 15176739/251G written:硬盘读取 14866544 次;写入了约 15176739 次。
1.2.3 进程信息
在进程信息区中,显示了系统各个进程的资源使用情况。主要字段的含义:
- PID:进程 id
- COMMAND:命令名/命令行
- %CPU:进程占用的 CPU 使用率
- TIME:进程使用的 CPU 时间总计,单位 1/100 秒
- MEM:进程使用的物理内存和虚拟内存大小,单位 KB
Windows 用户可以使用 tasklist 命令来查看进程信息。
1.2.4 vmstat:监控内存和 CPU
vmstat 是 Linux 上的一款功能比较齐全的性能监测工具。它可以统计 CPU、内存、swap 的使用情况。
一般 vmstat 工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数,单位是秒,第二个参数是采样的次数,如:
vmstat 1 3 命令表示每秒采样一次,共三次。
输出的各个列的含义:
vmstat 的用法如下:
vmstat [options] [delay [count]]
vmstat 的主要选项:
[options]:提供不同的输出选项,例如 -a 显示活跃和非活跃内存,-d 显示磁盘统计,-s 显示内存统计等。[delay]:在连续模式下,两次报告之间的延迟时间(秒)。[count]:要显示的报告数量。
1.2.5 iostat:监控 IO 使用
iostat 用于统计 CPU 使用信息和磁盘的 IO 信息。
基本用法如下:
iostat [options] [interval [count]]
[options]:提供不同的输出选项。例如,-c 显示 CPU 使用情况,-d 显示磁盘使用情况,-x 显示扩展统计信息等。[interval]:报告之间的延迟时间(秒)。[count]:显示报告的次数。
iostat 的输出包括两个主要部分:
-
CPU 使用情况:
user:用户程序使用的 CPU 时间百分比。system:系统(内核)级程序使用的 CPU 时间百分比。idle:CPU 空闲时间百分比。
-
磁盘 I/O 统计:
tps(Transfers Per Second):每秒传输次数。kB_read/s:每秒读取的千字节数。kB_wrtn/s:每秒写入的千字节数。kB_read和kB_wrtn:分别是读取和写入的总千字节数。
如果使用
-x选项,会显示更详细的统计信息,例如:%util:表示磁盘的繁忙程度。await:I/O 请求的平均等待时间(毫秒)。svctm:服务时间,即完成一个 I/O 请求所需的平均时间。
使用示例如下:
①、查看 CPU 和所有磁盘设备的基本 I/O 统计信息:
iostat
②、查看磁盘 I/O 统计信息,每 2 秒更新一次:
iostat -dx 2
③、只查看 CPU 使用情况:
iostat -c
1.2.6 netstat:监控网络使用
netstat(network statistics)用于监控和显示网络相关信息。基本用法如下:
netstat [options]
[options]:提供不同的输出选项。常见的选项包括-a(显示所有连接和侦听端口),-t(显示 TCP 连接),-u(显示 UDP 连接),-n(以数字形式显示地址和端口号),-r(显示路由表)等。
netstat 的输出通常包括以下几个方面的信息:
①、网络连接:显示活动的或监听的套接字连接,包括服务名、本地地址和端口、远程地址和端口、连接状态等。
②、路由表:显示网络路由表,包括目的地址、网关、子网掩码、使用的接口等。
二、小林-图解网络-HTTP
1、HTTP 基本概念
1.1 HTTP 是什么?
HTTP 是超文本传输协议,也就是HyperText Transfer Protocol。
能否详细解释「超文本传输协议」?
HTTP 的名字「超文本协议传输」,它可以拆成三个部分:
- 超文本
- 传输
- 协议
- 「协议」
HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范(两个以上的参与者),以及相关的各种控制和错误处理方式(行为约定和规范)。
- 「传输」
所谓的「传输」,很好理解,就是把一堆东西从 A 点搬到 B 点,或者从 B 点 搬到 A 点。
别轻视了这个简单的动作,它至少包含两项重要的信息。
HTTP 协议是一个双向协议。
数据虽然是在 A 和 B 之间传输,但允许中间有中转或接力。
在 HTTP 里,需要中间人遵从 HTTP 协议,只要不打扰基本的数据传输,就可以添加任意额外的东西。
针对传输,我们可以进一步理解了 HTTP。
HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。
- 「超文本」
HTTP 传输的内容是「超文本」。
我们先来理解「文本」,在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等,在 HTTP 眼里这些都算作「文本」。
再来理解「超文本」,它就是超越了普通文本的文本,它是文字、图片、视频等的混合体,最关键有超链接,能从一个超文本跳转到另外一个超文本。
HTML 就是最常见的超文本了,它本身只是纯文字文件,但内部用很多标签定义了图片、视频等的链接,再经过浏览器的解释,呈现给我们的就是一个文字、有画面的网页了。
OK,经过了对 HTTP 里这三个名词的详细解释,就可以给出比「超文本传输协议」这七个字更准确更有技术含量的答案:
HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
那「HTTP 是用于从互联网服务器传输超文本到本地浏览器的协议」,这种说法正确吗?
这种说法是不正确的。因为也可以是「服务器< -- >服务器」,所以采用两点之间的描述会更准确。
1.2 HTTP 常见的状态码有哪些?
1.2.1 1xx
1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
1.2.2 2xx
2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
- 「200 OK」是最常见的成功状态码,表示一切正常。如果是非
HEAD请求,服务器返回的响应头都会有 body 数据。 - 「204 No Content」也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
- 「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
1.2.3 3xx
3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
- 「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
- 「302 Found」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
- 「304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。
1.2.4 4xx
4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
- 「400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
- 「403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
- 「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。
1.2.5 5xx
5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
- 「500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
- 「501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
- 「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
- 「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。
1.3 HTTP 常见字段有哪些?
1.3.1 Host 字段
客户端发送请求时,用来指定服务器的域名。
Host: www.A.com
有了 Host 字段,就可以将请求发往「同一台」服务器上的不同网站。
1.3.2 Content-Length 字段
服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据长度。
Content-Length: 1000
如上面则是告诉浏览器,本次服务器回应的数据长度是 1000 个字节,后面的字节就属于下一个回应了。
大家应该都知道 HTTP 是基于 TCP 传输协议进行通信的,而使用了 TCP 传输协议,就会存在一个“粘包”的问题,HTTP 协议通过设置回车符、换行符作为 HTTP header 的边界,通过 Content-Length 字段作为 HTTP body 的边界,这两个方式都是为了解决“粘包”的问题。
1.3.3 Connection 字段
Connection 字段最常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用。
HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定 Connection 首部字段的值为 Keep-Alive。
Connection: Keep-Alive
开启了 HTTP Keep-Alive 机制后, 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接。
PS:大家不要把 HTTP Keep-Alive 和 TCP Keepalive 搞混了,这两个虽然长的像,但是不是一个东西。
1.3.4 Content-Type 字段
Content-Type 字段用于服务器回应时,告诉客户端,本次数据是什么格式。
Content-Type: text/html; Charset=utf-8
上面的类型表明,发送的是网页,而且编码是UTF-8。
客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。
Accept: */*
上面代码中,客户端声明自己可以接受任何格式的数据。
1.3.5 Content-Encoding 字段
Content-Encoding 字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式
Content-Encoding: gzip
上面表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。
客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法。
Accept-Encoding: gzip, deflate
2、GET 与 POST
2.1 GET 和 POST 有什么区别?
根据 RFC 规范,GET 的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。
比如,你打开我的文章,浏览器就会发送 GET 请求给服务器,服务器就会返回文章的所有文字及资源。
根据 RFC 规范,POST 的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。
比如,你在我文章底部,敲入了留言后点击「提交」,浏览器就会执行一次 POST 请求,把你的留言文字放进了报文 body 里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器。
2.2 GET 和 POST 方法都是安全和幂等的吗?
先说明下安全和幂等的概念:
- 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
- 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。
如果从 RFC 规范定义的语义来看:
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
做个简要的小结。
GET 的语义是请求获取指定的资源。GET 方法是安全、幂等、可被缓存的。
POST 的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 不安全,不幂等,(大部分实现)不可缓存。
注意, 上面是从 RFC 规范定义的语义来分析的。
但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:
- 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等。
- 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。
如果「安全」放入概念是指信息是否会被泄漏的话,虽然 POST 用 body 传输数据,而 GET 用 URL 传输,这样数据会在浏览器地址栏容易看到,但是并不能说 GET 不如 POST 安全的。
因为 HTTP 传输的内容都是明文的,虽然在浏览器地址拦看不到 POST 提交的 body 数据,但是只要抓个包就都能看到了。
所以,要避免传输过程中数据被窃取,就要使用 HTTPS 协议,这样所有 HTTP 的数据都会被加密传输。
GET 请求可以带 body 吗?
RFC 规范并没有规定 GET 请求不能带 body 的。理论上,任何请求都可以带 body 的。只是因为 RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到 body。
另外,URL 中的查询参数也不是 GET 所独有的,POST 请求的 URL 中也可以有参数的。
3、HTTP 缓存技术
3.1 HTTP 缓存有哪些实现方式?
对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,这样的话 HTTP/1.1 的性能肯定肉眼可见的提升。
所以,避免发送 HTTP 请求的方法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。
3.2 什么是强制缓存?
强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
如下图中,返回的是 200 状态码,但在 size 项中标识的是 from disk cache,就是使用了强制缓存。
强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
Cache-Control, 是一个相对时间;Expires,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
- 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
3.3 什么是协商缓存?
当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。
上图就是一个协商缓存的过程,所以协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。
协商缓存可以基于两种头部来实现。
第一种:请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现,这两个字段的意思是:
- 响应头部中的
Last-Modified:标示这个响应资源的最后修改时间; - 请求头部中的
If-Modified-Since:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。
第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段,这两个字段的意思是:
- 响应头部中
Etag:唯一标识响应资源; - 请求头部中的
If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。
第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。
如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有 Etag 和 Last-Modified 字段,那么客户端再下一次请求的时候,如果带上了 ETag 和 Last-Modified 字段信息给服务端,这时 Etag 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 有变化就不用在判断 Last-Modified 了,如果 Etag 没有变化,然后再看 Last-Modified。
为什么 ETag 的优先级更高?这是因为 ETag 主要能解决 Last-Modified 几个比较难以解决的问题:
- 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
- 可能有些文件是在秒级以内修改的,
If-Modified-Since能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次; - 有些服务器不能精确获取文件的最后修改时间。
注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
下图是强制缓存和协商缓存的工作流程:
当使用 ETag 字段实现的协商缓存的过程:
-
当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识,这个唯一标识的值是根据当前请求的资源生成的;
-
当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:
- 如果没有过期,则直接使用本地缓存;
- 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
-
服务器再次收到请求后,会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较:
- 如果值相等,则返回 304 Not Modified,不会返回资源;
- 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
-
如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。