-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.json
1 lines (1 loc) · 173 KB
/
search.json
1
[{"title":"网卡 bond","path":"/p/网卡-bond/","content":"Bond 的概念网卡bond模式是一种网络绑定或链路聚合技术,它可以将多个物理网卡绑定成一个逻辑网卡,从而提高数据报文的可用性和吞吐量。从这段介绍中,我们其实看出一些使用场景,高可用性是为了解决单点故障,提高吞吐量是为了增加网络带宽。从可用性的角度看,网卡的绑定其实就是将多个网卡看作一个整体,当一张网卡挂掉或者异常之后,其他的网卡可以承载流量请求;从吞吐量的角度看,假如说有机器上有两张 100G的网卡,如果分开,就需要配置两个 ip,通信上不方便,这样我们就可以通过 bond 将其作为一个整体进行使用。变相成为了一张 200G 的网卡 bond的作用 提高带宽:通过绑定多个物理网卡,可以实现网络带宽的叠加,从而提高整体网络传输速度。 实现冗余:当其中一个物理网卡出现故障时,其他网卡可以接管其工作,确保网络连接的连续性。 负载均衡:根据一定的策略将网络流量分配到不同的物理网卡上,实现负载均衡,避免单个网卡过载。 bond模式bond 的模式一共有七种,每个模式都有各自的特点和适用场景,同时部分 bond 需要交换机的支持,以下是各种模式的概述和优缺点阐述: 模式编号 模式名称 概述 优点 缺点 mode-0 balance-rr (轮询负载均衡) 在所有可用的接口上依次轮询发送数据包 最大化带宽利用,提供容错能力 可能出现包乱序问题,需交换机支持 mode-1 active-backup (主备模式) 只有一个接口处于活动状态,其他接口作为备份 高可用性,简单配置 无负载均衡,带宽利用率低 mode-2 balance-xor (XOR 负载均衡) 根据哈希值决定使用哪个接口,保持会话一致性 保持会话一致性,提供冗余 需要交换机支持,不适合流量集中场景 mode-3 broadcast (广播模式) 所有数据包都会通过每个接口发送一次 极高冗余性 带宽浪费严重,适用于特殊场景 mode-4 802.3ad (LACP) 基于 IEEE 802.3ad 标准动态链路聚合,实现带宽聚合 动态带宽聚合,高性能,高冗余 需要交换机支持 LACP,配置复杂 mode-5 balance-tlb (传输流量负载均衡) 根据接口负载动态调整出站流量分配 出站负载均衡,不需交换机支持 入站流量无法负载均衡,适合出站流量大的场景 bond的配置配置 bond 的方式多钟多样,包括 nmcli、命令操作以及配置文件的方式,这里我们通过配置文件的方式进行操作。 在配置之前,我们需要了解到操作系统支持的 bond 参数有哪些?首先我们可以查看下内核中关于 bond 的支持情况 12345678910111213141516171819202122232425262728293031323334353637[root@node1 ~]# modinfo bondingfilename: /lib/modules/4.18.0-193.28.1.el7.aarch64/kernel/drivers/net/bonding/bonding.ko.xzauthor: Thomas Davis, tadavis@lbl.gov and many othersdescription: Ethernet Channel Bonding Driver, v3.7.1version: 3.7.1license: GPLalias: rtnl-link-bondrhelversion: 8.2srcversion: 1E6D86D1C8403DB78D3FBDDdepends:intree: Yname: bondingvermagic: 4.18.0-193.28.1.el7.aarch64 SMP mod_unload modversions aarch64parm: max_bonds:Max number of bonded devices (int)parm: tx_queues:Max number of transmit queues (default = 16) (int)parm: num_grat_arp:Number of peer notifications to send on failover event (alias of num_unsol_na) (int)parm: num_unsol_na:Number of peer notifications to send on failover event (alias of num_grat_arp) (int)parm: miimon:Link check interval in milliseconds (int)parm: updelay:Delay before considering link up, in milliseconds (int)parm: downdelay:Delay before considering link down, in milliseconds (int)parm: use_carrier:Use netif_carrier_ok (vs MII ioctls) in miimon; 0 for off, 1 for on (default) (int)parm: mode:Mode of operation; 0 for balance-rr, 1 for active-backup, 2 for balance-xor, 3 for broadcast, 4 for 802.3ad, 5 for balance-tlb, 6 for balance-alb (charp)parm: primary:Primary network device to use (charp)parm: primary_reselect:Reselect primary slave once it comes up; 0 for always (default), 1 for only if speed of primary is better, 2 for only on active slave failure (charp)parm: lacp_rate:LACPDU tx rate to request from 802.3ad partner; 0 for slow, 1 for fast (charp)parm: ad_select:802.3ad aggregation selection logic; 0 for stable (default), 1 for bandwidth, 2 for count (charp)parm: min_links:Minimum number of available links before turning on carrier (int)parm: xmit_hash_policy:balance-alb, balance-tlb, balance-xor, 802.3ad hashing method; 0 for layer 2 (default), 1 for layer 3+4, 2 for layer 2+3, 3 for encap layer 2+3, 4 for encap layer 3+4 (charp)parm: arp_interval:arp interval in milliseconds (int)parm: arp_ip_target:arp targets in n.n.n.n form (array of charp)parm: arp_validate:validate src/dst of ARP probes; 0 for none (default), 1 for active, 2 for backup, 3 for all (charp)parm: arp_all_targets:fail on any/all arp targets timeout; 0 for any (default), 1 for all (charp)parm: fail_over_mac:For active-backup, do not set all slaves to the same MAC; 0 for none (default), 1 for active, 2 for follow (charp)parm: all_slaves_active:Keep all frames received on an interface by setting active flag for all slaves; 0 for never (default), 1 for always. (int)parm: resend_igmp:Number of IGMP membership reports to send on link failure (int)parm: packets_per_slave:Packets to send per slave in balance-rr mode; 0 for a random slave, 1 packet per slave (default), >1 packets per slave. (int)parm: lp_interval:The number of seconds between instances where the bonding driver sends learning packets to each slaves peer switch. The default is 1. (uint) 实战配置bond4假设我们现在有两张网卡做 bond4,我们通过实战的形式来看一下如何做 bond: 假设我们有两张网卡 enp1s0f0 和 enp1s0f1,首先可以通ethtool 查看下网卡的信息,例如 12345678910111213141516171819202122[root@node1 ~]# ethtool enp1s0f0Settings for enp1s0f0:\tSupported ports: [ FIBRE ]\tSupported link modes: xxxx\tSupported pause frame use: Symmetric\tSupports auto-negotiation: Yes\tSupported FEC modes: None RS\tAdvertised link modes: xxxx\tAdvertised pause frame use: No\tAdvertised auto-negotiation: Yes\tAdvertised FEC modes: RS\tSpeed: 100000Mb/s\tDuplex: Full\tPort: FIBRE\tPHYAD: 0\tTransceiver: internal\tAuto-negotiation: on\tSupports Wake-on: d\tWake-on: d\tCurrent message level: 0x00000004 (4) link\tLink detected: yes 我们可以看到这张卡的配置是 100000Mb/s,也就是一张百 G 的网卡,做bond4 就是将两张百 G 的网卡绑定在一起形成一张200G 的卡 不同的操作系统网卡的配置方式存在不同,我们这里以 centos7为例,进入/etc/sysconfig/network-scripts 目录下,我们可以看到网卡的配置文件名称是ifcfg-网卡名的格式。 创建 bond 文件 12345678DEVICE=bond0TYPE=bondONBOOT=yesBOOTPROTO=noneUSERCTL=noBONDING_OPTS="mode=4 miimon=100 xmit_hash_policy=layer3+4"IPADDR=192.168.11.10NETMASK=255.255.255.0 参数释义: DEVICE=bond0:指定这个配置是用于名为bond0的设备,bond0是逻辑绑定的接口名。 TYPE=bond:表明这个设备是一个 bond 接口。 ONBOOT=yes:在系统启动时自动启动这个网络接口。 BOOTPROTO=none: 不使用任何启动协议(如DHCP)来获取IP地址。这意味着IP地址将静态配置。 USERCTL=no:不允许非root用户控制这个网络接口。 BONDING_OPTS=”mode=4 miimon=100 xmit_hash_policy=layer3+4”:这是绑定接口的选项,这一项支持的参数详细类型可以通过上面的modinfo bonding查看,在上述示例中,具体包含: mode=4:指定绑定模式为模式4(802.3ad 动态链路聚合)。这种模式使用LACP(链路聚合控制协议)来自动协商链路聚合。 miimon=100:链路监控间隔时间,单位为毫秒。这里设置为100ms,意味着系统每100毫秒检查一次链路状态。 xmit_hash_policy=layer3+4:传输哈希策略,这里设置为基于第三层(IP地址)和第四层(TCP/UDP端口号)的哈希。这有助于在多个物理接口之间均匀分布流量。 IPADDR=192.168.11.10:为这个绑定接口静态配置的IP地址。 NETMASK=255.255.255.0:子网掩码 创建被绑定网卡文件:12345678910111213141516[root@node1 network-scripts]# cat ifcfg-enp1s0f0DEVICE=enp1s0f0TYPE=EthernetONBOOT=yesBOOTPROTO=noneMASTER=bond0SLAVE=yesHOTPLUG=no[root@node1 network-scripts]# cat ifcfg-enp1s0f1DEVICE=enp1s0f1TYPE=EthernetONBOOT=yesBOOTPROTO=noneMASTER=bond0SLAVE=yesHOTPLUG=no 参数释义: DEVICE=enp1s0f1:指定这个配置是用于名为enp1s0f1的设备。enp1s0f1是物理网络接口的名称,遵循现代Linux的命名约定(基于硬件属性和位置)。 TYPE=Ethernet:表明这个设备是一个以太网接口。 ONBOOT=yes:在系统启动时自动启动这个网络接口。 BOOTPROTO=none:不使用任何启动协议(如DHCP)来获取IP地址。这意味着这个接口将不会从DHCP服务器获取IP配置,而是可能需要静态配置(尽管作为绑定接口的从接口,它通常不会直接分配IP地址)。 MASTER=bond0:指定这个接口的主接口(master interface)是bond0。这意味着enp1s0f1将作为bond0绑定接口的一个从接口。 SLAVE=yes:表明这个接口是一个从接口(slave interface),它是绑定到MASTER指定的主接口上的。 HOTPLUG=no:禁用热插拔功能。这通常用于确保在系统启动时或配置更改时,接口不会被自动激活或禁用,而是根据配置文件中的ONBOOT设置来管理。 配置完成后,重启网络:1systemctl restart network 检查网口12345[root@node1 network-scripts]# ip a | grep -E "bond0|enp1s0f0|enp1s0f1"6: enp1s0f0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 10007: enp1s0f1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 10009: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 inet 192.168.11.11/24 brd 192.168.11.255 scope global noprefixroute bond0 可以看到 enp1s0f0 和 enp1s0f1 网卡的状态都是 slave,都以 bond0 为 master bond 测试在配置完 bond0 之后,我们可以看一下 bond0 的具体情况 123456789101112131415161718[root@node1 network-scripts]# ethtool bond0Settings for bond0:\tSupported ports: [ ]\tSupported link modes: Not reported\tSupported pause frame use: No\tSupports auto-negotiation: No\tSupported FEC modes: Not reported\tAdvertised link modes: Not reported\tAdvertised pause frame use: No\tAdvertised auto-negotiation: No\tAdvertised FEC modes: Not reported\tSpeed: 200000Mb/s\tDuplex: Full\tPort: Other\tPHYAD: 0\tTransceiver: internal\tAuto-negotiation: off\tLink detected: yes 这里的网卡速度变成了 200G,也就是聚合成功,如果显示的还是 100G,那么是有问题的,需要对配置进行检查,包括交换机侧是否已经开启支持该功能的配置。 详细的信息也可以通过以下方式查看: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677[root@node1 network-scripts]# cat /proc/net/bonding/bond0Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)Bonding Mode: IEEE 802.3ad Dynamic link aggregationTransmit Hash Policy: layer3+4 (1)MII Status: upMII Polling Interval (ms): 100Up Delay (ms): 0Down Delay (ms): 0Peer Notification Delay (ms): 0802.3ad infoLACP rate: slowMin links: 0Aggregator selection policy (ad_select): stableSystem priority: 65535System MAC address: 9c:63:c0:0b:37:4eActive Aggregator Info:\tAggregator ID: 5\tNumber of ports: 2\tActor Key: 29\tPartner Key: 2897\tPartner Mac Address: 3c:c7:86:87:8a:21Slave Interface: enp1s0f0MII Status: upSpeed: 100000 MbpsDuplex: fullLink Failure Count: 0Permanent HW addr: 9c:63:c0:0b:37:4eSlave queue ID: 0Aggregator ID: 5Actor Churn State: nonePartner Churn State: noneActor Churned Count: 0Partner Churned Count: 1details actor lacp pdu: system priority: 65535 system mac address: 9c:63:c0:0b:37:4e port key: 29 port priority: 255 port number: 1 port state: 61details partner lacp pdu: system priority: 32768 system mac address: 3c:c7:86:87:8a:21 oper key: 2897 port priority: 32768 port number: 1 port state: 61Slave Interface: enp1s0f1MII Status: upSpeed: 100000 MbpsDuplex: fullLink Failure Count: 0Permanent HW addr: 9c:63:c0:0b:37:4fSlave queue ID: 0Aggregator ID: 5Actor Churn State: nonePartner Churn State: noneActor Churned Count: 1Partner Churned Count: 1details actor lacp pdu: system priority: 65535 system mac address: 9c:63:c0:0b:37:4e port key: 29 port priority: 255 port number: 2 port state: 61details partner lacp pdu: system priority: 32768 system mac address: 3c:c7:86:87:8a:21 oper key: 2897 port priority: 32768 port number: 2 port state: 61 性能测试在网卡bond 做完之后,我们需要对性能进行测试,测试一下网卡的带宽是否能达到期望的要求,比较常用的测试工具有 iperf 和 netperf, 我们这里使用iperf: 12345678# 在服务器和客户端安装 iperf, 这里需要注意的是 iperf 有两个版本, iperf3 需要采用多服务器模式才可以打到性能要求,所以这里安装 iperf2yum install -y iperf # 安装iperf2# 服务器端(做 bond的服务器)启动 serveriperf -s -p 9999# 客户端发起请求iperf -c 192.168.11.11 -p 9999 -P 200 -t 100 # 200个线程,持续 100s 等完成后,观察测试结果: 测试结果 可以看到这里的网卡总带宽跑到了 197Gb/s,已经接近跑满。同时我们也可以观察下网卡的监控: 网卡监控 可以看到 bond 的流量均匀的打到了两个slave 网卡上。 性能调优在网卡bond 完成之后,有的时候测试的性能可能并不理想,那么这个时候一般有以下方法进行调优,以 bond4 为例: 确保 slave 网卡均匀的接收到了流量因为bond4 是动态聚合类型的,需要两张网卡都接收到流量,我们可以通过监控查看网卡的流量情况,以 prometheus 为例,可以通过以下指标查看: 1rate(node_network_receive_packets_total{node="xxx"}[1m]) 启用 Jumbo Frames启用 Jumbo Frames 是指将网卡的最大传输单元(MTU,Maximum Transmission Unit)增大到标准以太网帧(1500 字节)以上,通常设置为 9000 字节左右。Jumbo Frames 可以显著提高传输效率,减少 CPU 开销,在需要传输大量数据的场景中(如高性能计算和数据中心网络),启用 Jumbo Frames 能提升网络性能。 优势:1.\t减少数据包数量:由于每个数据包承载的数据更多,数据传输时需要的包数量会减少,进而减少网络和主机的负载。2.\t降低 CPU 开销:更少的数据包意味着更少的中断请求(IRQ),降低了 CPU 的处理压力,尤其在高吞吐量网络下效果更明显。3.\t提高吞吐量:在大数据传输的场景中,Jumbo Frames 可以有效增加带宽利用率。 操作步骤: 查看当前网卡的 mtu12[root@node1 ~]# ip a show eth02: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 可以看到当前网卡的 mtu 是 1500 设置 mtu 为 9000123ip link set dev <网卡名> mtu 9000# 如果需要重启之后也保持,那么需要在配置文件中添加MTU=9000 系统调优 调整 CPU 中断绑定: 如果两张网卡的中断处理都绑定在同一个 CPU 核心上,可能会导致性能瓶颈。可以使用 irqbalance 或手动调整中断的 CPU 绑定,将中断分布到多个 CPU 核心,以减少单核负载。 优化队列数量和大小:可以通过调整网卡的队列数量和大小,提高网卡的并行处理能力1ethtool -G enp133s0f1 rx 8192 tx 8192 调整网络栈参数:通过 /etc/sysctl.conf 文件进行 TCP 参数优化,例如增大 TCP 缓冲区,优化内核参数以支持高吞吐量:12345# 增加 TCP 缓冲区大小net.core.rmem_max = 16777216net.core.wmem_max = 16777216net.ipv4.tcp_rmem = 4096 87380 16777216net.ipv4.tcp_wmem = 4096 65536 16777216 故障测试既然是双网卡做 bond,尝试关闭其中一个网卡进行测试: 1[root@node2 ~]# ifdown enp1s0f0 然后继续使用 iperf 进行测试,可以看到带宽只能跑到接近 100G 测试结果 bond0 和 另外一张up 的网卡有流量并且曲线重合 网卡监控 从结果来看符合预期 声明此文章已经发布在个人博客上: baixiaozhou.github.io码字不易,希望文章对各位读者朋友们有所帮助和启发,文章的撰写有的时候是根据自己的经验和遇到的一些场景所思考的,存在不足和错误的地方,希望读者朋友们指正","tags":["Linux","网卡绑定"],"categories":["网卡绑定"]},{"title":"NFS 详解","path":"/p/NFS-详解/","content":"概述NFS 全拼 Network File System, 由 Sun Microsystems 于 1984 年开发,旨在允许不同机器间通过网络共享文件。它是一个基于客户端-服务器架构的分布式文件系统协议,允许多个客户端系统像访问本地存储一样访问远程服务器上的文件。 NFS 使得用户可以通过网络访问远程计算机上的文件,并对文件进行读取、写入、删除等操作,这些操作像本地文件系统一样透明,用户无需关心文件实际存储的位置。 相关概念NFS 关键概念 服务器和客户端: 服务器:存储文件并将文件系统通过网络共享给客户端。 客户端:通过网络挂载服务器提供的文件系统,并进行文件操作。 挂载(Mounting): 客户端使用 NFS 挂载(mount)服务器上共享的文件系统。挂载后,远程文件系统可以像本地文件系统一样进行访问。 RPC(Remote Procedure Call): NFS 基于 RPC(远程过程调用)协议进行通信。通过 RPC,客户端发送请求,服务器响应请求并返回相应的数据。 文件句柄: NFS 使用文件句柄来唯一标识远程文件。客户端在访问文件时,文件句柄将传递给服务器以确定目标文件。 Stateless(无状态)协议: 早期版本的 NFS 是无状态的,这意味着服务器不保存任何有关客户端的信息。每个请求都独立处理,且服务器不跟踪会话状态。 无状态的设计提高了系统的容错性,但也限制了某些功能。现代的 NFS 引入了一些状态管理来增强功能。 版本: NFS 经过多次版本迭代,最常用的版本有 NFSv3 和 NFSv4。 NFSv3:最广泛使用的版本,支持较大的文件系统,并增加了多种优化。 NFSv4:引入了状态管理、增强的安全性以及简化了防火墙穿越。 文件锁定: NFS 支持文件锁定机制,使得客户端可以在多人同时操作同一文件时避免冲突。NFSv4 将锁定功能内置,而 NFSv3 需要外部锁定协议(如 NLM)。 安全性: 传统的 NFS 依赖于基于 IP 地址的信任机制。现代的 NFSv4 支持通过 Kerberos 进行强身份认证。 性能和缓存: 为了减少网络通信带来的延迟,NFS 客户端通常使用缓存机制。客户端可以缓存文件的部分内容和属性,并在适当的时候将缓存的数据写回服务器。 部署安装我们以 centos 为例来进行NFS 的部署和安装: 安装 nfs 服务(服务器端和客户端都需要安装) 1yum install -y nfs-utils 安装完毕后,我们可以查看下这个包提供的文件信息: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798/etc/exports.d/etc/gssproxy/24-nfs-server.conf/etc/modprobe.d/lockd.conf/etc/nfs.conf/etc/nfsmount.conf/etc/request-key.d/id_resolver.conf/etc/sysconfig/nfs/sbin/mount.nfs/sbin/mount.nfs4/sbin/osd_login/sbin/rpc.statd/sbin/umount.nfs/sbin/umount.nfs4/usr/lib/systemd/scripts/nfs-utils_env.sh/usr/lib/systemd/system-generators/nfs-server-generator/usr/lib/systemd/system-generators/rpc-pipefs-generator/usr/lib/systemd/system/auth-rpcgss-module.service/usr/lib/systemd/system/nfs-blkmap.service/usr/lib/systemd/system/nfs-client.target/usr/lib/systemd/system/nfs-config.service/usr/lib/systemd/system/nfs-idmap.service/usr/lib/systemd/system/nfs-idmapd.service/usr/lib/systemd/system/nfs-lock.service/usr/lib/systemd/system/nfs-mountd.service/usr/lib/systemd/system/nfs-secure.service/usr/lib/systemd/system/nfs-server.service/usr/lib/systemd/system/nfs-utils.service/usr/lib/systemd/system/nfs.service/usr/lib/systemd/system/nfslock.service/usr/lib/systemd/system/proc-fs-nfsd.mount/usr/lib/systemd/system/rpc-gssd.service/usr/lib/systemd/system/rpc-statd-notify.service/usr/lib/systemd/system/rpc-statd.service/usr/lib/systemd/system/rpc_pipefs.target/usr/lib/systemd/system/rpcgssd.service/usr/lib/systemd/system/rpcidmapd.service/usr/lib/systemd/system/var-lib-nfs-rpc_pipefs.mount/usr/sbin/blkmapd/usr/sbin/exportfs/usr/sbin/mountstats/usr/sbin/nfsdcltrack/usr/sbin/nfsidmap/usr/sbin/nfsiostat/usr/sbin/nfsstat/usr/sbin/rpc.gssd/usr/sbin/rpc.idmapd/usr/sbin/rpc.mountd/usr/sbin/rpc.nfsd/usr/sbin/rpcdebug/usr/sbin/showmount/usr/sbin/sm-notify/usr/sbin/start-statd/usr/share/doc/nfs-utils-1.3.0/usr/share/doc/nfs-utils-1.3.0/ChangeLog/usr/share/doc/nfs-utils-1.3.0/KNOWNBUGS/usr/share/doc/nfs-utils-1.3.0/NEW/usr/share/doc/nfs-utils-1.3.0/README/usr/share/doc/nfs-utils-1.3.0/THANKS/usr/share/doc/nfs-utils-1.3.0/TODO/usr/share/man/man5/exports.5.gz/usr/share/man/man5/nfs.5.gz/usr/share/man/man5/nfs.conf.5.gz/usr/share/man/man5/nfsmount.conf.5.gz/usr/share/man/man7/nfs.systemd.7.gz/usr/share/man/man7/nfsd.7.gz/usr/share/man/man8/blkmapd.8.gz/usr/share/man/man8/exportfs.8.gz/usr/share/man/man8/gssd.8.gz/usr/share/man/man8/idmapd.8.gz/usr/share/man/man8/mount.nfs.8.gz/usr/share/man/man8/mountd.8.gz/usr/share/man/man8/mountstats.8.gz/usr/share/man/man8/nfsd.8.gz/usr/share/man/man8/nfsdcltrack.8.gz/usr/share/man/man8/nfsidmap.8.gz/usr/share/man/man8/nfsiostat.8.gz/usr/share/man/man8/nfsstat.8.gz/usr/share/man/man8/rpc.gssd.8.gz/usr/share/man/man8/rpc.idmapd.8.gz/usr/share/man/man8/rpc.mountd.8.gz/usr/share/man/man8/rpc.nfsd.8.gz/usr/share/man/man8/rpc.sm-notify.8.gz/usr/share/man/man8/rpc.statd.8.gz/usr/share/man/man8/rpcdebug.8.gz/usr/share/man/man8/showmount.8.gz/usr/share/man/man8/sm-notify.8.gz/usr/share/man/man8/statd.8.gz/usr/share/man/man8/umount.nfs.8.gz/var/lib/nfs/var/lib/nfs/etab/var/lib/nfs/rmtab/var/lib/nfs/rpc_pipefs/var/lib/nfs/statd/var/lib/nfs/statd/sm/var/lib/nfs/statd/sm.bak/var/lib/nfs/state/var/lib/nfs/v4recovery/var/lib/nfs/xtab 其中包含了一些 nfs 相关的服务,服务的详细介绍如下: 服务名称 功能描述 详细说明 nfs-blkmap.service 支持块映射功能 用于支持在 NFS 文件系统上的块映射,适合复杂的存储需求,普通 NFS 共享中很少使用。 nfs-idmapd.service 提供用户和组的 ID 映射功能 NFSv4 所需,负责将用户名和组名映射为 UID 和 GID,用于身份验证。 nfs-lock.service 提供文件锁定功能 支持 NFSv3 和之前版本的文件锁定,防止多个客户端同时写入导致的数据损坏。 nfs-mountd.service 处理客户端挂载请求 NFSv3 所需的挂载守护进程,用于管理客户端挂载请求并验证权限。 nfs-secure.service 提供安全的 RPC 服务 提供基于 Kerberos 认证的安全 RPC 连接,适合需要身份验证的 NFS 环境。 nfs.service NFS 客户端和服务器主服务 核心服务,通过 nfs-utils 包提供基本的 NFS 客户端和服务器功能。 nfs-config.service 加载和应用 NFS 配置 启动 NFS 服务时自动加载配置文件,确保系统配置是最新的。 nfs-idmap.service NFSv4 的用户 ID 映射 与 nfs-idmapd 类似,NFSv4 环境中用于将用户和组 ID 映射,以匹配权限。 nfs-rquotad.service 远程磁盘配额管理 允许设置和查询 NFS 共享中的磁盘配额,适合需要磁盘配额管理的场景。 nfs-server.service NFS 服务器主服务 负责处理服务器端的 NFS 共享请求,是 NFS 服务的核心。 nfs-utils.service 管理 NFS 的核心工具包 nfs-utils 提供 NFS 的工具和守护进程,包括 nfsd 和 rpcbind。 我们这里只模拟下简单的挂载,所以启动 nfs-server 服务并设置开机自启动(服务器端操作) 12systemctl start nfs-serversystemctl enable nfs-server 配置共享目录 (服务器端操作) 创建共享目录,例如目录为/mnt/nfs_share:1mkdir /mnt/nfs_share 设置权限,允许其他用户访问:1chmod -R 777 /mnt/nfs_share 编辑 /etc/exports 文件以配置共享路径及其权限。在文件末尾添加以下内容:1/mnt/nfs_share *(rw,sync,no_root_squash) 12345这里的配置选项含义如下:\t•\t* 表示允许所有 IP 访问。如果需要限制特定 IP,可以替换为特定的 IP 地址或子网。\t•\trw 表示读写权限。\t•\tsync 确保数据同步写入。\t•\tno_root_squash 允许客户端以 root 身份访问。 应用 NFS 配置 (服务器端操作) 1exportfs -r 应用完成后,可以查看: 12[root@node2 mnt]# exportfs -s/mnt/nfs_share *(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash) 配置防火墙,确保防火墙允许 NFS 服务的流量通过。可以使用以下命令打开相应的端口(服务器端操作,测试环境可以直接关闭): 1234firewall-cmd --permanent --add-service=nfsfirewall-cmd --permanent --add-service=mountdfirewall-cmd --permanent --add-service=rpc-bindfirewall-cmd --reload 客户端进行挂载 12mkdir /mnt/testmount -t nfs serverip:/mnt/nfs_share /mnt/test 挂载完成后,通过 df 查看: 12345[root@node1 ~]# df -hFilesystem Size Used Avail Use% Mounted on/dev/sda3 1.1T 257G 787G 25% /tmpfs 126G 2.3M 126G 1% /tmpnode2:/mnt/nfs_share 1.1T 103G 941G 10% /mnt/test 至此,nfs 部署和挂载就完成了 测试我们可以在客户端的挂载目录上,进行文件读写,比如创建文件,写入以及读取等 123456[root@node1 ~]# cd /mnt/test/[root@node1 test]# ls[root@node1 test]# touch test.txt[root@node1 test]# echo 123 > test.txt[root@node1 test]# cat test.txt123 在服务器端的 /mnt/nfs_share目录下可以看到同样的文件信息","tags":["NFS","存储"],"categories":["分布式存储"]},{"title":"Nginx详解","path":"/p/Nginx详解/","content":"Nginx 简介Nginx 是一个高性能的 HTTP 和反向代理服务器,同时也可以作为 IMAP/POP3 邮件代理服务器。Nginx 以其轻量级、速度快、并发处理能力强等特点而闻名,广泛用于网站架构中作为反向代理、负载均衡器和静态内容服务器。 Nginx 的主要特点 高并发处理能力: Nginx 采用异步、事件驱动的架构,与传统的 Apache 等基于进程或线程的模型不同。它可以同时处理大量并发连接,具有很高的性能和较低的资源消耗。 反向代理: Nginx 作为反向代理服务器时,可以将客户端的请求转发给后端服务器(如 Apache、Tomcat、Node.js 等),然后将后端的响应返回给客户端。常用于负载均衡、缓存加速和安全增强。 负载均衡: Nginx 支持多种负载均衡算法,如轮询(Round Robin)、最少连接(Least Connections)、IP 哈希等,能够将请求分发到多个后端服务器以提高应用的可靠性和处理能力。 静态内容服务: Nginx 在处理静态文件(如 HTML、CSS、JS、图片等)方面非常高效,常用于静态文件托管。它可以作为内容分发网络(CDN)的一部分来加速网页加载。 模块化设计: Nginx 支持模块化设计,允许根据需要启用或禁用功能模块。可以通过模块扩展 Nginx 的功能,如 Lua、SSL、缓存等。 内存消耗低: Nginx 的内存占用相对较低,适合高并发环境下的稳定运行。其设计目标是处理大量的请求而不占用过多的资源。 高可扩展性: 通过编写自定义模块,Nginx 可以与多种语言(如 Lua、Python、Go)集成,极大地增强了其功能和灵活性。OpenResty 就是基于 Nginx 的一个扩展框架,集成了 Lua 来支持动态处理。 HTTPS/SSL 支持: Nginx 具备完整的 HTTPS 支持,可以通过简单的配置启用 SSL/TLS 加密,从而提高网站的安全性。 Nginx 的常见使用场景 静态内容服务: Nginx 非常适合用于提供网站的静态资源,如 HTML 文件、图片、CSS、JavaScript 文件等。 反向代理和负载均衡: Nginx 常用于前端反向代理,将客户端的请求转发给后端服务器集群。通过负载均衡算法,Nginx 能有效分配请求并提高后端服务器的利用率和可靠性。 API 网关: 在微服务架构中,Nginx 常作为 API 网关来路由和管理 API 请求,提供身份验证、速率限制等功能。 缓存服务器: Nginx 可以作为缓存代理,缓存静态和动态内容,提高性能并减少后端服务器的负担。 邮件代理服务器: 除了 HTTP 服务器,Nginx 还可以用作 IMAP/POP3/SMTP 代理服务器,处理邮件请求并进行负载均衡。 Nginx vs. Apache 架构: Nginx 是基于事件驱动的异步架构,适合高并发;Apache 使用的是基于进程/线程的架构,每个请求占用一个进程或线程,容易在高并发场景下消耗大量资源。 性能: 在处理大量静态内容和高并发请求时,Nginx 的表现优于 Apache,且内存消耗更少。 配置和模块: Nginx 的模块在编译时确定,而 Apache 支持运行时动态加载模块,因此 Apache 在模块化灵活性方面稍胜一筹。 整体结构Nginx 整体框架 部署安装安装 centos:123yum install -y nginxsystemctl start nginxsystemctl enable nginx docker12docker pull nginxdocker run -d --name nginx -p 8080:80 nginx 组件我们先看一下 nginx 包中提供的相关组件: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253## 配置/etc/logrotate.d/nginx/etc/nginx/fastcgi.conf/etc/nginx/fastcgi.conf.default/etc/nginx/fastcgi_params/etc/nginx/fastcgi_params.default/etc/nginx/koi-utf/etc/nginx/koi-win/etc/nginx/mime.types/etc/nginx/mime.types.default/etc/nginx/nginx.conf/etc/nginx/nginx.conf.default/etc/nginx/scgi_params/etc/nginx/scgi_params.default/etc/nginx/uwsgi_params/etc/nginx/uwsgi_params.default/etc/nginx/win-utf## 服务及程序/usr/bin/nginx-upgrade/usr/lib/systemd/system/nginx.service/usr/lib64/nginx/modules/usr/sbin/nginx## 文档/usr/share/doc/nginx-1.20.1/usr/share/doc/nginx-1.20.1/CHANGES/usr/share/doc/nginx-1.20.1/README/usr/share/doc/nginx-1.20.1/README.dynamic/usr/share/doc/nginx-1.20.1/UPGRADE-NOTES-1.6-to-1.10/usr/share/licenses/nginx-1.20.1/usr/share/licenses/nginx-1.20.1/LICENSE/usr/share/man/man3/nginx.3pm.gz/usr/share/man/man8/nginx-upgrade.8.gz/usr/share/man/man8/nginx.8.gz/usr/share/nginx/html/404.html/usr/share/nginx/html/50x.html/usr/share/nginx/html/en-US/usr/share/nginx/html/icons/usr/share/nginx/html/icons/poweredby.png/usr/share/nginx/html/img/usr/share/nginx/html/index.html/usr/share/nginx/html/nginx-logo.png/usr/share/nginx/html/poweredby.png/usr/share/nginx/modules/usr/share/vim/vimfiles/ftdetect/nginx.vim/usr/share/vim/vimfiles/ftplugin/nginx.vim/usr/share/vim/vimfiles/indent/nginx.vim/usr/share/vim/vimfiles/syntax/nginx.vim/var/lib/nginx/var/lib/nginx/tmp## 日志/var/log/nginx/var/log/nginx/access.log/var/log/nginx/error.log 命令操作我们可以看下 nginx 的命令操作和服务配置: 123456789101112131415161718192021222324252627282930313233343536373839404142[root@node1 ~]# /usr/sbin/nginx -hnginx version: nginx/1.20.1Usage: nginx [-?hvVtTq] [-s signal] [-p prefix] [-e filename] [-c filename] [-g directives]Options: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: /usr/share/nginx/) -e filename : set error log file (default: /var/log/nginx/error.log) -c filename : set configuration file (default: /etc/nginx/nginx.conf) -g directives : set global directives out of configuration file[root@node1 ~]# systemctl cat nginx.service# /usr/lib/systemd/system/nginx.service[Unit]Description=The nginx HTTP and reverse proxy serverAfter=network-online.target remote-fs.target nss-lookup.targetWants=network-online.target[Service]Type=forkingPIDFile=/run/nginx.pid# Nginx will fail to start if /run/nginx.pid already exists but has the wrong# SELinux context. This might happen when running `nginx -t` from the cmdline.# https://bugzilla.redhat.com/show_bug.cgi?id=1268621ExecStartPre=/usr/bin/rm -f /run/nginx.pidExecStartPre=/usr/sbin/nginx -tExecStart=/usr/sbin/nginxExecReload=/usr/sbin/nginx -s reloadKillSignal=SIGQUITTimeoutStopSec=5KillMode=processPrivateTmp=true[Install]WantedBy=multi-user.target nginx 命令提供的参数非常简单,包括检查配置文件、发送信号、配置文件路径、error 日志路径等。 1234nginx -t # 检查配置文件是否有语法错误nginx -s reload # 热加载,重新加载配置文件nginx -s stop # 快速关闭nginx -s quit # 等待工作进程处理完成后关闭 配置详解我们先看一下 nginx 默认的配置文件内容: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283# For more information on configuration, see:# * Official English Documentation: http://nginx.org/en/docs/# * Official Russian Documentation: http://nginx.org/ru/docs/user nginx;worker_processes auto;error_log /var/log/nginx/error.log;pid /run/nginx.pid;# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.include /usr/share/nginx/modules/*.conf;events { worker_connections 1024;}http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }# Settings for a TLS enabled server.## server {# listen 443 ssl http2;# listen [::]:443 ssl http2;# server_name _;# root /usr/share/nginx/html;## ssl_certificate "/etc/pki/nginx/server.crt";# ssl_certificate_key "/etc/pki/nginx/private/server.key";# ssl_session_cache shared:SSL:1m;# ssl_session_timeout 10m;# ssl_ciphers HIGH:!aNULL:!MD5;# ssl_prefer_server_ciphers on;## # Load configuration files for the default server block.# include /etc/nginx/default.d/*.conf;## error_page 404 /404.html;# location = /40x.html {# }## error_page 500 502 503 504 /50x.html;# location = /50x.html {# }# }} 我们可以看到配置文件基本上被分为了三部分,我们可以划分为全局块,event 块和 http 块 全局块配置文件中是一些全局的基本配置, 除此之外还包括了一些其他的配置,主要参数如下: 参数名 含义 示例 user 指定运行 Nginx 的用户和用户组 user www-data; worker_processes 设置 Nginx 启动的工作进程数,通常根据 CPU 核心数设置 worker_processes auto; error_log 定义错误日志文件路径和日志级别 error_log /var/log/nginx/error.log warn; pid 定义存储 Nginx 主进程 PID 的文件路径 pid /var/run/nginx.pid; worker_rlimit_nofile 设置工作进程可打开的最大文件描述符数 worker_rlimit_nofile 1024; worker_priority 设置工作进程的优先级,负值表示更高优先级 worker_priority -5; include 包含其他配置文件,用于模块化管理配置 include /etc/nginx/conf.d/*.conf; daemon 控制 Nginx 是否以守护进程方式运行,on 为默认值 daemon on; worker_cpu_affinity 绑定 Nginx 工作进程到特定 CPU 核心,提升并行处理性能 worker_cpu_affinity auto; worker_shutdown_timeout 设置工作进程终止时的最大等待时间 worker_shutdown_timeout 10s; lock_file 定义锁文件路径,用于管理 accept_mutex 下的进程锁 lock_file /var/run/nginx.lock; env 设置环境变量 env OPENSSL_CONF=/etc/ssl/openssl.cnf; Nginx进程的工作模式我们先通过ps看一下 nginx 进程的详细情况: 12345678910111213141516[root@node1 nginx]# ps -auxf | grep nginx | grep -v greproot 20588 0.0 0.0 39308 1064 ? Ss 15:11 0:00 nginx: master process /usr/sbin/nginxnginx 20589 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20590 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20591 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20592 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20593 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20594 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20595 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20596 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20597 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20598 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20599 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20600 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker processnginx 20601 0.0 0.0 39696 1904 ? S< 15:11 0:00 \\_ nginx: worker process..... 这里我们可以看到 nginx 有一个 master 进程和多个 worker 进程,这里worker 进程的数量取决于配置文件中 worker_processes的配置。主进程的主要目的是读取和评估配置,以及维护工作进程。工作进程实际处理请求。nginx采用基于事件的模型和依赖于操作系统的机制来有效地在工作进程之间分发请求。 一旦主进程收到重新加载配置的信号,它会检查新配置文件的语法有效性,并尝试应用其中提供的配置。如果成功,主进程将启动新的工作进程,并向旧的工作进程发送消息,请求它们关闭。否则,主进程回滚更改并继续使用旧配置。旧的工作进程,收到命令关闭,停止接受新的连接,并继续服务当前的请求,直到所有这些请求都得到服务。之后,旧的worker进程退出。 event 块Nginx events 模块的配置参数用于控制 Nginx 如何处理并发连接以及选择合适的事件模型。主要涉及 Nginx 的网络事件处理机制,帮助优化高并发性能。 以下是 Nginx events 模块的参数及其含义: 参数名 含义 示例 worker_connections 每个工作进程能够处理的最大连接数,直接影响并发连接数量 worker_connections 1024; use 指定使用的事件驱动模型。通常根据操作系统选择适合的模型 use epoll;(Linux) multi_accept 是否启用一次接受尽可能多的连接,on 表示接受所有可用连接 multi_accept on; accept_mutex 是否启用接受连接的互斥锁,避免多个工作进程同时抢占新连接 accept_mutex on; accept_mutex_delay 在 accept_mutex 启用时,设置工作进程获取新连接前的等待时间 accept_mutex_delay 500ms; debug_connection 启用特定 IP 地址的连接调试日志,便于诊断连接问题 debug_connection 192.168.1.1; 参数请参考: https://github.com/nginx/nginx/blob/00637cce366f17b78fe1ed5c1ef0e534143045f6/src/event/ngx_event.c http 块用于配置 HTTP 协议下的相关行为。该块通常位于 nginx.conf 配置文件中,负责处理 HTTP 请求的行为、代理、缓存、日志等。http块中可以包含多个server块,server块也可以包含多个location块。 1.全局配置参数 参数 说明 include 引入外部配置文件,便于模块化管理配置。 default_type 为那些在 mime.types 文件中没有特定映射的文件设置默认 MIME 类型 | sendfile 启用 sendfile 以提高文件传输效率,适用于静态资源的传输。 tcp_nopush 提高 TCP 数据包发送效率,常与 sendfile 一起使用,用于减少网络延迟。 tcp_nodelay 在保持长连接时,尽快将数据发送到客户端,减少数据延迟。 keepalive_timeout 设置保持连接的超时时间,控制空闲连接的保持时间。 server_tokens 控制是否显示 NGINX 版本信息。关闭可以增加安全性,避免暴露版本细节。 client_max_body_size 限制客户端请求的最大内容大小,通常用于限制上传文件的大小。 client_body_timeout 设置读取客户端请求体的超时时间。如果在指定时间内无法读取完成,请求将被关闭。 reset_timedout_connection 启用后,连接超时会主动发送 RST 包关闭连接,而不是等待超时。 关于 mime types部分: 123456789101112131415include /etc/nginx/mime.types; # 这里引用了一个文件,文件里面指定了一些内容映射default_type application/octet-stream; # 默认类型## 我们可以看下 mime.types的一些映射types { text/html html htm shtml; text/css css; text/xml xml; image/gif gif; image/jpeg jpeg jpg; application/javascript js; application/atom+xml atom; application/rss+xml rss; ......} 大家可以参考这篇文章:MIME 类型(IANA 媒体类型) 2.日志管理 参数 说明 access_log 设置访问日志的路径和格式,用于记录所有客户端请求的详细信息。 log_format 定义日志格式,可以自定义哪些信息会写入访问日志。 error_log 指定错误日志的路径及日志级别,记录服务器运行过程中发生的错误。 12345678910# 在全局 可以定义不同的日志格式类型,比如log_format maina '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';log_format mainb '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" "xxx"' ;# 在使用的时候就可以指定类型,比如有两个服务, servicea,serviceb,分别使用两个不同格式的日志access_log /var/log/nginx/servicea.log maina;access_log /var/log/nginx/serviceb.log mainb; 3.代理配置 参数 说明 作用域 proxy_buffers 设置代理响应的缓冲区数量和大小。 http, server, location proxy_buffer_size 设置代理服务器用于读取响应头的缓冲区大小。 http, server, location proxy_connect_timeout 设置与上游服务器建立连接的超时时间。 http, server, location proxy_hide_header 隐藏来自上游服务器的指定 HTTP 响应头。 http, server, location proxy_http_version 指定代理使用的 HTTP 版本(如 1.0 或 1.1)。 http, server, location proxy_ignore_client_abort 当客户端中止请求时,是否让 NGINX 继续代理到上游服务器。 http, server, location proxy_max_temp_file_size 设置用于临时文件存储的最大大小。 http, server, location proxy_next_upstream 定义上游服务器发生错误时是否将请求转发到下一个上游服务器。 http, server, location proxy_next_upstream_tries 设置最大尝试数,当上游服务器出错时可转发到下一个服务器。 http, server, location proxy_pass 定义代理请求的上游服务器地址。 location proxy_read_timeout 设置读取上游服务器响应的超时时间。 http, server, location proxy_redirect 重写上游服务器返回的 Location 或 Refresh 头中的地址。 http, server, location proxy_request_buffering 设置是否将客户端请求的主体缓存在代理服务器中。 http, server, location proxy_send_timeout 设置发送请求到上游服务器时的超时时间。 http, server, location proxy_set_header 设置在代理请求中发送的 HTTP 请求头。 http, server, location proxy_temp_path 设置存储代理临时文件的目录。 http, server, location 4. 连接和请求控制 参数 作用 作用域 limit_conn_zone 定义共享区域以限制连接数 http limit_rate 限制响应传输速率 http, server, location limit_req_zone 定义共享区域以限制请求速率 http limit_upload_rate 限制上传传输速率 http, server, location limit_conn 限制客户端的连接数 http, server, location limit_req 限制请求速率 http, server, location limit_rate 限制客户端的下载或上传速率 http, server, location keepalive_requests 设置一个连接在关闭之前允许的最大请求数 http, server, location keepalive_timeout 设置 Keep-Alive 连接的超时时间 http, server, location 5.压缩和请求 参数 说明 作用域 gzip 启用 Gzip 压缩。 http, server, location gzip_min_length 设置启用压缩的响应体最小长度,低于此长度的响应不会被压缩。 http, server, location gzip_buffers 设置用于存储压缩数据的缓冲区数量和大小。 http, server, location gzip_http_version 设置支持的最小 HTTP 版本,低于该版本的请求不会使用 Gzip 压缩。 http, server, location gzip_comp_level 设置 Gzip 压缩级别,范围从 1 到 9,数字越大压缩率越高,资源消耗也越大。 http, server, location gzip_vary 启用 Vary: Accept-Encoding 头,指示代理和浏览器缓存应根据 Accept-Encoding 头处理不同的响应。 http, server, location gzip_types 指定需要压缩的 MIME 类型。 http, server, location proxy_cache 启用代理缓存,缓存上游服务器的响应。 http, server, location proxy_cache_path 定义缓存路径和缓存相关的控制参数,如缓存大小和策略。 http proxy_cache_valid 设置缓存的有效期,不同状态码可设置不同的缓存时间。 http, server, location Server块作用: server 块定义了一个虚拟主机配置。每个 server 块处理指定的域名或 IP 地址的请求,并根据配置响应客户端请求。可以在一个 http 块中定义多个 server 块。server 块中可以包含多个 location 块来处理不同的请求路径。 用途: -\t配置特定域名或 IP 的虚拟主机,包括监听端口、域名、根目录等。 -\t设置特定于虚拟主机的配置,例如访问控制、SSL/TLS 设置、错误页面等。 -\t定义处理请求的 location 块,用于细化请求的路由和处理 支持参数: 参数 描述 示例 listen 定义 nginx 监听的 IP 地址和端口号 listen 80; server_name 指定虚拟主机的域名或 IP 地址 server_name example.com; root 设置服务器的根目录 root /var/www/html; index 指定默认的首页文件 index index.html; location 定义匹配 URI 的块 location / { try_files $uri $uri/ =404; } error_page 自定义错误页面 error_page 404 /404.html; return 返回指定的状态码或重定向 return 301 https://$host$request_uri; rewrite 重写 URL rewrite ^/old-path/(.*)$ /new-path/$1; proxy_pass 代理请求到另一个服务器 proxy_pass http://backend_server; ssl 启用 HTTPS 支持 listen 443 ssl; ssl_certificate SSL 证书文件路径 ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key SSL 证书私钥文件路径 ssl_certificate_key /etc/nginx/ssl/example.key; allow 允许指定 IP 地址访问 allow 192.168.1.0/24; deny 拒绝指定 IP 地址访问 deny 192.168.1.100; client_max_body_size 限制客户端请求体的最大大小 client_max_body_size 10M; client_body_timeout 请求体接收超时时间 client_body_timeout 60s; keepalive_timeout 设置 keep-alive 连接超时时间 keepalive_timeout 65; send_timeout 发送响应超时时间 send_timeout 30s; expires 设置缓存过期时间 expires 30d; proxy_cache 启用代理缓存 proxy_cache my_cache; add_header 添加自定义 HTTP 响应头 add_header X-Frame-Options SAMEORIGIN; 示例: 12345678910111213141516171819202122232425server { listen 80; server_name testa.com; charset utf-8; #include mime.types; access_log /var/logs/nginx/servivea.log maina; large_client_header_buffers 4 8k; location / { proxy_ignore_client_abort on; proxy_connect_timeout 5s; proxy_read_timeout 60s; proxy_send_timeout 10s; client_body_timeout 60; client_max_body_size 20m; proxy_request_buffering off; proxy_buffers 64 256k; proxy_next_upstream error timeout http_502 http_504; proxy_set_header X-Forward-For $remote_addr; proxy_pass http://servicea; proxy_set_header Host $http_host; }} 实战配置","tags":["Nginx","反向代理","负载均衡"],"categories":["反向代理"]},{"title":"Chronyd服务详解","path":"/p/Chronyd服务详解/","content":"介绍我们先看一下官网的介绍: 1chrony is a versatile implementation of the Network Time Protocol (NTP). It can synchronise the system clock with NTP servers, reference clocks (e.g. GPS receiver), and manual input using wristwatch and keyboard. It can also operate as an NTPv4 (RFC 5905) server and peer to provide a time service to other computers in the network. 简而言之,chronyd 就是用来校准时间的,基于 ntp 协议(ntp 加强版),既可以做服务器,也可以做客户端(既能为其他机器提供时钟同步服务,也能从其他机器上同步时间) 主要功能: 时间同步:chronyd 主要负责将系统时间与网络时间服务器进行同步。它通过网络时间协议(NTP)或精确时间协议(PTP)从外部时间源获取时间信息,并调整本地系统时间。 系统时间校准:Chrony 能够处理系统时钟漂移问题,尤其是在系统启动时或在虚拟化环境中,Chrony 能够在更短的时间内校准系统时间。 网络时钟漂移:chronyd 能够处理网络延迟和时钟漂移,使得系统时间更加准确。 部署安装现在很多 linux 发行版默认都会安装 chronyd 服务,如果没有安装,我们需要手动进行安装: 1yum install -y chrony 安装完成后,我们可以看下 chrony 包中都提供了哪些东西: 123456789101112131415161718192021222324252627[root@node2 ~]# rpm -ql chrony/etc/NetworkManager/dispatcher.d/20-chrony/etc/chrony.conf/etc/chrony.keys/etc/dhcp/dhclient.d/chrony.sh/etc/logrotate.d/chrony/etc/sysconfig/chronyd/usr/bin/chronyc/usr/lib/systemd/ntp-units.d/50-chronyd.list/usr/lib/systemd/system/chrony-dnssrv@.service/usr/lib/systemd/system/chrony-dnssrv@.timer/usr/lib/systemd/system/chrony-wait.service/usr/lib/systemd/system/chronyd.service/usr/libexec/chrony-helper/usr/sbin/chronyd/usr/share/doc/chrony-3.4/usr/share/doc/chrony-3.4/COPYING/usr/share/doc/chrony-3.4/FAQ/usr/share/doc/chrony-3.4/NEWS/usr/share/doc/chrony-3.4/README/usr/share/man/man1/chronyc.1.gz/usr/share/man/man5/chrony.conf.5.gz/usr/share/man/man8/chronyd.8.gz/var/lib/chrony/var/lib/chrony/drift/var/lib/chrony/rtc/var/log/chrony 经常涉及的主要包括: /etc/chrony.conf: 配置文件 /usr/bin/chronyc: 命令行工具 /usr/sbin/chronyd: 启动程序 配置文件详解下面是 chronyd 配置文件中提供的参数及每项含义: 配置项 解释 server <ntp-server> iburst 指定 NTP 服务器地址,iburst 选项表示在启动时快速发送一系列请求,以加快初始同步过程。 driftfile /var/lib/chrony/drift 指定时钟漂移文件的位置,该文件记录系统时钟的增益或损失率,以帮助 Chrony 在重启后更快地调整时钟。 makestep 1.0 3 允许系统时钟在前 3 次更新中,若时钟偏差超过 1 秒,可以进行大步调整(即直接调至正确时间),而不是逐渐调整。 rtcsync 启用内核与实时时钟(RTC)的同步功能,以确保系统时钟与硬件时钟保持一致。 hwtimestamp * 启用所有支持的接口上的硬件时间戳(该行被注释掉,未启用)。 minsources 2 增加可选时间源的最小数量,当达到该数量时,才会调整系统时钟(该行被注释掉,未启用)。 allow 192.168.0.0/16 允许从指定的局域网(如 192.168.0.0/16)访问 NTP 客户端,这通常用于允许局域网内的其他设备与本机进行时间同步(该行被注释掉,未启用)。 local stratum 10 即使本地时间未与时间源同步,也允许本地系统作为时间服务器。stratum 指定了时间层级,stratum 10 表示较低的层次,用于避免对外部 NTP 服务器的依赖(该行被注释掉,未启用)。 keyfile /etc/chrony.keys 指定 NTP 认证的密钥文件路径,用于确保 NTP 客户端和服务器之间的通信是安全的(该行被注释掉,未启用)。 logdir /var/log/chrony 指定日志文件的目录,Chrony 会将日志存储在该目录下。 log measurements statistics tracking 选择哪些信息需要被记录到日志中,包含测量结果、统计信息和跟踪信息(该行被注释掉,未启用)。 chronyc 命令详解 命令 说明 tracking 显示当前时间跟踪状态,包括时间源、时钟偏移、频率偏移等信息。 sources 列出当前配置的 NTP 时间源及其状态。 sourcestats 显示各个 NTP 时间源的统计信息,包括时间源的延迟、偏移、抖动等。 ntpdata 显示与各个时间源相关的低级 NTP 数据,如参考时钟 ID 和偏移量等。 makestep 立即将系统时间同步到时间源(即大幅度调整系统时间,而非逐步调整)。 burst 向所有时间源发送一组时间请求,以加快同步速度。 reload sources 重新加载配置文件中的时间源,而不需要重新启动 chronyd。 clients 列出通过 chronyd 进行时间同步的客户端。 settime 手动设置系统时间。 rtcdata 显示实时时钟(RTC)的状态信息。 manual 启动手动时间输入模式,用于手动设置时间(如通过键盘或手表)。 dump 输出 chronyd 内存中时间源的状态信息。 waitsync 等待系统时钟与时间源同步,通常用于确保系统启动时时间同步完成。 activity 显示当前活动的时间源数量和状态。 reselect 强制 chronyd 重新选择最佳的时间源。 serverstats 显示 chronyd 的服务器统计信息,如请求数量、响应延迟等。 manual list 列出手动设置的时间值。 trimrtc 通过调整实时时钟(RTC)的频率,使其更接近系统时钟的频率。 quit 退出 chronyc 交互模式。 常用命令我们先看一下常用的一些命令: chronyc sources1234567[root@node2 ~]# chronyc sources210 Number of sources = 3MS Name/IP address Stratum Poll Reach LastRx Last sample===============================================================================^+ node1 3 6 377 46 +330us[ +400us] +/- 3523us^+ 8.8.8.8 2 6 377 47 +570us[ +640us] +/- 2602us^* 8.8.8.9 2 6 377 45 +366us[ +436us] +/- 3478us 这里显示了当前这个机器同步的一些源信息,包含 源节点: node1、8.8.8.8、9.9.9.9, 最前面的*表示最优先的时钟源, Stratum: NTP 时间源的层级(stratum),表示时间源的准确性层次。Stratum 0 是参考时钟(如 GPS),Stratum 1 是直接从参考时钟获取时间的服务器,以此类推。较高的层级表示离参考时钟的距离越远,时钟的准确性也会降低。 Poll: 系统与时间源之间的轮询间隔(以秒为单位)。该值表示在上一次时间同步之后,系统等待多久再次向该时间源请求同步。Poll 值会根据网络状况自动调整,通常范围是 64 到 1024 秒。 Reach: 到达值(reach),是一个 8 位的八进制值,表示最近 8 次对该时间源的请求是否成功。该值通常为 377(所有 8 次请求都成功),较低的值表示有请求失败。 LastRx: 最近一次从该时间源接收 NTP 数据包的时间(以秒为单位),表示距离上次成功接收数据包的时间长度。值越大表示该时间源未及时响应,可能存在问题。 Last sample:最近一次时间样本的偏差值,表示系统时钟与时间源时钟之间的偏移量。正数表示系统时钟快于时间源,负数表示系统时钟慢于时间源。偏差通常以毫秒(ms)或微秒(µs)为单位显示。 我们可以看下这个节点的配置文件: 123server node1commandkey 1keyfile /etc/chrony.keys 这里大家会好奇,我这里明明只配置了 node1 为源服务器,为啥会多出两个 ip? 正好 Redhat 官方给了解释:Why does “chronyc sources” output unexpected NTP servers 但是有点烦的是这个文档需要开通红帽订阅才能看,这里我直接粘贴一下原因: 123456789101112131415Resolution:Add "PEERNTP=no" entry to /etc/sysconfig/network will prevent dhclient from receiving a list of NTP servers from the DHCP server.If you already set the network connection down but still gets the NTP server in chronyc sources, delete /var/lib/dhclient/chrony.servers.* file and restart chronyd service.Root Cause:If a network connection is set to use DHCP to get IP address, when NetworkManager starts or network connection is up, dhclient receives a list of NTP servers from the DHCP server and generates /var/lib/dhclient/chrony.servers.* file (since chrony 4.1-1, the file is /run/chrony-helper/nm-dhcp.*).Chronyd not only reads /etc/chrony.conf file, but also reads /var/lib/dhclient/chrony.servers.* file to get NTP server list.If an NTP server has already been configured in /etc/chrony.conf file, it won't appear in /var/run/chrony-helper/added_servers.Thus, user can confirm the added servers in /var/run/chrony-helper/added_servers.Note:The environment variable, PEERNTP is used in /etc/dhcp/dhclient.d/chrony.sh(chrony rpm ) and /etc/dhcp/dhclient.d/ntp.sh(ntp rpm) 简单来说就是 chronyd 不仅会从 chrony.conf中去获取源,也会去 dhcp client 生成的信息中去获取源服务器 chronyc tracking1234567891011121314[root@ ode2 ~]# chronyc trackingReference ID : xxx (8.8.8.8)Stratum : 3Ref time (UTC) : Mon Sep 09 07:58:14 2024System time : 0.000109912 seconds fast of NTP timeLast offset : +0.000090043 secondsRMS offset : 0.000686857 secondsFrequency : 11.188 ppm slowResidual freq : +0.026 ppmSkew : 0.786 ppmRoot delay : 0.003966943 secondsRoot dispersion : 0.000785699 secondsUpdate interval : 64.8 secondsLeap status : Normal 这里能看到更加细致的同步信息,比如时间的偏移量、更新间隔等 chronyc makestep12[root@node2 ~]# chronyc makestep200 OK 这个命令主要用于时间跨度太大一次性直接进行同步的,由于时钟的调整是非常微妙要求精确的,时间跨度太大的话同步完成的周期可能比较久,所以可以通过这个命令直接同步。","tags":["Linux","Chronyd","时钟同步"],"categories":["运维工具"]},{"title":"Pacemaker+Corosync使用简介","path":"/p/Pacemaker-Corosync使用简介/","content":"参考文档 红帽官方文档 介绍pacemaker 和 corosync是两种开源软件组件,通常结合使用以构建高可用性(HA)集群。 PacemakerPacemaker 是一个集群资源管理器,负责管理集群中所有资源的启动、停止、迁移等操作。它通过与 Corosync 协作,确保在节点故障或服务异常时,资源能够自动在其他健康节点上接管。从这里我们就可以发现,pacemaker 的核心在于管理 组件pacemaker 主要包括以下组件: CIB (Cluster Information Base):存储集群的配置信息,包括资源、约束、节点等。 CRM (Cluster Resource Manager):决定如何在集群中分配和管理资源。 PEngine (Policy Engine):根据集群状态和配置策略做出决策。 Fencing:通过 STONITH(Shoot The Other Node In The Head)机制来隔离失效的节点,防止脑裂。 使用场景 管理集群中的各种资源(如虚拟 IP、数据库服务、文件系统等)。 确保服务的高可用性,在故障发生时自动切换资源到其他节点。 CorosyncCorosync 是一个集群通信引擎,负责在集群节点之间提供消息传递、组成员资格管理、心跳检测等功能。它确保集群中所有节点之间的信息同步,监控节点的健康状况,并在节点故障时通知 pacemaker。 组件 组通信:用于确保集群中所有节点保持一致的视图。 故障检测:通过心跳机制监控节点状态,当节点失联时,通知 Pacemaker。 配置管理:管理集群节点的配置和成员资格。 使用场景 集群中节点间的实时通信。 监控节点的可用性,并在节点失效时做出响应。 安装部署安装依赖 在集群的所有节点上安装相关依赖:1yum install -y pcs pacemaker corosync # Centos 启动相关服务并设置服务开机自启动:123systemctl start pcsd systemctl enable pcsd 设置hacluster用户的密码(此用户在包安装的过程中会自动创建)1sudo passwd hacluster 在 /etc/hosts 中加入节点配置,例如:12192.168.1.2 node2192.168.1.3 node3 命令操作集群的命令行操作基本上都是通过 pcs 进行,pcs 提供了如下一些命令: 命令 说明 示例命令 cluster 配置集群选项和节点 pcs cluster start 启动集群 resource 管理集群资源 pcs resource create myresource ocf:heartbeat:IPaddr2 ip=192.168.1.1 创建一个资源 stonith 管理 fence 设备 pcs stonith create myfence fence_ipmilan ipaddr=192.168.1.100 login=admin passwd=password lanplus=1 创建 STONITH 设备 constraint 管理资源约束 pcs constraint location myresource prefers node1=100 设置资源约束 property 管理 Pacemaker 属性 pcs property set stonith-enabled=false 禁用 STONITH acl 管理 Pacemaker 访问控制列表 pcs acl role create readonly 创建只读角色 qdevice 管理本地主机上的仲裁设备提供程序 pcs qdevice add model net 添加网络仲裁设备 quorum 管理集群仲裁设置 pcs quorum status 查看仲裁状态 booth 管理 booth (集群票据管理器) pcs booth status 查看 booth 状态 status 查看集群状态 pcs status 查看集群运行状态 config 查看和管理集群配置 pcs config show 显示集群配置 pcsd 管理 pcs 守护进程 pcs pcsd status 查看 pcsd 服务状态 node 管理集群节点 pcs node standby node1 将节点设置为备用 alert 管理 Pacemaker 警报 pcs alert create node=node1 severity=critical 创建警报 client 管理 pcsd 客户端配置 pcs client cert-key-gen --force 生成新的客户端证书 此外,packmaker 还提供了其他的命令,比如 crm 的一系列工具: 工具名 解释 示例 crm_attribute 管理集群属性,包括设置、修改或删除节点属性。 crm_attribute --node node1 --name attr_name --update attr_value 设置节点属性。 crm_diff 比较两个 CIB 配置文件的差异,便于配置版本管理。 crm_diff cib_old.xml cib_new.xml 比较两个 CIB 文件的差异。 crm_error 显示集群运行过程中遇到的错误信息,帮助排查故障。 crm_error -s 12345 显示特定错误代码的详细信息。 crm_failcount 查看或管理资源的失败计数,影响资源的自动重新调度。 crm_failcount --query --resource my_resource --node node1 查看失败计数。 crm_master 管理主从资源(如 DRBD)状态的工具,用于启动或停止主从资源。 crm_master --promote my_resource 提升资源为主状态。 crm_mon 实时监控集群状态,显示资源、节点、失败信息。 crm_mon --interval=5s --show-detail 每5秒更新监控,显示详细信息。 crm_node 管理集群节点的工具,包括查看节点状态、删除节点等。 crm_node -l 列出所有集群节点。 crm_report 生成集群故障报告的工具,汇总集群状态、日志和诊断信息。 crm_report -f report.tar.bz2 生成详细的故障报告。 crm_resource 管理集群资源,包括启动、停止、迁移和清除资源。 crm_resource --move my_resource --node node2 将资源迁移到另一个节点。 crm_shadow 允许对 CIB 进行“影子”配置,便于测试和调试。 crm_shadow --create shadow_test 创建影子配置。 crm_simulate 模拟集群运行状态的工具,用于测试集群配置的行为。 crm_simulate --live --save-output output.xml 运行模拟,并保存输出。 crm_standby 将节点设置为待机状态,临时不参与资源调度,或重新激活节点。 crm_standby --node node1 --off 将节点设置为待机状态。 crm_ticket 管理集群的 ticket,用于决定哪些资源在哪些位置可以运行(多站点集群)。 crm_ticket --grant my_ticket --node node1 授权 ticket 给指定节点。 crm_verify 验证当前集群配置的工具,检查配置文件的完整性和正确性。 crm_verify --live-check 验证当前运行中的集群配置。 pacemaker 和 crm 的命令对比: pcs(Pacemaker/Corosync Shell) 简介: pcs 是 Pacemaker 和 Corosync 集群管理的命令行工具。它主要用于 Red Hat 系列操作系统(例如 RHEL、CentOS 等)。pcs 提供了一个简单的命令行界面,用于管理集群、资源、节点、约束等功能。 功能: 管理 Pacemaker 集群、Corosync 配置、STONITH 设备、资源和约束等。 提供集群的创建、启动、停止、删除、资源添加、约束设置等命令。 提供简单易用的命令接口,能够将集群管理的命令封装成一步到位的操作。 支持通过 pcsd 提供 Web 界面的管理。 适用场景: pcs 更加适用于初学者和需要快速操作的用户,因为它提供了很多高层次的命令,简化了集群管理。 crm(Cluster Resource Manager Shell) 简介: crm 是 Pacemaker 的原生命令行工具,提供更加底层的控制。crm 主要用于 Pacemaker 集群资源管理和调度,支持在更细粒度上配置和管理集群资源。 功能: 提供更细致的资源管理和集群控制功能。 crm 的指令可以进行更复杂的操作,比如编辑 CIB (Cluster Information Base) 的 XML 配置文件。 允许更加精细的配置,适合对集群系统有深度了解的用户。 适用场景: crm 更加适合高级用户,特别是那些需要精确配置、排查问题或操作底层 Pacemaker 资源的场景。 节点认证和集群创建在集群中的任意一个节点上执行: 认证: 1pcs cluster auth node2 node3 -u hacluster 认证完整后创建集群 1pcs cluster setup --name mycluster node2 node3 (同时添加所有节点) 创建完成后,会生成 corosync 的配置文件,默认位置/etc/corosync/corosync.conf, 其中的内容如下: 1234567891011121314151617181920212223242526272829totem { version: 2 cluster_name: mycluster secauth: off transport: udpu}nodelist { node { ring0_addr: node2 nodeid: 1 } node { ring0_addr: node3 nodeid: 2 }}quorum { provider: corosync_votequorum two_node: 1}logging { to_logfile: yes logfile: /var/log/cluster/corosync.log to_syslog: yes} 启动集群 12pcs cluster start --all # 这里启动失败的话,可以后面加上 --debug参数查看更详细的信息,可能会因为防火墙等问题导致启动失败pcs cluster enable --all # 设置自启动 查看集群状态 1pcs status 我们看下输出情况: 12345678910111213141516171819202122Cluster name: myclusterWARNINGS:No stonith devices and stonith-enabled is not falseStack: corosyncCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorumLast updated: Mon Sep 2 18:43:57 2024Last change: Mon Sep 2 18:35:08 2024 by hacluster via crmd on node22 nodes configured0 resource instances configuredOnline: [ node2 node3 ]No resourcesDaemon Status: corosync: active/disabled pacemaker: active/disabled pcsd: active/enabled 这里我们可以看到集群的总体情况,包括节点状态、服务状态(有两个服务还处于 disabled 状态, 通过systemctl enable corosync pacemaker 设置开机启动)、资源信息(还没有添加 resource)等 stonith 配置在上文集群的状态输出中还包括了一个告警信息: No stonith devices and stonith-enabled is not false, 这里的 stonith(Shoot The Other Node In The Head) 是一种防止“脑裂” (split-brain) 的机制。当集群中的一个节点失去与其他节点的连接时,stonith 设备可以强制重启或关闭这个失联的节点,避免两个或多个节点同时操作同一个资源,导致数据损坏。想要消除这个告警,有两种解决方案: 禁用 stonith: 如果是自己的测试环境,那么可以禁用掉 stonith 来消除告警,操作方法为: 1pcs property set stonith-enabled=false 配置 stonith 设备: 在生产环境中,建议配置 stonith 我们先根据官方文档的指示看一下stonith 有哪些可用代理: 12[root@node2 corosync]# pcs stonith listError: No stonith agents available. Do you have fence agents installed? 这里提示没有代理的 agent 可用,所以我们首先需要安装fence agent: 1yum install -y fence-agents 我们再 list 一下,就可以看到支持的代理了 Fence Agent 描述 fence_amt_ws 适用于 AMT (WS) 的 Fence 代理 fence_apc 通过 telnet/ssh 控制 APC 的 Fence 代理 fence_apc_snmp 适用于 APC 和 Tripplite PDU 的 SNMP Fence 代理 fence_bladecenter 适用于 IBM BladeCenter 的 Fence 代理 fence_brocade 通过 telnet/ssh 控制 HP Brocade 的 Fence 代理 fence_cisco_mds 适用于 Cisco MDS 的 Fence 代理 fence_cisco_ucs 适用于 Cisco UCS 的 Fence 代理 fence_compute 用于自动复活 OpenStack 计算实例的 Fence 代理 fence_drac5 适用于 Dell DRAC CMC/5 的 Fence 代理 fence_eaton_snmp 适用于 Eaton 的 SNMP Fence 代理 fence_emerson 适用于 Emerson 的 SNMP Fence 代理 fence_eps 适用于 ePowerSwitch 的 Fence 代理 fence_evacuate 用于自动复活 OpenStack 计算实例的 Fence 代理 fence_heuristics_ping 基于 ping 进行启发式 Fencing 的代理 fence_hpblade 适用于 HP BladeSystem 的 Fence 代理 fence_ibmblade 通过 SNMP 控制 IBM BladeCenter 的 Fence 代理 fence_idrac 适用于 IPMI 的 Fence 代理 fence_ifmib 适用于 IF MIB 的 Fence 代理 fence_ilo 适用于 HP iLO 的 Fence 代理 fence_ilo2 适用于 HP iLO2 的 Fence 代理 fence_ilo3 适用于 IPMI 的 Fence 代理 fence_ilo3_ssh 通过 SSH 控制 HP iLO3 的 Fence 代理 fence_ilo4 适用于 IPMI 的 Fence 代理 fence_ilo4_ssh 通过 SSH 控制 HP iLO4 的 Fence 代理 fence_ilo5 适用于 IPMI 的 Fence 代理 fence_ilo5_ssh 通过 SSH 控制 HP iLO5 的 Fence 代理 fence_ilo_moonshot 适用于 HP Moonshot iLO 的 Fence 代理 fence_ilo_mp 适用于 HP iLO MP 的 Fence 代理 fence_ilo_ssh 通过 SSH 控制 HP iLO 的 Fence 代理 fence_imm 适用于 IPMI 的 Fence 代理 fence_intelmodular 适用于 Intel Modular 的 Fence 代理 fence_ipdu 通过 SNMP 控制 iPDU 的 Fence 代理 fence_ipmilan 适用于 IPMI 的 Fence 代理 fence_kdump 与 kdump 崩溃恢复服务一起使用的 Fence 代理 fence_mpath 用于多路径持久保留的 Fence 代理 fence_redfish 适用于 Redfish 的 I/O Fencing 代理 fence_rhevm 适用于 RHEV-M REST API 的 Fence 代理 fence_rsa 适用于 IBM RSA 的 Fence 代理 fence_rsb 适用于 Fujitsu-Siemens RSB 的 I/O Fencing 代理 fence_sbd 适用于 SBD 的 Fence 代理 fence_scsi 用于 SCSI 持久保留的 Fence 代理 fence_virt 适用于虚拟机的 Fence 代理 fence_vmware_rest 适用于 VMware REST API 的 Fence 代理 fence_vmware_soap 通过 SOAP API 控制 VMware 的 Fence 代理 fence_wti 适用于 WTI 的 Fence 代理 fence_xvm 适用于虚拟机的 Fence 代理 想查看代理的具体用法,可以使用: 1pcs stonith describe stonith_agent 使用 fence_heuristics_ping 作为代理,先通过pcs stonith describe fence_heuristics_ping 看下具体的用法和配置 12345678910111213141516171819202122232425262728293031323334353637fence_heuristics_ping - Fence agent for ping-heuristic based fencingfence_heuristics_ping uses ping-heuristics to control execution of another fence agent on the same fencing level.This is not a fence agent by itself! Its only purpose is to enable/disable another fence agent that lives on the same fencing level but after fence_heuristics_ping.Stonith options: method: Method to fence ping_count: The number of ping-probes that is being sent per target ping_good_count: The number of positive ping-probes required to account a target as available ping_interval: The interval in seconds between ping-probes ping_maxfail: The number of failed ping-targets to still account as overall success ping_targets (required): A comma separated list of ping-targets (optionally prepended by 'inet:' or 'inet6:') to be probed ping_timeout: The timeout in seconds till an individual ping-probe is accounted as lost quiet: Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. verbose: Verbose mode debug: Write debug information to given file delay: Wait X seconds before fencing is started login_timeout: Wait X seconds for cmd prompt after login power_timeout: Test X seconds for status change after ON/OFF power_wait: Wait X seconds after issuing ON/OFF shell_timeout: Wait X seconds for cmd prompt after issuing command retry_on: Count of attempts to retry power on pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names. Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2 pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list). pcmk_host_check: How to determine which machines are controlled by the device. Allowed values: dynamic-list (query the device via the 'list' command), static-list (check the pcmk_host_list attribute), status (query the device via the 'status' command), none (assume every device can fence every machine) pcmk_delay_max: Enable a random delay for stonith actions and specify the maximum of random delay. This prevents double fencing when using slow devices such as sbd. Use this to enable a random delay for stonith actions. The overall delay is derived from this random delay value adding a static delay so that the sum is kept below the maximum delay. pcmk_delay_base: Enable a base delay for stonith actions and specify base delay value. This prevents double fencing when different delays are configured on the nodes. Use this to enable a static delay for stonith actions. The overall delay is derived from a random delay value adding this static delay so that the sum is kept below the maximum delay. pcmk_action_limit: The maximum number of actions can be performed in parallel on this device Pengine property concurrent-fencing=true needs to be configured first. Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.Default operations: monitor: interval=60s 这里我们进行创建(其他参数都有默认值,按需修改即可): 12pcs stonith create my_ping_fence_device fence_heuristics_ping \\ ping_targets="node2,node3" 创建完成后pcs status查看状态 1234567891011121314151617181920212223Cluster name: myclusterStack: corosyncCurrent DC: node2 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorumLast updated: Tue Sep 3 14:40:56 2024Last change: Tue Sep 3 14:35:39 2024 by root via cibadmin on node22 nodes configured1 resource instance configuredOnline: [ node2 node3 ]Full list of resources: my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node2Failed Fencing Actions:* reboot of my_apc_fence_device failed: delegate=, client=stonith_admin.40341, origin=node2, last-failed='Tue Sep 3 14:12:23 2024'Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: active/enabled 验证 stonith 是否生效: 12[root@node2 cluster]# pcs stonith fence node3Node: node3 fenced 执行完这个操作后,节点会离线,pcs 服务会停止,想要加回来的话,在停止的节点上重新启动集群即可:pcs cluster start && pcs cluster enable 实战操作添加节点上文中,我们构建了一个两节点的集群,我们可以尝试增加一个节点,构建一个三节点的集群 首先在新节点上安装各种依赖,设置密码等。 在原集群上认证新 node 在原集群上添加 node:pcs cluster node add node4 在 node4 上执行:pcs cluster start && pcs cluster enable 再通过pcs status就可以看到新的节点已经加入 添加完之后还需要更新新节点的一些配置,比如上文提到的 stonith: 12pcs stonith update my_ping_fence_device fence_heuristics_ping \\ ping_targets="node2,node3,node4" 配置 resource资源类型创建 resource 的基本格式为: 1pcs resource create resource-name ocf:heartbeat:apache [--options] 这里的 ocf:heartbeat:apache 第一个部分ocf,指明了这个资源采用的标准(类型),第二个部分标明这个资源脚本的在ocf中的名字空间,在这个例子中是heartbeat。最后一个部分指明了资源脚本的名称。 我们先看下有哪些标准类型 12345[root@node3 ~]# pcs resource standardslsbocfservicesystemd 查看可用的ocf资源提供者: 1234[root@node3 ~]# pcs resource providersheartbeatopenstackpacemaker 查看特定标准下所支持的脚本,例:ofc:heartbeat 下的脚本(列举了部分): 12345678910111213141516171819202122232425[root@node3 ~]# pcs resource agents ocf:heartbeataliyun-vpc-move-ipapacheaws-vpc-move-ipaws-vpc-route53awseipawsvipazure-eventsazure-lbclvmconntrackdCTDBdb2DelaydhcpddockerDummyethmonitorexportfsFilesystemgaleragarbdiface-vlanIPaddrIPaddr2 设置虚拟 ip虚拟 IP(Virtual IP)是在高可用性集群中使用的一种技术,通过为服务提供一个不依赖于特定物理节点的 IP 地址来实现服务的高可用性。当集群中的某个节点出现故障时,虚拟 IP 可以迅速转移到另一个健康的节点上,从而保证服务的连续性。 虚拟 IP 的使用场景 高可用性:虚拟 IP 最常见的使用场景是高可用性集群(如 Pacemaker 或 Keepalived),它允许一个服务在集群中的多个节点之间进行切换,而不会更改客户端访问的 IP 地址。 负载均衡:虚拟 IP 可以结合负载均衡器使用,将来自客户端的请求分配到多个后端服务器,以实现流量的均匀分布。 灾难恢复:在灾难恢复场景中,虚拟 IP 可以用于快速恢复服务,将业务流量从故障节点转移到备用节点上 在 pcs 集群中,我们可以通过以下方式增加一个虚拟 ip: 1pcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=x.x.x.x cidr_netmask=32 nic=bond1 op monitor interval=30s 执行完成后,通过pcs status就可以看到 ip 绑定在哪里: 1virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node3 当我们关停 node3 的服务时,就会发现这个虚拟ip 绑定到了其他节点的 bond1 网卡上。 增加服务我们以 httpd 服务为例,在集群中创建资源,首先安装对应服务: 123sudo yum install httpd -ysudo systemctl start httpd # 这里可选择不启动,后续如果通过pcs 直接托管,需要先停掉,sudo systemctl enable httpd 创建 resource: 1pcs resource create WebService ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s 结合 LVS + ldirectord 进行使用如果环境是一套多节点集群,在生产中我们肯定需要充分利用起这些节点,所以就要考虑流量分发。在这一层面上,我们可以使用 lvs 进行流量分发。这里首先对 lvs 对一个简单的介绍 LVS(Linux Virtual Server)是一个基于 IP 负载均衡技术的开源软件项目,主要用于构建高可用、高性能的负载均衡集群。LVS 是 Linux 内核的一部分,通过网络层的负载均衡技术,将来自客户端的请求分发到多个后端服务器,从而实现分布式处理、提高系统的处理能力和可靠性。 LVS 主要通过三种负载均衡模式(NAT 模式、DR 模式、TUN 模式)来实现流量的分发,支持大规模并发请求的处理,通常用于大型网站、电子商务平台和高访问量的 Web 应用中。 LVS 的特点 高性能: LVS 工作在网络层(第4层),基于 IP 进行流量转发,性能极高。它能够处理大量的并发连接,适合高流量、大规模的网站和服务。 高可用性: LVS 通常与 Keepalived、Pacemaker 等高可用性工具配合使用,以实现负载均衡器的自动故障切换,确保服务的高可用性和稳定性。 多种负载均衡算法: LVS 提供了多种负载均衡算法,如轮询(Round Robin)、最小连接(Least Connection)、基于目标地址哈希(Destination Hashing)等,可以根据具体需求选择合适的算法进行流量分发。 多种工作模式, LVS 支持三种主要工作模式: NAT 模式(网络地址转换模式):LVS 充当请求和响应的中介,适用于小规模集群。 DR 模式(直接路由模式):请求由 LVS 转发,但响应直接返回给客户端,适用于大型集群,性能高。 TUN 模式(IP 隧道模式):类似于 DR 模式,但支持跨网络部署,非常适合广域网负载均衡。 高扩展性: LVS 可以轻松地扩展和管理多台服务器,支持动态添加或移除后端服务器,适应业务需求的变化,且不影响服务的正常运行。 透明性: 对客户端和后端服务器来说,LVS 的存在是透明的。客户端并不感知负载均衡的存在,访问体验一致。后端服务器也不需要做特殊的配置,只需处理 LVS 转发的请求。 成熟且稳定: 作为一个成熟的负载均衡解决方案,LVS 被广泛应用于生产环境中,经过多年发展,功能完备,稳定性高。 安全性: LVS 可以与防火墙等安全工具结合使用,增强系统的安全性。此外,LVS 还支持 IP 地址过滤、端口过滤等功能,提供一定程度的安全保护。 ldirectord 是一个守护进程,用于管理和监控由 LVS 提供的虚拟服务(Virtual Services)。其主要功能包括: 监控后端服务器:ldirectord 定期检查后端服务器的健康状况,确保只有健康的服务器参与流量分配。 动态配置:基于后端服务器的健康状况,ldirectord 可以动态调整 LVS 的配置。例如,当一台服务器宕机时,ldirectord 会自动将其从 LVS 配置中移除。 高可用性:结合 heartbeat 等高可用性工具,ldirectord 可以确保在主节点故障时,负载均衡服务能够自动切换到备用节点,继续提供服务。 安装部署安装lvs: 1yum install lvm2 ipvsadm -y 在这里找包有一些技巧,比如一开始 chatgpt 提供的说法是要安装lvs 和 ipvsadm,但是在我的环境上通过yum install -y lvs 的时候提示没有这个包,那我们可以通过 yum 提供的一些命令来简单锁定一下,比如 yum provides lvs,这样就会把包含了这个命令的包显示出来(适用于知道命令但是不知道是哪个包的场景), 安装 ldirector: 123456789# 这里我用 yum 下载是没有找到对应包的,找了一圈也没找到安装方法,所以直接找的 rpm 包# 下载地址: ftp://ftp.icm.edu.pl/vol/rzm3/linux-opensuse/update/leap/15.2/oss/x86_64/ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm# 上传到机器上后,进行安装rpm -Uvh --force ldirectord-4.4.0+git57.70549516-lp152.2.9.1.x86_64.rpm# 需要依赖,先安装依赖,再装包yum install -y perl-IO-Socket-INET6 perl-MailTools perl-Net-SSLeay perl-Socket6 perl-libwww-perl# 操作完成之后,启动服务systemctl start ldirectord.service 服务启动失败: ldirectord服务 这里有点坑,缺少了依赖的文件,但是装包的时候没有提示,需要再安装: yum install -y perl-Sys-Syslog, 安装完成后此问题消失,但是此时配置文件还没配置,所以服务还起不来。 pcs 结合 lvs、ldirectord在上文中,我们创建了一个 httpd 服务和 vip 资源。 在实际生产中,要充分利用节点性能,我们可能要在多个节点上启动httpd 示例,我们在每个节点上都启动一个实例,然后将他们归到一个组中: 12345678pcs resource delete WebService # 移除之前创建的服务pcs resource create WebService1 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30spcs resource create WebService2 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --forcepcs resource create WebService3 ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf op monitor interval=30s --force # 创建三个服务pcs constraint location WebService1 prefers node2pcs constraint location WebService2 prefers node3pcs constraint location WebService3 prefers node4 # 限制对应 resource 服务只能在指定节点上运行 配置 ldirectord: 1234567891011121314151617checktimeout=10checkinterval=2autoreload=yeslogfile="/var/log/ldirectord.log"quiescent=yesvirtual=vip:80 # 之前绑定的 VIP real=192.168.1.2:80 gate real=192.168.1.3:80 gate real=192.168.1.4:80 gate fallback=127.0.0.1:80 service=http request="index.html" receive="HTTP/1.1 200 OK" scheduler=rr protocol=tcp checktype=negotiate 然后在通过 ipvsadm -ln 就可以查看到详细的信息: 12345678IP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP vip:80 rr -> 192.168.1.2:80 Route 0 0 0 -> 192.168.1.3:80 Route 0 0 0 -> 192.168.1.4:80 Route 0 0 0 -> 127.0.0.1:80 Route 1 0 0 然后我们可以在 pcs 上创建一个资源 lvs 相关的资源: 12345pcs resource create my_lvs ocf:heartbeat:ldirectord \\ configfile=/etc/ha.d/ldirectord.cf \\ ldirectord=/usr/sbin/ldirectord \\ op monitor interval=15s timeout=60s \\ op stop timeout=60s 这里的ocf:heartbeat:ldirectord 在有的版本中会默认安装,有的版本不会,如果没有的话需要手动下载: https://github.com/ClusterLabs/resource-agents/blob/main/ldirectord/OCF/ldirectord.in存放到: /usr/lib/ocf/resource.d/heartbeat/ldirectord 并添加可执行权限: chmod +x /usr/lib/ocf/resource.d/heartbeat/ldirectord 创建完成后,我们可以将 vip 和 lvs 绑定到一个组中,这样 lvs 就会跟着 vip 进行转移了: 1pcs resource group add balanceGroup virtual_ip my_lvs 通过pcs status查看就可以看到: 123Resource Group: balanceGroup virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2 my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2 不断对节点进行关闭测试,可以看到 lvs 和 vip 始终都在同一个节点上 增加节点属性我们这里使用另外一个观察集群状态的命令:crm_mon, 比如crm_mon -A1 1234567891011121314151617181920212223242526[root@node2 rpm]# crm_mon -A1Stack: corosyncCurrent DC: node3 (version 1.1.23-1.el7_9.1-9acf116022) - partition with quorumLast updated: Thu Sep 5 22:09:53 2024Last change: Wed Sep 4 18:17:25 2024 by root via cibadmin on node33 nodes configured6 resource instances configuredOnline: [ node2 node3 node4 ]Active resources: my_ping_fence_device\t(stonith:fence_heuristics_ping):\tStarted node3 WebService1\t(ocf::heartbeat:apache):\tStarted node4 WebService2\t(ocf::heartbeat:apache):\tStarted node3 WebService3\t(ocf::heartbeat:apache):\tStarted node4 Resource Group: balanceGroup virtual_ip\t(ocf::heartbeat:IPaddr2):\tStarted node2 my_lvs\t(ocf::heartbeat:ldirectord):\tStarted node2Node Attributes:* Node node2:* Node node3:* Node node4:..... 输出和pcs status查看到的效果基本上是差不多的。但是在下面有Node Attributes,这里我们看下节点属性怎么设置: 123pcs node attribute node2 role=masterpcs node attribute node3 role=standbypcs node attribute node4 role=standby 或者 12crm_attribute --node node2 --name mysql --update mastercrm_attribute --node node3 --name mysql --update standby 设置完成之后,我们就可以看到节点属性: 123456789Node Attributes:* Node node2: + mysql : master + role : master* Node node3: + mysql : standby + role : standby* Node node4: + role : standby 那有人就会好奇这样设置有什么用呢?主要用途是在哪里呢。 这里的指标往往是动态的,可以根据自己喜好结合一些扩展进行变化,比如部署了一套 postgresql 集群,集群中有主有备,有同步节点也有异步节点,有的节点状态可能有问题,那我们怎么能够显示出这个集群的整体情况呢,这样就可以使用 Node Attributes进行设置,关于如果搭建 pcs + postgresql 的集群,大家可以参考这篇文章: 基于Pacemaker的PostgreSQL高可用集群 最终我们看到的效果如下: 1234567891011121314Node Attributes:* Node pg01: + master-pgsql : 1000 + pgsql-data-status : LATEST + pgsql-master-baseline : 0000000008000098 + pgsql-status : PRI * Node pg02: + master-pgsql : -INFINITY + pgsql-data-status : STREAMING|ASYNC + pgsql-status : HS:async * Node pg03: + master-pgsql : 100 + pgsql-data-status : STREAMING|SYNC + pgsql-status : HS:sync 当集群发生节点变动,状态异常时,我们就可以根据 attibutes 的一些信息查看定位。","tags":["Linux","Pacemaker","Corosync","高可用方案","负载均衡","LVS"],"categories":["高可用"]},{"title":"工作随记","path":"/p/工作随记/","content":"Centos 安装 EBPF安装必要工具和依赖: 1sudo yum install python3 python3-pip python3-devel gcc gcc-c++ make bcc bcc-tools bcc-devel 安装 BCC Python 模块 1pip3 install bcc 离线安装的方式如下: 下载 bcc 和 Python 模块源包123456# 下载 bcc 的源码包wget https://github.com/iovisor/bcc/archive/refs/tags/v0.22.0.tar.gz -O bcc.tar.gz# 下载 bcc 的 Python 模块源代码包# 可以去这里 https://pypi.org/project/bcc/#files 进行查找wget https://files.pythonhosted.org/packages/38/dc/3ca34874926789f8df53f3c1d1c38e77ebf876f43760e8745316bb8bd1c0/bcc-0.1.10.tar.gz 上传文件到离线环境上,解压并进行安装:12345678910111213tar -xzf bcc.tar.gzcd bcc-0.22.0 # 目录名可能会有所不同# 安装系统依赖(可能需要根权限)sudo yum install -y gcc gcc-c++ make bpfcc-tools # CentOS/RHELsudo apt-get install -y gcc g++ make bpfcc-tools # Ubuntu/Debian# 编译和安装 bccmkdir buildcd buildcmake ..makesudo make install 安装 bcc Python 模块123456# 解压下载的 Python 模块源代码包tar -xzf bcc-python.tar.gzcd bcc-python # 目录名可能会有所不同# 安装 Python 模块pip3 install . iperf 测试带宽打不满iperf3 作为iperf 系列网络测试工具新一代工具,开发团队重写代码使之有全新的实现方式,更少的代码量,更加小巧,但这也导致了其与iperf工具前后不兼容,一些命令执行具有差异化 iperf3 是不支持多线程的,与iperf 通过-P 参数增加数据并行流开启多线程不同,iperf3 增加-P 参数也是单线程的,测试过程中所有并行流运行与同一个CPU核心,这将限制我们获得最大的带宽测试结果,为了解决这一问题,可以增加iperf3的进程。 Server端: 1iperf3 -s -p 5201 & iperf3 -s -p 5202 & iperf3 -s -p 5203& Client端: 123iperf3 -c $ip -i 5 -t 100 -P 11 -p 5201 & iperf3 -c $ip -i 5 -t 100 -P 11 -p 5202 & iperf3 -c $ip -i 5 -t 100 -P 11 -p 5203 & mac tar 打包文件后到 linux 解压后出现 ._ 开头的文件1tar --disable-copyfile -czvf archive_name.tar.gz /path/to/file_or_directory","tags":["Linux","ebpf"],"categories":["工作随记"]},{"title":"ansible详细介绍","path":"/p/ansible工具使用/","content":"官方文档 英文文档中文文档 ansible 介绍我们先看一下 ansible 的相关介绍: 1Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com. 这里有几点核心: ansible 是一个自动化平台 ansible 使用 ssh 协议(通过密码或者密钥等方式进行访问),部署简单,没有客户端,只需在主控端部署 Ansible 环境,无需在远程系统上安装代理程序 模块化:调用特定的模块,完成特定任务 支持自定义扩展 每开发一个工具或者平台的时候,这些工具和平台提供了各种各样的功能,那么我们能用 ansible 来干什么呢? 下面就是一些 ansible 核心的功能介绍: 功能 描述 常见场景示例 配置管理 自动化管理服务器和设备的配置,确保它们处于期望的状态。 自动化安装和配置 Web 服务器,确保所有服务器配置一致。 应用部署 自动化应用程序的部署过程,从代码库拉取到在服务器上部署和配置应用。 自动部署多层 Web 应用程序,包括数据库设置、应用服务部署、负载均衡配置等。 持续交付与持续集成(CI/CD) 与 CI/CD 工具集成,实现自动化的构建、测试和部署流程。 代码提交后自动执行测试、构建容器镜像,并部署到 Kubernetes 集群中。 基础设施即代码(IaC) 编写和管理基础设施的配置文件,使其像管理代码一样。 使用 Ansible Playbooks 定义云环境资源配置,实现可重复的基础设施部署。 云管理 自动化云服务资源的管理,包括虚拟机、存储、网络等的创建和配置。 自动化创建和管理 AWS EC2 实例、配置 VPC 和安全组。 网络自动化 管理和配置网络设备,使得大规模的网络设备配置变得简单和一致。 自动化配置多台交换机的 VLAN 设置和路由协议。 安全与合规 自动化安全补丁的部署、系统安全配置的强化以及合规性检查。 自动化应用系统安全补丁,配置防火墙规则,执行安全扫描和合规性检查。 多平台环境管理 支持多种操作系统,在混合环境中统一管理平台上的配置和应用。 在混合的 Linux 和 Windows 服务器环境中,统一部署和配置监控软件。 灾难恢复 自动化灾难恢复流程,如备份、数据恢复和服务恢复。 自动化数据库备份并在需要时恢复和重建数据库服务。 任务调度与批量操作 通过 Playbooks 调度定期任务或对大量服务器执行批量操作。 定期清理服务器上的临时文件或批量更新多个服务器的操作系统。 ansible的工作机制Ansible 在管理节点将 Ansible 模块通过 SSH 协议推送到被管理端执行,执行完之后自动删除,可以使用 SVN 等来管理自定义模块及编排 ansible结构 从这张图中我们可以看到,ansible 由以下模块组成 1234567Ansible: ansible的核心模块Host Inventory:主机清单,也就是被管理的主机列表Playbooks:ansible的剧本,可想象为将多个任务放置在一起,一块执行Core Modules:ansible的核心模块Custom Modules:自定义模块Connection Plugins:连接插件,用于与被管控主机之间基于SSH建立连接关系Plugins:其他插件,包括记录日志等 ansible 安装在 centos 环境下,我们可以通过: 1yum install ansible -y 进行安装 安装完成后我们看下 ansible 提供了哪些命令: 命令 描述 示例 ansible 用于在一个或多个主机上运行单个模块(通常用于临时命令)。 ansible all -m ping ansible-playbook 用于运行 Ansible playbook 文件,是 Ansible 的核心命令之一。 ansible-playbook site.yml ansible-galaxy 用于管理 Ansible 角色和集合。可以下载、创建和分享角色。 ansible-galaxy install geerlingguy.apache ansible-vault 用于加密和解密敏感数据(如密码、密钥)。 ansible-vault encrypt secrets.yml ansible-doc 显示 Ansible 模块的文档和示例用法。 ansible-doc -l ansible-config 用于查看、验证和管理 Ansible 配置文件。 ansible-config list ansible-inventory 管理和检索 Ansible inventory 信息。 ansible-inventory --list -i inventory.yml ansible-pull 用于从远程版本控制系统(如 Git)拉取 playbook 并在本地执行,常用于自动化部署。 ansible-pull -U https://github.com/username/repo.git ansible-console 提供一个交互式命令行接口,可用于动态执行 Ansible 任务和命令。 ansible-console 对两个比较常用的命令:ansible 和 ansible-plabook 做一下具体的介绍: ansible 命令命令格式: 1ansible 组名 -m 模块名 -a '参数' 这里的组名是自定义的一系列组信息,组的定义在后面会讲到。 模块名是ansible提供的一些列支持模块,默认模块是 command,查看 ansible 支持的模块: 1ansible-doc -l #大概有 3000 多个 ansible涉及到的模块非常非常多,按照实际需要使用,初步先掌握一些比较常用的就可以 查看模块描述: 1ansible-doc -s 模块名称 实例: 1234# 查看webserver 组机器的时间信息ansible webserver -m shell -a "date" # 这里的组就是 webserver,shell 是模块名(比较常用的模块)date 是具体执行的命令# 将本机的/tmp/test.sh 拷贝到其他机器上的 /etc目录下ansible webserver -m copy -a "src=/tmp/test.sh dest=/etc/test.sh" # 这里的 copy 是模块名,里面的 src 是源路径,dest 是目标路径 ansible-playbook命令用于执行 Ansible Playbooks。Playbooks 是一系列任务的集合,用于自动化配置管理、应用部署、任务执行等操作。基本语法: 1ansible-playbook [options] playbook.yml 命令帮助: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485positional arguments: playbook Playbook(s)optional arguments: --ask-vault-pass ask for vault password --flush-cache clear the fact cache for every host in inventory --force-handlers run handlers even if a task fails --list-hosts outputs a list of matching hosts; does not execute anything else --list-tags list all available tags --list-tasks list all tasks that would be executed --skip-tags SKIP_TAGS only run plays and tasks whose tags do not match these values --start-at-task START_AT_TASK start the playbook at the task matching this name --step one-step-at-a-time: confirm each task before running --syntax-check perform a syntax check on the playbook, but do not execute it --vault-id VAULT_IDS the vault identity to use --vault-password-file VAULT_PASSWORD_FILES vault password file --version show program's version number, config file location, configured module search path, module location, executable location and exit -C, --check don't make any changes; instead, try to predict some of the changes that may occur -D, --diff when changing (small) files and templates, show the differences in those files; works great with --check -M MODULE_PATH, --module-path MODULE_PATH prepend colon-separated path(s) to module library (def ault=~/.ansible/plugins/modules:/usr/share/ansible/plu gins/modules) -e EXTRA_VARS, --extra-vars EXTRA_VARS set additional variables as key=value or YAML/JSON, if filename prepend with @ -f FORKS, --forks FORKS specify number of parallel processes to use (default=64) -h, --help show this help message and exit -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY specify inventory host path or comma separated host list. --inventory-file is deprecated -l SUBSET, --limit SUBSET further limit selected hosts to an additional pattern -t TAGS, --tags TAGS only run plays and tasks tagged with these values -v, --verbose verbose mode (-vvv for more, -vvvv to enable connection debugging)Connection Options: control as whom and how to connect to hosts --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE use this file to authenticate the connection --scp-extra-args SCP_EXTRA_ARGS specify extra arguments to pass to scp only (e.g. -l) --sftp-extra-args SFTP_EXTRA_ARGS specify extra arguments to pass to sftp only (e.g. -f, -l) --ssh-common-args SSH_COMMON_ARGS specify common arguments to pass to sftp/scp/ssh (e.g. ProxyCommand) --ssh-extra-args SSH_EXTRA_ARGS specify extra arguments to pass to ssh only (e.g. -R) -T TIMEOUT, --timeout TIMEOUT override the connection timeout in seconds (default=10) -c CONNECTION, --connection CONNECTION connection type to use (default=smart) -k, --ask-pass ask for connection password -u REMOTE_USER, --user REMOTE_USER connect as this user (default=None)Privilege Escalation Options: control how and which user you become as on target hosts --become-method BECOME_METHOD privilege escalation method to use (default=sudo), use `ansible-doc -t become -l` to list valid choices. --become-user BECOME_USER run operations as this user (default=root) -K, --ask-become-pass ask for privilege escalation password -b, --become run operations with become (does not imply password prompting) ansible 基本使用我们先看一个最基本的 ansible 组成: ansible基本组成 如上图所示,大部分的 ansible 环境都包含以下三个组件: 控制节点:安装了Ansible的系统。可以在控制节点上运行Ansible命令,例如ansible或ansible-inventory。 Inventory: 按逻辑组织的托管节点的列表。可以在控制节点上创建一个清单,以向Ansible描述主机部署。 被管理节点: Ansible控制的远程系统或主机。 Inventory文件inventory 主要包括主机和组两个概念, 默认文件 /etc/ansible/hosts 主机主机是 Ansible 可以管理的单个设备或虚拟机。主机可以是物理服务器、虚拟机、容器,甚至是网络设备(如路由器和交换机)。每个主机都有一个唯一的标识(通常是主机名或 IP 地址),并且可以通过 Ansible 的 inventory 文件或其他动态方法来定义 1234567891011# 定义单个主机web1.example.com# 定义多个主机web2.example.com192.168.1.10# 主机变量[atlanta]host1 http_port=80 maxRequestsPerChild=808host2 http_port=303 maxRequestsPerChild=909 组组是主机的集合,可以对一组主机应用相同的配置或操作。组允许用户在多个主机上批量执行任务。一个主机可以属于多个组。组之间还可以嵌套,例如,你可以将所有 Web 服务器放在一个组中,然后将该组嵌套在一个更大的生产环境组中 12345678910111213141516171819202122232425# 定义一下所有节点的信息[all_hosts]node1 public_ip=192.168.1.101 ansible_host=192.168.1.101 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22node2 public_ip=192.168.1.102 ansible_host=192.168.1.102 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22node3 public_ip=192.168.1.103 ansible_host=192.168.1.103 ansible_user=your_user ansible_ssh_pass=your_password ansible_port=22# 定义一个 mysql 组,假如要在 node1 和 node2上部署 mysql[mysql]node1node2# 定义一个 nginx 组,假如要在 node1 和 node3上部署 nginx[nginx]node1node3# 假如我们还想部署一个 redis 组,mysql 部署在那里,redis 就部署在那里,重新写一遍很麻烦,那么我们可以把 redis 当做 mysql 的子集# 这里有一个疑问,我们既然用一个一模一样的组再了,为啥搞个 children,直接用原来的组不行吗,这里可以是可以,但是从模块划分来看,做一下区分易于后续的管理[redis:children]mysql# 定义组变量[atlanta:vars]ntp_server=ntp.atlanta.example.comproxy=proxy.atlanta.example.com 简单使用在我们了解完 inventory 之后,我们开始做一些简单的模拟: 在 node1 上安装 ansible,作为控制节点,在/etc/ansbile/hosts中加入三个节点的信息 如果是通过密码连接的话,需要在 ansible_ssh_pass中输入机器密码,如果是通过密钥链接,这里可不填;配置 ssh 免密登录,可以自行百度,这里只需要配置 node1 到所有节点的免密(包括 node1 到 node1 自己) 免密配置完成后,我们可以做一下简单的操作:12345# 查看 mysql 组机器的时间信息ansilbe mysql -m shell -a "date"# 查看 nginx 组机器的启动时间ansible nginx -m shell -a "uptime" playbooksplaybook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务 playbook语法:12345678910111213141516171819playbook使用yaml语法格式,后缀可以是yaml,也可以是yml。在单个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。使用#号注释代码。缩进必须统一,不能空格和tab混用。缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感k/v的值可同行写也可以换行写。同行使用:分隔。v可以是个字符串,也可以是一个列表一个完整的代码块功能需要最少元素包括 name: task playbook核心元素1. PlayPlay 是 Playbook 的基本单元,用于定义在一组主机上执行的一系列任务。一个 Playbook 可以包含多个 Play,每个 Play 在不同的主机或组上执行不同的任务。 关键字: hosts: 指定要在哪些主机或主机组上执行 Play。 tasks: 包含一系列任务,这些任务会按顺序执行。 vars: 定义在 Play 中使用的变量。 roles: 指定要应用的角色。 gather_facts: 控制是否收集主机的事实信息(默认 true)。 become: 是否使用 sudo 或其他特权提升执行任务。 2. TasksTasks 是 Play 中的核心部分,定义了要执行的具体操作。每个 Task 通常使用一个 Ansible 模块,并可包含条件、循环、错误处理等。 关键字: name: 任务的描述性名称(可选,但推荐使用)。 action 或模块名称: 具体执行的操作,如 apt、yum、copy 等。 when: 定义条件,满足时才会执行任务。 with_items: 用于循环执行任务。 register: 保存任务的结果到变量。 ignore_errors: 忽略任务执行失败(设为 yes 时)。 3. VariablesVariables 是 Playbook 中的动态值,用于提高复用性和灵活性。可以在多个地方定义变量,如 vars、group_vars、host_vars、inventory 文件,或通过命令行传递。 关键字:-\tvars: 在 Play 或 Task 中定义变量。-\tvars_files: 引入外部变量文件。-\tvars_prompt: 运行时提示用户输入变量值。 4. HandlersHandlers 是一种特殊类型的 Task,只会在被触发时执行。通常用于在配置更改后执行动作,如重启服务。比如我要等服务重启完检查端口监听,就可以用handler 关键字:-\tname: Handler 的名称。-\tnotify: 在普通 Task 中调用 notify 触发对应的 Handler。 5. RolesRoles 是 Playbook 中组织和复用任务、变量、文件、模板等的一种方式。Roles 使得 Playbook 更加模块化和可维护。举个例子,我现在要在服务器上部署各种各样的组件,webserver、mysql、redis、ng 等等,我们就可以用这个不同的 roles 来管理,我们可以在创建四个文件夹,分别对应起名 webserver、mysql、redis、ng,然后在这些文件夹里面添加服务部署或者更新需要的东西。 角色的目录结构: 12345678910111213my_role/├── tasks/│ └── main.yml├── handlers/│ └── main.yml├── templates/├── files/├── vars/│ └── main.yml├── defaults/│ └── main.yml└── meta/ └── main.yml roles内各自目录含义: files\t用来存放copy模块或script模块调用的文件 templates\t用来存放jinjia2模板,template模块会自动在此目录中寻找jinjia2模板文件 tasks\t此目录应当包含一个main.yml文件,用于定义此角色的任务列表,此文件可以使用include包含其它的位于此目录的task文件 handlers\t此目录应当包含一个main.yml文件,用于定义此角色中触发条件时执行的动作 vars\t此目录应当包含一个main.yml文件,用于定义此角色用到的变量 defailts\t此目录应当包含一个main.yml文件,用于为当前角色设定默认变量 meta\t此目录应当包含一个main.yml文件,用于定义此角色的特殊设及其依赖关系 6. Includes and ImportsIncludes 和 Imports 用于在 Playbook 中包含其他任务、变量、文件等。import_tasks 和 include_tasks 的区别在于,import_tasks 在解析 Playbook 时执行,而 include_tasks 在运行时执行。 7. TemplatesTemplates 是使用 Jinja2 模板引擎的文件,用于动态生成配置文件或其他文件。模板通常存放在 templates/ 目录下,并通过 template 模块应用到目标主机 8. Tags标签是用于对 play 进行标注,当你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全部任务,这时,我们可以借助tags模块为任务进行打标签操作,任务存在标签后,我们可以在执行playbook时利用标签,指定执行哪些任务,或者不执行哪些任务 比如说在实际线上环境中,我们有更新二进制包的操作,那么我们可以在更新二进制的相关 task 中添加名为bin 的 tags,有更新配置文件的操作,那么可以在相关的 tasks 中添加 conf 的 tags 完整示例我们通过一个安装 nginx 的操作来完整演示一下: 设置项目目录结构: 123456789101112131415161718.├── ansible.cfg├── inventory├── playbook.yml└── roles └── nginx ├── tasks │ ├── main.yml │ └── install.yml ├── handlers │ └── main.yml ├── templates │ └── nginx.conf.j2 ├── files ├── vars │ └── main.yml └── defaults └── main.yml inventory配置文件 12[webservers]192.168.1.101 ansible_ssh_user=your_user ansible_ssh_pass=your_password ansible_host=203.0.113.1 playbook.yml: 123456789---- name: Update and Install Nginx hosts: webservers #引用组 become: yes #开启 sudo roles: - role: nginx #角色是nginx,对应到 roles/nginx 目录 tags: #两个 tags - bin - conf 任务文件 roles/nginx/tasks/main.yml: 123---# 包含其他任务文件, 这里直接用 install.yml里面的文件内容肯定也是没问题的,但是我们可以通过这样的方式更好的进行管理- include_tasks: install.yml 安装任务 roles/nginx/tasks/install.yml: 123456789101112131415161718192021222324252627282930---# 更新安装 Nginx- name: Update apt cache and install Nginx apt: #安装nginx 相关包, 不同平台不太一样,比如 centos 可以使用 package name: nginx state: latest update_cache: yes tags: - bin #这里添加了 bin 的 tag# 部署 Nginx 配置文件- name: Deploy Nginx configuration from template template: #这里是更新 nginx 配置文件 src: nginx.conf.j2 dest: /etc/nginx/nginx.conf mode: '0644' notify: Restart Nginx #这里配合handler 使用,handlers 里面会有一个名称为 “Restart Nginx”的操作 tags: - conf #这里添加了 conf 的 tag# 检查 Nginx 是否监听正确端口- name: Check Nginx is listening on port 80 command: ss -tuln | grep :80 #通过 command 模块,ss 命令监听 80 端口是否启动 register: result ignore_errors: yes- name: Print Nginx listening port check result debug: msg: "{{ result.stdout }}" when: result.rc == 0 处理程序 roles/nginx/handlers/main.yml: 123456---# 当配置文件变更时,重启 Nginx, 和上面的 notify 是对应的- name: Restart Nginx service: name: nginx state: restarted 模板文件 roles/nginx/templates/nginx.conf.j2: 1234567891011121314151617server { listen {{ nginx_port }}; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { }} 默认变量 roles/nginx/defaults/main.yml: 12---nginx_port: 80 当我们更新安装时,可以通过(在正式执行前,可以在后面加-C -D 做测试使用): 1ansible-playbook playbook.yml 后续有二进制更新时,可以通过: 1ansible-playbook playbook.yml -t bin 后续有配置文件更新时,可以通过: 1ansible-playbook playbook.yml -t conf 逻辑控制语句条件语句whenwhen条件支持多种判断类型,主要用于根据某些条件决定是否执行某个任务。这些判断类型通常基于Python的语法,因为Ansible的任务是用Python编写的 主要支持的判断类型: 比较运算符:==, !=, >, <, >=, <= 用于比较两个值。 字符串方法:.startswith(), .endswith(), .find(), .contains() 等字符串方法可以用来检查字符串的特性。 逻辑运算符:and, or, not 用于组合多个条件。 Jinja2模板表达式:由于Ansible使用Jinja2作为模板引擎,因此你也可以在when条件中使用Jinja2的表达式和过滤器。 Ansible事实(facts)和变量:你可以使用Ansible收集的主机事实(facts)和定义的变量来进行条件判断。 函数和内置方法:Python的内置函数和方法也可以在when条件中使用,比如isinstance(), len(), 等等。 正则表达式:使用Python的正则表达式模块(如re)进行更复杂的字符串匹配。 其中facts涉及到的判断条件非常多,可以通过如下形式获取 123456---- hosts: mysql tasks: - name: show ansible facts debug: var: ansible_facts 执行以上yml文件之后,会输出一个json串,我们就可以获取到所有的fact信息了. 示例:假如我们想在ip为 192.168.0.102 的机器上创建文件 123456789---- name: when测试练习 hosts: webservers tasks: - name: 文件测试创建 file: path: /tmp/when.txt state: touch when: "'192.168.0.102' in ansible_all_ipv4_addresses" 循环语句loop用于在任务中循环执行操作 示例: 1234567891011121314---- name: Install multiple packages hosts: webservers become: yes tasks: - name: Install packages apt: name: "{{ item }}" state: present loop: - nginx - git - curl 上面的示例就是循环装包 块语句block在 Ansible 中,block 关键字允许你将多个任务组合成一个逻辑块,并对这个块应用一些条件或错误处理逻辑。这是 Ansible 2.5 版本及以后引入的一个功能,它提供了更高级的任务组织方式。简单来说,block任务块就是一组逻辑的tasks。使用block可以将多个任务合并为一个组。 playbook会定义三种块,三种块的作用分别如下: block: block里的tasks,如果运行正确,则不会运行rescue; rescue:block里的tasks,如果运行失败,才会运行rescue里的tasks always:block和rescue里的tasks无论是否运行成功,都会运行always里的tasks","tags":["ansible","运维工具"],"categories":["运维工具"]},{"title":"线上故障排查方法和工具介绍","path":"/p/线上问题排查方法汇总/","content":"参考文档 Examining Load AverageWhat-is-CPU-Load-AverageBrendan Gregg个人网站 写在前面在很多文章中,每当提到去解决线上问题的时候,大部分的处理方式就是登录环境,哐哐各种敲命令。操作本身没什么问题,但是对于很多人而言,我觉得这种做法其实是本末倒置的,过于在乎去快速抓住重点问题,而忽略了从全局去看问题。那么如果最开始不去操作各种命令,那应该干什么呢? 看监控!!!! 首先不要觉得这个是废话,对于很多场景来说,业务规模是不断变化的,有的时候并发超过了极限的性能,那么这种情况下都没有必要去后台进行各种查询。举个简单的例子,假如说某套业务系统,本身只能支持 500 并发,现在实际上的量到了 2000,导致线上各种内存、CPU、负载的告警,这种情况下还有必要去后台敲top、free吗?答案当然是否定的,这种情况下,就需要考虑对业务系统进行快速的扩容等。 看监控的意义在于尽可能的找到更多的性能瓶颈或者异常的点,从全局出发,对系统当前存在的问题和异常点有全面的了解。 监控系统多种多样,从较早的 zabbix 到现在比较流行的prometheus+grafana(举两个常用的例子),对于系统业务都有比较完善的监控,可以帮助我们更加具体的了解到系统运行全貌。如果你对这些都不喜欢,那么你自己写一个监控系统也没什么问题。 当我们看完监控之后(假设你真的看了),接下来进入实际操作环节,我会从这些指标的详细含义出发,然后尽可能地将各种处理方式分享给大家。 Linux性能谱图在分析问题前,我们首先需要明确 Linux 有哪些性能分析工具,我们先上一下LINUX 性能专家 Brendan Gregg 总结的图(大家如果对性能分析等感兴趣的话,可以认真看下这位大佬的个人网站): Linux Performance Observability Tool 上面这张图是引用大佬文章的图,原文链接在这里: https://www.brendangregg.com/linuxperf.html CPU使用率飙升如何让CPU使用率飙升这个问题其实很简单,只要有计算任务一直存在,让 CPU 一直处于繁忙之中,那么 CPU 必然飙升。我们可以通过一系列的工具去模拟这个情况。 github SysStress 这是我自己用 golang 写的压测工具(还在开发中,可以点个 star 让我更有动力😂) 使用方法: 1./sysstress cpu --cpu-number 10 --duration 10m 这个就是模拟占用 10 核心的 CPU 并持续 10min,当然大家也可以用其他的压测工具,比如stress-ng 如何判断和发现CPU使用率飙升首先我们先看一下,跟 CPU 使用率相关的有哪些指标。我们通过 top 命令就可以看到具体的信息 top 这些输出中有一行是 %Cpu(s), 这行展示了 CPU 的整体使用情况,是一个百分比的形式,我们详细阐述下这几个字段的含义 12345678us, user : time running un-niced user processes 未降低优先级的用户进程所占用的时间sy, system : time running kernel processes 内核进程所占用的时间ni, nice : time running niced user processes 降低优先级的用户进程所占用的时间id, idle : time spent in the kernel idle handler 空闲的时间wa, IO-wait : time waiting for I/O completion 等待 I/O 操作完成所花费的时间hi : time spent servicing hardware interrupts 处理硬件中断所花费的时间si : time spent servicing software interrupts 处理软件中断所花费的时间st : time stolen from this vm by the hypervisor 被虚拟机管理程序从此虚拟机中窃取的时间 在这些指标中,一般关注的比较多的就是 us、sy、id、wa(其他几个指标很高的情况我个人目前基本上没有遇到过) 上述指标反映了系统整体的 CPU 情况。而程序在操作系统中实际上是以一个个的进程存在的,那我们如何确定到占用 CPU 高的进程呢?让我们的目光从 top 的头部信息往下移动,下面就展示了详细的进程信息 这些程序默认是按照 CPU 的使用率从高到底进行排序的,当然你也可以通过在top的时候输入P进行排序,这样我们就可以看到系统中消耗 CPU 资源的详细进程信息 上面是我通过 ./sysstress cpu --cpu-number 10 --duration 10m 压测程序跑出来的,可以看到这里的 sysstress 程序占用了 1002 的 %CPU,也就是说基本上是 10 个核心,那我们跑一个更高的,将--cpu-number加到 60 看看发生了什么 stress-cpu 我们可以看到这次%CPU打到了 6000,那很多人就好奇我日常的程序跑到多高算高呢? 这里我们需要明确一点,现在的服务器绝大部分都是多核心 CPU(1C2G这种自己用来玩的忽略),CPU 的核心数决定了我们程序在同一时间能够执行多少个线程,也就是说,这个高不高是相对于机器配置而言的。如果你的机器只有 16C,那么单个进程占用的 %CPU 到 1000,那么其实已经算是比较高了。如果是 256C 的CPU(土豪级配置),那么单个进程占用的 %CPU 到 6000,对于系统的稳定性影响就没有那么大了。 上述我们说的情况是进程占用 CPU 对整个系统的影响,那么进程占用的 CPU 对系统的影响不大就代表这个程序一定没有问题吗?答案显然是未必的。 我们还是要回归到业务本身,如果进程的 CPU 占用在业务变动不大的情况下,发生了异常波动,或者正常情况下业务不会消耗这么高的 CPU,那么我们就需要继续排查了。 如何确定CPU飙升的根源这个问题的 核心是 CPU 上在运行什么东西。 多核心CPU 下,每个核心都可以执行不同的程序,我们如何确定一个进程中那些方法在消耗 CPU 呢?从而引申下面详细的问题: 程序的调用栈是什么样的? 调用栈信息中哪些是需要关注的,那些是可以忽略的? 热点函数是什么? 老话说得好,”工欲善其事,必先利其器”, 我们需要这些东西,就必须了解到什么样的工具可以拿到上面我提到的一些信息。接下来我将通过常用的后端语言:golang 和 java 为例构造一些高 CPU 的程序来进行展示。 perf命令perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。 安装: 1yum install perf #Centos 安装完成后,我们可以首先看下 perf的用法,这里不展开具体用法,只列出我平常使用的几个命令: 123top System profiling tool. #对系统性能进行实时分析。record Run a command and record its profile into perf.data #收集采样信息report Read perf.data (created by perf record) and display the profile #分析采样信息,和record配合使用 record 和 report 的使用更多在于 dump 当前环境的信息用于后续分析,如果在自己环境上测试,可以用 top 进行一些简单的实时分析(类似于 top 命令)。 还是用之前的压测工具,我们模拟一个 10 核心的 10min 的压测场景 1nohup ./sysstress cpu --cpu-number 10 --duration 10m > /dev/null 2>&1 & 执行这个语句,让压测程序在后台执行,然后我们通过perf top查看具体的情况(可以通过-p 指定 pid) perf top, 从截图的信息中我们可以看到占用资源最多的一些方法,包括 sysstress 进程的各种方法(从图片中基本上就可以确定高消耗的方法在哪里)以及底层的 __vdso_clock_gettime, 那再结合压测工具的代码分析下: 12345678910func burnCpu(wg *sync.WaitGroup, start time.Time, durSec int64) {\tdefer wg.Done()\tfor { _ = 1 * 1 now := time.Now() if now.Sub(start) > time.Duration(durSec)*time.Second { break }\t}} 这是方法的核心,其实就是做无意义的计算,外加时间的判断,超过 duration 就结束。这样和上面的 perf top 信息就能对应起来。 然后我们用 java 写一个同样的程序,再看看 perf top的情况: perf top, 从这一大段显示来看,是不是看的一脸懵逼,很难发现到底是什么程序在占用CPU 资源。大家可以看一下源程序: 123456789101112131415161718import java.time.LocalDateTime;public class Main { public static void main(String[] args) { int n = 10; for (int i = 0; i < 10; i++) { new Thread(new Runnable() { public void run() { while (true) { Math.sin(Math.random()); LocalDateTime currentTime = LocalDateTime.now(); } } }).start(); } }} 这里的程序也是非常简单,启动 10 个线程,做一个无意义的数学运算,然后获取当前时间。从这段代码中是不是很难和上面perf top的显示关联起来? 原因也非常简单, 像Java 这种通过 JVM 来运行的应用程序,运行堆栈用的都是 JVM 内置的函数和堆栈管理。所以,从系统层面只能看到 JVM 的函数堆栈,而不能直接得到 Java 应用程序的堆栈。那我们好能通过 perf 去看到 java 相关的堆栈吗?答案是可以的。 可以借助 perf-map-agent 这样的开源工具,去生成和perf 工具一起使用的方法映射,但是需要做额外的一些配置。这里的方法大家可以自己探究,为什么不详细的讲这个呢,原因也简单,排查问题的工具多种多样,没必要在一棵树上吊死。 jstack既然 perf top 去查看 JAVA 的调用栈不太方便,我们就直接上 java 提供的 jstack 工具去分析。 jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式 kill -3, jstack 用不了的情况下可以使用 kill -3 pid 的形式,堆栈默认会输出在系统日志中(根据不同的配置,信息也可能输出在其他地方,比如这个程序的日志中)。 具体的操作步骤: top -Hp $pid 找到占用 CPU 的具体线程 jstack -l $pid > /tmp/$pid.jstack 或者 kill -3 $pid将 java 进程的堆栈情况输出的日志中,然后根据 top -Hp 看到的线程信息在输出的堆栈日志中进行查找(top -Hp 输出的是 10 进制的 id,jstack 输出的是 16 进制的,在查找时注意进制转换) 我们看下上面 java 程序的堆栈的信息: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151162024-08-16 15:15:40Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode):"Attach Listener" #35 daemon prio=9 os_prio=0 tid=0x00007f52b4001000 nid=0x71f4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None"DestroyJavaVM" #34 prio=5 os_prio=0 tid=0x00007f53e0009800 nid=0x1693 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None"Thread-1" #25 prio=5 os_prio=0 tid=0x00007f53e015a800 nid=0x16d9 runnable [0x00007f52f64e3000] java.lang.Thread.State: RUNNABLE\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\tat java.time.ZoneId.of(ZoneId.java:411)\tat java.time.ZoneId.of(ZoneId.java:359)\tat java.time.ZoneId.of(ZoneId.java:315)\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\tat java.time.Clock.systemDefaultZone(Clock.java:178)\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\tat Main$1.run(Main.java:12)\tat java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers:\t- None"Thread-0" #24 prio=5 os_prio=0 tid=0x00007f53e0159000 nid=0x16d8 runnable [0x00007f52f65e4000] java.lang.Thread.State: RUNNABLE\tat sun.misc.Unsafe.getObjectVolatile(Native Method)\tat java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)\tat java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:938)\tat java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:267)\tat java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)\tat java.time.ZoneRegion.ofId(ZoneRegion.java:120)\tat java.time.ZoneId.of(ZoneId.java:411)\tat java.time.ZoneId.of(ZoneId.java:359)\tat java.time.ZoneId.of(ZoneId.java:315)\tat java.util.TimeZone.toZoneId(TimeZone.java:556)\tat java.time.ZoneId.systemDefault(ZoneId.java:274)\tat java.time.Clock.systemDefaultZone(Clock.java:178)\tat java.time.LocalDateTime.now(LocalDateTime.java:180)\tat Main$1.run(Main.java:12)\tat java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers:\t- None --- 10 个 thread"Service Thread" #23 daemon prio=9 os_prio=0 tid=0x00007f53e0143800 nid=0x16d6 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f53e010e000 nid=0x16c5 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None --- 一大堆 C2 CompilerThread"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f53e010b000 nid=0x16c4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f53e0109800 nid=0x16c3 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers:\t- None"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f53e00d8800 nid=0x16c2 in Object.wait() [0x00007f52f7bfa000] java.lang.Thread.State: WAITING (on object monitor)\tat java.lang.Object.wait(Native Method)\t- waiting on <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)\t- locked <0x000000008021a5e8> (a java.lang.ref.ReferenceQueue$Lock)\tat java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)\tat java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216) Locked ownable synchronizers:\t- None"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f53e00d3800 nid=0x16c1 in Object.wait() [0x00007f52f7cfb000] java.lang.Thread.State: WAITING (on object monitor)\tat java.lang.Object.wait(Native Method)\t- waiting on <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\tat java.lang.Object.wait(Object.java:502)\tat java.lang.ref.Reference.tryHandlePending(Reference.java:191)\t- locked <0x0000000080218d38> (a java.lang.ref.Reference$Lock)\tat java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) Locked ownable synchronizers:\t- None"VM Thread" os_prio=0 tid=0x00007f53e00ca000 nid=0x16c0 runnable"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f53e001f000 nid=0x1694 runnable--- 一大堆 GC task thread"VM Periodic Task Thread" os_prio=0 tid=0x00007f53e0146000 nid=0x16d7 waiting on conditionJNI global references: 202 我们通过 top -Hp 的信息就可以快速定位到 Thread-[0-9] 这几个线程,而每个线程的调用栈都是 java.time.LocalDateTime.now, 也说明了这个方法在不停消耗 CPU。(但是 jstack 只能捕获短时间或者瞬时的堆栈信息,没法处理长时间的,所以我们在获取时可以多打印几次或者使用其他方法) 至于 jstack 的详细用法,请参考我的另一篇博客:java问题定位 除此之外,还有非常多的分析工具,pstack\\gstack\\strace\\gdb等等,大家可以自行探索使用 火焰图上面我们介绍了很多操作的命令和方法,那么有没有一种比较直观的方式能够直接看到各种方法执行的耗时比重等情况呢?火焰图就是为了解决这种情况而生的。 火焰图的分类有很多,常用的包括: CPU 火焰图 (CPU Flame Graph) 描述:展示 CPU 在不同方法上的消耗情况,显示每个方法调用所占用的 CPU 时间。 用途:用于分析 CPU 性能瓶颈,识别哪些方法消耗了最多的 CPU 资源。 应用:Java、C++ 等多种编程语言的性能分析。 内存火焰图 (Memory Flame Graph) 描述:展示内存分配情况,显示每个方法调用分配的内存量。 用途:用于检测内存泄漏、过度内存分配问题,帮助优化内存使用。 应用:常用于分析内存密集型应用,如 Java 应用的堆内存分析。 I/O 火焰图 (I/O Flame Graph) 描述:展示 I/O 操作的耗时情况,显示不同方法的 I/O 操作占用的时间。 用途:用于分析应用程序的 I/O 性能,识别慢速或频繁的 I/O 操作。 应用:数据库查询、文件系统操作、网络通信等场景的性能调优。 我们这里通过 async-profiler 对文章上面的java压测程序进行抓取(这个工具只能抓 java 的, 对于 golang 程序,可以利用 golang 提供的 pprof) 123tar -xzf async-profiler-3.0-linux-x64.tar.gzcd async-profiler-3.0-linux-x64/bin./asprof -d 60 pid -f /tmp/javastress.html 我们用浏览器打开生成的 html 文件,可以看到如下的火焰图信息(可以在网页进行点击,查看更细节的方法) java 程序的火焰图, 这样看起来就比 jstack这些信息更加直观一点。 负载飙升负载的定义以及如何查看负载我们先看下系统负载的官方描述: 1System load averages is the average number of processes that are either in a runnable or uninterruptable state. A process in arunnable state is either using the CPU or waiting to use the CPU. A process in uninterruptable state is waiting for some I/O access,eg waiting for disk. The averages are taken over the three time intervals. Load averages are not normalized for the number of CPUs ina system, so a load average of 1 means a single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time. 系统负载平均值表示处于可运行或不可中断状态的进程的平均数量。处于可运行状态的进程要么正在使用 CPU,要么正在等待使用 CPU。处于不可中断状态的进程正在等待某些 I/O 访问,例如等待磁盘。这里的核心概念就是 loadavg 这个数值体现了某些特定状态进程的数量。 那引申出两个问题: 进程的状态有哪些? 如何在 Linux 上查看进程状态 可运行和不可中断状态的进程具体含义是什么 查看的方式,我们可以通过 ps 命令进行查看,比如通过ps -auxf, 我么可以看到有一列为 STAT,这列就代表该进程的状态: 进程状态 进程的状态和具体含义: D uninterruptible sleep (usually IO) R running or runnable (on run queue) S interruptible sleep (waiting for an event to complete) T stopped by job control signal t stopped by debugger during the tracing W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z defunct (“zombie”) process, terminated but not reaped by its parent 这里我们看到处于不可中断的状态的进程和正在运行的进程分别为 D 和 R,换个说法,也就是说造成负载升高的原因也就是这两个状态的进程引起的。 (插个题外话,按照官方的说法,X 状态的进程应该是不应该被看到的, 但是之前在腾讯云做ES的时候,偶然间碰到了一次,当时还截了个图用做留念😂,但是没有捕获到具体的信息) 负载的指标可以通过 top 以及 uptime 指令获取 123:35:00 up 1 day, 46 min, 1 user, load average: 49.16, 18.35, 7.87 这里展示了 loadavg 的三个数值: 分别代表的含义是 1min、5min、15min 的系统平均负载 那我们如何判断系统的负载是高是低呢? 这里一般有个经验值,我们一般和 CPU 和核心数进行对比,一般负载在 CPU 核心的 70% 左右以及以下,对系统一般没什么影响,超过 70%,系统可能收到影响。但是这里还需要注意的一点就是,负载的比例在 70% 以下时不一定代表系统就没问题,举个简单的例子,如果一个系统上基本上没有业务在运行,那么负载基本上就在零点几左右,那么这种情况下,负载有升高不一定是合理的(后面举一个简单的例子) 如何让系统负载飙高纯计算任务对负载的影响既然说正在运行的进程会引起负载的变化,那么跑一些程序,让程序不停运行,那么自然而然就能构造出持续运行的进程了。我这里找了三台机器(64C),用我的压测工具先跑一些纯 CPU 的运算,然后观察下效果: 测试分为三组,测试前关闭不必要的服务和进程: 10 并发 30min nohup ./sysstress cpu --cpu-number 10 --duration 30m > /dev/null 2>&1 30 并发 30min nohup ./sysstress cpu --cpu-number 30 --duration 30m > /dev/null 2>&1 60 并发 30min nohup ./sysstress cpu --cpu-number 60 --duration 30m > /dev/null 2>&1 效果如下: 10并发负载, 30并发负载, 60并发负载, 从上述测试过程中,我们可以发现,在纯运算这种场景下,并发的量基本上和负载是对应的。也就是说随着 CPU的使用量 上涨,负载也会不断变高。 磁盘 IO 对负载的影响在刚才的例子中,我们看到了纯运算对负载的影响(R 进程的代表),然后在关于 D 进程的说明中,我们可以看到有一个比较明显的说明 (usually IO) ,即通常是 IO 引起的,那么接下来我们通过磁盘 IO 来测试一下 测试分为三组,测试前关闭不必要的服务和进程: 10 并发 15min nohup ./sysstress io --operation read --filepath test.access.log -p 10 -d 15m > /dev/null 2>&1 & 30 并发 15min nohup ./sysstress io --operation read --filepath test.access.log -p 30 -d 15m > /dev/null 2>&1 & 60 并发 15min nohup ./sysstress io --operation read --filepath test.access.log -p 60 -d 15m > /dev/null 2>&1 & 效果如下: 10并发负载 30并发负载 60并发负载 我们也顺便看一下,60 并发下 CPU 的情况: 60并发系统整体情况 这里我们可以观察到,系统的 CPU 基本上已经跑满了。us 和 sy 都占的比较多,但是这种读取非常有可能走到缓存中,我们想测绕过缓存,可以通过 DIRECT 的方式。 但是上面的例子其实也证明了一件事,IO 的操作也是会导致负载产生飙升。 那么问题来了,磁盘IO 和 CPU 操作都会导致系统负载飙升,那么负载飙升一定会是这两个原因吗?答案也是未必的,因为上述我们曾经提到过 D 状态的进程,到目前为止我们好像还没介绍过,那么我们来继续模拟,既然 D 状态的进程是 IO 操作引起的,普通的磁盘读写 IO 很难模拟,那我们就换个 IO 场景继续模拟 – 网络 IO。 通过网络 IO 模拟 D 状态进程观察负载影响这里直接上一个模拟方法: A 机器开启 NFS Server B 机器作为客户端进行挂载 断开网络 疯狂 df -h 详细的操作步骤: 1234567891011121314151617181920212223242526# 安装 Centossudo yum install nfs-utils# 服务端配置sudo mkdir -p /mnt/nfs_sharesudo chown nobody:nogroup /mnt/nfs_sharesudo chmod 755 /mnt/nfs_share## 打开 /etc/exports 配置,添加一行来定义共享目录及其权限。例如,将 /mnt/nfs_share 共享给网络 192.168.1.0/24,并提供读写权限:/mnt/nfs_share 192.168.1.0/24(rw,sync,no_subtree_check)## 启动 NFSsudo exportfs -asudo systemctl restart nfs-kernel-serversudo systemctl enable nfs-kernel-server# 客户端配置sudo mkdir -p /mnt/nfs_clientsudo mount -t nfs 192.168.1.100(server ip):/mnt/nfs_share /mnt/nfs_client## 验证df -h /mnt/nfs_client# 断网模拟(客户端)iptables -I INPUT -s serverip -j DROP# 持续(疯狂)执行:du -sh /mnt/nfs_client 因为网络已经断掉,所以du -sh /mnt/nfs_client,而且这个程序没有自动退出或者报错,这样就导致程序无法顺利执行下去,继而阻塞住就变成了 D 状态的进程。 基于这种模拟方法大家可以自行测试下,笔者之前做过一个场景,将一个两核心的 CPU负载干到了 200 多,但是因为这种情况下更多是阻塞在网络中,所以此时的负载虽高,并不一定影响系统运行。 当然这只是其中一个例子,笔者曾经也因为见过 ping 操作阻塞导致的负载飙升,所以这种场景是多种多样的😂,大家有更多的例子也可以在下方留言,共同学习进步。 负载飙升如何排查基于上面的例子和场景模拟,我们其实应该已经有一套基本的排查方法了,下面这张图是我个人的一些总结(图还会不断完善) 负载高排查导图 内存占用过高我们先来了解一下有哪些常用的内存性能工具: 工具 功能 用法 常用选项 free 显示系统的内存使用情况 free -h -h:以人类可读的格式显示 top 实时显示系统的进程和内存使用情况 top 无 htop 提供更友好的用户界面的进程监控工具 htop 无 vmstat 报告虚拟内存、进程、CPU 活动等统计信息 vmstat 1 1:每秒更新一次数据 ps 查看系统中进程的内存使用情况 ps aux --sort=-%mem aux:显示所有用户的进程, --sort=-%mem:按内存使用量降序排列 pmap 显示进程的内存映射 pmap -x <pid> -x:显示详细信息 smem 提供详细的内存使用报告 smem -r 无 /proc 提供系统和进程的详细内存信息 cat /proc/meminfocat /proc/<pid>/status 无 如何判断内存占用过高系统内存的使用情况可以通过 top 以及 free 命令进行查看,以 free -m为例: free查看内存信息 字段说明: total: 系统总内存(RAM)或交换空间(Swap)的总量。 used: 已用内存或交换空间的量。这包括正在使用的内存以及系统缓存(对于内存)或已经使用的交换空间。 free: 空闲的内存或交换空间的量。表示当前未被任何进程使用的内存或交换空间。 shared: 被多个进程共享的内存量(对于内存)。通常是共享库和进程间通信的内存。 buff/cache: 用作文件系统缓存和缓冲区的内存量。这包括缓存的文件数据(cache)和缓冲区(buff)。这些内存可以被系统用作其他用途。 available: 可以分配给新启动的应用程序的内存量,而不需要交换到磁盘。这个数字更能反映系统的实际可用内存 这里我们需要关注下,一般可用内存我们就是以 available 为准。当 available 的指标越来越小时,我们就需要关注系统内存的整体使用情况。 这里还有一个需要关注的点:Swap,我们先看一下详细介绍: 123456Swap space in Linux is used when the amount of physical memory (RAM) is full. If the system needs more memory resources and the RAM is full, inactive pages in memory are moved to the swap space. While swap space can help machines with a small amount of RAM, it should not be considered a replacement for more RAM. Swap space is located on hard drives, which have a slower access time than physical memory.Swap space can be a dedicated swap partition (recommended), a swap file, or a combination of swap partitions and swap files. SWAP意思是交换,顾名思义,当某进程向OS请求内存发现不足时,OS会把内存中暂时不用的数据交换出去,放在SWAP分区中,这个过程称为SWAP OUT。当某进程又需要这些数据且OS发现还有空闲物理内存时,又会把SWAP分区中的数据交换回物理内存中,这个过程称为SWAP IN。简单来说,就是物理内存不足时,把磁盘空间当作 swap 分区,解决容量不足的问题。 这里我们其实会发现一个问题,物理内存的读写性能肯定要比磁盘强不少,使用了磁盘空间作为内存存储,本身读写的性能就不高,还涉及到频繁的交换,反而增加了系统的负载,所以在线上环境中我们一般建议关闭 swap,具体的关闭方法请自行百度。 如何定位内存占用高针对内存的占用,常规情况下(当然也有非常规情况),我们需要找到占用内存高的具体进程,详细的操作方式是:top的时候输入M进行排序,这样我们就可以看到系统中进程消耗内存的详细占比了: 这里我们重点关注两列: %MEM 和 RES, 前者是这个进程占用系统内存的百分比,后者是占用实际内存的大小。 我们还是以 JAVA 和 GOLANG 程序为例来分析内存高该如何排查 JAVA 占用内存过高Java 应用的内存管理依赖于 JVM (Java Virtual Machine),通常会涉及到堆内存(Heap)、非堆内存(Non-Heap)以及本地内存(Native Memory)。 堆内内存分析 查看 JVM 启动参数,尤其是 -Xms 和 -Xmx 选项,这两个参数分别设置了初始堆内存大小和最大堆内存大小。可以通过 ps 命令查看这些参数:ps -auxf | grep 程序名称 使用 jstat 查看 gc 情况, 实例:jstat -gc pid 1000gc 信息 如果 GC 频繁,那么我们需要进一步进行分析 通过 jmap 生成进程的堆内存快照 (在 JVM启动时,建议添加 OOM 时自动生成 heap dump: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile):1jmap -dump:live,format=b,file=heapdump.hprof <pid> 拿到快照文件后,我们可以通过 MAT 或者 Jprofiler 这样的工具去具体分析以 MAT 为例: MAT 分析 JAVA内存 本地内存分析NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。 启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。启动命令: -XX:NativeMemoryTracking=[off | summary | detail]。 off:NMT 默认是关闭的; summary:只收集子系统的内存使用的总计数据; detail:收集每个调用点的内存使用数据。 开启后,我们可以通过如下命令访问 NMT 内存: 1jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB] 其他非堆内存主要包括: JVM 自身运行占用的空间; 线程栈分配占用的系统内存; DirectByteBuffer 占用的内存; JNI 里分配的内存; Java 8 开始的元数据空间; NIO 缓存 Unsafe 调用分配的内存; codecache 对于这些问题,遇到内存升高的情况较少,所以也没有进行过详细的排查,如果有读者朋友有做过类似的排查,可以在下面留言讨论。 golang程序占用内存高golang 内置了 pprof 性能分析工具,支持 CPU、内存(Heap)、栈、协程等的分析。可以通过 HTTP 服务暴露 pprof 接口,并使用浏览器或 go tool pprof 进行分析:在代码中引入 net/http/pprof 包: 12345678import _ "net/http/pprof"func main() { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // 你的应用代码} 启动应用后,通过浏览器访问 http://localhost:6060/debug/pprof/heap 下载堆内存快照,并使用以下命令分析: 1go tool pprof -http=:8080 heap.prof 这将启动一个 Web 界面,供你分析内存占用的热点。具体的使用方式大家可以自行研究。 基于内存的文件系统占用在上文中,我们说了常规情况下的排查,那来个非常规的,先上张图: 内存占用模拟 这里大家看看到,内存总量大概有 250 个 G,可用内存只有 126G,但是我们通过 top 看内存占比跟实际的数值相差甚远。 这种场景是不是非常奇怪呢?没有进程占用内存,但是内存被消耗了,这种情况下,很有可能跟基于内存的文件系统有关。 这里我说一下模拟方法: 123456789101112131415# systemd 的包中会默认自带一个 tmp.mount服务,这个服务默认是关闭的(默认是总内存的一半)# 这个服务本质上是一个基于内存的文件系统(把内存当磁盘使,在上面可以创建文件)systemctl start tmp.mount# 然后我们通过 df -h /tmp 可以看到Filesystem Size Used Avail Use% Mounted ontmpfs 126G 111G 16G 88% /tmp这里挂载了/tmp 目录,文件系统是 tmpfs# 然后我们进入 /tmp目录# 模拟文件创建dd if=/dev/zero of=test.txt bs=G count=100# 然后我们就可以看到文件创建成功,然后 free -g 就可以看到内存成功消耗了100G😂 这种场景也是我之前在排查问题的时候遇到的,大家有其他类似场景也可以进行补充 磁盘问题线上的磁盘问题,除了磁盘故障(坏块、文件系统损坏、RAID 等)之外,常见的比较多的问题可以分为两大类:磁盘空间不足和 IO 性能问题 磁盘空间不足对于这类问题,一般情况下,本身可能就是数据文件、日志信息等过多导致的真实占用,还有一类是文件句柄泄露导致磁盘没有释放从而占据了多余的磁盘空间 对于这类问题我们一般通过以下手段进行排查,假如说我们现在接到告警 说根目录的磁盘空间不足了 通过df -h命令查看磁盘分区的使用情况。 进入 "/" 目录下,可以对常用的目录进行 du -sh操作,然后进行简单的相加 如果加起来的磁盘空间,和分区使用的磁盘空间相差不多,那就基本上说明磁盘空间是被真实占用的,找到占用高的目录继续通过du -sh 目录,不断递归。或者想快速找到磁盘中超过 1G 的文件,可以通过find 目录 -type f -size +1G 这样的方式。如果想快速统计到所有子目录的磁盘信息,那么可以通过这样的方式进行操作1du -ah / 2>/dev/null | grep -E '^([0-9]+([KMGT]?)|([0-9]+(\\.[0-9]+)?[KMGT]))' | sort -rh | head -10 如果加起来的磁盘空间,和分区使用的磁盘空间相差的比较大,那么可能存在句柄泄露,比如日志文件本身被删掉,但是句柄没有释放,可以通过一下方式: 1234567#列出进打开文件最多的 10 个进程;lsof +L1 | awk '{print $2}' | sort | uniq -c | sort -rn | head -n 10# 查看某个进程的占用lsof -p $pid (查看是否存在已经删除的文件句柄没有释放,可以通过 grep deleted 过滤)# 或者 ls /proc/$pid/fd 进行查看 磁盘IO性能问题我们可以通过 iostat 看下磁盘整体的读写情况: iostat 关于 iostat 的采集,建议多采集几次观察,比如 iostat -x 2,每 2s 检查一次, 一直输出,当不需要的时候 ctrl c 掉 输出的指标每一项详细的介绍如下(也可以参考 man iostat): CPU 使用情况 Metric Description %user 用户模式下消耗的 CPU 时间百分比 %nice 调整优先级的用户模式下的 CPU 时间百分比 %system 内核模式下消耗的 CPU 时间百分比 %iowait CPU 等待 I/O 操作完成的时间百分比 %steal 等待虚拟 CPU 被实际 CPU 服务的时间百分比 %idle CPU 空闲时间百分比 设备 I/O 使用情况 Metric Description Device 设备名称 tps 每秒传输数(I/O 请求数) kB_read/s 每秒读取的 kB 数 kB_wrtn/s 每秒写入的 kB 数 kB_read 读取的 kB 总数 kB_wrtn 写入的 kB 总数 rrqm/s 每秒进行的合并读请求数 wrqm/s 每秒进行的合并写请求数 r/s 每秒完成的读请求数 w/s 每秒完成的写请求数 rMB/s 每秒读取的 MB 数 wMB/s 每秒写入的 MB 数 avgrq-sz 平均每次 I/O 请求的数据大小 avgqu-sz 平均 I/O 队列长度 await 平均每次 I/O 操作的等待时间 svctm 平均每次 I/O 操作的服务时间 %util 设备 I/O 活动时间百分比(表示设备忙碌度) 通过这个我们就可以获取到磁盘的整体情况。需要注意的是 %util 到 100% 之后,不一定代表磁盘出现了瓶颈,还需要结合其他指标共同进行判断,比如有一张 nvme 的盘,持续的读写,这种情况下有不断地 io 操作, util 的指标会显示为 100%,但是如果磁盘的读写还没有达到预期的范围并且 avgqu-sz 指标始终为 0(没有 io 阻塞),那就说明磁盘完全承载当前的用量,所以无需关心。 当我们想查看进程占用的 io 情况时,可以通过 iotop命令进行查看,如下: iotop 结合上文讲的一些堆栈分析工具,推测到进程具体在做那些操作然后针对性进行处理。","tags":["Linux","Java","Golang","commands"],"categories":["问题排查"]},{"title":"JAVA问题定位","path":"/p/JAVA问题定位/","content":"一、JAVA 相关命令1.jpsjps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported. 相关参数 123456789101112131415OPTIONS The jps command supports a number of options that modify the output of the command. These options are subject to change or removal in the future. -q Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers. -m Displays the arguments passed to the main method. The output may be null for embedded JVMs. -l Displays the full package name for the application's main class or the full path name to the application's JAR file. -v Displays the arguments passed to the JVM. -V Suppresses the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local JVM identifiers. -Joption Passes option to the JVM, where option is one of the options described on the reference page for the Java application launcher. For example, -J-Xms48m sets the startup memory to 48 MB. See java(1). 2.jinfojinfo(Java Virtual Machine Configuration Information)是JDK提供的一个可以实时查看Java虚拟机各种配置参数和系统属性的命令行工具。使用jps命令的-v参数可以查看Java虚拟机启动时显式指定的配置参数,如果想查看没有显式指定的配置参数就可以使用jinfo命令进行查看。另外,jinfo命令还可以查询Java虚拟机进程的System.getProperties()的内容。 以tomcat进程为例 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889Attaching to process ID 2045, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.242-b08Java System Properties:java.vendor = Huawei Technologies Co., Ltdsun.java.launcher = SUN_STANDARDcatalina.base = /usr/share/tomcatsun.management.compiler = HotSpot 64-Bit Tiered Compilerssun.nio.ch.bugLevel = catalina.useNaming = truejnidispatch.path = /var/cache/tomcat/temp/jna--903012287/jna4240128671455089550.tmpos.name = Linuxsun.boot.class.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/rt.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/sunrsasign.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/jfr.jar:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/classesjava.vm.specification.vendor = Oracle Corporationjava.runtime.version = 1.8.0_242-b08jna.loaded = trueuser.name = xxxtomcat.util.scan.StandardJarScanFilter.jarsToScan = taglibs-standard-impl*.jarshared.loader = tomcat.util.buf.StringCache.byte.enabled = trueuser.language = enjava.naming.factory.initial = org.apache.naming.java.javaURLContextFactorysun.boot.library.path = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64java.version = 1.8.0_242java.util.logging.manager = org.apache.juli.ClassLoaderLogManageruser.timezone = Asia/Shanghaisun.arch.data.model = 64java.util.concurrent.ForkJoinPool.common.threadFactory = org.apache.catalina.startup.SafeForkJoinWorkerThreadFactoryjava.endorsed.dirs = sun.cpu.isalist = sun.jnu.encoding = UTF-8file.encoding.pkg = sun.iopackage.access = sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat.file.separator = /java.specification.name = Java Platform API Specificationjava.class.version = 52.0user.country = USjava.home = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jrejava.vm.info = mixed modeos.version = 4.19.90-24.4.v2101.ky10.x86_64sun.font.fontmanager = sun.awt.X11FontManagerpath.separator = :java.vm.version = 25.242-b08jboss.i18n.generate-proxies = truejava.awt.printerjob = sun.print.PSPrinterJobsun.io.unicode.encoding = UnicodeLittleawt.toolkit = sun.awt.X11.XToolkitpackage.definition = sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.naming.,org.apache.tomcat.java.naming.factory.url.pkgs = org.apache.namingmail.mime.splitlongparameters = falsejava.security.egd = file:/dev/./urandomuser.home = /home/shtermjava.specification.vendor = Oracle Corporationtomcat.util.scan.StandardJarScanFilter.jarsToSkip = activ*.jar,amqp-client.jar,annotations-api.jar,ant-junit*.jar,ant-launcher.jar,ant.jar,antlr.jar,aopalliance.jar,asm-*.jar,aspectj*.jar,bcp*.jar,bootstrap.jar,catalina-ant.jar,catalina-ha.jar,catalina-jmx-remote.jar,catalina-storeconfig.jar,catalina-tribes.jar,catalina-ws.jar,catalina.jar,cglib-*.jar,classmate.jar,cobertura-*.jar,commons-*.jar,compress-lzf.jar,curator-*.jar,db2-jdbc.jar,dom4j-*.jar,easymock-*.jar,ecj-*.jar,el-api.jar,elasticsearch.jar,geronimo-spec-jaxrpc*.jar,groovy-all.jar,guava.jar,h2*.jar,hamcrest-*.jar,hibernate*.jar,hppc.jar,http*.jar,icu4j-*.jar,itext*.jar,jackson-*.jar,jandex.jar,jasper-el.jar,jasper.jar,jasperreports*.jar,jaspic-api.jar,javamail.jar,javassist.jar,jaxb-*.jar,jaxen*.jar,jboss*.jar,jc*.jar,jdom-*.jar,jedis.jar,jetty-*.jar,jfreechart.jar,jgit.jar,jline.jar,jmx-tools.jar,jmx.jar,jna.jar,joda-time.jar,jr-*.jar,jsch.jar,json*.jar,jsoup.jar,jsp-api.jar,jsr166e.jar,jstl.jar,jta*.jar,junit-*.jar,junit.jar,liquibase-*.jar,log4j*.jar,lucene*.jar,mail*.jar,mariadb-jdbc.jar,mssql-jdbc.jar,mybatis.jar,netty.jar,nmap4j.jar,objenesis*.jar,olap4j.jar,opc*.jar,oracle-jdbc.jar,oraclepki.jar,oro-*.jar,poi*.jar,postgresql-jdbc.jar,quartz.jar,servlet-api-*.jar,servlet-api.jar,slf4j*.jar,snakeyaml.jar,snmp4j.jar,spring*.jar,sshd-core.jar,taglibs-standard-spec-*.jar,tagsoup-*.jar,t-digest.jar,tomcat-api.jar,tomcat-coyote.jar,tomcat-dbcp.jar,tomcat-i18n-*.jar,tomcat-jdbc.jar,tomcat-jni.jar,tomcat-juli-adapters.jar,tomcat-juli.jar,tomcat-util-scan.jar,tomcat-util.jar,tomcat-websocket.jar,tools.jar,validation-api.jar,velocypack.jar,websocket-api.jar,wl*.jar,wsdl4j*.jar,xercesImpl.jar,xml-apis.jar,xmlbeans.jar,xmlParserAPIs-*.jar,xmlParserAPIs.jar,xom-*.jar,xz.jar,zip4j.jar,zookeeper.jarjava.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/libjava.vendor.url = http://jdk.rnd.huawei.com/java.vm.vendor = Huawei Technologies Co., Ltdcommon.loader = "${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"java.runtime.name = OpenJDK Runtime Environmentsun.java.command = org.apache.catalina.startup.Bootstrap startjava.class.path = /usr/share/tomcat/bin/bootstrap.jar:/usr/share/tomcat/bin/tomcat-juli.jar:/usr/lib/java/commons-daemon.jarjava.vm.specification.name = Java Virtual Machine Specificationjava.vm.specification.version = 1.8catalina.home = /usr/share/tomcatsun.cpu.endian = littlesun.os.patch.level = unknownjava.awt.headless = truejava.io.tmpdir = /var/cache/tomcat/tempjava.vendor.url.bug = http://jdk.rnd.huawei.com/server.loader = java.rmi.server.hostname = 127.0.0.1jna.platform.library.path = /usr/lib64:/lib64:/usr/lib:/lib:/usr/lib64/tracker-miners-2.0:/usr/lib64/tracker-2.0:/usr/lib64/dyninst:/usr/libexec/sudo:/usr/lib64/sssd:/usr/pgsql-9.6/lib:/usr/lib64/perl5/CORE:/usr/lib64/opencryptoki:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/amd64/jli:/usr/lib64/bind9-exportos.arch = amd64java.awt.graphicsenv = sun.awt.X11GraphicsEnvironmentjava.ext.dirs = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.h5.ky10.x86_64/jre/lib/ext:/usr/java/packages/lib/extuser.dir = /usr/share/tomcatline.separator = java.vm.name = OpenJDK 64-Bit Server VMlog4j.configurationFile = /etc/tomcat/log4j2.xmlfile.encoding = UTF-8com.sun.jndi.ldap.object.disableEndpointIdentification = java.specification.version = 1.8VM Flags:Non-default VM flags: -XX:CICompilerCount=4 -XX:GCLogFileSize=20971520 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=243269632 -XX:MaxHeapSize=1610612736 -XX:MaxNewSize=536870912 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=80740352 -XX:NumberOfGCLogFiles=15 -XX:OldSize=162529280 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseGCLogFileRotation -XX:+UseParallelGC Command line: -Xmx1536m -Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=15 -XX:GCLogFileSize=20m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/var/log/tomcat/tomcat-gc-%t.log -Dcom.sun.jndi.ldap.object.disableEndpointIdentification -Dcatalina.base=/usr/share/tomcat -Dcatalina.home=/usr/share/tomcat -Djava.endorsed.dirs= -Djava.io.tmpdir=/var/cache/tomcat/temp -Dlog4j.configurationFile=/etc/tomcat/log4j2.xml -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 3.jstat命令参数说明: generalOptions:通用选项,如果指定一个通用选项,就不能指定任何其他选项或参数。它包括如下两个选项: -help:显示帮助信息。 -options:显示outputOptions参数的列表。 outputOptions:输出选项,指定显示某一种Java虚拟机信息。 -t:把时间戳列显示为输出的第一列。这个时间戳是从Java虚拟机的开始运行到现在的秒数。 -h n:每显示n行显示一次表头,其中n为正整数。默认值为 0,即仅在第一行数据显示一次表头。 vmid:虚拟机唯一ID(LVMID,Local Virtual Machine Identifier),如果查看本机就是Java进程的进程ID。 interval:显示信息的时间间隔,单位默认毫秒。也可以指定秒为单位,比如:1s。如果指定了该参数,jstat命令将每隔这段时间显示一次统计信息。 count:显示数据的次数,默认值是无穷大,这将导致jstat命令一直显示统计信息,直到目标JVM终止或jstat命令终止。输出选项如果不指定通用选项(generalOptions),则可以指定输出选项(outputOptions)。输出选项决定jstat命令显示的内容和格式,具体如下: -class:显示类加载、卸载数量、总空间和装载耗时的统计信息。 -compiler:显示即时编译的方法、耗时等信息。 -gc:显示堆各个区域内存使用和垃圾回收的统计信息。 -gccapacity:显示堆各个区域的容量及其对应的空间的统计信息。 -gcutil:显示有关垃圾收集统计信息的摘要。 -gccause:显示关于垃圾收集统计信息的摘要(与-gcutil相同),以及最近和当前垃圾回收的原因。 -gcnew:显示新生代的垃圾回收统计信息。 -gcnewcapacity:显示新生代的大小及其对应的空间的统计信息。 -gcold: 显示老年代和元空间的垃圾回收统计信息。 -gcoldcapacity:显示老年代的大小统计信息。 -gcmetacapacity:显示元空间的大小的统计信息。 -printcompilation:显示即时编译方法的统计信息。 二、线程堆栈1.输出Java虚拟机提供了线程转储(Thread dump)的后门,通过这个后门,可以将线程堆栈打印出来。这个后门就是通过向Java进程发送一个QUIT信号,Java虚拟机收到该信号之后,将系统当前的JAVA线程调用堆栈打印出来。 打印方法: jstack -l pid > xxx.txt 需要注意的是,linux系统中往往会用不同的用户去执行不同的程序,此时可能需要通过sudu -u xxx jstack的形式 kill -3同时请确保Java命令行中没有DISABLE_JAVADUMP运行选项 2.线程分析通过输出堆栈进行分析 jstack -l $(jps | grep xxx | awk '{print $1}') > /tmp/xxx.jstack 12345678910111213141516171819202122232425262728293031"SYS_STATUS_CHECKER" #14 daemon prio=5 os_prio=0 tid=0x00007f5e047bf000 nid=0xe15 waiting on condition [0x00007f5dd43d1000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)ru at com.xxx.xxx.SystemStatusChecker.run(SystemStatusChecker.java:xx) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None "RMI Reaper" #39 prio=5 os_prio=0 tid=0x00007f5e04e4c800 nid=0xf0b in Object.wait() [0x00007f5dae2c4000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at sun.rmi.transport.ObjectTable$Reaper.run(ObjectTable.java:351) at java.lang.Thread.run(Thread.java:748) Locked ownable synchronizers: - None "main" #1 prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409) at java.net.ServerSocket.implAccept(ServerSocket.java:545) at java.net.ServerSocket.accept(ServerSocket.java:513) at com.xxx.common.xxx.await(CommonMain.java:244) at com.xxx.common.xxx.startup(CommonMain.java:207) at com.xxx.common.xxx.main(CommonMain.java:147) Locked ownable synchronizers: - None 在RMI线程中可以看到 “ - locked <0x00000000c0c88d20> (a java.lang.ref.ReferenceQueue$Lock)” 表示该线程已经使用了ID为”0x00000000c0c88d2”的锁,锁的ID由系统自动产生 123"main" prio=5 os_prio=0 tid=0x00007f5e0400a000 nid=0xdcb runnable [0x00007f5e0b393000]| | | | | | |线程名称 线程优先级 操作系统级别的优先级 线程id 对应的本地线程ID 状态 线程占用内存地址 其中”线程对应的本地线程id号”所指的”本地线程”是指该Java线程所对应的虚拟机中的本地线程。我们知道Java是解析型语言,执行的实体是Java虚拟机,因此Java语言中的线程是 依附于Java虚拟机中的本地线程来运行的,实际上是本地线程在执行Java线程代码。 Java代码 中创建一个thread,虚拟机在运行期就会创建一个对应的本地线程,而这个本地线程才是真正的线程实体。为了更加深入得理解本地线程和Java线程的关系,在Unix/Linux下,我们可以通 如下方式把Java虚拟机的本地线程打印出来: 使用ps -ef | grep java 获得Java进程ID。 使用pstack 获得Java虚拟机的本地线程的堆栈其中本地线程各项含义如下:12345Thread 56 (Thread 0x7f5e0b394700 (LWP 3531))| | || | +----本地线程id(另一种表示,LWP-light weight process)| +-------------------本地线程id+------------------------------线程名称 而通过jstack输出的main本地线程ID为0xdcb,其10进制正好为3531。 “runnable”表示当前线程处于运行状态。这个runnable状态是从虚拟机的角度来看的, 表示这个线程正在运行 ⚠️ NOTE: 但是处于Runnable状态的线程不一定真的消耗CPU. 处于Runnable的线程只能说明该线程没有阻塞在java的wait或者sleep方法上,同时也没等待在锁上面。但是如果该线程调用了本地方法,而本地方法处于等待状态,这个时候虚拟机是不知道本地代码中发生 了什么(但操作系统是知道的,pstack就是操作提供的一个命令,它知道当前线程正在执行的本地代码上下文),此时尽管当前线程实际上也是阻塞的状态,但实际上显示出来的还是runnable状态, 这种情况下是不消耗CPU的 121. 处于waittig和blocked状态的线程都不会消耗CPU 2. 线程频繁地挂起和唤醒需要消耗CPU, 而且代价颇大 TIMED_WAITING(on object monitor) 表示当前线程被挂起一段时间,说明该线程正在 执行obj.wait(int time)方法. TIMED_WAITING(sleeping) 表示当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. TIMED_WAITING(parking) 当前线程被挂起一段时间,即正在执行Thread.sleep(int time)方法. WAINTING(on object monitor) 当前线程被挂起,即正在执行obj.wait()方法(无参数的wait()方法).1234处于TIMED_WAITING、WAINTING状态的线程一定不消耗CPU. 处于RUNNABLE的线程,要结合当前线程代码的性质判断,是否消耗CPU.• 如果是纯Java运算代码,则消耗CPU.• 如果是网络IO,很少消耗CPU.• 如果是本地代码,结合本地代码的性质判断(可以通过pstack/gstack获取本地线程堆栈), 如果是纯运算代码,则消耗CPU, 如果被挂起,则不消耗CPU,如果是IO,则不怎么消 耗CPU。 三、相关的排查方法1.CPU生产环境中往往会出现CPU飙高的情况,对于JAVA应用而言,此类问题相对较好确定问题方向。 1.1 使用jstack确定CPU占用高的线程\\通过top指令,可以看到进程占用的一些基础资源信息,然后“P”键可以按照CPU使用率进行排序,“M”键可以按照内存占用情况进行排序 找到CPU占用高的进程pid,然后将jstack信息定向到一个文件中去,通过top -Hp pid查看具体的情况。 通过 printf '%x ' pid将pid转换为16进制,然后在jstack文件中根据对应的数字进行查找,然后针对性的进行分析 1.2 频繁GC有时候我们可以先确定下gc是不是太频繁,使用jstat -gc pid 1000命令来对gc分代变化情况进行观察,1000表示采样间隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。YGC/YGT、FGC/FGCT、GCT则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。 1.3 频繁上下文切换针对频繁上下文问题,我们可以使用vmstat命令来进行查看cs(context switch)一列则代表了上下文切换的次数。 如果我们希望对特定的pid进行监控那么可以使用 pidstat -w pid命令,cswch和nvcswch表示自愿及非自愿切换。 2.内存对于JAVA应用,涉及到的内存问题主要包括OOM、GC问题和堆外内存。 2.1 OOMJVM中的内存不足,OOM大致可以分为以下几种情况 Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread 这个意思是没有足够的内存空间给线程分配java栈,基本上还是线程池代码写的有问题,比如说忘记shutdown,所以说应该首先从代码层面来寻找问题,使用jstack或者jmap。如果一切都正常,JVM方面可以通过指定Xss来减少单个thread stack的大小。另外也可以在系统层面,可以通过修改/etc/security/limits.confnofile和nproc来增大os对线程的限制 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 这个意思是堆的内存占用已经达到-Xmx设置的最大值,应该是最常见的OOM错误了。解决思路仍然是先应该在代码中找,怀疑存在内存泄漏,通过jstack和jmap去定位问题。如果说一切都正常,才需要通过调整Xmx的值来扩大内存。 Caused by: java.lang.OutOfMemoryError: Meta space 这个意思是元数据区的内存占用已经达到XX:MaxMetaspaceSize设置的最大值,排查思路和上面的一致,参数方面可以通过XX:MaxPermSize来进行调整 Exception in thread "main" java.lang.StackOverflowError 表示线程栈需要的内存大于Xss值,同样也是先进行排查,参数方面通过Xss来调整,但调整的太大可能又会引起OOM。 2.2 GC问题gc问题除了影响cpu也会影响内存,排查思路也是一致的。一般先使用jstat来查看分代变化情况,比如youngGC或者fullGC次数是不是太多呀;EU、OU等指标增长是不是异常等。 线程的话太多而且不被及时gc也会引发oom,大部分就是之前说的unable to create new native thread。除了jstack细细分析dump文件外,我们一般先会看下总体线程,通过pstreee -p pid |wc -l 2.3 堆外内存JVM 的堆外内存主要包括: JVM 自身运行占用的空间; 线程栈分配占用的系统内存; DirectByteBuffer 占用的内存; JNI 里分配的内存; Java 8 开始的元数据空间; NIO 缓存 Unsafe 调用分配的内存; codecache 冰山对象:冰山对象是指在 JVM 堆里占用的内存很小,但其实引用了一块很大的本地内存。DirectByteBuffer 和 线程都属于这类对象。 2.3.1NMT分析堆外内存NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。 NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。 2.3.2 开启 NMT启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。启动命令: -XX:NativeMemoryTracking=[off | summary | detail]。 off:NMT 默认是关闭的; summary:只收集子系统的内存使用的总计数据; detail:收集每个调用点的内存使用数据。 2.3.3 jcmd 访问 NMT 数据命令:jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]","tags":["JAVA","Linux"],"categories":["问题排查"]},{"title":"个人介绍","path":"/about/index.html","content":"简介 职业: 运维开发工程师 出生时间: 98年 性别: 男 联系方式: 446302864@qq.com/baixiaozhou96@gmail.com 工作履历 2020.7-2023.6 杭州齐治科技 软件开发工程师 2023.7-2024.1 腾讯云 ES SRE 2024.3- 金山云 SRE 个人项目 SysStress 性能压测工具,还在不断完善中 perfmonitorscan 性能自采集工具"},{"title":"个人介绍","path":"/friends/index.html","content":"Test"},{"path":"/comments/index.html","content":"留言板 欢迎大家在这里进行留言,进行问题探讨"}]