## 2 平均负载
平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和CPU使用率并没有直接关系
> 获取平均负载
```shell
$ uptime
13:52:16 up 2 days, 16:20, 1 user, load average: 0.81, 0.75, 0.68
# 当前时间、系统运行时间以及正在登录用户数、过去1分钟、5分钟、15分钟的平均负载(Load Average)
```
平均负载最理想的情况是等于**CPU个数**
> 判断系统有几个CPU
```shell
$ cat /proc/cpuinfo | grep 'model name' | wc -l
20
```
CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应:
- CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的
- I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高
- 大量等待 CPU 的进程调度也会导致平均负载升高,此时的CPU使用率也会比较高
通过 iostat、mpstat、pidstat 等工具,找出平均负载升高的根源
-----
**场景一:CPU 密集型进程**
> 在第一个终端运行 stress 命令,模拟一个 CPU 使用率 100% 的场景
```shell
$ stress --cpu 1 --timeout 600
```
> 接着,在第二个终端运行 uptime,查看平均负载的变化情况
```shell
# -d 参数表示高亮显示变化的区域
$ watch -d uptime
```
> 最后,在第三个终端运行mpstat查看 CPU 使用率的变化情况:
```shell
# -P ALL 表示监控所有CPU,后面数字5表示间隔5秒后输出一组数据
$ mpstat -P ALL 5
```
> 使用 pidstat 来查询哪个进程导致了 CPU 使用率升高
```shell
# 间隔5秒后输出一组数据
$ pidstat -u 5 1
```
-----
**场景二:I/O 密集型进程**
> 运行 stress 命令,模拟 I/O 压力,即不停地执行 sync
```shell
$ stress -i 1 --timeout 600
```
> 在第二个终端运行 uptime 查看平均负载的变化情况
```shell
$ watch -d uptime
```
> 第三个终端运行 mpstat 查看 CPU 使用率的变化情况
```shell
# 显示所有 CPU 的指标,并在间隔 5 秒输出一组数据
$ mpstat -P ALL 5 1
```
----
**场景三:大量进程的场景**
> 使用 stress,但这次模拟的是 25 个进程
```shell
$ stress -c 25 --timeout 600
```
```
$ uptime
```
```
$ pidstat -u 5 1
```
小结:
- 平均负载高有可能是 CPU 密集型进程导致的
- 平均负载高并不一定代表 CPU 使用率高,还有可能是 I/O 更繁忙了
- 当发现负载高的时候,可以使用 mpstat、pidstat 等工具,辅助分析负载的来源
## 3 CPU 上下文切换
根据任务的不同,CPU 的上下文切换可以分为几个不同的场景:进程上下文切换、线程上下文切换以及中断上下文切换
**进程上下文切换** 会重新加载 Task 的上下文到CPU寄存器和程序计数器
![进程的运行空间分为内核空间和用户空间](https://static001.geekbang.org/resource/image/4d/a7/4d3f622f272c49132ecb9760310ce1a7.png "进程的运行空间分为内核空间和用户空间")
- 内核空间(Ring 0)具有最高权限,可以直接访问所有资源
- 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过**系统调用**(发生两次CPU上下文切换)陷入到内核中,才能访问这些特权资源
- 但是系统调用过程通常称为特权模式切换,而不是上下文切换。因为进程上下文切换,是指从一个进程切换到另一个进程运行,而系统调用过程中一直是同一个进程在运行
每次上下文切换都需要几十纳秒到数微秒的 CPU 时间([Tsuna 的测试报告](https://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html))
为了排查出现上下文切换的性能问题,需要了解**进程被调度到 CPU 上运行的时机**:
- 某个进程的 CPU 时间片耗尽了,就会被系统挂起,切换到其它正在等待 CPU 的进程运行
- 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行
- 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行
- 发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序
----
**线程上下文切换** 会重新加载线程私有数据
线程与进程最大的区别在于,**线程是调度的基本单位,而进程则是资源拥有的基本单位**;多线程上下文切换比多进程上下文切换消耗更少的资源
----
**中断上下文切换** 会打断进程的正常调度和执行,转而调度中断处理程序,响应设备事件
对于同一个CPU,中断处理比进程拥有更高的优先级,所以中断上下文切换与进程上下文切换不会同时发生
中断上下文切换也需要消耗CPU
----
小结:
- CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要特别关注
- 但过多的上下文切换,会把CPU时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降
----
> 通过 vmstat 获取系统总体的上下文切换情况
```shell
$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 797696 10637784 2301852 11319852 0 0 4 26 3 2 1 0 98 0 0
# cs(context switch) 每秒上下文切换次数
# in(interrupt) 每秒中断次数
# r(Running or Runable) 就绪队列的长度,即正在运行和等待CPU的进程数
# b(Blocked) 处于不可中断睡眠状态的进程数
```
> 通过 pidstat 获取每个进程的详细情况
```shell
$ pidstat -w 5
Linux 5.10.18-amd64-desktop (swan) 2021年06月11日 _x86_64_ (20 CPU)
21时56分26秒 UID PID cswch/s nvcswch/s Command
21时56分31秒 0 1 1.59 0.60 systemd
21时56分31秒 0 12 4.37 0.00 ksoftirqd/0
21时56分31秒 0 13 63.02 0.00 rcu_sched
# cswch(voluntary context switches),每秒自愿上下文切换次数:指进程无法获取所需资源,导致的上下文切换(例如 I/O、内存等系统资源不足时)
# nvcswch(non voluntary context switches),每秒非自愿上下文切换次数:指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换(例如大量进程都在争抢 CPU)
```
- 空白目录
- 精简版Spring的实现
- 0 前言
- 1 注册和获取bean
- 2 抽象工厂实例化bean
- 3 注入bean属性
- 4 通过XML配置beanFactory
- 5 将bean注入到bean
- 6 加入应用程序上下文
- 7 JDK动态代理实现的方法拦截器
- 8 加入切入点和aspectj
- 9 自动创建AOP代理
- Redis原理
- 1 Redis简介与构建
- 1.1 什么是Redis
- 1.2 构建Redis
- 1.3 源码结构
- 2 Redis数据结构与对象
- 2.1 简单动态字符串
- 2.1.1 sds的结构
- 2.1.2 sds与C字符串的区别
- 2.1.3 sds主要操作的API
- 2.2 双向链表
- 2.2.1 adlist的结构
- 2.2.2 adlist和listNode的API
- 2.3 字典
- 2.3.1 字典的结构
- 2.3.2 哈希算法
- 2.3.3 解决键冲突
- 2.3.4 rehash
- 2.3.5 字典的API
- 2.4 跳跃表
- 2.4.1 跳跃表的结构
- 2.4.2 跳跃表的API
- 2.5 整数集合
- 2.5.1 整数集合的结构
- 2.5.2 整数集合的API
- 2.6 压缩列表
- 2.6.1 压缩列表的结构
- 2.6.2 压缩列表结点的结构
- 2.6.3 连锁更新
- 2.6.4 压缩列表API
- 2.7 对象
- 2.7.1 类型
- 2.7.2 编码和底层实现
- 2.7.3 字符串对象
- 2.7.4 列表对象
- 2.7.5 哈希对象
- 2.7.6 集合对象
- 2.7.7 有序集合对象
- 2.7.8 类型检查与命令多态
- 2.7.9 内存回收
- 2.7.10 对象共享
- 2.7.11 对象空转时长
- 3 单机数据库的实现
- 3.1 数据库
- 3.1.1 服务端中的数据库
- 3.1.2 切换数据库
- 3.1.3 数据库键空间
- 3.1.4 过期键的处理
- 3.1.5 数据库通知
- 3.2 RDB持久化
- 操作系统
- 2021-01-08 Linux I/O 操作
- 2021-03-01 Linux 进程控制
- 2021-03-01 Linux 进程通信
- 2021-06-11 Linux 性能优化
- 2021-06-18 性能指标
- 2022-05-05 Android 系统源码阅读笔记
- Java基础
- 2020-07-18 Java 前端编译与优化
- 2020-07-28 Java 虚拟机类加载机制
- 2020-09-11 Java 语法规则
- 2020-09-28 Java 虚拟机字节码执行引擎
- 2020-11-09 class 文件结构
- 2020-12-08 Java 内存模型
- 2021-09-06 Java 并发包
- 代码性能
- 2020-12-03 Java 字符串代码性能
- 2021-01-02 ASM 运行时增强技术
- 理解Unsafe
- Java 8
- 1 行为参数化
- 1.1 行为参数化的实现原理
- 1.2 Java 8中的行为参数化
- 1.3 行为参数化 - 排序
- 1.4 行为参数化 - 线程
- 1.5 泛型实现的行为参数化
- 1.6 小结
- 2 Lambda表达式
- 2.1 Lambda表达式的组成
- 2.2 函数式接口
- 2.2.1 Predicate
- 2.2.2 Consumer
- 2.2.3 Function
- 2.2.4 函数式接口列表
- 2.3 方法引用
- 2.3.1 方法引用的类别
- 2.3.2 构造函数引用
- 2.4 复合方法
- 2.4.1 Comparator复合
- 2.4.2 Predicate复合
- 2.4.3 Function复合
- 3 流处理
- 3.1 流简介
- 3.1.1 流的定义
- 3.1.2 流的特点
- 3.2 流操作
- 3.2.1 中间操作
- 3.2.2 终端操作
- 3.3.3 构建流
- 3.3 流API
- 3.3.1 flatMap的用法
- 3.3.2 reduce的用法
- 3.4 collect操作
- 3.4.1 collect示例
- 3.4.2 Collector接口