L B T

记 录 过 去 的 经 验

环境信息

  • Centos 7 5.4.239-1

Linux 的 namespace 的作用是 隔离内核资 ,目前主要实现了以下 namespace

  • mount namespace - 文件系统挂载点
  • UTS namespace - 主机名
  • IPC namespace - POSIX 进程间通信消息队列
  • PID namespace - 进程 pid 数字空间
  • network namespace - network
  • user namespace - user ID 数字空间
  • cgroup - 资源使用控制
  • time - 隔离时钟(Clock)

其中,除了 network namespace,其他 namespace 的操作需要使用 C 语言调用系统 API 实现。network namespace 的增删改查功能已经集成到了 Linux 的 ip 工具集的 netns 子命令中

Linux 里面的 namespace 给处在其中的进程造成 2 个错觉:

  1. 它是系统里面唯一的进程
  2. 它独享系统的所有资源

默认情况下,Linux 里面的所有进程处在和宿主机相同的 namespace ,即初始 namespace 里,默认享有全局系统资源。

lsns 命令可以查看当前系统上存在哪些 Namespace

# lsns
NS TYPE NPROCS PID USER COMMAND
4026531834 time 251 1 root /lib/systemd/systemd --system --deserialize 52
4026531835 cgroup 224 1 root /lib/systemd/systemd --system --deserialize 52
4026531836 pid 224 1 root /lib/systemd/systemd --system --deserialize 52
4026531837 user 686 1 root /lib/systemd/systemd --system --deserialize 52
4026531838 uts 221 1 root /lib/systemd/systemd --system --deserialize 52
4026531839 ipc 224 1 root /lib/systemd/systemd --system --deserialize 52
4026531840 net 229 1 root /lib/systemd/systemd --system --deserialize 52
4026531841 mnt 213 1 root /lib/systemd/systemd --system --deserialize 52
4026531862 mnt 1 61 root kdevtmpfs
4026532219 mnt 1 1426338 root /lib/systemd/systemd-udevd
4026532220 uts 1 1426338 root /lib/systemd/systemd-udevd
4026532230 mnt 1 2124756 root /lib/systemd/systemd-logind
4026532231 uts 1 2124756 root /lib/systemd/systemd-logind
4026532232 mnt 1 1426274 systemd-timesync /lib/systemd/systemd-timesyncd
4026532233 mnt 1 1426257 root /usr/sbin/irqbalance --foreground
4026532326 mnt 1 2134793 root /usr/sbin/NetworkManager --no-daemon
4026532593 cgroup 7 937225 root python manage.py runserver 0.0.0.0:8080
4026532603 mnt 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532604 uts 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532605 ipc 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532606 pid 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532607 net 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532664 cgroup 1 1416468 472 grafana server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:defau
4026532665 mnt 1 1416534 1001 /opt/bitnami/blackbox-exporter/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml
4026532666 uts 1 1416534 1001 /opt/bitnami/blackbox-exporter/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml
4026532667 ipc 1 1416534 1001 /opt/bitnami/blackbox-exporter/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml
4026532668 pid 1 1416534 1001 /opt/bitnami/blackbox-exporter/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml
4026532669 net 1 1416534 1001 /opt/bitnami/blackbox-exporter/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml

想要查看某个进程都在哪些 namespace 中,可以找到进程 ID (PID),通过查看以下内容或者 namespace 信息

$ ps -elf | grep nginx
4 S root 32679 32659 0 80 0 - 2248 sigsus Apr07 ? 00:00:00 nginx: master process nginx -g daemon off;

$ ll /proc/32679/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 19 13:51 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 ipc -> ipc:[4026534784]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 mnt -> mnt:[4026534583]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 net -> net:[4026534787]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 pid -> pid:[4026534878]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 pid_for_children -> pid:[4026534878]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 19 13:51 uts -> uts:[4026534877]

通过以上命令,可以看到 nginx 进程所属的 namespace,要查看系统初始 namespace ,可以查看 PID 为 1 的进程的 namespace 信息

$ ll /proc/1/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 19 13:53 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 net -> net:[4026531992]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 pid_for_children -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 19 13:53 uts -> uts:[4026531838]

链接文件的内容的格式为 ns 类型: [inode number]。这里的 inode number 则用来标识一个 namespace,我们也可以把它理解为 namespace 的 ID。如果两个进程的某个 namespace 文件指向同一个链接文件,说明其相关资源在同一个 namespace 中。 [1]

脚注

环境信息

  • Centos 7 kernel 5.4.221

启动过程

systemd 管理的系统中,提供了工具 systemd-analyze 用于分析具体的启动过程,使用 systemd-analyze --help 查看使用帮助

检查系统启动时间

使用 systemd-analyze 命令会显示系统启动所用的时间,等同于 systemd-analyze time

# systemd-analyze 
Startup finished in 1.830s (kernel) + 36.827s (userspace) = 38.657s
graphical.target reached after 12.604s in userspace

systemd-analyze blame 列出系统上各个 Unit 启动的时间

# systemd-analyze blame
6.414s wazuh-agent.service
3.161s dracut-initqueue.service
2.473s network.service
1.004s watchdog.service
...
45ms sysstat.service
14ms plymouth-switch-root.service
14ms systemd-journald.service
4ms systemd-logind.service
3ms sys-kernel-config.mount
3ms initrd-udevadm-cleanup-db.service
3ms systemd-random-seed.service
2ms google-shutdown-scripts.service

列出系统各个 Unit 启动消耗的时间

# systemd-analyze critical-chain
The time when unit became active or started is printed after the "@" character.
The time the unit took to start is printed after the "+" character.

graphical.target @12.604s
└─multi-user.target @12.601s
└─skylight-agent.service @36.639s
└─network.target @9.073s
└─NetworkManager.service @8.795s +275ms
└─dbus.service @8.788s
└─basic.target @8.774s
└─sockets.target @8.772s
└─snapd.socket @8.766s +5ms
└─sysinit.target @8.669s
└─cloud-init.service @6.850s +1.811s
└─systemd-networkd-wait-online.service @4.970s +1.871s
└─systemd-networkd.service @4.864s +91ms
└─network-pre.target @4.850s
└─cloud-init-local.service @3.228s +1.620s
└─systemd-remount-fs.service @1.113s +93ms
└─systemd-fsck-root.service @1.030s +69ms
└─systemd-journald.socket @853ms
└─-.mount @692ms
└─-.slice @692ms

查看内存信息

内存相关概念说明:

  • VSS ,Virtual Set Size , VIRT - 虚拟耗用内存(包含共享库占用的内存), 通常 VIRT 是系统承诺分配给应用的内存,不是实际使用的内存
  • RSS , Resident Set Size , RES - 实际使用物理内存(包含共享库占用的内存)
  • PSS , Proportional Set Size - 实际使用的物理内存(比例分配共享库占用的内存)。 top 命令中的 SHR 列展示的就是共享库按比例分配给进程的内存
  • USS , Unique Set Size - 进程独自占用的物理内存(不包含共享库占用的内存)

系统内存使用量统计

free

$ free -h
total used free shared buff/cache available
Mem: 15Gi 7.8Gi 707Mi 449Mi 7.0Gi 6.9Gi
Swap: 30Gi 1.0Gi 29Gi

ps

例如查看使用内存排名前十的进程:

ps aux | sort -k4,4nr | head -n 10

sar 命令

使用 sar 命令检查系统上的内存及 Swap 使用情况

查看某个进程使用的内存量

比如检查 docker 使用的内存量,首先通过 ps 命令查询到 docker 的 PID 信息

$ ps -elf | grep docker
4 S root 1243 1 4 80 0 - 1067527 futex_ Jan03 ? 15:14:45 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

例如此处的 docker 进程的 PID 为 1243

  • 使用 top 命令动态查看 docker 使用的内存信息

    $ top -p 1243
    top - 11:47:40 up 14 days, 2:09, 3 users, load average: 0.65, 1.42, 1.70
    Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
    %Cpu(s): 1.0 us, 0.6 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
    KiB Mem : 32068748 total, 2494500 free, 18536188 used, 11038060 buff/cache
    KiB Swap: 0 total, 0 free, 0 used. 9586340 avail Mem

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    1243 root 20 0 4270108 1.4g 53956 S 1.0 4.6 914:55.80 dockerd
  • 使用 ps aux 命令查看内存使用量

    $ ps aux | grep 1243
    root 1243 4.5 4.6 4270108 1486460 ? Ssl Jan03 914:57 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

    输出结果中

    • 第 3、4 列 (4.5 4.6) 分别表示 cpu 使用率内存使用率
    • 第 5、6 列 (4270108 1486460) 分别表示 虚拟内存使用量物理内存使用量,单位为 k
  • 通过进程的 status 文件查看内存使用

    $ cat /proc/1243/status
    Name: dockerd
    Umask: 0022
    State: S (sleeping)
    Pid: 1243
    PPid: 1
    VmPeak: 4270364 kB
    VmSize: 4270108 kB
    VmLck: 0 kB
    VmPin: 0 kB
    VmHWM: 1562204 kB
    VmRSS: 1492340 kB
    ...

    其中,VmRSS 为进程使用的物理内存

  • 使用 pmap 命令查看进程使用的内存信息

    pmap -x 1243
    pmap -p 1243
  • 使用 pidstat 命令查看进程使用的内存信息

    # pidstat -r -t -p 1424681 1 1
    Linux 6.8.0-1017-aws (U-3TSDMAL9IVFAQ) 11/26/2024 _x86_64_ (4 CPU)

    04:15:40 PM UID TGID TID minflt/s majflt/s VSZ RSS %MEM Command
    04:15:41 PM 408001114 1424681 - 0.00 0.00 34656400 256620 1.59 chrome
    04:15:41 PM 408001114 - 1424681 0.00 0.00 34656400 256620 1.59 |__chrome
    04:15:41 PM 408001114 - 1424696 0.00 0.00 34656400 256620 1.59 |__sandbox_ipc_thr
    04:15:41 PM 408001114 - 1424702 0.00 0.00 34656400 256620 1.59 |__chrome
    04:15:41 PM 408001114 - 1424703 0.00 0.00 34656400 256620 1.59 |__HangWatcher
    04:15:41 PM 408001114 - 1424704 0.00 0.00 34656400 256620 1.59 |__ThreadPoolServi
    04:15:41 PM 408001114 - 1424705 0.00 0.00 34656400 256620 1.59 |__ThreadPoolForeg
    04:15:41 PM 408001114 - 1424706 0.00 0.00 34656400 256620 1.59 |__ThreadPoolForeg

    阅读全文 »

环境信息

  • ansible-core 2.16
  • Docker image python:3.12.3
Ansible 安装部署参考 Ansible templates 使用介绍

Ansible Playbook 语法

Playbooks 使用 YAML 语法定义(描述)。一个 playbook 由一个或多个 play 依序组成。每个 play 运行一个或多个 task,每个 task 也称为一个 module

每一个 play 中包含了一个 tasks 列表,tasks 列表中的每个 task 在其对应的 hosts依次执行即一个 task 执行完毕,下一个 task 才会执行

在运行 playbook 的过程中,如果一个 host 执行 task 失败,这个 host 将从整个 playbook 中移除。如果发生执行失败的情况,需要修正 playbook 中的错误,重新执行。

Ansible playbook 示例:

playbook.yml
---
- name: Update web servers
hosts: webservers
remote_user: root

tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest

- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf

- name: Update db servers
hosts: databases
remote_user: root
vars:
port: 8080

tasks:
- name: Ensure postgresql is at the latest version
ansible.builtin.yum:
name: postgresql
state: latest

- name: Ensure that postgresql is started
ansible.builtin.service:
name: postgresql
state: started

---
- name: Install multiple packages
hosts: webservers
tasks:
- name: Install packages
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curl

一个 Ansible playbook 由一个或多个 plays 组成,每个 play 包含以下部分:

  • name : 描述性的名称
  • hosts : 指定目标主机
  • become : 提升权限(默认是使用 sudo 提升到 root 用户)
  • remote_user : 用于连接到远程主机的账户。(如果 Inventory 中定义了远程连接的用户,会覆盖此处的配置)
  • tasks : 要执行的一系列任务列表
  • vars : 用于定义变量,便于管理和重用
  • gather_facts : 收集 Facts, 默认值为 yes

tasks 是一个任务列表,每个任务执行特定的操作。任务包含以下元素:

  • name : 描述任务的目的。
  • module_name : Ansible 模块名称,如 aptservice 等。
  • module_options : 模块的参数,以键值对的形式提供。
  • when : 条件语句,控制任务是否执行。
  • loop : 循环执行任务

执行以下命令运行 playbook.yml

ansible-playbook playbook.yml -f 10

常用选项说明

选项 说明 示例
-f
--forks
指定并发执行的数量,默认为 5
-v
--verbose
-vvvvvv
打印 debug 信息,详细程度从 -v-vvvvvv
-C
--check
Check mode,不执行任何实际操作,而是对要执行的操作进行验证
-D
--diff
- 只使用 --diff 会执行 play 定义的实际操作,并对所有受影响的文件或者模板显示其变更前后的具体差异
- --check 一起使用,不会执行 play 定义的实际操作,只显示变更前后的差异,可以在实际执行前,调试/预览将要进行的变更,防止意外配置变更或文件修改
主要用于文件或者模板的变更,对于其他类型的任务(如包安装、服务管理、修改主机名等),不会显示具体的差异( 配合 --check 使用时,结果会显示为 skipping,实际执行时结果为 changed )。
--list-hosts 不执行任何实际操作,只列出符合 pattern 的目标主机
--list-tasks 不执行任何实际操作,只列出将要执行的 task
--syntax-check 不执行任何实际操作,只检查 playbook 文件是否有语法错误
阅读全文 »

环境信息

  • Centos 7

sudo 可以配置适当的权限授予普通用户,使普通用户执行 root 用户才能执行的操作

配置 sudo 权限的主要配置文件为 /etc/sudoers/etc/sudoers是一个只读文件,不能直接使用 vim 等编辑器来编辑,要修改此文件,需要以 root 用户身份使用 visudo 命令来修改。

主要配置文件内容如下

/etc/sudoers
## Allow root to run any commands anywhere
root ALL=(ALL) ALL

%wheel ALL=(ALL) NOPASSWD: ALL

各列值含义说明:

  • root 第一列为用户名,如 rootusers%wheel% 开头表示这是一个组,而不是用户
  • ALL=(ALL) 第二列等号左边的 ALL 表示允许从任何主机登录当前的用户账户;等号右边的 ALL 表示第一列的用户可以切换成系统中任何一个其它用户(如:su users);
  • ALL 第三列表示第一列的用户能下达的命令,ALL 表示可以下达任何命令。NOPASSWD: ALL 意味着成员可以执行指定的命令而无需输入密码。

当我们以普通用户身份(以 test 为例)登录,在使用 sudo 命令时报出如下信息:

test is not in the sudoers file. This incident will be reported.

则说明该用户没有在 /etc/sudoers 文件中进行配置,因此无法使用 sudo 命令

AWS 的 Centos 镜像部署后的虚拟机默认使用 centos 用户登陆,登陆后即可执行 sudo su - 切换到 root 用户,此配置由 /etc/sudoers.d/90-cloud-init-users 配置,内容如下:

/etc/sudoers.d/90-cloud-init-users
# cat /etc/sudoers.d/90-cloud-init-users
# Created by cloud-init v. 19.4 on Mon, 31 Oct 2022 07:58:58 +0000

# User rules for centos
centos ALL=(ALL) NOPASSWD:ALL

若要禁止此行为,删除此文件即可。

使用示例

限制特定用户只能执行指定目录下的脚本

visudo 中,你可以使用 Cmnd_Alias 限制用户只能执行特定目录下的脚本。假设你想让用户 centos 只能执行 /usr/local/scripts/ 目录下的脚本,可以按照以下方式配置:

visudo
Cmnd_Alias ALLOWED_SCRIPTS = /usr/local/scripts/*

centos ALL=(ALL) NOPASSWD: ALLOWED_SCRIPTS

含义

  • Cmnd_Alias ALLOWED_SCRIPTS = /usr/local/scripts/*

    定义 ALLOWED_SCRIPTS ,表示 允许执行 /usr/local/scripts/ 目录下的所有脚本

  • qqc ALL=(ALL) NOPASSWD: ALLOWED_SCRIPTS

    允许 centos 用户以 sudo 执行 ALLOWED_SCRIPTS 目录下的所有脚本,且无需输入密码。

切换到 centos 用户,并尝试执行:

sudo /usr/local/scripts/test.sh

成功执行 ,说明规则生效。

但是,如果 centos 用户 试图运行 /bin/bash/usr/bin/python ,或者访问其他路径

$ sudo /bin/bash
Sorry, user centos is not allowed to execute '/bin/bash' as root on this host.

如果希望 centos 用户可以查看自己可以运行的命令:

visudo
Cmnd_Alias ALLOWED_SCRIPTS = /usr/local/scripts/*
qqc ALL=(ALL) NOPASSWD: ALLOWED_SCRIPTS, /usr/bin/sudo -l

这样,centos 用户可以运行:

sudo -l

来查看自己被允许的 sudo 命令。

Nginx 服务配置

全局通用配置

nginx.conf
user nginx nginx;    

# 建议设置为 cpu 核心数或者 cpu 核心数的 2 倍,进程会包含一个 `master process`,多个 `worker process`
# master process 负责绑定端口、调度进程等,不负责业务的处理
# worker process 是业务进程,负责业务的处理
worker_processes auto;

# 一个 worker 进程可以打开的最大的 fd 个数,受 Linux 内核限制
# 理论值应该是系统最多打开文件数(ulimit -n)与 nginx 进程数相除
# 可通过 ulimit 设置或修改系统文件:`/etc/securit/limits.conf`
worker_rlimit_nofile 1024;

# cpu 亲和性设置
worker_cpu_affinity 0001 0010 0100 1000;

# 工作进程调度优先级,-20 到 19 之间的值,值越小越优先调用。
# 如果系统同时运行多个任务,你可能需要提高 nginx 的工作进程的优先级
worker_priority 0;

# ssl 硬件加速服务器,需要硬件支持
# ssl_engine ssl_engine device;

# nginx 是否以守护进程运行,是否让 nignx 运行于后台;调试时可为 off,使得所有信息直接输出在控制台
daemon on | off;

# events 模块中包含 nginx 中所有处理连接的设置。
events {
# 每个 worker 进程允许的最多连接数,
# nginx 服务最大连接数:worker_processes * worker_connections (受 worker_rlimit_nofile 限制)
worker_connections 1024;
use epoll;

# 是否允许一次性地响应多个用户请求
multi_accept on;

# 是否打开 nginx 的 accept 锁;此锁能够让多个 worker 进行轮流地、序列化地与新的客户端建立连接;
# 而通常当一个 worker 进程的负载达到其上限的 7/8,master 就尽可能不将请求调度至 worker.
accept_mutex on | off;
}

# HTTP 模块控制着 nginx http 处理的所有核心特性
http {
include mime.types;
default_type application/octet-stream;

# 是否在错误页面中显示和响应头字段中发出 nginx 版本号。
# 安全考虑建议关闭
server_tokens on | off | string;

# 是否启用 sendfile 内核复制模式功能。作为静态服务器可以提供最大的 IO 访问速度。
sendfile on | off;

# 尽快发送数据,否则会在数据包达到一定大小后再发送数据。这样会减少网络通信次数,降低阻塞概率,但也会影响响应及时性。
# 比较适合于文件下载这类的大数据通信场景。
tcp_nodelay on|off;

# 单位s,适当降低此值可以提高响应连接数量
keepalive_timeout 65;

# 一次长连接上允许的最大请求数
keepalive_requests 100;

# 禁止指定浏览器使用 keepalive
keepalive_disable msie6|none;

# 读取 http 请求首部的超时时长。如果客户端在此时间内未传输整个头,则会向客户端返回 408(请求超时)错误
client_header_timeout 1;

# 读取 http 请求包体的超时时间。
client_body_timeout 2;

# 发送响应的超时时长。超时后连接将关闭。
send_timeout 5;

# http 请求包体的最大值,常用于限定客户端所能够请求的最大包体,根据请求首部中的 Content-Length 来检查,以避免无用的传输。
client_max_body_size 1m;

# 限制客户端每秒传输的字节数,默认为0,表示没有限制。单位 Byte/s
limit_rate 0;

# nginx 向客户端发送响应报文时,如果大小超过了此处指定的值,则后续的发送过程开始限速,单位 Byte
limit_rate_after 0;

# 是否忽略不合法的 http 首部,默认为 on,off 意味着请求首部中出现不合规的首部将拒绝响应。
ignore_invalid_headers on|off;

# 用户访问的文件不存在时,是否将其记录到错误日志中。
log_not_found on|off;

# nginx 使用的 dns 地址,及缓存解析结果的时间
resolver 8.8.8.8 [valid=time] [ipv6=on|off];

# dns 解析超时时间
resolver_timeout 2;

# 是否打开文件缓存功能,max:用于缓存条目的最大值,
# inactive:某缓存条目在指定时长内没有被访问过时,将自动被删除,即缓存有效期,通常默认为 60s。
open_file_cache off;
open_file_cache max=N [inactive=time];

# 是否缓存文件找不到或没有权限访问等相关信息。
open_file_cache_errors on | off;

# 多长时间检查一次缓存中的条目是否超出非活动时长。
# 建议值:小于等于 open_file_cache inactive
open_file_cache_valid 60;

# 在 open_file_cache inactive指 定的时长内被访问超过此处指定的次数时,才不会被删除(删除低命中率的缓存)。
open_file_cache_min_uses 2;

# 开启内容压缩,可以有效降低客户端的访问流量和网络带宽
gzip on | off;

# 内容超过最少长度后才开启压缩,太短的内容压缩效果不佳,且会浪费系统资源。
# 压缩长度会作为 http 响应头 Content-Length 字段返回给客户端。 建议值:64
gzip_min_length length;

# 压缩级别,默认值为 1。范围为1~9级,压缩级别越高压缩率越高,但对系统性能要求越高。建议值:4
gzip_comp_level 1~9;

# 压缩内容类型,默认为 text/html;。只压缩 html 文本,一般我们都会压缩 js、css、json 之类的,可以把这些常见的文本数据都配上。
如:text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_types mime-type …;

# 自动显示目录
autoindex on;

# off : 以人类易读的方式显示文件大小,on:以 bytes 显示文件大小
autoindex_exact_size off;

# 定义限制区域,imit_req_zone 只能放在 http {} 内,使用此限制可以在 http {} (对服务器内所有的网站生效)、server {} (对具体的一个网站生效)或 location {} (对具体的一个网址生效)
# 此区域名称为 test,可根据需求自定义; 10m 表示此区域存储 key($binary_remote_addr)使用的大小
# 存储大小,一般 1m 空间大约能保存 1.6 万条 IP 地址,空间满了新数据会覆盖旧数据
# rate=1r/m ,限制访问请求频率为每分钟 1 次,可根据需要自行设置,1r/s 是 1 秒 1 次,时间单位只能选择 s (秒)或 m (分),最低频率限制是每分钟 1 次访问请求
# rate=10r/m,1分钟最多访问 10 次,同时不能超过 1r/6s,即 6s 内最多访问 1 次。超过 1r/6s 返回 503
limit_req_zone $binary_remote_addr zone=test:10m rate=1r/m;

# 定义日志格式
log_format main '{ time: $time_iso8601|'
'http_host:$http_host|'
'cdn_ip:$remote_addr|'
'request:$request|'
'request_method:$request_method|'
'http_user_agent:$http_user_agent|'
'size:$body_bytes_sent|'
'responsetime:$request_time|'
'upstreamtime:$upstream_response_time|'
'upstreamhost:$upstream_addr|'
'upstreamstatus:$upstream_status|'
'url:$http_host$uri|'
'http_x_forwarded_for:$clientRealIp|'
'status:$status}';

# server 负责具体的 http 服务器实现
server {
listen 80 [default_server] [rcvbuf=SIZE] [sndbuf=SIZE] [ssl];

# 可使用通配符*或正则表达式(~开头),多个域名先精确匹配,再通配,再正则,'_'表示空主机头
server_name _ ;

access_log logs/access.log main;
error_log logs/access.err.log;

# 跨域配置
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With';
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Credentials: true;

location / {
# web 资源路径
root html;

# 定义默认页面,从左往右匹配
index index.html index.htm;

# 自左向右读取指定路径,找到即停止,如果都不存在,返回一个错误码
try_files $uri $uri.html $uri/index.html =404;

# 自左向右读取指定路径,找到即停止,如果都不存在,返回一个 uri
try_files $uri $uri.html $uri/index.html /404.html;
}

location /i/ {
# 路径别名,只能用于 location 中。
# 访问 http://a.com/i/a.html, 资源路径为:/data/www/html/a.html
# 若是root指令,访问 http://a.com/i/a.html,资源路径为:/data/www/html/i/a.html
alias /data/www/html/;
}

# 对于某个请求发生错误,如果匹配到错误码,重定向到新的 url
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

# 对于某个请求发生错误,如果匹配到错误码,重定向到新的 url,同时可以更改返回码
error_page 404 =200 /404.html;
}

# 包含其他配置文件
include vhosts/*.conf;
}


阅读全文 »

Memory 相关的术语说明

  • Main Memory - 也经常称为 Physical Memory,计算机上的 Fast Data Storage Area。
  • Virtual Memory - Main Memory 的一个抽象层,他几乎有无限大的空间,Virtual Memory 不是 Main Memory
  • Resident Memory - 驻留(Reside)在 Main Memory 中的内存,相当于实际使用的物理内存(Main Memory/Physical Memory),如 top 命令中的 RESps aux 命令中的 RSS 就是指 Resident Memory.
  • Anonymous Memory - 未关联文件系统位置和路径的内存,通常指 Process Address Space 中的程序运行过程中的数据(Working Data),通常被称为 Heap
  • Address Space - 内存地址空间,内存地址相关的上下文(Context),包含程序(Processes)和内核(Kernel)使用的 Virtual Address Space
  • Segment - 用于标识 Virtual Memory 中的有特殊作用的一个区域,如可执行程序(Executable)或可写(Writable)的 Page
  • Instruction Text - CPU 指令(Instructions) 在内存中的引用地址,通常位于 Segment
  • OOM - Out Of Memory,当内核检测到系统可用内存不足时采取的动作
  • Page - OS 和 CPU 使用和分配内存的单位,早期大小一般为 4 或 8 Kbytes,现代化的 CPU 和 OS 通常支持 Multi Page Sizes
  • Page Fault - 通常在需要访问的内容不存在于 Virtual Memory 中时,系统产生一个中断,导致所需内容加载入内存
  • Paging - 当内存中的内容不再使用或内存空间不足时进行的在内存和 Storage Devices 中的内容交换,主要是为了空出内存供需要内存的进程使用
  • Swapping - Linux 中将不再使用或内存空间不足时,将部分内存中的内容 Paging 到 Swap Devices
  • Swap - Linux 中 Swapping 时,将内容转移到的目标,可能是 Storage Devices 上的一个区域,被称为 Physical Swap Device,或者是一个文件系统文件,称为 Swap File。

Memory 部分概念详细说明参考

MMU

Memory Management Unit(MMU) 负责虚拟内存地址(Virtual Memory Address)到物理内存地址(Physical Memory Address)的转换

Freeing Memory

当系统上可用内存低或不足时,系统会采用一系列的手段释放内存。主要包括下图所示方式

  • Free List
    不在使用中的 Pages 列表,也称为 Idle Memory,这部分内存可以被系统立即分配给需要的程序使用
  • Page Cache
    文件系统缓存(Filesystem Cache)。有个 swappiness 的参数可以配置系统是使用 Page Cache 还是 Swapping 来释放内存
  • Swapping
    通过内核进程 kswapd 实现 Paging Out 到 Swap Device 或者 File System-Based Swap File,这只有在系统上有 Swap 时才有用。
  • Reaping
    也被称为 Shrinking ,当系统可用内存小到一个临界值后,内核就会开始释放可以回收的内存
  • OOM Killer
    Out Of Memory Killer ,系统内存不足时,系统会使用 OOM Killer 机制来 kill 掉某个进程来释放内存。

在 Linux 中,当系统可用内存低于阈值(vm.min_free_kbytes)时,Page Out Daemon(kswapd) 会启动 Page Scanning

进程的内存分层结构

进程的内存结构一般被分成多个 segment,包括

  • Executable Text segment - 存放程序代码(the executable CPU Instructions), 只读
  • Executable Data section - 存放程序初始化全局变量(global variables),通常 可读写 ,写权限用于程序运行期间更新变量值。
  • Heap section - 程序运行过程中动态分配的内存,属于 Anonymous Memory
  • Stack section - 调用程序功能时的临时数据存储,如函数参数、返回地址、本地变量等。

下图展示了 C 程序(Program)在内存中的分层结构(layout of a C program in memory)

  • 其中, Data section 被分成了 2 部分,包括 (a) initialized data(b) uninitialized data

使用 GNU 工具 size 可以检查 Projram 在磁盘上的 内存布局。这些值在程序编译时确定,并不会在程序运行时变化,因此它们是固定不变的。

# size /usr/sbin/sshd
text data bss dec hex filename
817397 15460 37664 870521 d4879 /usr/sbin/sshd

输出信息中:

  • text : 代表 Text section 的大小
  • data : 初始化数据段(initialized data)的大小,包含已初始化的全局和静态变量。
  • bss : 未初始化数据段的大小,包含未初始化的全局和静态变量。
  • dec : 上述所有部分的总大小,以十进制表示。
  • hex : 上述所有部分的总大小,以十六进制表示。
阅读全文 »

Linux 中典型的文件系统模型(以接口形式)如下图: [1]

File System Cache

典型的 File System Cache 模型如下图:

因为文件系统缓存(File System Cache)的存在,在统计 Applications 的读写请求延迟(IO Latency)时,要注意区分分析的统计数据是 文件系统(File System)的延迟(Request Latency) 还是 物理存储设备(Physical Device)的延迟

OS 通常提供的 IO 统计数据是 存储设备级别(Disk Device-level) ,而不是 文件系统级别(File System Level) ,但是大多数情况下,影响 Application 性能的通常是 File System 级别的延迟(Latency),而不是物理存储设备(Physical Device)级别的延迟。比如 File System 对 Application 的写操作(Write Operations)会进行缓存(Buffers),缓存成功后立即向 Application 返回写成功的响应,文件系统会在后台定期的将 Buffer 里面的内容刷新(Write-back, 写回)回磁盘设备,这个写回操作会导致磁盘设备出现较高或者突发(Burstable)的 Disk IO Latency,从 Disk Device-level 统计数据来看,这可能是个问题,但是,Application 并不需要等待此时的写回操作,此时的 Disk Device-level IO Latency 对 Application 无任何的性能影响。

File System 通常使用 Main Memory(RAM)作为缓存(Cache)介质来提高性能 ,Cache 的处理过程对 Applications 来说是透明的。

OS 中 File System 涉及到的相关 Cache 如下表:

Cache Example
Page cache Operating system page cache
File system primary cache ZFS ARC
File system secondary cache ZFS L2ARC
Directory cache dentry cache
inode cache inode cache
Device cache ZFS vdev
Block device cache Buffer cache

Prefetch

文件系统预取(File System Prefetch) 是 Linux 内核的一种优化机制,用于 提前加载 可能会被访问的文件或数据到内存,以提高 读取性能系统响应速度

当程序读取文件时,Linux 内核可能会:

  • 提前读取相邻的数据块,即顺序预取(Read-Ahead)
  • 基于访问模式预测未来的数据请求,即智能预取(Adaptive Readahead)
  • 结合缓存管理(Page Cache)减少磁盘 I/O,提高性能

Linux 文件系统预取主要依赖于以下几种机制:

  • Page Cache(页缓存) 。Linux 通过 Page Cache 缓存已经读取的数据,以 减少磁盘 I/O,提高性能
    • 当进程请求读取文件时,内核会先检查 Page Cache,如果数据已存在,则直接返回,避免磁盘读取。
    • 如果数据不在缓存中,Linux 会从磁盘加载数据,并存入 Page Cache,方便后续访问。
  • Read-Ahead(预读机制) 是 Linux 预取的核心机制之一,它会 提前加载文件数据,减少未来读取时的磁盘 I/O
    • 当应用程序顺序读取文件时,Linux 会自动预取更多的数据,提高性能。
    • Linux 预取大小 动态调整 ,如果发现访问是顺序的,会增加预取数据量。
  • Readahead 调优参数 。Linux 通过 /sys/class/bdi/ 目录下的参数进行 Read-Ahead 调优
    • /sys/class/bdi/default/read_ahead_kb 这个值通常默认为 128 KB 或 256 KB,表示内核每次读取至少 128 KB 以优化性能。
    • echo 1024 > /sys/class/bdi/default/read_ahead_kb 设置为 1024 KB(1MB),适用于 大文件顺序读取
    • Fadvise 和 Madvise 提示 。Linux 提供了 posix_fadvise()madvise() ,让应用程序 主动提示内核 预取策略
      • posix_fadvise(fd, offset, len, POSIX_FADV_WILLNEED) 提示内核: 这个文件很快会被读取,可以提前加载进 Page Cache
      • posix_fadvise(fd, offset, len, POSIX_FADV_SEQUENTIAL) 告诉内核: 文件是顺序读取的,可以增大 Read-Ahead
  • Prefetching Daemon(预取守护进程)

查看和设置当前 Read-Ahead 的值,单位为 Block,一般为 512B

# lsblk 
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 9.1T 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 1G 0 part /boot
├─sda3 8:3 0 32G 0 part [SWAP]
└─sda4 8:4 0 9.1T 0 part /

# blockdev --getra /dev/sda
256

# blockdev --setra 512 /dev/sda

# blockdev --getra /dev/sda
512

Write-Back Caching

Write-Back Caching 通常用于文件系统 写缓存(Write Cache、Buffer) ,在 Application 需要写入内容时,文件系统将数据写入 Main Memory 就算成功,文件系统随后再将内容(Dirty Data)异步(Asynchronous)写入(Flushing)磁盘(Disk),通过此过程来 提高文件系统写入性能

在文件系统使用 Write-Back Caching 的情况下,假如应用程序发布(Issue)了写请求(Write Requests),Kernel 将内容写入 Main Memory 后便向 Application 返回了写入成功的响应,假设此时系统断电,因为缓存中的内容(Dirty Data)并未写入(Flushing)磁盘(Disk),会导致 RAM 中的内容丢失,出现文件不一致的情况,为了平衡 性能(Performance)和可靠性(Reliability),File System 会默认使用 Write-Back Caching,同时提供 同步写(Synchronous Write)选项来跳过 Write-Back Caching,直接将数据写入磁盘(Disk/Persistent Storage Device)

Synchronous Write

Synchronous Write(同步写入) 只有当数据完全写入 Persistent Storage Device(持久化存储设备)后才算写入完成,包括任何的 File System Metadata 的变更。它比 Asynchronous Writes(Write-Back Caching) 慢,因为需要额外的 Disk Device IO Latency 以及 File System Metadata 变更导致的 IO,Synchronous Write 通常应用在对数据一致性要求较高的应用中,如 Database Log Writers.

Raw IO

Raw IO 直接向存储设备发送请求,完全绕过了文件系统,通常在 Database 场景中较为常见,因为数据库软件可以比文件系统更好的管理和缓存他们的数据,缺点是其增加了软件的复杂度和管理的复杂度。

存储设备接口

在计算机存储领域,SCSI、SAS、ATA、SATA、FC 和 NVMe 是常见的存储接口标准。以下是对这些接口的简要介绍和比较:

接口类型 传输方式 最大传输速率 特点说明 应用场景
ATA(Advanced Technology Attachment)
也称为 IDEPATA
并行 133 MB/s 是一种并行接口标准,主要用于连接存储设备。由于传输速度和性能的限制,ATA 已逐渐被 SATA 所取代。 个人电脑(已被淘汰)
SATA(Serial ATA) 串行 6 Gbps SATA 是 ATA 的串行版本,旨在提高传输速度和效率。它采用串行通信方式,具有更高的传输速率和更长的电缆长度。
传输速度高,支持热插拔,广泛应用于个人电脑和低端服务器。
个人电脑、低端服务器
SCSI(Small Computer System Interface) 并行 320 MB/s 最初用于连接计算机与硬盘、光驱等外部设备。它支持多设备连接,具有较高的传输速度和可靠性。
支持多任务处理,系统占用率低,适用于服务器等高端应用场景。
服务器、高端存储
SAS(Serial Attached SCSI) 串行 12 Gbps SAS 是 SCSI 的串行版本,旨在提高传输速度和扩展性。它采用串行通信方式,支持更高的传输速率,并向下兼容 SATA 设备。
传输速度高,支持热插拔,适用于企业级存储系统。SAS 控制器可以直接控制 SATA 硬盘,但 SATA 控制器无法控制 SAS 硬盘。
企业级存储
FC(Fibre Channel) 串行 16 Gbps FC 是一种高速网络技术,最初用于连接大型存储系统。它支持高带宽和低延迟,常用于存储区域网络(SAN)。传输速度高,可靠性强,适用于大型企业级存储环境。 存储区域网络
NVMe 串行 32 Gbps(PCIe 4.0 x4) NVMe 是为固态硬盘(SSD)设计的高速接口协议,旨在充分利用 NAND 闪存的性能优势。它通过 PCIe 总线直接与 CPU 通信,提供低延迟和高并发性。
传输速度极高,延迟低,适用于高性能存储需求。
高性能存储

Observability Tools

在基于 Linux 的系统中,可以使用以下工具来观察存储设备(磁盘)I/O 的性能统计数据

Tool Description Examples
iostat 存储设备(磁盘)上的 IO 统计数据及 CPU 使用率
sar -b 文件系统层(VFS)的 IO 统计数据 sar 命令使用参考
sar -d 物理磁盘(存储设备)层上的 IO 统计数据 sar 命令使用参考

iostat

iostat 查看系统上的存储设备及分区的 IO 使用情况,常用选项及输出指标说明请参考 man iostat

# iostat -h -p -x 1 
Linux 3.10.0-1160.36.2.el7.x86_64 (qz1-aws-flutter-api2) 02/14/2025 _x86_64_ (32 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
9.36 0.00 3.15 0.00 0.03 87.46

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1
0.00 0.18 0.02 3.25 0.50 124.24 76.37 0.00 0.90 0.62 0.90 0.85 0.28
nvme0n1p1
0.00 0.18 0.02 3.25 0.50 124.24 76.37 0.00 0.90 0.62 0.90 0.85 0.28

参考链接|Bibliography

Systems Performance: Enterprise and the Cloud v2

脚注

sysctl 控制参数

下表列出一些常见的 Linux (Kernel 5.3)内存相关的调节参数,具体参数根据内核版本可能有所不同,其使用场景和含义需要查看对应内核版本的相关文档

Option Default Value Description Examples
vm.dirty_background_bytes
/proc/sys/vm/dirty_background_bytes
0 bytes 0 表示使用 vm.dirty_background_ratio 来决定将内存脏页(Memroy Dirty Pages)写回磁盘的阈值而不是使用 vm.dirty_background_bytes
- 设置为相对较高的值,会使内存中的数据延迟写入磁盘,产生较小的 IOPS,但是可能会导致数据不一致或丢失
- 设置为相对较低的值,会使内存数据及时写入磁盘,导致 IOPS 较高,数据丢失或不一致的风险较低
vm.dirty_background_ratio
/proc/sys/vm/dirty_background_ratio
10 默认当内存脏页数据达到内存大小的 10% 时,在后台触发 per-bdi writeback(Linux 早期(Linux 内核 3.0 之前)由 pdflush 负责处理脏页(dirty pages)写回磁盘的机制)将脏页数据写回磁盘。回写(Write-Back)操作由统一的内核线程 kworkerflush-<设备名> 处理
vm.dirty_bytes
/proc/sys/vm/dirty_bytes
0 bytes 定义强制写回的脏页阈值(以字节为单位)。
vm.dirty_ratio
/proc/sys/vm/dirty_ratio
20 定义强制写回的脏页阈值(以总内存的百分比表示)。
vm.dirty_writeback_centisecs
/proc/sys/vm/dirty_writeback_centisecs
500 定义写回线程的执行间隔(以百分之一秒为单位)。
vm.dirty_expire_centisecs
/proc/sys/vm/dirty_expire_centisecs
3000 定义脏页的最大“年龄”(超过这个时间的脏页会被优先写回)。
如需手动执行写回操作,可以使用命令 sync
vm.min_free_kbytes
/proc/sys/vm/min_free_kbytes
通常为 min_free_kbytes = sqrt(总内存 * 16) 控制系统保留的最小空闲内存量(以 KB 为单位),确保系统在内存压力下仍有足够的内存用于关键操作,如处理中断、内核操作等。
如果设置过小
- 系统可能在内存紧张时无法及时回收内存,导致性能下降。
- 网络流量或 I/O 密集型任务可能因内存分配失败而中断。
- 可能增加系统触发 OOM(Out-Of-Memory)的风险。
如果设置过大
- 系统可用内存减少,因为更多内存被预留。
- 可能导致用户空间任务频繁触发内存回收,降低整体性能。
vm.watermark_scale_factor
/proc/sys/vm/watermark_scale_factor
10 内核的内存水位标记( watermarks )机制
vm.watermark_boost_factor
/proc/sys/vm/watermark_boost_factor
15000 在内存压力下临时提升水位,保证一定的空闲内存。
vm.percpu_pagelist_high_fraction
/proc/sys/vm/percpu_pagelist_high_fraction
0 vm.percpu_pagelist_high_fraction
vm.overcommit_memory
/proc/sys/vm/overcommit_memory
vm.overcommit_ratio
vm.overcommit_kbytes
Linux 内存 Overcommit 机制详解
vm.swappiness
/proc/sys/vm/swappiness
60 控制 内核在内存不足前主动使用 Swap 的程度 ,取值范围: 0 - 100
- 0 : 尽可能 不使用 Swap ,只有在内存耗尽时才使用(适合数据库、低延迟应用)。
- 100 : 尽可能 频繁使用 Swap(适合桌面系统)
vm.vfs_cache_pressure 100 vm.vfs_cache_pressure
vm.admin_reserve_kbytes
/proc/sys/vm/admin_reserve_kbytes
3% Free Pages 为系统管理员保留一定量的内存,以防止在内存紧张时关键的管理任务(如登录、执行命令等)无法正常运行。这部分内存不会被普通用户进程占用,即使在系统内存紧张时也会保留。
memory_failure_early_kill
/proc/sys/vm/memory_failure_early_kill
0 用于控制在发生内存故障时,系统是否立即将有问题的内存页标记为不可用,并杀死访问该内存的进程。
- 默认值是 0 ,表示不立即杀死进程。
- 启用后(值为 1) 当检测到内存错误时,系统会尽早杀死访问该内存的进程,以避免更多的系统崩溃或错误发生。
memory_failure_recovery
/proc/sys/vm/memory_failure_recovery
1 当检测到内存故障时,系统可以尝试 恢复错误的内存页
- 默认值是 1 ,表示启用内存恢复机制。
- 禁用后(值为 0) : 系统不尝试恢复内存错误,可能会更直接地采取 杀死进程标记内存为不可用 的操作。
/proc/sys/vm/drop_caches 默认值始终为 0 允许管理员手动清理 Linux 内存缓存,包括:
- Page Cache(页面缓存) : 主要用于加速文件读取。
- Dentry Cache(目录项缓存) : 记录文件路径信息,加快目录访问。
- Inode Cache(索引节点缓存) : 记录文件元数据,如大小、权限等。
drop_caches 不会影响进程已使用的内存,只是释放 内核缓存 适用于 测试、性能调优、观察内存使用情况 ,但不建议频繁使用。
可选值包括:
- 1 : 清理 Page Cache ,释放文件数据缓存
- 2 : 清理 DentryInode Cache,释放目录路径和文件元数据缓存
- 3 : 清理 Page CacheDentryInode Cache(全部)
建议同步数据到磁盘(sync)后清理,防止数据丢失
/proc/sys/vm/compact_memory 默认值始终为 0 compact_memory
阅读全文 »

S3

s3 配置跨域

当使用 aws 的 cloudfront 或其他第三方 cdn (如 cdn77)为域名加速,资源是回源到 aws s3 的情况下,一般都需要配置 允许跨域,此种情况需要在 aws s3 存储桶中配置允许跨域 [1]

配置步骤如下:

  1. 定位到目标 s3 桶,进入 权限 管理页面

  2. 找到 CORS 配置,配置以下 JSON 格式内容,允许所有源的跨域

    [
    {
    "AllowedHeaders": [
    "*"
    ],
    "AllowedMethods": [
    "POST",
    "GET"
    ],
    "AllowedOrigins": [
    "*"
    ],
    "ExposeHeaders": []
    }
    ]

  3. Cloudfront 中,回源到此 S3 的加速域名配置中,行为 按照下图配置,主要为开启 OPTIONS 缓存,并在响应标头策略中选择: CORS-with-preflight-and-SecurityHeadersPolicy [2]

    此配置为可选配置,可以解决客户端偶尔会遇到的因跨域问题而导致的资源获取失败问题。

    阅读全文 »

环境信息

  • Python3.10

Python 执行 shell 命令

subprocess 模块

subprocess.check_output() 执行一个外部命令并以Python字符串的形式获取执行结果 [1]

import subprocess
out_bytes = subprocess.check_output(['netstat','-a'])

如果你需要文本形式返回,加一个解码步骤即可

out_text = out_bytes.decode('utf-8')

如果被执行的命令以非零码返回,就会抛出异常。 下面的例子捕获到错误并获取返回码:

try:
out_bytes = subprocess.check_output(['cmd','arg1','arg2'])
except subprocess.CalledProcessError as e:
out_bytes = e.output # Output generated before error
code = e.returncode # Return code

默认情况下,check_output() 仅仅返回输入到标准输出的值。 如果你需要同时收集标准输出和错误输出,使用 stderr 参数:

out_bytes = subprocess.check_output(['cmd','arg1','arg2'],
stderr=subprocess.STDOUT)
阅读全文 »

环境信息说明

  • Docker 镜像 majiajue/jdk1.8

以下示例记录实现获取百度云账户余额演示简单 Java 脚本在 Docker 容器中运行的过程和错误

运行 docker 容器

docker run -it majiajue/jdk1.8 bash

在容器中下载 SDK 并将其放在 /app/ 目录下,如 /app/bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar,为了让 JAVA 能找到此 JAR 文件,在容器路径 ~/.bashrc 中添加自定义的 Jar 文件路径

~/.bashrc
export CLASSPATH=$CLASSPATH:/app/bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar:/app/bce-java-sdk-0.10.351/lib/guava-33.4.0-jre.jar:/app/bce-java-sdk-0.10.351/third-party/*

获取余额的代码如下,容器中代码路径为 /app/BalanceQuery.java

/app/BalanceQuery.java
import com.baidubce.auth.DefaultBceCredentials;
import com.baidubce.services.billing.BillingClient;
import com.baidubce.services.billing.BillingClientConfiguration;
import com.baidubce.services.billing.model.finance.SupervisorBalanceQueryRequest;
import com.baidubce.services.billing.model.finance.UserBalanceQueryRequest;
import com.baidubce.services.billing.model.finance.UserBalanceQueryResponse;
import com.baidubce.services.billing.model.finance.SupervisorBalanceResponse;
import java.util.Arrays;

public class BalanceQuery {

public static void main(String[] args) {
String accessKeyId = "ALTAKINjySCAAAAAAAAAAAAAAAAAAAAAAAA";
String secretAccessKey = "5290cfc18BBBBBBBBBBBBBBBBBBBBB";

BillingClientConfiguration config = new BillingClientConfiguration();
config.setCredentials(new DefaultBceCredentials(accessKeyId, secretAccessKey));
config.setEndpoint("https://billing.baidubce.com");

BillingClient client = new BillingClient(config);

try {

UserBalanceQueryResponse response = client.userBalanceQuery();

System.out.println("Balance: " + response.getCashBalance());
} catch (Exception e) {
e.printStackTrace();
System.err.println("Failed");
}
}
}

在容器中(/app/)编译执行

如果未在容器环境变量中添加自定义的 JAR 路径(如 /app/bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar:/app/bce-java-sdk-0.10.351/lib/guava-33.4.0-jre.jar:/app/bce-java-sdk-0.10.351/third-party/*) 需要在编译(javac) 和执行 (java)时使用参数 -cp "/app/bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar:/app/bce-java-sdk-0.10.351/lib/guava-33.4.0-jre.jar:/app/bce-java-sdk-0.10.351/third-party/*:." 指定自定义文件路径,否则会导致某些类找不到

需要注意 -cp 选项中需要包含当前目录 . ,否则会导致找不到编译后的脚本文件,报错: Error: Could not find or load main class BalanceQuery

# cd /app/

# javac BalanceQuery.java

# java BalanceQuery
Balance: 1999.5100

java -cp "./bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar:./bce-java-sdk-0.10.351/third-party/*:." BalanceQuery
Balance: 1999.5100

如需在宿主机(容器外)执行脚本,参考以下命令:

# docker compose exec -it java_jdk_1.8 sh -c 'cd /app/;java -cp "./bce-java-sdk-0.10.351/lib/bce-java-sdk-0.10.351.jar:./bce-java-sdk-0.10.351/third-party/*:." BalanceQuery'

Balance: 1999.5100

阅读全文 »

本文档中未明确指定 Linux 系统版本信息时,默认为 Centos 7.

automake

编译安装软件报错

error: require Automake 1.14, but have 1.13.4

Automake 版本不匹配,需要安装 Automake 1.14

$ rpm -qa | grep automake
automake-1.13.4-3.el7.noarch

以下步骤安装 automake-1.14.1

wget http://ftp.gnu.org/gnu/automake/automake-1.14.1.tar.gz
tar -xf automake-1.14.1.tar.gz
cd automake-1.14.1
./bootstrap.sh

以上步骤执行完成后,会生成 configure 可执行文件

./configure
make
make install

安装完成后,执行以下命令验证版本

$ automake --version
automake (GNU automake) 1.14.1
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Tom Tromey <tromey@redhat.com>
and Alexandre Duret-Lutz <adl@gnu.org>.

makeinfo

编译安装软件报错

makeinfo: command not found

makeinfo 命令不存在,执行以下命令安装

yum install texinfo

gcc

no acceptable C compiler found in $PATH

缺少 gcc 编译器,安装即可

yum install -y gcc

A compiler with support for C++11 language features is required

编译安装软件时报错

configure: error: *** A compiler with support for C++11 language features is required.

错误原因为 gcc 版本太低。查看当前 gcc 版本

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)

安装 gcc-8.3.0

以下步骤演示安装 gcc-8.3.0 [1]

  1. 下载安装包,官方下载地址
    wget ftp://ftp.irisa.fr/pub/mirrors/gcc.gnu.org/gcc/releases/gcc-8.3.0/gcc-8.3.0.tar.gz
    tar -xf gcc-8.3.0.tar.gz
    cd gcc-8.3.0
  2. 编译安装。编译依赖 GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+,需要先按照顺序安装这 3 个依赖。依赖安装参考: 安装 GMP安装 MPFR安装 MPC
    $ ./configure --prefix=/usr/local/gcc-8.3.0 --disable-multilib
    $ make
    $ make install
  3. 安装完成后,需要更新系统标准库 查看当前系统使用的 gcc 库文件,可以看到版本为 libstdc++.so.6.0.19
    $ ls /usr/lib64/libstdc++.so.6
    libstdc++.so.6 libstdc++.so.6.0.19

    $ ls /usr/lib64/libstdc++.so.6 -l
    lrwxrwxrwx 1 root root 19 May 30 08:05 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.19
    执行以下操作,更新 libstdc++.so.6 到最新安装的版本
    $ rm -rf /usr/lib64/libstdc++.so.6

    $ ln -s /usr/local/gcc-8.3.0/lib64/libstdc++.so.6.0.25 /usr/lib64/libstdc++.so.6

    $ ls /usr/lib64/libstdc++.so.6 -l
    lrwxrwxrwx 1 root root 46 Jun 9 09:31 /usr/lib64/libstdc++.so.6 -> /usr/local/gcc-8.3.0/lib64/libstdc++.so.6.0.25

安装 GMP

安装包下载地址

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/gmp-6.1.0.tar.bz2
tar -jxvf gmp-6.1.0.tar.bz2
cd gmp-6.1.0
./configure
make && make install

安装 MPFR

安装包下载地址

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpfr-3.1.4.tar.bz2
tar -jxvf mpfr-3.1.4.tar.bz2
cd mpfr-3.1.4
./configure
make && make install

安装 MPC

安装包下载地址

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/mpc-1.0.3.tar.gz
tar -zxvf mpc-1.0.3.tar.gz
cd mpc-1.0.3
./configure
make && make install
阅读全文 »

BPF(Berkeley Packet Filter) 是由 Berkeley 学院于 1992 年开发的一款,主要用于提高抓包工具的性能。并于 2014 年被重写进 Linux 内核中,可以用于网络、性能观测、安全等方面。 [1]

BPF 是一种灵活且高效的技术实现,主要由 指令集(Instruction Set)存储对象(Storage Objects maps)帮助函数(Helper Functions) 构成。因为其 虚拟指令集规范(Virtual Instruction Set Specification) ,可以认为 BPF 是一个虚拟机,BPF 运行于 内核模式(Kernel Mode) 。BPF 基于事件(Events)运行: Socket EventsTracepointsUSDT Probeskprobesuprobesperf_events.

bpftrace

bpftrace 是一个基于 BPF 的追踪工具(Trace Tools),提供了高级别的编程能力,同时包含了命令行和脚本使用方式

bpftrace 命令行

bpftrace 命令详细帮助文档请查看 man bpftrace,下表列出常用选项和参数

选项 说明 示例
-l [SEARCH] 列出匹配的事件(Event/Probe),没有 SEARCH 表达式则列出所有的 Event。SEARCH 支持通配符

筛选指定的事件

# bpftrace -l "tracepoint:*exec*"
tracepoint:libata:ata_exec_command
tracepoint:sched:sched_kthread_work_execute_end
tracepoint:sched:sched_kthread_work_execute_start
tracepoint:sched:sched_process_exec
tracepoint:syscalls:sys_enter_execve
tracepoint:syscalls:sys_enter_execveat
tracepoint:syscalls:sys_enter_kexec_file_load
tracepoint:syscalls:sys_enter_kexec_load
tracepoint:syscalls:sys_exit_execve
tracepoint:syscalls:sys_exit_execveat
tracepoint:syscalls:sys_exit_kexec_file_load
tracepoint:syscalls:sys_exit_kexec_load
tracepoint:workqueue:workqueue_execute_end
tracepoint:workqueue:workqueue_execute_start
tracepoint:writeback:writeback_exec

跟踪新启动的进程

执行以下命令,可以追踪系统中新启动的进程及其参数

# bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }'
Attaching 1 probe...
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ rsync@66.6.9.25::backup
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ rsync@66.6.9.25::backup
runc --root /var/run/docker/runtime-runc/moby --log /run/containerd/io.containerd.runtime.v2.task/moby/53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c/log.json --log-format json --systemd-cgroup exec --process /tmp/runc-process3178774999 --detach --pid-file /run/containerd/io.containerd.runtime.v2.task/moby/53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c/c73d8d4d46ac3995bdc1d7648a3562ddb4715a549a509f6125a7cfc4e88abe50.pid 53daccf2e93fcf83831ce6773a495361afe9bb9694881269d1902f399f5c621c
runc init
/watchtower --health-check
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ rsync@66.6.9.25::backup
/usr/lib/x86_64-linux-gnu/dcv/dcvpamhelper --stdout -s dcv
/usr/libexec/sssd/krb5_child --debug-microseconds=0 --debug-timestamps=1 --debug-fd=28 --debug-level=0x0070 --chain-id=7187 --sss-creds-password --canonicalize --realm=OPSDEV666.COM --fast-ccache-gid=0 --fast-ccache-uid=0
/home/oneuser/cwrsync_6.2.4_x64_free/data/ops/rsync2Server.sh
/usr/bin/rsync --progress -a -c -u --timeout=300 -z --password-file /home/oneuser/cwrsync_6.2.4_x64_free/rsync.client.pswd --exclude .idea --exclude rsync2Server.sh /home/oneuser/cwrsync_6.2.4_x64_free/data/ops/ rsync@66.6.9.25::backup

参考链接

Systems Performance: Enterprise and the Cloud v2

脚注

perf 是 Linux 中标准的 Profiler,常用于 CPU Profiling、CPU flame Graphs、Syscall Tracing 等。

perf 命令行操作

perf 常用子命令,命令帮助及选项参数帮助文档可查看 man perfman perf-recordman perf-report

子命令 说明 示例
record 进行 Profile 操作并将结果写入 perf.data
report 读取 perf.data (created by perf record)并展示 Profile 内容
script 读取 perf.data (created by perf record)并展示堆栈内容
stat 显示 PMC(Performance Monitor Counter) 统计数据
list 列出系统支持的事件(Event)列表

perf record 命令常用选项

选项 说明 示例
-F, --freq= 指定 Profile 的频率,使用 max 以当前系统允许的最大频率(对应内核参数 kernel.perf_event_max_sample_rate)进行 Profile
通常建议 99 Hz
-a, --all-cpus 采集所有 CPU 的全局数据,默认操作
-g Kernel Space 和 User Space Stack Traces
-p, --pid= 对指定 PIDs (comma separated list) 进行采样
-e, --event= 针对 Event 进行采样追踪 perf record -F 99 -a -e sched:sched_process_exec

perf report 命令常用选项

选项 说明 示例
-n, --show-nr-samples 显示每个 Symbol 采样的数量
-s, --sort= 按照指定的(CSV)字段排序

perf 使用示例

记录使用 exec 启动的进程

通过监控系统调用事件,可以采样/追踪系统上符合条件的行为,比如以下命令追踪使用 exec 系统调用启动的进程

# perf record -F 99 -a -e sched:sched_process_exec

# perf report
Overhead Command Shared Object Symbol
40.48% runc [kernel.kallsyms] [k] exec_binprm
34.86% exe [kernel.kallsyms] [k] exec_binprm
24.57% rsync [kernel.kallsyms] [k] exec_binprm
0.06% rsync2Server.sh [kernel.kallsyms] [k] exec_binprm
0.03% watchtower [kernel.kallsyms] [k] exec_binprm

添加 -g 选项追踪详细的堆栈信息

# perf record -F 99 -a -e sched:sched_process_exec -g

# perf report
Children Self Command Shared Object Symbol
+ 59.92% 59.92% rsync [kernel.kallsyms] [k] exec_binprm
+ 59.92% 0.00% rsync ld-linux-x86-64.so.2 [.] _start
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] entry_SYSCALL_64_after_hwframe
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] do_syscall_64
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] x64_sys_call
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] __x64_sys_execve
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] do_execveat_common.isra.0
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] bprm_execve
+ 59.92% 0.00% rsync [kernel.kallsyms] [k] bprm_execve.part.0
+ 40.02% 40.02% rsync2Server.sh [kernel.kallsyms] [k] exec_binprm
+ 40.02% 0.00% rsync2Server.sh ld-linux-x86-64.so.2 [.] _start
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] entry_SYSCALL_64_after_hwframe
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] do_syscall_64
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] x64_sys_call
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] __x64_sys_execve
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] do_execveat_common.isra.0
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] bprm_execve
+ 40.02% 0.00% rsync2Server.sh [kernel.kallsyms] [k] bprm_execve.part.0
0.07% 0.07% sleep [kernel.kallsyms] [k] exec_binprm
0.07% 0.00% sleep ld-linux-x86-64.so.2 [.] _start
0.07% 0.00% sleep [kernel.kallsyms] [k] entry_SYSCALL_64_after_hwframe
0.07% 0.00% sleep [kernel.kallsyms] [k] do_syscall_64
0.07% 0.00% sleep [kernel.kallsyms] [k] x64_sys_call
0.07% 0.00% sleep [kernel.kallsyms] [k] __x64_sys_execve
0.07% 0.00% sleep [kernel.kallsyms] [k] do_execveat_common.isra.0
0.07% 0.00% sleep [kernel.kallsyms] [k] bprm_execve
0.07% 0.00% sleep [kernel.kallsyms] [k] bprm_execve.part.0

perf 读取 PMC 统计数据

使用以下命令可以读取系统中的 PMC 统计数据,其中包含了 CPU 使用时钟、CPU Context Switch、CPU Migrations、Page-Faults 等统计信息

# perf stat -a -- sleep 10

Performance counter stats for 'system wide':

40,019.66 msec cpu-clock # 4.000 CPUs utilized
51,242 context-switches # 1.280 K/sec
4,912 cpu-migrations # 122.740 /sec
7,944 page-faults # 198.502 /sec
<not supported> cycles
<not supported> instructions
<not supported> branches
<not supported> branch-misses

10.004545496 seconds time elapsed

<not supported> 可能包括以下原因:

  • 缺乏硬件支持
    • 某些处理器可能不支持所需的硬件性能计数器(如 cyclesinstructions
    • 这可能出现在虚拟机环境中,尤其是未启用硬件性能监控功能的虚拟机。
  • 权限不足
    • 性能计数器访问可能需要更高权限,通常是 root 权限
  • 内核配置问题
    • perf 依赖于 Linux 内核的性能监控功能。如果内核未启用某些性能计数器,可能会导致 <not supported>
  • 禁用了特定的事件源
    • 某些环境中,特定性能监控功能可能被内核禁用(例如为了安全性)。

参考链接|Bibliography

Systems Performance: Enterprise and the Cloud v2

脚注

Grafana 是一款用 GO 语言开发的开源数据可视化工具,可以做数据监控和数据统计,带有告警功能。

基础概念

组织(Organization) 与用户(User)

Organization 相当于一个 Namespace,一个 Organization 完全独立于另一个 Organization,包括 datasourcedashboard 等,创建一个 Organization 就相当于打开了一个全新的视图,所有的 datasourcedashboard 等都需要重新创建。一个用户(User) 可以属于多个 Organization。

User 是 Grafana 里面的用户,用户可以有以下 角色

  • admin - 管理员权限,可以执行任何操作。
  • editor - p不可以创建用户不可以新增 Datasource可以创建 Dashboard**
  • viewer - 仅可以查看 Dashboard
  • read only editor - 允许用户修改 Dashboard,但是 不允许保存

数据源 Datasource

Grafana 中操作的数据集、可视化数据的来源

Dashboard

在 Dashboard 页面中,可以组织可视化数据图表。

  • Panel - 在一个 Dashboard 中,Panel 是最基本的可视化单元。通过 Panel 的 Query Editor 可以为每一个 Panel 添加查询的数据源以及数据查询方式。每一个 Panel 都是独立的,可以选择一种或者多种数据源进行查询。一个 Panel 中可以有多个 Query Editor 来汇聚多个可视化数据集
  • Row - 在 Dashboard 中,可以定义一个 Row,来组织和管理一组相关的 Panel

Variables

在 Dashboard 的设置页面中,有 Variables 页面,在其中可以为 Dashboard 配置变量,之后可以在 Panel 的 Query Editor 中使用这些预定义的变量。变量的值也可以是通过表达式获取的值。也可以在 Panel 的标题中使用变量

例如以下 Variables 配置

Node    label_values(kubernetes_io_hostname)

在 Dashboard 中定义了这些变量后,可以在 Panel 的 Query Editor 中使用,在 Query Editor 中使用了 Variables 中定义的变量后,在 Dashboard 的顶部下拉菜单中可以选择预定义的变量的值(需要在定义 Variables 时配置 Show on dashboardLabel and Value 以使在 Dashboard 顶部显示下拉菜单),Panel 中的 Query 表达式就会使用这些变量的值进行计算以及显示图表。

阅读全文 »

环境信息

  • centos7
  • python3
  • Django 4

ModuleNotFoundError: No module named ‘MySQLdb’

ModuleNotFoundError: No module named ‘MySQLdb’

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.

解决方法

pip3 install pymysql

编辑文件./python36/lib/python3.6/site-packages/django/db/backends/mysql/__init__.py, 输入以下内容

import pymysql 
pymysql.install_as_MySQLdb()
阅读全文 »

在 Python 中,可以使用 Prometheus Python Client Library 来输出 Metrics,供 Prometheus 采集。

安装 Prometheus 客户端库

pip install prometheus-client

以下是一个完整的示例,展示如何设置和暴露 Metrics:

from prometheus_client import start_http_server, Summary, Counter, Gauge, Histogram
import random
import time

# 定义 Metrics
REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
REQUEST_COUNT = Counter('request_count', 'Total number of requests')
IN_PROGRESS = Gauge('in_progress_requests', 'Number of requests in progress')
REQUEST_LATENCY = Histogram('request_latency_seconds', 'Histogram of request latency')

# 模拟一个请求处理函数
@REQUEST_TIME.time() # 使用 Summary 记录函数运行时间
def process_request():
REQUEST_COUNT.inc() # 记录请求次数
IN_PROGRESS.inc() # 增加正在进行的请求数

latency = random.random() # 模拟随机延迟
REQUEST_LATENCY.observe(latency) # 记录延迟
time.sleep(latency)

IN_PROGRESS.dec() # 减少正在进行的请求数

if __name__ == "__main__":
# 启动 HTTP Server,用于暴露 Metrics
start_http_server(8000) # 默认端口 8000
print("Prometheus metrics available at http://localhost:8000/metrics")

# 模拟请求处理
while True:
process_request()

Metrics 类型说明(对应 Prometheus 中相关的数据类型)

  • Summary : 用于记录事件的持续时间或大小(例如请求处理时间)。
  • Counter : 计数器,记录事件的总发生次数(例如请求总数)。
  • Gauge : 测量当前值,可以增加或减少(例如当前活跃请求数)。
  • Histogram : 创建直方图,记录数据分布(例如请求延迟分布)。

向 Prometheus 暴露 Metrics

start_http_server(8000) 会启动一个 HTTP 服务器,在 /metrics 路径下暴露指标数据。Prometheus 将通过该路径采集数据。

装饰器

使用 @REQUEST_TIME.time() 装饰器自动记录函数执行时间。

标签

可以在指标中添加 标签(Labels) 来提供多维度的指标。

REQUEST_COUNT.labels(method='GET').inc()
阅读全文 »

Program 是一个静态实体,只是存储在操作系统中的文件(集合)

Process 是操作系统上的活动(Active)实体,是 Program 由操作系统加载到内存并运行之后的实体。

Process 运行过程中需要操作系统为其分配各种资源,如 CPU、Memory、Files、IO 等来完成其运行。

如果一个 Program 被操作系统运行(启动)了多次,那么其产生的多个 Process 属于分割(单独)的实体。

Linux 系统中调度的进程/线程,通常被称为任务(Task)

Linux 中常见的进程状态如下表:

状态标识 状态名称 状态说明 示例
R task_running 进程处于运行或就绪状态
S task_interruptible
sleeping
可中断的睡眠状态
D task_uninterruptible 不可中断的睡眠状态
1. 它是一种睡眠状态,意味着处于此状态的进程不会消耗 CPU
2. 睡眠的原因是等待某些资源(比如锁或者磁盘 IO),这也是非常多 D 状态的进程都处在处理 IO 操作的原因
3. 是它不能被中断,这个要区别于 硬件中断 的中断,是指不希望在其获取到资源或者超时前被终止。因此他不会被信号唤醒,也就不会响应 kill -9 这类信号。这也是它跟 S(可中断睡眠)状态的区别
T task_stopped
task_traced
Traced
暂停状态或跟踪状态
Z task_dead
exit_zombie
zombie
退出状态,进程成为僵尸进程
X task_dead
exit_dead
退出状态,进程即将被销毁
I idle 空闲状态
+ 表示关联了前台操作(Foreground Operation),比如前台运行的进程

进程调度

Linux 中任务(Task)调度算法默认使用 CFS(Completely Fair Scheduler,内核版本 >= 2.6.23).

CFS 调度算法没有使用固定的进程(此处的进程可能是进程或者线程,Linux 中统称为 Task)优先级(Priority),而是根据进程的 nice value 为进程分配一定比例的(Proportional) CPU 计算时间。 [1]

nice 的取值从 -20 - +19,值越小,优先级越高,默认值为 0 。优先级高的进程会被分配到更高比例的 CPU 处理时间。

在 Linux 内核中,优先级分为两种:静态优先级和动态优先级。CFS 使用的是动态优先级,它是根据 nice 值计算出来的。

CFS Scheduler 不会直接设定 Priorities,而是为每个 Task 维护变量 vruntime(virtual runtime)(可以检查系统 /proc/<PID>/sched),这个值记录了每个 task 使用了多少 CPU 时间。

CFS 通过将进程的虚拟运行时间(vruntime)与其他进程的虚拟运行时间进行比较来决定调度优先级。权重越大,vruntime 增长越慢,意味着进程的优先级越高,能够更频繁地被调度。

假入一个任务有默认的 Priority(nice=0),那么他的 vruntime 和实际使用的 CPU 时间相同。例如一个 nice=0 的进程使用了 CPU 200ms,那么他的 vruntime=200ms低优先级(low priority,nice>0 的任务在 CPU 上运行了 200ms,那么他的 vruntime>200ms,相反的,一个 高优先级(high priority,nice<0 的任务在 CPU 上运行了 200ms,那么他的 vruntime<200ms基于此,CFS Scheduler 在选择下一个要执行的任务时,会选择 vruntime 最小的任务来运行。如果有高优先级的任务就绪(可执行),它会抢占(preemptive)正在执行的低优先级的任务。

假如 Linux 中有 2 个优先级一样的任务(nice=0),一个进程为 I/O-bound,另一个为 CPU-bound。通常情况下,在一个 CPU 周期内,I/O-bound 的任务会使用很少的 CPU 时间就会因等待 IO 而中断在 CPU 上的执行并进入 CPU 的等待调度队列(schedule queue),而 CPU-bound 的任务会用尽分配给它的整个 CPU 周期。执行一段时间之后, I/O-bound 的任务的 vruntime 的值会显著的小于 CPU-bound 的任务的 vruntime,导致 I/O-bound 的任务拥有比 CPU-bound 的任务更高的优先级,当 I/O-bound 的任务就绪(IO 返回数据)时,它会立即抢占 CPU 开始执行。

nice 只能针对单一的进程调整优先级,无法同时将优先级配置关联到相关进程,如 子进程或者同一个服务中的其他进程

修改 nice 的值

在 Linux 中,nice 值用于调整进程的优先级,数值范围从 -20最高优先级 )到 19最低优先级 )。较低的 nice 值表示进程有更高的优先级,会更频繁地获得 CPU 时间,而较高的 nice 值表示进程有较低的优先级。

你可以使用 nicerenice 命令来修改进程的 nice

普通用户(root 之外的用户)启动应用程序时默认只能配置初始 nice0-19

普通用户(root 之外的用户)修改正在运行的应用程序的 nice 值,只能改大,不能改小。比如进程启动时,其 nice10,拥有权限的普通用户只能将其 nice 值改为大于 10 的值。

nice 命令

nice 命令用于在启动进程时指定 nice 。如果不指定,默认 nice 值为 0

nice -n 10 command

renice 命令

renice 命令可以修改已经在运行的进程的 nice 值。需要提供进程 ID( PID )来指定要修改的进程。

renice <nice_value> -p <PID>

如果想要一次修改多个进程的 nice 值,可以传递多个 PID:

sudo renice 10 -p 1234 2345 3456

Linux 进程资源控制 cgroups

cgroups 可以将一个任务(task)标识(绑定)到一个特殊的 控制组(Control Group ,此任务启动的子进程可以继承父进程的 控制组(Control Group

控制组(Control Group 可以限制的资源类型如下:

Type Description
blkio Storage
限制到存储设备(如 Hard Disk、USB Drivers等)的请求
cpu CPU
限制 CPU 调度
cpuacct Process Accounting
上报 CPU 使用状态,可以用于统计客户端使用的处理量并进行收费
cpuset CPU Assignment
在多处理器的系统上,将 Task 分配到特定的处理器以及关联的内存
devices Device Access
限制 控制组(Control Group 中的 Task 对目标设备类型的使用
freezer Suspend/Resume
暂停/恢复 控制组(Control Group 中的 Task
memory Memory Usage
限制并报告 控制组(Control Group 中的 Task 使用的内存
net_cls Network Bandwidth
制并报告 控制组(Control Group 中的 Task 使用的网络流量,主要通过对网络流量打上 cgroups 相关标签实现
net_prio Network Traffic
控制 控制组(Control Group 中的网络流量的优先级
ns Namespaces
cgroups 分配到不同的 Namespaces,如此同一个 cgroups 中的进程只能看到本 Namespace 中的进程

Linux 进程管理

进程命令名和进程可执行文件名

在系统中遇到以下进程:

# ps -elf | grep 18686
5 S root 18686 1239 0 80 0 - 46620 pipe_w 15:50 ? 00:00:00 /usr/sbin/CROND -n
0 R root 18694 18686 7 80 0 - 610547 - 15:50 ? 00:00:02 /usr/local/php73/bin/php /home/www/admin/artisan PullData
0 S root 18754 18686 0 80 0 - 22453 pipe_w 15:50 ? 00:00:00 /usr/sbin/sendmail -FCronDaemon -i -odi -oem -oi -t -f root

其中 PID 为 18686 的进程名为 /usr/sbin/CROND,其启动了另外两个子进程。但是在系统中检查,并不存在路径 /usr/sbin/CROND

# ls -l /usr/sbin/CROND
ls: cannot access /usr/sbin/CROND: No such file or directory

出现此种现象,主要是因为 在启动时,进程的命令名是根据路径传递给 execve() 函数的参数决定的,而不是直接与系统中的文件进行匹配

在 Linux 系统中,ps 命令显示的进程信息是从 /proc 文件系统中获取的,而 /proc 文件系统包含有关正在运行的进程的信息,包括每个进程的命令名。因此,即使实际上系统中不存在 /usr/sbin/CROND 文件,但如果进程的命令名是 /usr/sbin/CROND,那么 ps 命令仍然会显示进程的命令名为 /usr/sbin/CROND

进程的命令名可以查看 /proc/<PID>/cmdline 文件,本示例中显示如下:

# cat /proc/18686/cmdline 
/usr/sbin/CROND-n

对应的系统上的可执行文件的名称可以查看 /proc/<PID>/stat/proc/<PID>/comm/proc/<PID>/status 等文件

# cat /proc/900/comm 
crond

# cat /proc/900/status
Name: crond
Umask: 0022
State: S (sleeping)
Tgid: 900
Ngid: 0
Pid: 900
PPid: 1239
TracerPid: 0

# cat /proc/900/stat
900 (crond) S 1239 1239 1239 0 -1 4202816 1627 0 0 0 0 0 0 0 20 0 1 0 139129633 190955520 1478 18446744073709551615 94685936058368 94685936118156 140733000396032 140733000262488 140427856103840 0 0 4096 16387 18446744071797306256 0 0 17 3 0 0 0 0 0 94685938219080 94685938221648 94685948321792 140733000400770 140733000400789 140733000400789 140733000400872 0

在本示例中,实际执行的命令为 crond

进程状态检查

top 命令

使用 top 命令可以查看系统负载、CPU 和 内存使用情况。也可以查看单个进程的具体信息。

htoptop 命令的一个变种,它提供了更多的交互性、定制性以及其他一些功能,但是它同时使用了相比 top 更多的资源(4 倍多的 syscalls

top 命令常用选项

选项 说明 示例
-H Threads Mode,线程模式。默认情况 top 展示进程的简要信息,使用此选项显示进程中的线程状态。
对应交互式命令 H

top 常用交互命令

命令 说明
P CPU Utilization 排序,默认排序方式
M Memory Utilization 排序
I Irix/Solaris-Mode 切换。
默认为 Itrix Mode,在这种模式下,如果某个进程使用了系统中的 2 个 CPU 的所有计算资源,则其 CPU 使用率展示为 200%,依此类推。
在 Solaris Mode 下,进程的 CPU 使用率是整体 CPU 使用的资源除于 CPU 数。如在 4 CPU 的系统中,Itrix 模式下,进程 CPU 使用率为 200%,在 Solaris 模式下,则为 50%
  • 显示单个进程的(线程)详细信息
    # top -H -p 1423
    top - 09:44:42 up 54 days, 23:53, 2 users, load average: 8.82, 6.84, 7.21
    Threads: 15 total, 0 running, 15 sleeping, 0 stopped, 0 zombie
    %Cpu(s): 40.9 us, 10.8 sy, 0.0 ni, 48.1 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st
    KiB Mem : 15790488 total, 466056 free, 7761544 used, 7562888 buff/cache
    KiB Swap: 0 total, 0 free, 0 used. 3895716 avail Mem

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    1423 root 20 0 1477368 778788 4260 S 39.5 4.9 11999:41 [watchdog:1:6]
    2572 root 20 0 1477368 778788 4260 S 37.9 4.9 11363:48 [watchdog:1:6]
    1436 root 20 0 1477368 778788 4260 S 34.2 4.9 11286:08 [watchdog:1:6]
    1435 root 20 0 1477368 778788 4260 S 33.9 4.9 12059:53 [watchdog:1:6]
    1434 root 20 0 1477368 778788 4260 S 33.2 4.9 10249:00 [watchdog:1:6]
    1437 root 20 0 1477368 778788 4260 S 30.6 4.9 11717:47 [watchdog:1:6]
    1431 root 20 0 1477368 778788 4260 S 28.9 4.9 11222:06 [watchdog:1:6]
    21378 root 20 0 1477368 778788 4260 S 27.6 4.9 12143:35 [watchdog:1:6]
    1433 root 20 0 1477368 778788 4260 S 17.6 4.9 8738:21 [watchdog:1:6]
    1428 root 20 0 1477368 778788 4260 S 8.0 4.9 7650:56 [watchdog:1:6]
    1429 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.04 [watchdog:1:6]
    1430 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.05 [watchdog:1:6]
    1432 root 20 0 1477368 778788 4260 S 0.0 4.9 0:00.03 [watchdog:1:6]
    1438 root 20 0 1477368 778788 4260 S 0.0 4.9 12260:30 [watchdog:1:6]
    1529 root 20 0 1477368 778788 4260 S 0.0 4.9 11068:39 [watchdog:1:6]

pidstat

pidstat 命令用于检查 Linux 内核管理的进程的状态。帮助文档请查看 man pidstat。要查看特定的进程的信息,使用 -p [PID|ALL] 选项,查看子进程相关信息,参考 -T [TASK | CHILD | ALL] 选项

命令常用选项

选项 说明 示例
-d 报告 I/O statistics
-C comm 查看 command name 的详细信息, comm 可以是正则表达式 pidstat -C chrome -T CHILD 1 10
-p { pid[,...] / SELF / ALL } 查看指定的 PID 或者所有(ALL) 进程的统计信息,不指定默认使用 -p ALL
-e program args 使用指定的 args 运行 program 并使用 pidstat 监控其统计数据
-l 在统计输出中,包含详细的命令及其参数
-r 统计进程的 faults 和 Memory 使用情况
-T { TASK / CHILD / ALL }
-t
查看子进程相关统计信息
-u CPU 使用率
-w 统计进程上下文切换(Context Switch)信息

查看指定的 PID 以及关联的子进程的统计信息

# pidstat -t -p 22737 1 1
Linux 3.10.0-1160.49.1.el7.x86_64 (ecs-34a8) 11/26/2024 _x86_64_ (2 CPU)

04:04:52 PM UID TGID TID %usr %system %guest %CPU CPU Command
04:04:53 PM 0 22737 - 0.00 0.00 0.00 0.00 0 python3
04:04:53 PM 0 - 22737 0.00 0.00 0.00 0.00 0 |__python3
04:04:53 PM 0 - 22805 0.00 0.00 0.00 0.00 1 |__python3
04:04:53 PM 0 - 22806 0.00 0.00 0.00 0.00 0 |__python3
04:04:53 PM 0 - 22807 0.00 0.00 0.00 0.00 1 |__python3
04:04:53 PM 0 - 22808 0.00 0.00 0.00 0.00 0 |__python3
04:04:53 PM 0 - 22809 0.00 0.00 0.00 0.00 0 |__python3
04:04:53 PM 0 - 22810 0.00 0.00 0.00 0.00 1 |__python3

Average: UID TGID TID %usr %system %guest %CPU CPU Command
Average: 0 22737 - 0.00 0.00 0.00 0.00 - python3
Average: 0 - 22737 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22759 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22806 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22807 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22808 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22809 0.00 0.00 0.00 0.00 - |__python3
Average: 0 - 22810 0.00 0.00 0.00 0.00 - |__python3

查看进程使用的内存以及 faults 统计信息

# pidstat -r | more
Linux 6.8.0-1017-aws (U-3TSDMAL9IVFAQ) 11/26/2024 _x86_64_ (4 CPU)

03:17:25 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
03:17:25 PM 0 1 0.97 0.00 168668 13552 0.08 systemd
03:17:25 PM 0 147 1.85 0.00 228472 158232 0.98 systemd-journal
03:17:25 PM 0 189 0.00 0.00 289100 27392 0.17 multipathd
03:17:25 PM 0 199 0.00 0.00 12160 6376 0.04 systemd-udevd
03:17:25 PM 118 512 0.00 0.00 14624 6400 0.04 systemd-oomd
03:17:25 PM 100 551 0.00 0.00 16044 7296 0.05 systemd-network
03:17:25 PM 101 553 0.00 0.00 26512 10240 0.06 systemd-resolve
03:17:25 PM 0 643 0.00 0.00 2816 1920 0.01 acpid
03:17:25 PM 123 644 0.00 0.00 19088 3840 0.02 avahi-daemon
03:17:25 PM 102 645 0.00 0.00 31628 7436 0.05 dbus-daemon
03:17:25 PM 0 646 0.00 0.00 278416 14852 0.09 NetworkManager
03:17:25 PM 0 649 0.01 0.00 757540 17788 0.11 euc-analytics-a
03:17:25 PM 0 653 0.00 0.00 82768 3840 0.02 irqbalance
03:17:25 PM 0 656 0.00 0.00 48880 10368 0.06 networkd-dispat

查看进程使用的 CPU 统计信息

# pidstat -u -l | more
Linux 6.8.0-1017-aws (U-3TSDMAL9IVFAQ) 11/26/2024 _x86_64_ (4 CPU)

04:08:08 PM UID PID %usr %system %guest %wait %CPU CPU Command
04:08:08 PM 0 1 0.04 0.02 0.00 0.02 0.06 1 /sbin/init
04:08:08 PM 0 2 0.00 0.00 0.00 0.00 0.00 0 kthreadd
04:08:08 PM 0 15 0.00 0.00 0.00 0.01 0.00 0 ksoftirqd/0
04:08:08 PM 0 49 0.00 0.00 0.00 0.00 0.00 2 khugepaged
04:08:08 PM 0 63 0.00 0.00 0.00 0.00 0.00 3 kworker/3:1H-kblockd
04:08:08 PM 0 64 0.00 0.00 0.00 0.00 0.00 3 kswapd0
04:08:08 PM 0 75 0.00 0.00 0.00 0.00 0.00 2 kworker/2:1H-kblockd
04:08:08 PM 0 100 0.00 0.00 0.00 0.00 0.00 0 jbd2/nvme0n1p1-8
04:08:08 PM 0 102 0.00 0.00 0.00 0.00 0.00 1 kworker/1:1H-kblockd
04:08:08 PM 0 129 0.00 0.00 0.00 0.00 0.00 0 kworker/0:1H-kblockd
04:08:08 PM 0 147 0.00 0.00 0.00 0.00 0.01 3 /lib/systemd/systemd-journald
04:08:08 PM 0 189 0.00 0.00 0.00 0.00 0.01 3 /sbin/multipathd -d -s
04:08:08 PM 0 199 0.00 0.00 0.00 0.00 0.00 3 /lib/systemd/systemd-udevd
04:08:08 PM 0 382 0.00 0.00 0.00 0.00 0.00 2 jbd2/nvme1n1p1-8
04:08:08 PM 118 512 0.05 0.05 0.00 0.00 0.10 3 /lib/systemd/systemd-oom
d

在使用 pidstat 时如果未指定统计数据的采样间隔及采样次数,则 默认统计的是从系统启动以来的平均资源使用率,如果目标进程的运行时间相对于系统启动时间很短(例如仅几分钟),那么它的平均 CPU 使用率会趋近于 0 。比如以下示例:

未指定采样间隔及采样次数 ,统计结果显示进程的 CPU 使用率为 0

# pidstat  | grep gzip
01:37:06 PM 0 784 0.00 0.00 0.00 0.00 4 gzip

以下命令指定采样间隔和采样次数 ,统计结果显示 CPU 使用率较高

# pidstat 1 1 | grep gzip
01:37:14 PM 0 1974 15.69 1.96 0.00 87.65 7 gzip
Average: 0 1974 15.69 1.96 0.00 87.65 - gzip

参考链接|Bibliography

Operating System Concepts v10 Online
Linux进程状态说明

脚注


  1. 1.Operating System Concepts v10 Online 5.7.1 Example: Linux Scheduling

批量下载

Python3 批量下载文件中给定的 urls

假如需要下载的 urls 存在于给定的文件中(每行一个 url),本示例演示批量并发下载,假设 urls 存在于文件 urls.txt

import concurrent.futures
import requests

# 创建一个函数来下载图片
def download_image(url):
try:
response = requests.get(url)
response.raise_for_status() # 检查是否有 HTTP 错误
print(f"下载完成: {url}")
except requests.exceptions.RequestException as e:
print(f"下载失败: {url}, 错误: {e}")


# 从文件中读取图片链接
with open("urls.txt", "r") as file:
img_urls = file.read().splitlines()

# 使用 ThreadPoolExecutor 来限制并发线程数量为 10
max_concurrent_threads = 10
with concurrent.futures.ThreadPoolExecutor(max_concurrent_threads) as executor:
# 提交任务并下载图片
executor.map(download_image, img_urls)

print("所有图片下载完成")

shell 批量下载文件中给定的 urls

假如需要下载的 urls 存在于给定的文件中(每行一个 url),本示例演示批量并发下载,假设 urls 存在于文件 urls.txt

本示例中的 urls.txt 内容示例如下:

urls.txt
http://cdn-log-customer-bj4.obs.cn-north-4.myhuaweicloud.com:80/oversea/20241202/09/2024120209-domain-ov.gz?AccessKeyId=WWHEIFLWKIMHHDKIPRWLJJ40CFS&Expires=1733208754&response-content-disposition=attachment%3Bfilename%3D%222024120209-domain-ov.gz%22&Signature=bGHy1CPncaXJladgG9NdIRwASsdgSIvs%3D
http://cdn-log-customer-bj4.obs.cn-north-4.myhuaweicloud.com:80/mainland/20241202/09/2024120209-domain-cn.gz?AccessKeyId=WWHEIFLWKIMHHDKIPRWLJJ40CFS&Expires=1733208754&response-content-disposition=attachment%3Bfilename%3D%222024120209-domain-cn.gz%22&Signature=bGHy1CPncaXJladgG9NdIRwASsdgSIvs%3D
http://cdn-log-customer-bj4.obs.cn-north-4.myhuaweicloud.com:80/oversea/20241202/08/2024120208-domain-ov.gz?AccessKeyId=WWHEIFLWKIMHHDKIPRWLJJ40CFS&Expires=1733208754&response-content-disposition=attachment%3Bfilename%3D%222024120208-domain-ov.gz%22&Signature=ZT64%bGHy1CPncaXJladgG9NdIRwASsdgSIvs%3D
http://cdn-log-customer-bj4.obs.cn-north-4.myhuaweicloud.com:80/mainland/20241202/08/2024120208-domain-cn.gz?AccessKeyId=WWHEIFLWKIMHHDKIPRWLJJ40CFS&Expires=1733208754&response-content-disposition=attachment%3Bfilename%3D%222024120208-domain-cn.gz%22&Signature=bGHy1CPncaXJladgG9NdIRwASsdgSIvs%3D

简单的 串行下载命令 如下,从 url 中取出文件名作为下载文件名:

for i in `cat urls.txt`; do fn=`echo $i | cut -d'?' -f1  | cut -d'/' -f7` ; curl -o $fn "$i"; done

使用以上命令,如果下载内容太多,会比较慢,以下代码示例使用 xargs 命令批量下载,xargs 会自动分配任务,确保多个下载任务同时运行 。:

cat log-url | xargs -P 10 -I {} sh -c 'fn=$(echo "{}" | cut -d"?" -f1 | cut -d"/" -f7);  curl -o $fn "{}"'

xargs 参数说明:

  • -P 10 : 指定并发进程数为 10,可以根据系统资源调整。
  • -I {} : 替换(Pipe)输入中的每行(URL) 为 {}

如果无需提取文件名,可以 使用 wget 命令的并行下载功能

wget -i urls.txt -P output-dir -nc --max-threads=10

参数说明如下:

  • -i urls.txt : 从文件中读取下载 URL。
  • -P output-dir : 将下载文件存储到 output-dir
  • --max-threads=10 : 设置并发线程数为 10。
  • -nc : 跳过已下载的文件,避免重复下载。

CPU Architecture and Common Concepts

CPU Architecture

下图展示了一个简单的 CPU 架构图,有一个物理 CPU(Physical Processor),包含 4 个 CPU Cores,每个 CPU Core 包含 2 个 Hardware Threads,总计 8 个 CPUs。右侧的图是这 8 个 CPUs 在操作系统(Operating System)中的视图(也被称为 Logical CPU/Virtual Processor/Virtual Core) [1]

Operating System 可能对于 CPU 的拓扑结构(Topology)有一定程度的了解,如知道哪些 Logical CPU 位于同一个 CPU Core 或者 CPU Cache 是如何被共享的(Shared),这有助于 CPU Scheduler 做出更优的调度决策。

下图展示了通用 2 Core 处理器的组成。具体的组成取决于具体的处理器。

  • Control Unit 是 CPU 处理器的核心组件,负责指令(Instruction)的 FetchDecodingExecution 并负责保存执行结果(Storing Results)。
  • Shared Floating-Points Unit
  • Shared Level-3 Cache
  • MMU Memory Management Unit,负责将虚拟内存地址转换为物理内存地址(Virtual-to-Physical Address Translation)
  • TLB Translation Lookaside Buffer,用于 Cache 内存地址转换(Virtual-to-Physical Address Translation),未缓存的地址转换需要去 Main Memory 中的 Page Tables 中查询,MMU 的基本构成如下图

P-States and C-States

Intel 处理器的 ACPI(Advanced Configuration and Power Interface)标准定义了 P-States (Processor Performance States)S-States (Processor Power States)[4]

  • P-States 通过在 Processor 运行过程中提供不同的 Clock Rate 提供不同的性能选择。 P0 提供最高级别的性能,P1PN 提供较慢的 Clock Rate,这些级别通常可以被硬件(如依赖处理器温度)或者系统上的软件(如内核的节能策略)控制。
  • C-States 提供当 CPU 处于 Idle 时,提供不同的 idle states,用于节省电力(节能),下图列出了常见的 C-States

Linux 中可以配置和管理 CPU Status 的方式包括(是否支持取决于不同的 Processor、操作系统和内核版本):

  • /sys/devices/system/cpu/cpufreq/policy0/scaling_available_governors 可以查看可用的 CPU Scaling Governors,一般包括 performancepowersave,通过配置 /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 为对应值。 [6]
  • cpupower 工具箱,其中包括 cpupower frequency-infocpupower frequency-set 等一系列管理和查看 CPU 频率等信息的工具

CPU Memory Caches

Processors(物理 CPU 处理器)提供了各种个样的硬件缓存(Hardware Caches)用于提高 CPU 读写 Memory I/O 的性能(CPU 的处理速度是 Memory I/O 处理速度的多个量级),下图展示了 Processor Caches 在大小(Sizes)和响应速度之间的关系, 离 Processor 越近,其响应速度越快,存储空间越小,成本越高(贵)

Clock Rate

CPU 时钟频率(Clock Rate) 是指 Processor 每秒跳动(转换)的次数,每一个 Processor 的时钟变化也称为一个 Clock Cycle 。 Processor 通常是以特定的时钟频率运行,比如 4GHz 的 CPU 每秒会进行 4 Billion Clock Cycles。每个 CPU 指令(Instruction)都需要通过一个或者多个 Clock Cycles 来完成。 现代 CPU 通常会变频,即调整 Processor 的 Clock Rate,如果提高 CLock Rate 以提升性能,降低 Clock Rate 以节能 。变频操作通常是通过 OS 请求 Processor 变频或者是 Processor 自动进行变频,比如内核中的 idle 线程通常会要求 CPU 降低其 Clock Rate 以节能。

Clock Rate 是衡量 Processor 能力(Capacity)的一个主要指标。通常来说,较高 Clock Rate 的 Processor 会有更高的性能,但是在性能分析的场景中,提高 CPU 的 Clock Rate 不一定会提高 CPU 性能,这主要取决于 CPU 的 Clock Cycles 到底是在忙于什么,如果 Clock Cycles 主要忙于等待 Memory Access,那么换成更高频率的处理器,并不会提高整个系统的性能。

SMT

Simultaneous Multi-Threading(SMT) 是一种由 Processor 支持的硬件多线程(Hardware Multithreading)技术,用于实现同一个 Processor Core 上的并发(Parallelism),它允许一个 CPU Core 运行多个 Thread,每个 Hardware Thread 从 Operating System 层面来看都是一个 CPU。这种技术的典型应用包括 Intel 的 Hyper-Threading 技术,允许每个 Processor Core 运行 2 个 Threads,以及 POWER8,允许每个 Processor Core 运行 8 个 Threads。

SMT 的实现通常基于 Core 运行指令(Instructions)的过程中的 Stall Cycles,当某个指令处于 Stall Cycles 时,Core 会允许调度另一个指令来运行。基于 Hardware Thread 的 CPU,其性能和单独的 CPU Core 是有区别的,这个差别取决于其上的工作负载(Workload),Stall Cycles 严重的 Workloads 会比 Instruction Cycles 严重的 Workloads 性能更好,因为 Stall Cycles 会减少竞争(Core Contention)

阅读全文 »