Docker 常见问题

Docker 常见问题

启动

docker daemon启动的时候如果报 socket错误,是因为daemon启动参数配置了: -H fd:// ,但是 docker.socket是disable状态,启动daemon依赖socket,但是systemctl又拉不起来docker.socket,因为被disable了,先 sudo systemctl enable docker.socket 就可以了。

如果docker.socket service被mask后比disable更粗暴,mask后手工都不能拉起来了,但是disable后还可以手工拉起,然后再拉起docker service。 这是需要先 systemctl unmask

1
2
$sudo systemctl restart docker.socket
Failed to restart docker.socket: Unit docker.socket is masked.

另外 docker.socket 启动依赖环境的要有 docker group这个组,可以添加: groupadd docker

failed to start docker.service unit not found. rhel 7.7

systemctl list-unit-files |grep docker.service 可以看到docker.service 是存在并enable了

实际是redhat 7.7的yum仓库所带的docker启动参数变了, 如果手工启动的话也会报找不到docker-runc 手工:

1
ln -s /usr/libexec/docker/docker-runc-current /usr/bin/docker-runc

https://access.redhat.com/solutions/2876431 https://stackoverflow.com/questions/42754779/docker-runc-not-installed-on-system

yum安装docker会在 /etc/sysconfig 下放一些配置参数(docker.service 环境变量)

Docker 启动报错: Error starting daemon: Error initializing network controller: list bridge addresses failed: no available network

这是因为daemon启动的时候缺少docker0网桥,导致启动失败,手工添加:

1
2
ip link add docker0 type bridge
ip addr add dev docker0 172.30.0.0/24

启动成功后即使手工删除docker0,然后再次启动也会成功,这次会自动创建docker0 172.30.0.0/16 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#systemctl status docker -l
● docker.service - Docker Application Container Engine
Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Fri 2021-01-22 17:21:45 CST; 2min 12s ago
Docs: http://docs.docker.io
Process: 68318 ExecStartPost=/sbin/iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT (code=exited, status=0/SUCCESS)
Process: 68317 ExecStart=/opt/kube/bin/dockerd (code=exited, status=1/FAILURE)
Main PID: 68317 (code=exited, status=1/FAILURE)

Jan 22 17:21:43 l57f12112.sqa.nu8 dockerd[68317]: time="2021-01-22T17:21:43.991179104+08:00" level=warning msg="failed to load plugin io.containerd.snapshotter.v1.aufs" error="modprobe aufs failed: "modprobe: FATAL: Module aufs not found.\n": exit status 1"
Jan 22 17:21:43 l57f12112.sqa.nu8 dockerd[68317]: time="2021-01-22T17:21:43.991371956+08:00" level=warning msg="could not use snapshotter btrfs in metadata plugin" error="path /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.btrfs must be a btrfs filesystem to be used with the btrfs snapshotter"
Jan 22 17:21:43 l57f12112.sqa.nu8 dockerd[68317]: time="2021-01-22T17:21:43.991381620+08:00" level=warning msg="could not use snapshotter aufs in metadata plugin" error="modprobe aufs failed: "modprobe: FATAL: Module aufs not found.\n": exit status 1"
Jan 22 17:21:43 l57f12112.sqa.nu8 dockerd[68317]: time="2021-01-22T17:21:43.991388991+08:00" level=warning msg="could not use snapshotter zfs in metadata plugin" error="path /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.zfs must be a zfs filesystem to be used with the zfs snapshotter: skip plugin"
Jan 22 17:21:44 l57f12112.sqa.nu8 systemd[1]: Stopping Docker Application Container Engine...
Jan 22 17:21:45 l57f12112.sqa.nu8 dockerd[68317]: failed to start daemon: Error initializing network controller: list bridge addresses failed: PredefinedLocalScopeDefaultNetworks List: [172.17.0.0/16 172.18.0.0/16 172.19.0.0/16 172.20.0.0/16 172.21.0.0/16 172.22.0.0/16 172.23.0.0/16 172.24.0.0/16 172.25.0.0/16 172.26.0.0/16 172.27.0.0/16 172.28.0.0/16 172.29.0.0/16 172.30.0.0/16 172.31.0.0/16 192.168.0.0/20 192.168.16.0/20 192.168.32.0/20 192.168.48.0/20 192.168.64.0/20 192.168.80.0/20 192.168.96.0/20 192.168.112.0/20 192.168.128.0/20 192.168.144.0/20 192.168.160.0/20 192.168.176.0/20 192.168.192.0/20 192.168.208.0/20 192.168.224.0/20 192.168.240.0/20]: no available network
Jan 22 17:21:45 l57f12112.sqa.nu8 systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE
Jan 22 17:21:45 l57f12112.sqa.nu8 systemd[1]: Stopped Docker Application Container Engine.
Jan 22 17:21:45 l57f12112.sqa.nu8 systemd[1]: Unit docker.service entered failed state.
Jan 22 17:21:45 l57f12112.sqa.nu8 systemd[1]: docker.service failed.

参考:https://github.com/docker/for-linux/issues/123

或者这样解决:https://stackoverflow.com/questions/39617387/docker-daemon-cant-initialize-network-controller

This was related to the machine having several network cards (can also happen in machines with VPN)

The solution was to start manually docker like this:

1
/usr/bin/docker daemon --debug --bip=192.168.y.x/24

where the 192.168.y.x is the MAIN machine IP and /24 that ip netmask. Docker will use this network range for building the bridge and firewall riles. The –debug is not really needed, but might help if something else fails.

After starting once, you can kill the docker and start as usual. AFAIK, docker have created a cache config for that –bip and should work now without it. Of course, if you clean the docker cache, you may need to do this again.

本机网络信息默认保存在:/var/lib/docker/network/files/local-kv.db 想要清理bridge网络的话,不能直接 docker network rm bridge 因为bridge是预创建的受保护不能直接删除,可以删掉:/var/lib/docker/network/files/local-kv.db 并且同时删掉 docker0 然后重启dockerd就可以了

alios下容器里面ping不通docker0

alios上跑docker,然后启动容器,发现容器里面ping不通docker0, 手工重新brctl addbr docker0 , 然后把虚拟网卡加进去就可以了。应该是系统哪里bug了.

image.png

非常神奇的是不通的时候如果在宿主机上对docker0抓包就瞬间通了,停掉抓包就不通

docker0-tcpdump.gif

猜测是 alios 的bug

systemctl start docker

Failed to start docker.service: Unit not found.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UNIT LOAD PATH
Unit files are loaded from a set of paths determined during
compilation, described in the two tables below. Unit files found
in directories listed earlier override files with the same name
in directories lower in the list.

Table 1. Load path when running in system mode (--system).
┌────────────────────────┬─────────────────────────────┐
│Path │ Description │
├────────────────────────┼─────────────────────────────┤
│/etc/systemd/system │ Local configuration │
├────────────────────────┼─────────────────────────────┤
│/run/systemd/system │ Runtime units │
├────────────────────────┼─────────────────────────────┤
│/usr/lib/systemd/system │ Units of installed packages │
└────────────────────────┴─────────────────────────────┘

systemd 设置path环境变量,可以设置

[Service]
Type=notify
Environment=PATH=/opt/kube/bin:/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/opt/satools:/root/bin

容器没有systemctl

Failed to get D-Bus connection: Operation not permitted: systemd容器中默认无法启动,需要启动容器的时候

1
docker run -itd --privileged --name=ren drds_base:centos init //init 必须要或者systemd

1号进程需要是systemd(init 是systemd的link),才可以使用systemctl,推荐用这个来解决:https://github.com/gdraheim/docker-systemctl-replacement

systemd是用来取代init的,之前init管理所有进程启动,是串行的,耗时久,也不管最终状态,systemd主要是串行并监控进程状态能反复重启。

新版本init link向了systemd

busybox/Alpine/Scratch

busybox集成了常用的linux工具(nc/telnet/cat……),保持精细,方便一张软盘能装下。

Alpine一个精简版的Linux 发行版,更小更安全,用的musl libc而不是glibc

scratch一个空的框架,什么也没有

找不到shell

Dockerfile 中(https://www.ardanlabs.com/blog/2020/02/docker-images-part1-reducing-image-size.html):

1
2
3
CMD ./hello OR RUN 等同于 /bin/sh -c "./hello", 需要shell,
改用:
CMD ["./hello"] 等同于 ./hello 不需要shell

entrypoint VS cmd

dockerfile中:CMD 可以是命令、也可以是参数,如果是参数, 把它传递给:ENTRYPOINT

在写Dockerfile时, ENTRYPOINT或者CMD命令会自动覆盖之前的ENTRYPOINT或者CMD命令

从参数中传入的ENTRYPOINT或者CMD命令会自动覆盖Dockerfile中的ENTRYPOINT或者CMD命令

copy VS add

COPY指令和ADD指令的唯一区别在于是否支持从远程URL获取资源。 COPY指令只能从执行docker build所在的主机上读取资源并复制到镜像中。 而ADD指令还支持通过URL从远程服务器读取资源并复制到镜像中。

满足同等功能的情况下,推荐使用COPY指令。ADD指令更擅长读取本地tar文件并解压缩

Digest VS Image ID

pull镜像的时候,将docker digest带上,即使黑客使用手段将某一个digest对应的内容强行修改了,docker也能check出来,因为docker会在pull下镜像的时候,只要根据image的内容计算sha256

1
docker images --digests
  • The “digest” is a hash of the manifest, introduced in Docker registry v2.
  • The image ID is a hash of the local image JSON configuration. 就是inspect 看到的 RepoDigests

容器中抓包和调试 – nsenter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
获取pid:docker inspect -f {{.State.Pid}} c8f874efea06

进入namespace:nsenter --target 17277 --net --pid –mount

//只进入network namespace,这样看到的文件还是宿主机的,能直接用tcpdump,但是看到的网卡是容器的
nsenter --target 17277 --net

// ip netns 获取容器网络信息
1022 [2021-04-14 15:53:06] docker inspect -f '{{.State.Pid}}' ab4e471edf50 //获取容器进程id
1023 [2021-04-14 15:53:30] ls /proc/79828/ns/net
1024 [2021-04-14 15:53:57] ln -sfT /proc/79828/ns/net /var/run/netns/ab4e471edf50 //link 以便ip netns List能访问

// 宿主机上查看容器ip
1026 [2021-04-14 15:54:11] ip netns list
1028 [2021-04-14 15:55:19] ip netns exec ab4e471edf50 ifconfig

//nsenter调试网络
Get the pause container's sandboxkey:
root@worker01:~# docker inspect k8s_POD_ubuntu-5846f86795-bcbqv_default_ea44489d-3dd4-11e8-bb37-02ecc586c8d5_0 | grep SandboxKey
"SandboxKey": "/var/run/docker/netns/82ec9e32d486",
root@worker01:~#
Now, using nsenter you can see the container's information.
root@worker01:~# nsenter --net=/var/run/docker/netns/82ec9e32d486 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 0a:58:0a:f4:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.2/24 scope global eth0
valid_lft forever preferred_lft forever
Identify the peer_ifindex, and finally you can see the veth pair endpoint in root namespace.
root@worker01:~# nsenter --net=/var/run/docker/netns/82ec9e32d486 ethtool -S eth0
NIC statistics:
peer_ifindex: 7
root@worker01:~#
root@worker01:~# ip -d link show | grep '7: veth'
7: veth5e43ca47@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
root@worker01:~#

nsenter相当于在setns的示例程序之上做了一层封装,使我们无需指定命名空间的文件描述符,而是指定进程号即可,详细case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#docker inspect cb7b05d82153 | grep -i SandboxKey   //根据 pause 容器id找network namespace
"SandboxKey": "/var/run/docker/netns/d6b2ef3cf886",

[root@hygon252 19:00 /root]
#nsenter --net=/var/run/docker/netns/d6b2ef3cf886 ip addr show
3: eth0@if496: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default //496对应宿主机上的veth编号
link/ether 1e:95:dd:d9:88:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.3.22/24 brd 192.168.3.255 scope global eth0
valid_lft forever preferred_lft forever
#nsenter --net=/var/run/docker/netns/d6b2ef3cf886 ethtool -S eth0
NIC statistics:
peer_ifindex: 496

#ip -d -4 addr show cni0
475: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 8e:34:ba:e2:a4:c6 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
bridge forward_delay 1500 hello_time 200 max_age 2000 ageing_time 30000 stp_state 0 priority 32768 vlan_filtering 0 vlan_protocol 802.1Q bridge_id 8000.8e:34:ba:e2:a4:c6 designated_root 8000.8e:34:ba:e2:a4:c6 root_port 0 root_path_cost 0 topology_change 0 topology_change_detected 0 hello_timer 0.00 tcn_timer 0.00 topology_change_timer 0.00 gc_timer 43.31 vlan_default_pvid 1 vlan_stats_enabled 0 group_fwd_mask 0 group_address 01:80:c2:00:00:00 mcast_snooping 1 mcast_router 1 mcast_query_use_ifaddr 0 mcast_querier 0 mcast_hash_elasticity 4 mcast_hash_max 512 mcast_last_member_count 2 mcast_startup_query_count 2 mcast_last_member_interval 100 mcast_membership_interval 26000 mcast_querier_interval 25500 mcast_query_interval 12500 mcast_query_response_interval 1000 mcast_startup_query_interval 3124 mcast_stats_enabled 0 mcast_igmp_version 2 mcast_mld_version 1 nf_call_iptables 0 nf_call_ip6tables 0 nf_call_arptables 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 192.168.3.1/24 brd 192.168.3.255 scope global cni0
valid_lft forever preferred_lft forever

创建虚拟网卡

1
2
3
4
5
6
7
8
To make this interface you'd first need to make sure that you have the dummy kernel module loaded. You can do this like so:
$ sudo lsmod | grep dummy
$ sudo modprobe dummy
$ sudo lsmod | grep dummy
dummy 12960 0
With the driver now loaded you can create what ever dummy network interfaces you like:

$ sudo ip link add eth10 type dummy

修改网卡名字

1
2
3
4
5
6
7
8
9
ip link set ens33 down
ip link set ens33 name eth0
ip link set eth0 up

mv /etc/sysconfig/network-scripts/ifcfg-{ens33,eth0}
sed -ire "s/NAME=\"ens33\"/NAME=\"eth0\"/" /etc/sysconfig/network-scripts/ifcfg-eth0
sed -ire "s/DEVICE=\"ens33\"/DEVICE=\"eth0\"/" /etc/sysconfig/network-scripts/ifcfg-eth0
MAC=$(cat /sys/class/net/eth0/address)
echo -n 'HWADDR="'$MAC\" >> /etc/sysconfig/network-scripts/ifcfg-eth0

OS版本

搞Docker就得上el7, 6的性能太差了 Docker 对 Linux 内核版本的最低要求是3.10,如果内核版本低于 3.10 会缺少一些运行 Docker 容器的功能。这些比较旧的内核,在一定条件下会导致数据丢失和频繁恐慌错误。

清理mount文件

删除 /var/lib/docker 目录如果报busy,一般是进程在使用中,可以fuser查看哪个进程在用,然后杀掉进程;另外就是目录mount删不掉问题,可以 mount | awk ‘{ print $3 }’ |grep overlay2| xargs umount 批量删除

No space left on device

OSError: [Errno 28] No space left on device

​ 大部分时候不是真的磁盘没有空间了还有可能是inode不够了(df -ih 查看inode使用率)

​ 尝试用 fallocate 来测试创建文件是否成功

​ 尝试fdisk-l / tune2fs -l 来确认分区和文件系统的正确性

​ fallocate 创建一个文件名很长的文件失败(也就是原始报错的文件名),同时fallocate 创建一个短文件名的文件成功

​ dmesg 查看系统报错信息

1
2
[13155344.231942] EXT4-fs warning (device sdd): ext4_dx_add_entry:2461: Directory (ino: 3145729) index full, reach max htree level :2
[13155344.231944] EXT4-fs warning (device sdd): ext4_dx_add_entry:2465: Large directory feature is not enabled on this filesystem

​ 看起来是小文件太多撑爆了ext4的BTree索引,通过 tune2fs -l /dev/nvme1n1p1 验证下

1
2
3
4
5
6
7
8
#tune2fs -l /dev/nvme1n1p1 |grep Filesystem
Filesystem volume name: /flash2
Filesystem revision #: 1 (dynamic)
Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags: signed_directory_hash
Filesystem state: clean
Filesystem OS type: Linux
Filesystem created: Fri Mar 6 17:08:36 2020

​ 执行 tune2fs -O large_dir /dev/nvme1n1p1 打开 large_dir 选项

1
2
tune2fs -l /dev/nvme1n1p1 |grep -i large
Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg large_dir sparse_super large_file huge_file uninit_bg dir_nlink extra_isize

如上所示,开启后Filesystem features 多了 large_dir,不过4.13以上内核才支持这个功能

CPU 资源分配

对于cpu的限制,Kubernetes采用cfs quota来限制进程在单位时间内可用的时间片。当独享和共享实例在同一台node节点上的时候,一旦实例的工作负载增加,可能会导致独享实例工作负载在不同的cpu核心上来回切换,影响独享实例的性能。所以,为了不影响独享实例的性能,我们希望在同一个node上,独享实例和共享实例的cpu能够分开绑定,互不影响。

内核的默认cpu.shares是1024,也可以通过 cpu.cfs_quota_us / cpu.cfs_period_us去控制容器规格

cpu.shares 多层级限制后上层有更高的优先级,可能会经常看到 CPU 多核之间不均匀的现象,部分核总是跑不满之类的。 cpu.shares 是用来调配争抢用,比如离线、在线混部可以通过 cpu.shares 多给在线业务

sock

docker有两个sock,一个是dockershim.sock,一个是docker.sock。dockershim.sock是由实现了CRI接口的一个插件提供的,主要把k8s请求转换成docker请求,最终docker还是要 通过docker.sock来管理容器。

kubelet —CRI—-> docker-shim(kubelet内置的CRI-plugin) –> docker

docker image api

1
2
3
4
5
获取所有镜像名字: GET /v2/_catalog   
curl registry:5000/v2/_catalog

获取某个镜像的tag: GET /v2/<name>/tags/list
curl registry:5000/v2/drds/corona-server/tags/list

从registry中删除镜像

默认registry仓库不支持删除镜像,修改registry配置来支持删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#cat config.yml
version: 0.1
log:
fields:
service: registry
storage:
delete: //增加如下两行,默认是false,不能删除
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3

#docker cp ./config.yml registry:/etc/docker/registry/config.yml
#docker restart registry

然后通过API来查询要删除镜像的id:

1
2
3
4
5
6
7
8
9
//查询要删除镜像的tag
curl registry:5000/v2/drds/corona-server/tags/list
//根据tag查找Etag
curl -v registry:5000/v2/drds/corona-server/manifests/2.0.0_3012622_20220214_4ca91d96-arm64 -H 'Accept: application/vnd.docker.distribution.manifest.v2+json'
//根据前一步返回的Etag来删除对应的tag
curl -X DELETE registry:5000/v2/drds/corona-server/manifests/sha256:207ec19c1df6a3fa494d41a1a8b5332b969a010f0d4d980e39f153b1eaca2fe2 -v

//执行垃圾回收
docker exec -it registry bin/registry garbage-collect /etc/docker/registry/config.yml

检查是否restart能支持只重启deamon,容器还能正常运行

1
2
3
$sudo docker info | grep Restore
Live Restore Enabled: true

参考资料

https://www.ardanlabs.com/blog/2020/02/docker-images-part1-reducing-image-size.html

Linux LVM使用

Linux LVM使用

LVM是 Logical Volume Manager(逻辑卷管理)的简写, 用来解决磁盘分区大小动态分配。LVM不是软RAID(Redundant Array of Independent Disks)。

从一块硬盘到能使用LV文件系统的步骤:

硬盘—-分区(fdisk)—-PV(pvcreate)—-VG(vgcreate)—-LV(lvcreate)—-格式化(mkfs.ext4 LV为ext文件系统)—-挂载

img

LVM磁盘管理方式

image-20220725100705140

lvreduce 缩小LV

先卸载—>然后减小逻辑边界—->最后减小物理边界—>在检测文件系统 ==谨慎用==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[aliyun@uos15 15:07 /dev/disk/by-label]
$sudo e2label /dev/nvme0n1p1 polaru01 //给磁盘打标签

[aliyun@uos15 15:07 /dev/disk/by-label]
$lsblk -f
NAME FSTYPE LABEL UUID FSAVAIL FSUSE% MOUNTPOINT
sda
├─sda1 vfat EFI D0E3-79A8 299M 0% /boot/efi
├─sda2 ext4 Boot f204c992-fb20-40e1-bf58-b11c994ee698 1.3G 6% /boot
├─sda3 ext4 Roota dbc68010-8c36-40bf-b794-271e59ff5727 14.8G 61% /
├─sda4 ext4 Rootb 73fe0ac6-ff6b-46cc-a609-c574be026e8f
├─sda5 ext4 _dde_data 798fce56-fc82-4f59-bcaa-d2ed5c48da8d 42.1G 54% /data
├─sda6 ext4 Backup 267dc7a8-1659-4ccc-b7dc-5f2cd80f4e4e 3.7G 57% /recovery
└─sda7 swap SWAP 7a5632dc-bc7b-410e-9a50-07140f20cd13 [SWAP]
nvme0n1
└─nvme0n1p1 ext4 polaru01 762a5700-8cf1-454a-b385-536b9f63c25d 413.4G 54% /u01
nvme1n1 xfs u02 8ddf19c4-fe71-4428-b2aa-e45acf08050c
nvme2n1 xfs u03 2b8625b4-c67d-4f1e-bed6-88814adfd6cc
nvme3n1 ext4 u01 cda85750-c4f7-402e-a874-79cb5244d4e1

LVM 创建、扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
sudo vgcreate vg1 /dev/nvme0n1 /dev/nvme1n1 //两块物理磁盘上创建vg1
如果报错:
Can't open /dev/nvme1n1 exclusively. Mounted filesystem?
Can't open /dev/nvme0n1 exclusively. Mounted filesystem?
是说/dev/nvme0n1已经mounted了,需要先umount

vgdisplay
sudo lvcreate -L 5T -n u03 vg1 //在虚拟volume-group vg1上创建一个5T大小的分区or: sudo lvcreate -l 100%free -n u03 vg1
sudo mkfs.ext4 /dev/vg1/u03
sudo mkdir /lvm
sudo fdisk -l
sudo umount /lvm
sudo lvresize -L 5.8T /dev/vg1/u03 //lv 扩容,但此时还未生效;缩容的话风险较大,且需要先 umount
sudo e2fsck -f /dev/vg1/u03
sudo resize2fs /dev/vg1/u03 //触发生效
sudo mount /dev/vg1/u03 /lvm
cd /lvm/
lvdisplay
sudo vgdisplay vg1
lsblk -l
lsblk
sudo vgextend vg1 /dev/nvme3n1 //vg 扩容, 增加一块磁盘到vg1
ls /u01
sudo vgdisplay
sudo fdisk -l
sudo pvdisplay
sudo lvcreate -L 1T -n lv2 vg1 //从vg1中再分配一块1T大小的磁盘
sudo lvdisplay
sudo mkfs.ext4 /dev/vg1/lv2
mkdir /lv2
ls /
sudo mkdir /lv2
sudo mount /dev/vg1/lv2 /lv2
df -lh

//手工创建lvm
1281 18/05/22 11:04:22 ls -l /dev/|grep -v ^l|awk '{print $NF}'|grep -E "^nvme[7-9]{1,2}n1$|^df[a-z]$|^os[a-z]$"
1282 18/05/22 11:05:06 vgcreate -s 32 vgbig /dev/nvme7n1 /dev/nvme8n1 /dev/nvme9n1
1283 18/05/22 11:05:50 vgcreate -s 32 vgbig /dev/nvme7n1 /dev/nvme8n1 /dev/nvme9n1
1287 18/05/22 11:07:59 lvcreate -A y -I 128K -l 100%FREE -i 3 -n big vgbig
1288 18/05/22 11:08:02 df -h
1289 18/05/22 11:08:21 lvdisplay
1290 18/05/22 11:08:34 df -lh
1291 18/05/22 11:08:42 df -h
1292 18/05/22 11:09:05 mkfs.ext4 /dev/vgbig/big -m 0 -O extent,uninit_bg -E lazy_itable_init=1 -q -L big -J size=4000
1298 18/05/22 11:10:28 mkdir -p /big
1301 18/05/22 11:12:11 mount /dev/vgbig/big /big

创建LVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function create_polarx_lvm_V62(){
vgremove vgpolarx

#sed -i "97 a\ types = ['nvme', 252]" /etc/lvm/lvm.conf
parted -s /dev/nvme0n1 rm 1
parted -s /dev/nvme1n1 rm 1
parted -s /dev/nvme2n1 rm 1
parted -s /dev/nvme3n1 rm 1
dd if=/dev/zero of=/dev/nvme0n1 count=10000 bs=512
dd if=/dev/zero of=/dev/nvme1n1 count=10000 bs=512
dd if=/dev/zero of=/dev/nvme2n1 count=10000 bs=512
dd if=/dev/zero of=/dev/nvme3n1 count=10000 bs=512

#lvmdiskscan
vgcreate -s 32 vgpolarx /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
lvcreate -A y -I 16K -l 100%FREE -i 4 -n polarx vgpolarx
mkfs.ext4 /dev/vgpolarx/polarx -m 0 -O extent,uninit_bg -E lazy_itable_init=1 -q -L polarx -J size=4000
sed -i "/polarx/d" /etc/fstab
mkdir -p /polarx
echo "LABEL=polarx /polarx ext4 defaults,noatime,data=writeback,nodiratime,nodelalloc,barrier=0 0 0" >> /etc/fstab
mount -a
}

create_polarx_lvm_V62

-I 64K 值条带粒度,默认64K,mysql pagesize 16K,所以最好16K

默认创建的是 linear,一次只用一块盘,不能累加多快盘的iops能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#lvcreate -h
lvcreate - Create a logical volume

Create a linear LV.
lvcreate -L|--size Size[m|UNIT] VG
[ -l|--extents Number[PERCENT] ]
[ --type linear ]
[ COMMON_OPTIONS ]
[ PV ... ]

Create a striped LV (infers --type striped).
lvcreate -i|--stripes Number -L|--size Size[m|UNIT] VG
[ -l|--extents Number[PERCENT] ]
[ -I|--stripesize Size[k|UNIT] ]
[ COMMON_OPTIONS ]
[ PV ... ]

Create a raid1 or mirror LV (infers --type raid1|mirror).
lvcreate -m|--mirrors Number -L|--size Size[m|UNIT] VG
[ -l|--extents Number[PERCENT] ]
[ -R|--regionsize Size[m|UNIT] ]
[ --mirrorlog core|disk ]
[ --minrecoveryrate Size[k|UNIT] ]
[ --maxrecoveryrate Size[k|UNIT

remount

正常使用中的文件系统是不能被umount的,如果需要修改mount参数的话可以考虑用mount 的 -o remount 参数

1
2
3
4
5
6
[root@ky3 ~]# mount -o lazytime,remount /polarx/  //增加lazytime参数
[root@ky3 ~]# mount -t ext4
/dev/mapper/vgpolarx-polarx on /polarx type ext4 (rw,noatime,nodiratime,lazytime,nodelalloc,nobarrier,stripe=128,data=writeback)
[root@ky3 ~]# mount -o rw,remount /polarx/ //去掉刚加的lazytime 参数
[root@ky3 ~]# mount -t ext4
/dev/mapper/vgpolarx-polarx on /polarx type ext4 (rw,noatime,nodiratime,nodelalloc,nobarrier,stripe=128,data=writeback)

remount 时要特别小心,会大量回收 slab 等导致sys CPU 100% 打挂整机,remount会导致slab回收等,请谨慎执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[2023-10-26 15:04:49][kernel][info]EXT4-fs (dm-0): re-mounted. Opts: lazytime,data=writeback,nodelalloc,barrier=0,nolazytime
[2023-10-26 15:04:49][kernel][info]EXT4-fs (dm-1): re-mounted. Opts: lazytime,data=writeback,nodelalloc,barrier=0,nolazytime
[2023-10-26 15:05:16][kernel][warning]Modules linked in: ip_tables tcp_diag inet_diag venice_reduce_print(OE) bianque_driver(OE) 8021q garp mrp bridge stp llc ip6_tables tcp_rds_rt_j(OE) tcp_rt_base(OE) slb_vctk(OE) slb_vtoa(OE) hookers slb_ctk_proxy(OE) slb_ctk_session(OE) slb_ctk_debugfs(OE) loop nf_conntrack fuse btrfs zlib_deflate raid6_pq xor vfat msdos fat xfs libcrc32c ext3 jbd dm_mod khotfix_D902467(OE) kpatch_D537536(OE) kpatch_D793896(OE) kpatch_D608634(OE) kpatch_D629788(OE) kpatch_D820113(OE) kpatch_D723518(OE) kpatch_D616841(OE) kpatch_D602147(OE) kpatch_D523456(OE) kpatch_D559221(OE) ipflt(OE) kpatch_D656712(OE) kpatch_D753272(OE) kpatch_D813404(OE) i40e kpatch_D543129(OE) kpatch_D645707(OE) kpatch(OE) rpcrdma(OE) xprtrdma(OE) ib_isert(OE) ib_iser(OE) ib_srpt(OE) ib_srp(OE) ib_ipoib(OE) ib_addr(OE) ib_sa(OE)
[2023-10-26 15:05:16][kernel][warning]ib_mad(OE) bonding iTCO_wdt iTCO_vendor_support intel_powerclamp coretemp intel_rapl kvm_intel kvm crc32_pclmul ghash_clmulni_intel aesni_intel lrw gf128mul glue_helper ablk_helper cryptd ipmi_devintf pcspkr sg lpc_ich mfd_core i2c_i801 shpchp wmi ipmi_si ipmi_msghandler acpi_pad acpi_power_meter binfmt_misc aocblk(OE) mlx5_ib(OE) ext4 mbcache jbd2 crc32c_intel ast(OE) syscopyarea mlx5_core(OE) sysfillrect sysimgblt ptp i2c_algo_bit pps_core drm_kms_helper aocnvm(OE) vxlan ttm aocmgr(OE) ip6_udp_tunnel udp_tunnel drm i2c_core sd_mod crc_t10dif crct10dif_generic crct10dif_pclmul crct10dif_common ahci libahci libata rdma_ucm(OE) rdma_cm(OE) iw_cm(OE) ib_umad(OE) ib_ucm(OE) ib_uverbs(OE) ib_cm(OE) ib_core(OE) mlx_compat(OE) [last unloaded: ip_tables]
[2023-10-26 15:05:16][kernel][warning]CPU: 85 PID: 105195 Comm: mount Tainted: G W OE K------------ 3.10.0-327.ali2017.alios7.x86_64 #1
[2023-10-26 15:05:16][kernel][warning]Hardware name: Foxconn AliServer-Thor-04-12U-v2/Thunder2.0 2U, BIOS 1.0.PL.FC.P.026.05 03/04/2020
[2023-10-26 15:05:16][kernel][warning]task: ffff8898016c5b70 ti: ffff88b2b5094000 task.ti: ffff88b2b5094000
[2023-10-26 15:05:16][kernel][warning]RIP: 0010:[<ffffffff81656502>] [<ffffffff81656502>] _raw_spin_lock+0x12/0x50
[2023-10-26 15:05:16][kernel][warning]RSP: 0018:ffff88b2b5097d98 EFLAGS: 00000202
[2023-10-26 15:05:16][kernel][warning]RAX: 0000000000160016 RBX: ffffffff81657696 RCX: 007d44c33c3e3c3e
[2023-10-26 15:05:16][kernel][warning]RDX: 007d44c23c3e3c3e RSI: 00000000007d44c3 RDI: ffff88b0247b67d8
[2023-10-26 15:05:16][kernel][warning]RBP: ffff88b2b5097d98 R08: 0000000000000000 R09: 0000000000000007
[2023-10-26 15:05:16][kernel][warning]R10: ffff88b0247a7bc0 R11: 0000000000000000 R12: ffffffff81657696
[2023-10-26 15:05:16][kernel][warning]R13: ffff88b2b5097d80 R14: ffffffff81657696 R15: ffff88b2b5097d78
[2023-10-26 15:05:16][kernel][warning]FS: 00007ff7d3f4f880(0000) GS:ffff88bd6a340000(0000) knlGS:0000000000000000
[2023-10-26 15:05:16][kernel][warning]CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[2023-10-26 15:05:16][kernel][warning]CR2: 00007fff5286b000 CR3: 0000008177750000 CR4: 00000000003607e0
[2023-10-26 15:05:16][kernel][warning]DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[2023-10-26 15:05:16][kernel][warning]DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[2023-10-26 15:05:16][kernel][warning]Call Trace:
[2023-10-26 15:05:16][kernel][warning][<ffffffff812085df>] shrink_dentry_list+0x4f/0x480
[2023-10-26 15:05:16][kernel][warning][<ffffffff81208a9c>] shrink_dcache_sb+0x8c/0xd0
[2023-10-26 15:05:16][kernel][warning][<ffffffff811f3a7c>] do_remount_sb+0x4c/0x1a0
[2023-10-26 15:05:16][kernel][warning][<ffffffff81212519>] do_mount+0x6a9/0xa40
[2023-10-26 15:05:16][kernel][warning][<ffffffff8117830e>] ? __get_free_pages+0xe/0x50
[2023-10-26 15:05:16][kernel][warning][<ffffffff81212946>] SyS_mount+0x96/0xf0
[2023-10-26 15:05:16][kernel][warning][<ffffffff816600fd>] system_call_fastpath+0x16/0x1b
[2023-10-26 15:05:16][kernel][warning]Code: f6 47 02 01 74 e5 0f 1f 00 e8 a6 17 ff ff eb db 66 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 55 48 89 e5 b8 00 00 02 00 f0 0f c1 07 <89> c2 c1 ea 10 66 39 c2 75 02 5d c3 83 e2 fe 0f b7 f2 b8 00 80
[2023-10-26 15:05:44][kernel][emerg]BUG: soft lockup - CPU#85 stuck for 23s! [mount:105195]

复杂版创建LVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function disk_part(){
set -e
if [ $# -le 1 ]
then
echo "disk_part argument error"
exit -1
fi
action=$1
disk_device_list=(`echo $*`)

echo $disk_device_list
unset disk_device_list[0]

echo $action
echo ${disk_device_list[*]}
len=`echo ${#disk_device_list[@]}`
echo "start remove origin partition "
for dev in ${disk_device_list[@]}
do
#echo ${dev}
`parted -s ${dev} rm 1` || true
dd if=/dev/zero of=${dev} count=100000 bs=512
done
#替换98行,插入的话r改成a
sed -i "98 r\ types = ['aliflash' , 252 , 'nvme' ,252 , 'venice', 252 , 'aocblk', 252]" /etc/lvm/lvm.conf
sed -i "/flash/d" /etc/fstab

if [ x${1} == x"split" ]
then
echo "split disk "
#lvmdiskscan
echo ${disk_device_list}
#vgcreate -s 32 vgpolarx /dev/nvme0n1 /dev/nvme2n1
vgcreate -s 32 vgpolarx ${disk_device_list[*]}
#stripesize 16K 和MySQL pagesize适配
#lvcreate -A y -I 16K -l 100%FREE -i 2 -n polarx vgpolarx
lvcreate -A y -I 16K -l 100%FREE -i ${#disk_device_list[@]} -n polarx vgpolarx
#lvcreate -A y -I 128K -l 75%VG -i ${len} -n volume1 vgpolarx
#lvcreate -A y -I 128K -l 100%FREE -i ${len} -n volume2 vgpolarx
mkfs.ext4 /dev/vgpolarx/polarx -m 0 -O extent,uninit_bg -E lazy_itable_init=1 -q -L polarx -J size=4000
sed -i "/polarx/d" /etc/fstab
mkdir -p /polarx
opt="defaults,noatime,data=writeback,nodiratime,nodelalloc,barrier=0"
echo "LABEL=polarx /polarx ext4 ${opt} 0 0" >> /etc/fstab
mount -a
else
echo "unkonw action "
fi
}

function format_nvme_mysql(){

if [ `df |grep flash|wc -l` -eq $1 ]
then
echo "check success"
echo "start umount partition "
parttion_list=`df |grep flash|awk -F ' ' '{print $1}'`
for partition in ${parttion_list[@]}
do
echo $partition
umount $partition
done
else
echo "check host fail"
exit -1
fi

disk_device_list=(`ls -l /dev/|grep -v ^l|awk '{print $NF}'|grep -E "^nvme[0-9]{1,2}n1$|^df[a-z]$|^os[a-z]$"`)
full_disk_device_list=()
for i in ${!disk_device_list[@]}
do
echo ${i}
full_disk_device_list[${i}]=/dev/${disk_device_list[${i}]}
done
echo ${full_disk_device_list[@]}
disk_part split ${full_disk_device_list[@]}
}

if [ ! -d "/polarx" ]; then
umount /dev/vgpolarx/polarx
vgremove -f vgpolarx
dmsetup --force --retry --deferred remove vgpolarx-polarx
format_nvme_mysql $1
else
echo "the lvm exists."
fi

LVM性能还没有做到多盘并行,也就是性能和单盘差不多,盘数多读写性能也一样

查看 lvcreate 使用的参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#lvs -o +lv_full_name,devices,stripe_size,stripes
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert LV Devices Stripe #Str
drds vg1 -wi-ao---- 5.37t vg1/drds /dev/nvme0n1p1(0) 0 1
drds vg1 -wi-ao---- 5.37t vg1/drds /dev/nvme2n1p1(0) 0 1

# lvs -v --segments
LV VG Attr Start SSize #Str Type Stripe Chunk
polarx vgpolarx -wi-ao---- 0 11.64t 4 striped 128.00k 0

# lvdisplay -m
--- Logical volume ---
LV Path /dev/vgpolarx/polarx
LV Name polarx
VG Name vgpolarx
LV UUID Wszlwf-SCjv-Txkw-9B1t-p82Z-C0Zl-oJopor
LV Write Access read/write
LV Creation host, time ky4, 2022-08-18 15:53:29 +0800
LV Status available
# open 1
LV Size 11.64 TiB
Current LE 381544
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 2048
Block device 254:0

--- Segments ---
Logical extents 0 to 381543:
Type striped
Stripes 4
Stripe size 128.00 KiB
Stripe 0:
Physical volume /dev/nvme1n1
Physical extents 0 to 95385
Stripe 1:
Physical volume /dev/nvme3n1
Physical extents 0 to 95385
Stripe 2:
Physical volume /dev/nvme2n1
Physical extents 0 to 95385
Stripe 3:
Physical volume /dev/nvme0n1
Physical extents 0 to 95385

==要特别注意 stripes 表示多快盘一起用,iops能力累加,但是默认 stripes 是1,也就是只用1块盘,也就是linear==

安装LVM

1
sudo yum install lvm2 -y

dmsetup查看LVM

管理工具dmsetup是 Device mapper in the kernel 中的一个

1
2
dmsetup ls
dmsetup info /dev/dm-0

reboot 失败

在麒麟下OS reboot的时候可能因为mount: /polarx: 找不到 LABEL=/polarx. 导致OS无法启动,可以进入紧急模式,然后注释掉 /etc/fstab 中的polarx 行,再reboot

这是因为LVM的label、uuid丢失了,导致挂载失败。

查看设备的label

1
sudo lsblk -o name,mountpoint,label,size,uuid  or lsblk -f

修复:

紧急模式下修改 /etc/fstab 去掉有问题的挂载; 修改标签

1
2
3
4
5
6
7
8
#blkid   //查询uuid、label
/dev/mapper/klas-root: UUID="c4793d67-867e-4f14-be87-f6713aa7fa36" BLOCK_SIZE="512" TYPE="xfs"
/dev/sda2: UUID="8DCEc5-b4P7-fW0y-mYwR-5YTH-Yf81-rH1CO8" TYPE="LVM2_member" PARTUUID="4ffd9bfa-02"
/dev/nvme0n1: UUID="nJAHxP-d15V-Fvmq-rxa3-GKJg-TCqe-gD1A2Z" TYPE="LVM2_member"
/dev/sda1: UUID="29f59517-91c6-4b3c-bd22-0a47c800d7f4" BLOCK_SIZE="512" TYPE="xfs" PARTUUID="4ffd9bfa-01"
/dev/mapper/vgpolarx-polarx: LABEL="polarx" UUID="025a3ac5-d38a-42f1-80b6-563a55cba12a" BLOCK_SIZE="4096" TYPE="ext4"

e2label /dev/mapper/vgpolarx-polarx polarx

比如,下图右边的是启动失败的

image-20211228185144635

软RAID

mdadm(multiple devices admin)是一个非常有用的管理软raid的工具,可以用它来创建、管理、监控raid设备,当用mdadm来创建磁盘阵列时,可以使用整块独立的磁盘(如/dev/sdb,/dev/sdc),也可以使用特定的分区(/dev/sdb1,/dev/sdc1)

mdadm使用手册

mdadm –create device –level=Y –raid-devices=Z devices
-C | –create /dev/mdn
-l | –level 0|1|4|5
-n | –raid-devices device [..]
-x | –spare-devices device [..]

创建 -l 0表示raid0, -l 10表示raid10

1
2
3
4
5
6
7
8
9
10
mdadm -C /dev/md0 -a yes -l 0 -n2 /dev/nvme{6,7}n1  //raid0
mdadm -D /dev/md0
mkfs.ext4 /dev/md0
mkdir /md0
mount /dev/md0 /md0

//条带
mdadm --create --verbose /dev/md0 --level=linear --raid-devices=2 /dev/sdb /dev/sdc
检查
mdadm -E /dev/nvme[0-5]n1

删除

1
2
umount /md0 
mdadm -S /dev/md0

监控raid

1
2
3
4
5
6
7
8
9
#cat /proc/mdstat
Personalities : [raid0] [raid6] [raid5] [raid4]
md6 : active raid6 nvme3n1[3] nvme2n1[2] nvme1n1[1] nvme0n1[0]
7501211648 blocks super 1.2 level 6, 512k chunk, algorithm 2 [4/4] [UUUU]
[=>...................] resync = 7.4% (280712064/3750605824) finish=388.4min speed=148887K/sec
bitmap: 28/28 pages [112KB], 65536KB chunk //raid6一直在异步刷数据

md0 : active raid0 nvme7n1[3] nvme6n1[2] nvme4n1[0] nvme5n1[1]
15002423296 blocks super 1.2 512k chunks

控制刷盘速度

1
2
3
#sysctl -a |grep raid
dev.raid.speed_limit_max = 0
dev.raid.speed_limit_min = 0

nvme-cli

1
2
3
4
nvme id-ns /dev/nvme1n1 -H
for i in `seq 0 1 2`; do nvme format --lbaf=3 /dev/nvme${i}n1 ; done //格式化,选择不同的扇区大小,默认512,可选4K

fuser -km /data/

raid硬件卡

raid卡外观

image.png

mount 参数对性能的影响

推荐mount参数:defaults,noatime,data=writeback,nodiratime,nodelalloc,barrier=0 这些和 default 0 0 的参数差别不大,但是如果加了lazytime 会在某些场景下性能很差

比如在mysql filesort 场景下就可能触发 find_inode_nowait 热点,MySQL filesort 过程中,对文件的操作时序是 create,open,unlink,write,read,close; 而文件系统的 lazytime 选项,在发现 inode 进行修改了之后,会对同一个 inode table 中的 inode 进行修改,导致 file_inode_nowait 函数中,spin lock 的热点。

所以mount时注意不要有 lazytime

img

img

如果一个SQL 要创建大量临时表,而 /tmp/ 挂在参数有lazytime的话也会导致同样的问题,如图堆栈:

img

对应的内核代码:

image-20231027170529214

image-20231027170707041

另外一个应用,也经常因为find_inode_nowait 热点把CPU 爆掉:

img

image-20231027165103176

lazytime 的问题可以通过代码复现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "pthread.h"
#include "stdio.h"
#include "stdlib.h"
#include <atomic>
#include <string>
typedef unsigned long long ulonglong;
static const char *FILE_PREFIX = "stress";
static const char *FILE_DIR = "/flash4/tmp/";
static std::atomic<ulonglong> f_num(0);
static constexpr size_t THREAD_NUM = 128;
static constexpr ulonglong LOOP = 1000000000000;

void file_op(const char *file_name) {
int f;
char content[1024];
content[0] = 'a';
content[500] = 'b';
content[1023] = 'c';
f = open(file_name, O_RDWR | O_CREAT);
unlink(file_name);
for (ulonglong i = 0; i < 1024 * 16; i++) {
write(f, content, 1024);
}
close(f);
}
void *handle(void *data) {
char file[1024];
ulonglong current_id;
for (ulonglong i = 0; i < LOOP; i++) {
current_id = f_num++;
snprintf(file, 1024, "%s%s_%d.txt", FILE_DIR, FILE_PREFIX, current_id);
file_op(file);
}
}
int main(int argc, char** args) {
for (std::size_t i = 0; i < THREAD_NUM; i++) {
pthread_t tid;
int ret = pthread_create(&tid, NULL, handle, NULL);
}
}

主动;工具、生产效率;面向故障、事件

LVM 异常修复

文件系统损坏,是导致系统启动失败比较常见的原因。文件系统损坏,比较常见的原因是分区丢失和文件系统需要手工修复。

1、分区表丢失,只要重新创建分区表即可。因为分区表信息只涉及变更磁盘上第一个扇区指定位置的内容。所以只要确认有分区情况,在分区表丢失的情况下,重做分区是不会损坏磁盘上的数据的。但是分区起始位置和尺寸需要正确 。起始位置确定后,使用fdisk重新分区即可。所以,问题的关键是如何确定分区的开始位置。

确定分区起始位置:

MBR(Master Boot Record)是指磁盘第一块扇区上的一种数据结构,512字节,磁盘分区数据是MBR的一部分,可以使用通过dd if=/dev/vdc bs=512 count=1 | hexdump -C 以16进制列出扇区0的裸数据:

image-20240118103305675

可以看出磁盘的分区类型ID、分区起始扇区和分区包含扇区数量,通过这几个数值可以确定分区位置。后面LVM可以通过LABELONE计算出起始位置。

参考资料

https://www.tecmint.com/manage-and-create-lvm-parition-using-vgcreate-lvcreate-and-lvextend/

pvcreate error : Can’t open /dev/sdx exclusively. Mounted filesystem?

软RAID配置方法参考这里

Linux LVS 配置

Linux LVS 配置

NAT

  • Enable IP forwarding. This can be done by adding the following to

    1
    net.ipv4.ip_forward = 1 

then

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ipvsadm -A -t 172.26.137.117:9376 -s rr //创建了一个rr lvs
// -m 表示nat模式,不加的话默认是route模式
ipvsadm -a -t 172.26.137.117:9376 -r 172.20.22.195:9376 -m //往lvs中添加一个RS
ipvsadm -ln
ipvsadm -a -t 172.26.137.117:9376 -r 172.20.22.196:9376 -m //往lvs中添加另外一个RS
ipvsadm -ln

//删除realserver
ipvsadm -d -t 100.81.131.221:18507 -r 100.81.131.237:8507 -m

//连接状态查看
#ipvsadm -L -n --connection
IPVS connection entries
pro expire state source virtual destination
TCP 15:00 ESTABLISHED 127.0.0.1:40630 127.0.0.1:3001 127.0.0.1:3306
TCP 14:59 ESTABLISHED 127.0.0.1:40596 127.0.0.1:3001 127.0.0.1:3306
TCP 14:59 ESTABLISHED 127.0.0.1:40614 127.0.0.1:3001 127.0.0.1:3307
TCP 15:00 ESTABLISHED 127.0.0.1:40598 127.0.0.1:3001 127.0.0.1:3307

#流量统计
ipvsadm -L -n --stats -t 192.168.1.10:28080 //-t service-address
Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes
-> RemoteAddress:Port
TCP 192.168.1.10:28080 39835 1030M 863494K 150G 203G
-> 172.20.62.78:3306 774 46173852 38899725 6575M 9250M
-> 172.20.78.79:3306 781 45106566 37997254 6421M 9038M
-> 172.20.81.80:3306 783 45531236 38387112 6479M 9128M

#清空统计数据
#ipvsadm --zero
#列出所有连接信息
#/sbin/ipvsadm -L -n --connection

#ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 11.197.140.20:18089 wlc
-> 11.197.140.20:28089 Masq 1 0 0
-> 11.197.141.110:28089 Masq 1 0 0

ipvsadm常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
添加虚拟服务器
语法:ipvsadm -A [-t|u|f] [vip_addr:port] [-s:指定算法]
-A:添加
-t:TCP协议
-u:UDP协议
-f:防火墙标记
-D:删除虚拟服务器记录
-E:修改虚拟服务器记录
-C:清空所有记录
-L:查看
添加后端RealServer
语法:ipvsadm -a [-t|u|f] [vip_addr:port] [-r ip_addr] [-g|i|m] [-w 指定权重]
-a:添加
-t:TCP协议
-u:UDP协议
-f:防火墙标记
-r:指定后端realserver的IP
-g:DR模式
-i:TUN模式
-m:NAT模式
-w:指定权重
-d:删除realserver记录
-e:修改realserver记录
-l:查看
通用:
ipvsadm -ln:查看规则
service ipvsadm save:保存规则

查看连接对应的RS ip和端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ipvsadm -Lcn |grep "10.68.128.202:1406"
TCP 15:01 ESTABLISHED 10.68.128.202:1406 10.68.128.202:3306 172.20.188.72:3306

# ipvsadm -Lcn | head -10
IPVS connection entries
pro expire state source virtual destination
TCP 15:01 ESTABLISHED 10.68.128.202:1390 10.68.128.202:3306 172.20.185.132:3306
TCP 15:01 ESTABLISHED 10.68.128.202:1222 10.68.128.202:3306 172.20.165.202:3306
TCP 15:01 ESTABLISHED 10.68.128.202:1252 10.68.128.202:3306 172.20.222.65:3306
TCP 15:01 ESTABLISHED 10.68.128.202:1328 10.68.128.202:3306 172.20.149.68:3306

ipvsadm -Lcn
IPVS connection entries
pro expire state source virtual destination
TCP 00:57 NONE 110.184.96.173:0 122.225.32.142:80 122.225.32.136:80
TCP 01:57 FIN_WAIT 110.184.96.173:54568 122.225.32.142:80 122.225.32.136:80

当一个client访问vip的时候,ipvs或记录一条状态为NONE的信息,expire初始值是persistence_timeout的值,然后根据时钟主键变小,在以下记录存在期间,同一client ip连接上来,都会被分配到同一个后端。

FIN_WAIT的值就是tcp tcpfin udp的超时时间,当NONE的值为0时,如果FIN_WAIT还存在,那么NONE的值会从新变成60秒,再减少,直到FIN_WAIT消失以后,NONE才会消失,只要NONE存在,同一client的访问,都会分配到统一real server。

通过keepalived来检测RealServer的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# cat /etc/keepalived/keepalived.conf
global_defs {
notification_email {
}
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}
#添加虚拟服务器
#相当于 ipvsadm -A -t 172.26.137.117:9376 -s wrr
virtual_server 172.26.137.117 9376 {
delay_loop 3 #服务健康检查周期,单位是秒
lb_algo wrr #调度算法
lb_kind NAT #模式
# persistence_timeout 50 #会话保持时间,单位是秒
protocol TCP #TCP协议转发

#添加后端realserver
#相当于 ipvsadm -a -t 172.26.137.117:9376 -r 172.20.56.148:9376 -w 1
real_server 172.20.56.148 9376 {
weight 1
TCP_CHECK { # 通过TcpCheck判断RealServer的健康状态
connect_timeout 2 # 连接超时时间
nb_get_retry 3 # 重连次数
delay_before_retry 1 # 重连时间间隔
connect_port 9376 # 检测端口
}
}

real_server 172.20.248.147 9376 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 3
nb_get_retry 3
delay_before_retry 3
}
}
}

修改keepalived配置后只需要执行reload即可生效

systemctl reload keepalived

timeout

LVS的持续时间有2个

  1. 把同一个cip发来请求到同一台RS的持久超时时间。(-p persistent)
  2. 一个链接创建后空闲时的超时时间,这个超时时间分为3种。
    • tcp的空闲超时时间。
    • lvs收到客户端tcp fin的超时时间
    • udp的超时时间

连接空闲超时时间的设置如下:

1
2
3
4
5
6
7
[root@poc117 ~]# ipvsadm -L --timeout
Timeout (tcp tcpfin udp): 900 120 300
[root@poc117 ~]# ipvsadm --set 1 2 1
[root@poc117 ~]# ipvsadm -L --timeout
Timeout (tcp tcpfin udp): 1 2 1

ipvsadm -Lcn //查看

persistence_timeout

用于保证同一ip client的所有连接在timeout时间以内都发往同一个RS,比如ftp 21port listen认证、20 port传输数据,那么希望同一个client的两个连接都在同一个RS上。

persistence_timeout 会导致负载不均衡,timeout时间越大负载不均衡越严重。大多场景下基本没什么意义

PCC用来实现把某个用户的所有访问在超时时间内定向到同一台REALSERVER,这种方式在实际中不常用

1
2
3
ipvsadm -A -t 192.168.0.1:0 -s wlc -p 600(单位是s)     //port为0表示所有端口
ipvsadm -a -t 192.168.0.1:0 -r 192.168.1.2 -w 4 -g
ipvsadm -a -t 192.168.0.1:0 -r 192.168.1.3 -w 2 -g

此时测试一下会发现通过HTTP访问VIP和通过SSH登录VIP的时候都被定向到了同一台REALSERVER上面了

lvs 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
257  [2021-09-13 22:11:26] lscpu
258 [2021-09-13 22:11:34] dmidecode | grep Ser
259 [2021-09-13 22:11:53] dmidecode | grep FT
260 [2021-09-13 22:11:58] dmidecode | grep 2500
261 [2021-09-13 22:12:03] dmidecode
262 [2021-09-13 22:12:27] lscpu
263 [2021-09-13 22:12:37] ipvsadm -ln
264 [2021-09-13 22:12:59] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537
265 [2021-09-13 22:14:37] base_admin --help
266 [2021-09-13 22:14:44] base_admin --cpu-usage
267 [2021-09-13 22:14:56] ip link
268 [2021-09-13 22:16:04] base_admin --cpu-usage
269 [2021-09-13 22:16:28] cat /usr/local/etc/nf-var-config
270 [2021-09-13 22:16:43] base_admin --cpu-usage
271 [2021-09-13 22:17:35] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537
272 [2021-09-13 22:18:17] base_admin --cpu-usage
273 [2021-09-13 22:22:02] ls
274 [2021-09-13 22:22:06] ps -aux
275 [2021-09-13 22:22:17] tsar --help
276 [2021-09-13 22:22:24] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537
277 [2021-09-13 22:22:31] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stat
278 [2021-09-13 22:22:33] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats
279 [2021-09-13 22:23:10] tsar --lvs -li1 -D|awk '{print $1," ",($6)*8.0}'
280 [2021-09-13 22:24:29] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats
281 [2021-09-13 22:25:26] tsar --lvs -li1 -D
282 [2021-09-13 22:25:46] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537
283 [2021-09-13 22:26:37] appctl -cas | grep conns
284 [2021-09-13 22:31:16] ipvsadm -ln
286 [2021-09-13 22:31:43] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats
292 [2021-09-13 22:38:16] rpm -qa | grep slb
293 [2021-09-13 22:42:30] appctl -cas | grep conns
294 [2021-09-13 22:43:03] base_admin --cpu-usage
295 [2021-09-13 22:45:42] tsar --lvs -li1 -D|awk '{print $1," ",($6)*8.0}'
296 [2021-09-13 22:57:20] base_admin --cpu-usage
297 [2021-09-13 22:58:16] tsar --lvs -li1 -D|awk '{print $1," ",($6)*8.0}'
298 [2021-09-13 22:59:38] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats
299 [2021-09-13 23:00:16] appctl -a | grep conn
300 [2021-09-13 23:00:24] base_admin --cpu-usage
301 [2021-09-13 23:00:50] appctl -cas | grep conns
302 [2021-09-13 23:01:15] base_admin --cpu-usage
303 [2021-09-13 23:01:21] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats
304 [2021-09-13 23:02:09] appctl -cas | grep conns
305 [2021-09-13 23:03:12] base_admin --cpu-usage
306 [2021-09-13 23:04:43] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats | head -3
307 [2021-09-13 23:05:38] base_admin --cpu-usage
308 [2021-09-13 23:06:10] tsar --lvs -li1 -D|awk '{print $1," ",($6)*8.0}'
309 [2021-09-13 23:06:39] base_admin --cpu-usage
310 [2021-09-13 23:15:59] appctl -a | grep conn_limit_enable
311 [2021-09-13 23:15:59] appctl -a | grep cps_limit_enable
312 [2021-09-13 23:15:59] appctl -a | grep inbps_limit_enable
313 [2021-09-13 23:15:59] appctl -a | grep outbps_limit_enable
314 [2021-09-13 23:17:13] appctl -w conn_limit_enable=0
315 [2021-09-13 23:17:13] appctl -w cps_limit_enable=0
316 [2021-09-13 23:17:13] appctl -w inbps_limit_enable=0
317 [2021-09-13 23:17:13] appctl -w outbps_limit_enable=0
318 [2021-09-13 23:17:43] appctl -cas | grep conn
319 [2021-09-13 23:17:44] appctl -cas | grep conns
320 [2021-09-13 23:19:30] last=0;while true;do pre=`ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats| grep TCP|awk '{print $4}'`;let cut=pre-last;echo $cut;last=$pre;sleep 1;done
321 [2021-09-13 23:19:56] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats| grep TCP|awk '{print $4}'
322 [2021-09-13 23:20:01] last=0;while true;do pre=`ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats| grep TCP|awk '{print $4}'`;let cut=pre-last;echo $cut;last=$pre;sleep 1;done
323 [2021-09-13 23:20:55] base_admin --cpu-usage
324 [2021-09-13 23:22:05] ipvsadm -lnvt 166.100.129.249:3306 --in-vid 1560537
325 [2021-09-13 23:22:05] ipvsadm -lnvt 166.100.128.219:3306 --in-vid 1560537
326 [2021-09-13 23:22:05] ipvsadm -lnvt 166.100.129.40:80 --in-vid 1560537
327 [2021-09-13 23:24:22] base_admin --cpu-usage
328 [2021-09-13 23:24:29] last=0;while true;do pre=`ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 --stats| grep TCP|awk '{print $4}'`;let cut=pre-last;echo $cut;last=$pre;sleep 1;done
329 [2021-09-13 23:24:50] ipvsadm -lnvt 166.100.129.249:3306 --in-vid 1560537
332 [2021-09-13 23:25:38] ipvsadm -lnvt 166.100.128.234:3306 --in-vid 1560537 —stats
333 [2021-09-13 23:25:57] ipvsadm -lnvt 166.100.129.249:3306 --in-vid 1560537 --stats
334 [2021-09-13 23:25:58] ipvsadm -lnvt 166.100.128.219:3306 --in-vid 1560537 --stats
335 [2021-09-13 23:25:58] ipvsadm -lnvt 166.100.129.40:80 --in-vid 1560537 --stats
336 [2021-09-13 23:26:45] last=0;while true;do pre=`ipvsadm -lnvt 166.100.129.40:80 --in-vid 1560537 --stats| grep TCP|awk '{print $4}'`;let cut=pre-last;echo $cut;last=$pre;sleep 1;done

LVS 工作原理

1.当客户端的请求到达负载均衡器的内核空间时,首先会到达PREROUTING链。

2.当内核发现请求数据包的目的地址是本机时,将数据包送往INPUT链。

3.LVS由用户空间的ipvsadm和内核空间的IPVS组成,ipvsadm用来定义规则,IPVS利用ipvsadm定义的规则工作,IPVS工作在INPUT链上,当数据包到达INPUT链时,首先会被IPVS检查,如果数据包里面的目的地址及端口没有在规则里面,那么这条数据包将被放行至用户空间。

4.如果数据包里面的目的地址及端口在规则里面,那么这条数据报文将被修改目的地址为事先定义好的后端服务器,并送往POSTROUTING链。

5.最后经由POSTROUTING链发往后端服务器。

image.png

netfilter 原理

Netfilter 由多个表(table)组成,每个表又由多个链(chain)组成(此处可以脑补二维数组的矩阵了),链是存放过滤规则的“容器”,里面可以存放一个或多个iptables命令设置的过滤规则。目前的表有4个:raw table, mangle table, nat table, filter table。Netfilter 默认的链有:INPUT, OUTPUT, FORWARD, PREROUTING, POSTROUTING,根据的不同功能需求,不同的表下面会有不同的链,链与表的关系可用下图直观表示:

image.png

OSPF + LVS

OSPF:Open Shortest Path First 开放最短路径优先,SPF算法也被称为Dijkstra算法,这是因为最短路径优先算法SPF是由荷兰计算机科学家狄克斯特拉于1959年提出的。

通过OSPF来替换keepalived,解决两个LVS节点的高可用,以及流量负载问题。keepalived两个节点只能是master-slave模式,而OSPF两个节点都是master,同时都有流量

img

这个架构与LVS+keepalived 最明显的区别在于,两台Director都是Master 状态,而不是Master-Backup,如此一来,两台Director 地位就平等了。剩下的问题,就是看如何在这两台Director 间实现负载均衡了。这里会涉及路由器领域的一个概念:等价多路径

ECMP(等价多路径)

ECMP(Equal-CostMultipathRouting)等价多路径,存在多条不同链路到达同一目的地址的网络环境中,如果使用传统的路由技术,发往该目的地址的数据包只能利用其中的一条链路,其它链路处于备份状态或无效状态,并且在动态路由环境下相互的切换需要一定时间,而等值多路径路由协议可以在该网络环境下同时使用多条链路,不仅增加了传输带宽,并且可以无时延无丢包地备份失效链路的数据传输。

ECMP最大的特点是实现了等值情况下,多路径负载均衡和链路备份的目的,在静态路由和OSPF中基本上都支持ECMP功能。

参考资料

http://www.ultramonkey.org/papers/lvs_tutorial/html/

https://www.jianshu.com/p/d4222ce9b032

https://www.cnblogs.com/zhangxingeng/p/10595058.html

lvs持久性工作原理和配置

10+倍性能提升全过程

10+倍性能提升全过程–优酷账号绑定淘宝账号的TPS从500到5400的优化历程

背景说明

2016年的双11在淘宝上买买买的时候,天猫和优酷土豆一起做了联合促销,在天猫双11当天购物满XXX元就赠送优酷会员,这个过程需要用户在优酷侧绑定淘宝账号(登录优酷、提供淘宝账号,优酷调用淘宝API实现两个账号绑定)和赠送会员并让会员权益生效(看收费影片、免广告等等)

这里涉及到优酷的两个部门:Passport(在上海,负责登录、绑定账号,下文中的优化过程主要是Passport部分);会员(在北京,负责赠送会员,保证权益生效)

在双11活动之前,Passport的绑定账号功能一直在运行,只是没有碰到过大促销带来的挑战


整个过程分为两大块:

  1. 整个系统级别,包括网络和依赖服务的性能等,多从整个系统视角分析问题;
  2. 但服务器内部的优化过程,将CPU从si/sy围赶us,然后在us从代码级别一举全歼。

系统级别都是最容易被忽视但是成效最明显的,代码层面都是很细致的力气活。

整个过程都是在对业务和架构不是非常了解的情况下做出的。

会员部分的架构改造

  • 接入中间件DRDS,让优酷的数据库支持拆分,分解MySQL压力
  • 接入中间件vipserver来支持负载均衡
  • 接入集团DRC来保障数据的高可用
  • 对业务进行改造支持Amazon的全链路压测

主要的压测过程

screenshot.png

上图是压测过程中主要的阶段中问题和改进,主要的问题和优化过程如下:

- docker bridge网络性能问题和网络中断si不均衡    (优化后:500->1000TPS)
- 短连接导致的local port不够                   (优化后:1000-3000TPS)
- 生产环境snat单核导致的网络延时增大             (优化后生产环境能达到测试环境的3000TPS)
- Spring MVC Path带来的过高的CPU消耗           (优化后:3000->4200TPS)
- 其他业务代码的优化(比如异常、agent等)          (优化后:4200->5400TPS)

优化过程中碰到的比如淘宝api调用次数限流等一些业务原因就不列出来了


概述

由于用户进来后先要登录并且绑定账号,实际压力先到Passport部分,在这个过程中最开始单机TPS只能到500,经过N轮优化后基本能达到5400 TPS,下面主要是阐述这个优化过程

Passport部分的压力

Passport 核心服务分两个:

  • Login 主要处理登录请求
  • userservice 处理登录后的业务逻辑,比如将优酷账号和淘宝账号绑定

为了更好地利用资源每台物理加上部署三个docker 容器,跑在不同的端口上(8081、8082、8083),通过bridge网络来互相通讯

Passport机器大致结构

screenshot.png

userservice服务网络相关的各种问题


太多SocketConnect异常(如上图)

在userservice机器上通过netstat也能看到大量的SYN_SENT状态,如下图:
image.png

因为docker bridge通过nat来实现,尝试去掉docker,让tomcat直接跑在物理机上

这时SocketConnect异常不再出现
image.png

从新梳理一下网络流程

docker(bridge)—-短连接—>访问淘宝API(淘宝open api只能短连接访问),性能差,cpu都花在si上;

如果 docker(bridge)—-长连接到宿主机的某个代理上(比如haproxy)—–短连接—>访问淘宝API, 性能就能好一点。问题可能是短连接放大了Docker bridge网络的性能损耗

当时看到的cpu si非常高,截图如下:

image.png

去掉Docker后,性能有所提升,继续通过perf top看到内核态寻找可用的Local Port消耗了比较多的CPU,gif动态截图如下(可以点击看高清大图):

perf-top-netLocalPort-issue.gif

注意图中ipv6_rcv_saddr_equal和inet_csk_get_port 总共占了30%的CPU (系统态的CPU使用率高意味着共享资源有竞争或者I/O设备之间有大量的交互。)

一般来说一台机器默认配置的可用 Local Port 3万多个,如果是短连接的话,一个连接释放后默认需要60秒回收,30000/60 =500 这是大概的理论TPS值【这里只考虑连同一个server IP:port 的时候】

这500的tps算是一个老中医的经验。不过有些系统调整过Local Port取值范围,比如从1024到65534,那么这个tps上限就是1000附近。

同时观察这个时候CPU的主要花在sy上,最理想肯定是希望CPU主要用在us上,截图如下:
image.png

规则:性能优化要先把CPU从SI、SY上的消耗赶到US上去(通过架构、系统配置);然后提升 US CPU的效率(代码级别的优化)

sy占用了30-50%的CPU,这太不科学了,同时通过 netstat 分析连接状态,确实看到很多TIME_WAIT:
localportissue-time-wait.png

cpu要花在us上,这部分才是我们代码吃掉的

于是让PE修改了tcp相关参数:降低 tcp_max_tw_buckets和开启tcp_tw_reuse,这个时候TPS能从1000提升到3000

到这里总结下:因为短链接导致端口不够,进而使得内核在循环搜索可用端口的内核态

鼓掌,赶紧休息,迎接双11啊

image.png

测试环境优化到3000 TPS后上线继续压测

居然性能又回到了500,太沮丧了,其实最开始账号绑定慢,Passport这边就怀疑taobao api是不是在大压力下不稳定,一般都是认为自己没问题,有问题的一定是对方。我不觉得这有什么问题,要是知道自己有什么问题不早就优化掉了,但是这里缺乏证据支撑,也就是如果你觉得自己没有问题或者问题在对方,一定要拿出证据来(有证据那么大家可以就证据来讨论,而不是互相苍白地推诿)。

这个时候Passport更加理直气壮啊,好不容易在测试环境优化到3000,怎么一调taobao api就掉到500呢,这么点压力你们就扛不住啊。 但是taobao api那边给出调用数据都是1ms以内就返回了(alimonitor监控图表–拿证据说话)。

看到alimonitor给出的api响应时间图表后,我开始怀疑从优酷的机器到淘宝的机器中间链路上有瓶颈,但是需要设计方案来证明这个问题在链路上,要不各个环节都会认为自己没有问题的,问题就会卡死。但是当时Passport的开发也只能拿到Login和Userservice这两组机器的权限,中间的负载均衡、交换机都没有权限接触到。

在没有证据的情况下,肯定机房、PE配合你排查的欲望基本是没有的(被坑过很多回啊,你说我的问题,结果几天配合排查下来发现还是你程序的问题,凭什么我要每次都陪你玩?),所以我要给出证明问题出现在网络链路上,然后拿着这个证据跟网络的同学一起排查。

讲到这里我禁不住要插一句,在出现问题的时候,都认为自己没有问题这是正常反应,毕竟程序是看不见的,好多意料之外逻辑考虑不周全也是常见的,出现问题按照自己的逻辑自查的时候还是没有跳出之前的逻辑所以发现不了问题。但是好的程序员在问题的前面会尝试用各种手段去证明问题在哪里,而不是复读机一样我的逻辑是这样的,不可能出问题的。即使目的是证明问题在对方,只要能给出明确的证据都是负责任的,拿着证据才能理直气壮地说自己没有问题和干净地甩锅。

在尝试过tcpdump抓包、ping等各种手段分析后,设计了场景证明问题在中间链路上。

设计如下三个场景证明问题在中间链路上:

  1. 压测的时候在userservice ping 依赖服务的机器;
  2. 将一台userservice机器从负载均衡上拿下来(没有压力),ping 依赖服务的机器;
  3. 从公网上非我们机房的机器 ping 依赖服务的机器;

这个时候奇怪的事情发现了,压力一上来场景1、2的两台机器ping淘宝的rt都从30ms上升到100-150ms,场景1 的rt上升可以理解,但是场景2的rt上升不应该,同时场景3中ping淘宝在压力测试的情况下rt一直很稳定(说明压力下淘宝的机器没有问题),到此确认问题在优酷到淘宝机房的链路上有瓶颈,而且问题在优酷机房出口扛不住这么大的压力。于是从上海Passport的团队找到北京Passport的PE团队,确认在优酷调用taobao api的出口上使用了snat,PE到snat机器上看到snat只能使用单核,而且对应的核早就100%的CPU了,因为之前一直没有这么大的压力所以这个问题一直存在只是没有被发现。

于是PE去掉snat,再压的话 TPS稳定在3000左右


到这里结束了吗? 从3000到5400TPS

优化到3000TPS的整个过程没有修改业务代码,只是通过修改系统配置、结构非常有效地把TPS提升了6倍,对于优化来说这个过程是最轻松,性价比也是非常高的。实际到这个时候也临近双11封网了,最终通过计算(机器数量*单机TPS)完全可以抗住双11的压力,所以最终双11运行的版本就是这样的。 但是有工匠精神的工程师是不会轻易放过这么好的优化场景和环境的(基线、机器、代码、工具都具备配套好了)

优化完环境问题后,3000TPS能把CPU US跑上去,于是再对业务代码进行优化也是可行的了

进一步挖掘代码中的优化空间

双11前的这段封网其实是比较无聊的,于是和Passport的开发同学们一起挖掘代码中的可以优化的部分。这个过程中使用到的主要工具是这三个:火焰图、perf、perf-map-java。相关链接:http://www.brendangregg.com/perf.html ; https://github.com/jrudolph/perf-map-agent

通过Perf发现的一个SpringMVC 的性能问题

这个问题具体参考我之前发表的优化文章。 主要是通过火焰图发现spring mapping path消耗了过多CPU的性能问题,CPU热点都在methodMapping相关部分,于是修改代码去掉spring中的methodMapping解析后性能提升了40%,TPS能从3000提升到4200.

著名的fillInStackTrace导致的性能问题

代码中的第二个问题是我们程序中很多异常(fillInStackTrace),实际业务上没有这么多错误,应该是一些不重要的异常,不会影响结果,但是异常频率很高,对这种我们可以找到触发的地方,catch住,然后不要抛出去(也就是别触发fillInStackTrace),打印一行error日志就行,这块也能省出10%的CPU,对应到TPS也有几百的提升。

screenshot.png

部分触发fillInStackTrace的场景和具体代码行(点击看高清大图):
screenshot.png

对应的火焰图(点击看高清大图):
screenshot.png

screenshot.png

解析useragent 代码部分的性能问题

整个useragent调用堆栈和cpu占用情况,做了个汇总(useragent不启用TPS能从4700提升到5400)
screenshot.png

实际火焰图中比较分散:
screenshot.png

最终通过对代码的优化勉勉强强将TPS从3000提升到了5400(太不容易了,改代码过程太辛苦,不如改配置来得快)

优化代码后压测tps可以跑到5400,截图:

image.png

最后再次总结整个压测过程的问题和优化历程

- docker bridge网络性能问题和网络中断si不均衡    (优化后:500->1000TPS)
- 短连接导致的local port不够                   (优化后:1000-3000TPS)
- 生产环境snat单核导致的网络延时增大             (优化后能达到测试环境的3000TPS)
- Spring MVC Path带来的过高的CPU消耗           (优化后:3000->4200TPS)
- 其他业务代码的优化(比如异常、agent等)         (优化后:4200->5400TPS)

image.png

通过tcpdump对Unix Domain Socket 进行抓包解析

通过tcpdump对Unix domain Socket 进行抓包解析

背景介绍

大多时候我们可以通过tcpdump对网络抓包分析请求、响应数据来排查问题。但是如果程序是通过Unix Domain Socket方式来访问的那么tcpdump就看不到Unix Domain Socket里面具体流淌的内容了,本文希望找到一种方法达到如同抓包查看网卡内容一样来抓包查看Unix Domain Socket上具体的请求、响应数据。

socat工具

类似nc,但是是个超级增强版的nc,主要用作两个独立数据通道之间的双向数据传输的继电器(或者说代理)

基本原理,通过socat在Unix-Socket和TCP/UDP port之间建立一个代理,然后对代理上的端口进行抓包。

以下案例通过对 docker.sock 抓包来分析方案。大多时候我们都可以通过curl 来将http post请求发送到docker deamon所监听的端口,这些请求和响应都可以通过tcpdump抓包分析得到。但是我们通过 docker ps / docker run 将命令发给本地 docker-deamon的时候就是将请求翻译成 http请求发给了 docker.sock, 这个时候如果需要排查问题就没法用tcpdump来分析http内容了。

通过socat 启动一个tcp端口来代理Unix Domain Socket

启动本地8080端口,将docker.sock映射到8080端口,8080收到的东西都会转给docker.sock,docker.sock收到的东西都通过抓8080的包看到,但是要求应用访问8080而不是docker.sock。

socat -d -d TCP-LISTEN:8080,fork,bind=127.0.0.1 UNIX:/var/run/docker.sock

缺点:需要修改客户端的访问方式

sudo curl --unix-socket /var/run/docker.sock http://localhost/images/json

上面的访问方式对8080抓包还是抓不到,因为绕过了我们的代理。

只能通过如下方式访问8080端口,然后请求通过socat代理转发给docker.sock,整个结果跟访问–unix-socket是一样的,这个时候通过8080端口抓包能看到–unix-socket的工作数据

sudo curl http://localhost:8080/images/json

通过socat启动另外一个Unix Domain Socket代理,但是不是tcpdump抓包

sudo mv /var/run/docker.sock /var/run/docker.sock.original
sudo socat -t100 -d -x -v UNIX-LISTEN:/var/run/docker.sock,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock.original

优点:客户端访问方式不变,还是直接访问–unix-socket
缺点:输出的数据不如tcpdump方便,也就不能用wireshark来分析了

本质也还是socat代理,只是不是用的一个tcp端口来代理了,而是通过一个unix-socet代理了另外一个unix-socket,直接在代理上输出所有收发的数据

完美的办法,客户端不用改访问方式,tcpdump也能抓到数据

sudo mv /var/run/docker.sock /var/run/docker.sock.original
sudo socat TCP-LISTEN:8089,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock.original
sudo socat UNIX-LISTEN:/var/run/docker.sock,fork TCP-CONNECT:127.0.0.1:8089

然后客户端还是直接访问–unix-socket
sudo curl –unix-socket /var/run/docker.sock http://localhost/images/json

这个时候通过tcpdump在8089端口上就能抓到数据了

sudo tcpdump -i lo -netvv port 8089

实际是结合前面两种方法,做了两次代理,先将socket映射到8089端口上,然后再将8089端口映射到一个新的socket上,最后client访问这个新的socket。

实际流程如下: client -> 新socket -> 8089 -> 原来的socket 这个时候对8089可以任意抓包了

参考来源:https://mivehind.net/2018/04/20/sniffing-unix-domain-sockets/

一些socat的其它用法

把监听在远程主机12.34.56.78上的mysql服务Unix Domain Socket映射到本地的/var/run/mysqld.temp.sock, 这样就可以用mysql -S /var/run/mysqld/mysqld.sock来访问远程主机的mysql服务了。

socat "UNIX-LISTEN:/var/run/mysqld.temp.sock,reuseaddr,fork" EXEC:"ssh root@12.34.56.78 socat STDIO UNIX-CONNECT:/var/run/mysqld/mysqld.sock"

还可以用下面的命令把12.34.56.78上的mysql映射到本地的5500端口,然后使用mysql -p 5500命令访问。

socat TCP-LISTEN:5500 EXEC:'ssh root@12.34.56.78 "socat STDIO UNIX-CONNECT:/var/run/mysqld/mysqld.sock"'

把12.34.56.78的udp 161端口映射到本地的1611端口

socat udp-listen:1611 system:'ssh root@12.34.56.78 "socat stdio udp-connect:remotetarget:161"'	

通过socat启动server,带有各种参数,比nc更灵活

Server: socat -dd tcp-listen:2000,keepalive,keepidle=10,keepcnt=2,reuseaddr,keepintvl=1 -
Client: socat -dd - tcp:localhost:2000,keepalive,keepidle=10,keepcnt=2,keepintvl=1

Drop Connection (Unplug Cable, Shut down Link(WiFi/Interface)): sudo iptables -A INPUT -p tcp --dport 2000 -j DROP

启动本地8080端口,将docker.sock映射到8080端口(docker.sock收到的东西都通过抓8080的包看到)。 8080收到的东西都会转给docker.sock

socat -d -d TCP-LISTEN:8080,fork,bind=99.13.252.208 UNIX:/var/run/docker.sock

用socat远程Unix Domain Socket映射

除了将我们本地服务通过端口映射提供给其它人访问,我们还可以通过端口转发玩一些更high的。比如下面这条命令,它把监听在远程主机12.34.56.78上的mysql服务Unix Domain Socket映射到本地的/var/run/mysqld.temp.sock,这样,小明就可以用mysql -S /var/run/mysqld/mysqld.temp.sock来访问远程主机的mysql服务了。

socat "UNIX-LISTEN:/var/run/mysqld.temp.sock,reuseaddr,fork" EXEC:"ssh root@12.34.56.78 socat STDIO UNIX-CONNECT\:/var/run/mysqld/mysqld.sock"

当然,小明如果不喜欢本地Unix Domain Socket,他还可以用下面的命令把12.34.56.78上的mysql映射到本地的5500端口,然后使用mysql -p 5500命令访问。

socat TCP-LISTEN:5500 EXEC:'ssh root@12.34.56.78 "socat STDIO UNIX-CONNECT:/var/run/mysqld/mysqld.sock"'

# 把监听在远程主机12.34.56.78上的mysql服务Unix Domain Socket映射到本地的/var/run/mysqld.temp.sock, 这样就可以用mysql -S /var/run/mysqld/mysqld.sock来访问远程主机的mysql服务了。
socat "UNIX-LISTEN:/var/run/mysqld.temp.sock,reuseaddr,fork" EXEC:"ssh root@12.34.56.78 socat STDIO UNIX-CONNECT:/var/run/mysqld/mysqld.sock"
# 还可以用下面的命令把12.34.56.78上的mysql映射到本地
# 的5500端口,然后使用mysql -p 5500命令访问。
socat TCP-LISTEN:5500 EXEC:'ssh root@12.34.56.78 "socat STDIO UNIX-CONNECT:/var/run/mysqld/mysqld.sock"'
# 把12.34.56.78的udp 161端口映射到本地的1611端口:
socat udp-listen:1611 system:'ssh root@12.34.56.78 "socat stdio udp-connect:remotetarget:161"'

socat启动网络服务

在一个窗口中启动 socat 作为服务端,监听在 1000 端口:

1
2
# start a TCP listener at port 1000, and echo back the received data
$ sudo socat TCP4-LISTEN:1000,fork exec:cat

另一个窗口用 nc 作为客户端来访问服务端,建立 socket:

1
2
# connect to the local TCP listener at port 1000
$ nc localhost 1000

curl 7.57版本可以直接访问 –unix-socket

7.57之后的版本才支持curl –unix-socket,大大方便了我们的测试

//Leave 测试断开一个网络
curl -H "Content-Type: application/json" -X POST -d '{"NetworkID":"47866b0071e3df7e8053b9c8e499986dfe5c9c4947012db2d963c66ca971ed4b","EndpointID":"3d716436e629701d3ce8650e7a85c133b0ff536aed173c624e4f62a381656862"}' --unix-socket /run/docker/plugins/vlan.sock http://localhost/NetworkDriver.Leave

//取镜像列表
sudo curl --unix-socket /var/run/docker.sock http://localhost/images/json

curl 11.239.155.97:2376/debug/pprof/goroutine?debug=2
echo -e "GET /debug/pprof/goroutine?debug=2 HTTP/1.1\r\n" | sudo nc -U /run/docker/plugins/vlan.sock
echo -e "GET /debug/pprof/goroutine?debug=2 HTTP/1.1\r\n" | sudo nc -U /var/run/docker.sock
//升级curl到7.57后支持 --unix-socket
sudo curl --unix-socket /var/run/docker.sock http://localh卡路里ost/images/json
sudo curl --unix-socket /run/docker/plugins/vlan.sock http://localhost/NetworkDriver.GetCapabilities
//Leave
curl -H "Content-Type: application/json" -X POST -d '{"NetworkID":"47866b0071e3df7e8053b9c8e499986dfe5c9c4947012db2d963c66ca971ed4b","EndpointID":"3d716436e629701d3ce8650e7a85c133b0ff536aed173c624e4f62a381656862"}' --unix-socket /run/docker/plugins/vlan.sock http://localhost/NetworkDriver.Leave

sudo curl --no-buffer -XGET --unix-socket /var/run/docker.sock http://localhost/events

Unix Domain Socket工作原理

接收connect 请求的时候,会申请一个新 socket 给 server 端将来使用,和自己的 socket 建立好连接关系以后,就放到服务器正在监听的 socket 的接收队列中。这个时候,服务器端通过 accept 就能获取到和客户端配好对的新 socket 了。

Image

主要的连接操作都是在这个函数中完成的。和我们平常所见的 TCP 连接建立过程,这个连接过程简直是太简单了。没有三次握手,也没有全连接队列、半连接队列,更没有啥超时重传。

直接就是将两个 socket 结构体中的指针互相指向对方就行了。就是 unix_peer(newsk) = sk 和 unix_peer(sk) = newsk 这两句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//file: net/unix/af_unix.c
static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
struct sockaddr_un *sunaddr = (struct sockaddr_un *)uaddr;

// 1. 为服务器侧申请一个新的 socket 对象
newsk = unix_create1(sock_net(sk), NULL);

// 2. 申请一个 skb,并关联上 newsk
skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL);
...

// 3. 建立两个 sock 对象之间的连接
unix_peer(newsk) = sk;
newsk->sk_state = TCP_ESTABLISHED;
newsk->sk_type = sk->sk_type;
...
sk->sk_state = TCP_ESTABLISHED;
unix_peer(sk) = newsk;

// 4. 把连接中的一头(新 socket)放到服务器接收队列中
__skb_queue_tail(&other->sk_receive_queue, skb);
}

//file: net/unix/af_unix.c
#define unix_peer(sk) (unix_sk(sk)->peer)

收发包过程和复杂的 TCP 发送接收过程相比,这里的发送逻辑简单简单到令人发指。申请一块内存(skb),把数据拷贝进去。根据 socket 对象找到另一端,直接把 skb 给放到对端的接收队列里了

Image

Unix Domain Socket和127.0.0.1通信相比,如果包的大小是1K以内,那么性能会有一倍以上的提升,包变大后性能的提升相对会小一些。

tcpdump原理

image.png

tcpdump 抓包使用的是 libpcap 这种机制。它的大致原理是:在收发包时,如果该包符合 tcpdump 设置的规则(BPF filter),那么该网络包就会被拷贝一份到 tcpdump 的内核缓冲区,然后以 PACKET_MMAP 的方式将这部分内存映射到 tcpdump 用户空间,解析后就会把这些内容给输出了。

通过上图你也可以看到,在收包的时候,如果网络包已经被网卡丢弃了,那么 tcpdump 是抓不到它的;在发包的时候,如果网络包在协议栈里被丢弃了,比如因为发送缓冲区满而被丢弃,tcpdump 同样抓不到它。我们可以将 tcpdump 的能力范围简单地总结为:网卡以内的问题可以交给 tcpdump 来处理;对于网卡以外(包括网卡上)的问题,tcpdump 可能就捉襟见肘了。这个时候,你需要在对端也使用 tcpdump 来抓包。

tcpdump 技巧

tcpdump -B/**–buffer-size=*buffer_size:*Set the operating system capture buffer size to buffer_size, in units of KiB (1024 bytes). tcpdump 丢包,造成这种丢包的原因是由于libcap抓到包后,tcpdump上层没有及时的取出,导致libcap缓冲区溢出,从而覆盖了未处理包,此处即显示为**dropped by kernel,注意,这里的kernel并不是说是被linux内核抛弃的,而是被tcpdump的内核,即 libcap 抛弃掉的

获取接口设备列表

tcpdump的-D获取接口设备列表。看到此列表后,可以决定要在哪个接口上捕获流量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#tcpdump -D
1.eth0
2.bond0
3.docker0
4.nflog (Linux netfilter log (NFLOG) interface)
5.nfqueue (Linux netfilter queue (NFQUEUE) interface)
6.eth1
7.usbmon1 (USB bus number 1)
8.usbmon2 (USB bus number 2)
9.veth6f2ee76
10.veth8cb61c2
11.veth9d9d363
12.veth16c25ac
13.veth190f0fc
14.veth07103d7
15.veth09119c0
16.veth9770e1a
17.any (Pseudo-device that captures on all interfaces)
18.lo [Loopback]

# tcpdump -X //解析内容

TCP 疑难问题的轻量级分析手段:TCP Tracepoints

Tracepoint 是我分析问题常用的手段之一,在遇到一些疑难问题时,我通常都会把一些相关的 Tracepoint 打开,把 Tracepoint 输出的内容保存起来,然后再在线下环境中分析。通常,我会写一些 Python 脚本来分析这些内容,毕竟 Python 在数据分析上还是很方便的。

对于 TCP 的相关问题,我也习惯使用这些 TCP Tracepoints 来分析问题。要想使用这些 Tracepoints,你的内核版本需要为 4.16 及以上。这些常用的 TCP Tracepoints 路径位于 /sys/kernel/debug/tracing/events/tcp/ 和 /sys/kernel/debug/tracing/events/sock/,它们的作用如下表所示:

image.png

参考资料:

https://mivehind.net/2018/04/20/sniffing-unix-domain-sockets/

https://superuser.com/questions/484671/can-i-monitor-a-local-unix-domain-socket-like-tcpdump

https://payloads.online/tools/socat

计算机网络(Computer Networking: A Top-Down Approach)

从知识到能力,你到底欠缺了什么

从知识到能力,你到底欠缺了什么

写在最前面的

前面推送过文章《毕业3年,为何技术能力相差越来越大?》 有些同学觉得还是不知道如何落地,那么本文希望借助一个程序员经常遇到的一个问题:网络为什么不通? 来具体展开实践一下怎么将书本上的死知识真正变成我们解决问题的能力。

大学学到的基本概念

我相信你脑子里关于网络基础知识的概念都在下面这张图上,但是有点乱,都认识,又都模模糊糊,更谈不上将他们转化成生产力,用来解决实际问题了。这就是因为知识没有贯通、没有实践、没有组织。

image.png

上图中知识点的作用在RFC1180中讲的无比的通熟易懂了,看第一遍你也许就看懂了,但是一个月后又忘记了。其实这些东西我们在大学也学了,但是还是忘了(能够理解,缺少实操环境和条件),或者碰到问题才发现之前即使觉得看懂了的东西实际没懂

所以接下来我们将示范书本知识到实践的贯通,希望把网络概念之间的联系通过实践来组织起来

还是从一个网络不通的问题入手

最近的环境碰到一个网络ping不通的问题,当时的网络链路是(大概是这样,略有简化):

容器1->容器1所在物理机1->交换机->物理机2

现象

  • 从容器1 ping 物理机2 不通;
  • 从物理机1上的容器2 ping物理机2 通;
  • 同时发现即使是通的,有的容器 ping物理机1只需要0.1ms,有的容器需要200ms以上(都在同一个物理机上),不合理
  • 所有容器 ping 其它外网IP(比如百度)反而是通的

这个问题扯了一周才解决是因为容器的网络是我们自己配置的,交换机我们没有权限接触,由客户配置。出问题的时候都会觉得自己没问题对方有问题,另外就是对网络基本知识认识不够所以都觉得自己没问题而不去找证据。

这个问题的答案在大家看完本文的基础知识后会总结出来。

解决这个问题前大家先想想,假如有个面试题是:输入 ping IP 后敲回车,然后发生了什么?

复习一下大学课本中的知识点

要解决一个问题你首先要有基础知识,知识欠缺你的逻辑再好、思路再清晰、智商再高,也不一定有效。

route 路由表

$route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric RefUse Iface
0.0.0.0     1.1.15.254   0.0.0.0 UG0  00 eth0
1.0.0.0     1.1.15.254   255.0.0.0   UG0  00 eth0
1.1.0.0     0.0.0.0 255.255.240.0   U 0  00 eth0
11.0.0.0    1.1.15.254   255.0.0.0   UG0  00 eth0
30.0.0.0    1.1.15.254   255.0.0.0   UG0  00 eth0
100.64.0.0  1.1.15.254   255.192.0.0 UG0  00 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002   00 eth0
172.16.0.0  1.1.15.254   255.240.0.0 UG0  00 eth0
172.17.0.0  0.0.0.0 255.255.0.0 U 0  00 docker0
192.168.0.0 1.1.15.254   255.255.0.0 UG0  00 eth0

假如你现在在这台机器上ping 172.17.0.2 根据上面的route表得出 172.17.0.2这个IP符合下面这条路由:

172.17.0.0  0.0.0.0 255.255.0.0 U 0  00 docker0

这条路由规则,那么ping 包会从docker0这张网卡发出去。

但是如果是ping 1.1.4.4 根据路由规则应该走eth0这张网卡而不是docker0了。

接下来就要判断目标IP是否在同一个子网了

ifconfig

首先来看看这台机器的网卡情况:

$ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
    inet 172.17.42.1  netmask 255.255.0.0  broadcast 0.0.0.0
    ether 02:42:49:a7:dc:ba  txqueuelen 0  (Ethernet)
    RX packets 461259  bytes 126800808 (120.9 MiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 462820  bytes 103470899 (98.6 MiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 1.1.3.33  netmask 255.255.240.0  broadcast 10.125.15.255
    ether 00:16:3e:00:02:67  txqueuelen 1000  (Ethernet)
    RX packets 280918095  bytes 89102074868 (82.9 GiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 333504217  bytes 96311277198 (89.6 GiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
    inet 127.0.0.1  netmask 255.0.0.0
    loop  txqueuelen 0  (Local Loopback)
    RX packets 1077128597  bytes 104915529133 (97.7 GiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 1077128597  bytes 104915529133 (97.7 GiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

这里有三个网卡和三个IP,三个子网掩码(netmask),根据目标路由走哪张网卡,得到这个网卡的子网掩码,来计算目标IP是否在这个子网内。

arp协议

网络包在物理层传输的时候依赖的mac 地址而不是上面的IP地址,也就是根据mac地址来决定把包发到哪里去。

arp协议就是查询某个IP地址的mac地址是多少,由于这种对应关系一般不太变化,所以每个os都有一份arp缓存(一般15分钟过期),也可以手工清理,下面是arp缓存的内容:

$arp -a
e010010011202.bja.tbsite.net (1.1.11.202) at 00:16:3e:01:c2:00 [ether] on eth0
? (1.1.15.254) at 0c:da:41:6e:23:00 [ether] on eth0
v125004187.bja.tbsite.net (1.1.4.187) at 00:16:3e:01:cb:00 [ether] on eth0
e010010001224.bja.tbsite.net (1.1.1.224) at 00:16:3e:01:64:00 [ether] on eth0
v125009121.bja.tbsite.net (1.1.9.121) at 00:16:3e:01:b8:ff [ether] on eth0
e010010009114.bja.tbsite.net (1.1.9.114) at 00:16:3e:01:7c:00 [ether] on eth0
v125012028.bja.tbsite.net (1.1.12.28) at 00:16:3e:00:fb:ff [ether] on eth0
e010010005234.bja.tbsite.net (1.1.5.234) at 00:16:3e:01:ee:00 [ether] on eth0

进入正题,回车后发生什么

有了上面的基础知识打底,我们来思考一下 ping IP 到底发生了什么。

首先 OS 的协议栈需要把ping命令封成一个icmp包,要填上包头(包括src-IP、mac地址),那么OS先根据目标IP和本机的route规则计算使用哪个interface(网卡),确定了路由也就基本上知道发送包的src-ip和src-mac了。每条路由规则基本都包含目标IP范围、网关、MAC地址、网卡这样几个基本元素。

如果目标IP和本机使用的IP在同一子网

如果目标IP和本机IP是同一个子网(根据本机ifconfig上的每个网卡的netmask来判断是否是同一个子网–知识点:子网掩码的作用),并且本机arp缓存没有这条IP对应的mac记录,那么给整个子网的所有机器广播发送一个 arp查询

比如我ping 1.1.3.42,然后tcpdump抓包首先看到的是一个arp请求:

$sudo tcpdump -i eth0  arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
16:22:01.792501 ARP, Request who-has e010010003042.bja.tbsite.net tell e010125003033.bja, length 28
16:22:01.792566 ARP, Reply e010010003042.bja.tbsite.net is-at 00:16:3e:01:8d:ff (oui Unknown), length 28

上面就是本机发送广播消息,1.1.3.42的mac地址是多少?很快1.1.3.42回复了自己的mac地址。
收到这个回复后,先缓存起来,下个ping包就不需要再次发arp广播了。
然后将这个mac地址填写到ping包的包头的目标Mac(icmp包),然后发出这个icmp request包,按照mac地址,正确到达目标机器,然后对方正确回复icmp reply【对方回复也要查路由规则,arp查发送方的mac,这样回包才能正确路由回来,略过】。

来看一次完整的ping 1.1.3.43,tcpdump抓包结果:

$sudo tcpdump -i eth0  arp or icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
16:25:15.195401 ARP, Request who-has e010010003043.bja.tbsite.net tell e010010003033.bja, length 28
16:25:15.195459 ARP, Reply e010010003043.bja.tbsite.net is-at 00:16:3e:01:0c:ff (oui Unknown), length 28
16:25:15.211505 IP e010010003033.bja > e010010003043.bja.tbsite.net: ICMP echo request, id 27990, seq 1, length 64
16:25:15.212056 IP e010010003043.bja.tbsite.net > e010010003033.bja: ICMP echo reply, id 27990, seq 1, length 64

我换了个IP地址,接着再ping同一个IP地址,arp有缓存了就看不到arp广播查询过程了。

如果目标IP不是同一个子网

arp只是同一子网广播查询,如果目标IP不是同一子网的话就要经过本IP网关进行转发(知识点–网关的作用),如果本机没有缓存网关mac(一般肯定缓存了),那么先发送一次arp查询网关的mac,然后流程跟上面一样,只是这个icmp包发到网关上去了(mac地址填写的是网关的mac)

从本机1.1.3.33 ping 11.239.161.60的过程,因为不是同一子网按照路由规则匹配,根据route表应该走1.1.15.254这个网关,如下截图:

image.png

首先是目标IP 11.239.161.60 符合最上面红框中的路由规则,又不是同一子网,所以查找路由规则中的网关1.1.15.254的Mac地址,arp cache中有,于是将 0c:da:41:6e:23:00 填入包头,那么这个icmp request包就发到1.1.15.254上了,虽然包头的mac是 0c:da:41:6e:23:00,但是IP还是 11.239.161.60.

看看目标IP 11.239.161.60 的真正mac信息(跟ping包包头的Mac是不同的):

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 11.239.161.60  netmask 255.255.252.0  broadcast 11.239.163.255
    ether 00:16:3e:00:04:c4  txqueuelen 1000  (Ethernet)

这个包根据Mac地址路由到了网关上

网关接下来怎么办

为了简化问题,假设两个网关直连

网关收到这个包后(因为mac地址是它的),打开一看IP地址是 11.239.161.60,不是自己的,于是继续查自己的route和arp缓存,发现11.239.161.60这个IP的网关是11.239.163.247,于是把包的目的mac地址改成11.239.163.247的mac继续发出去。

11.239.163.247这个网关收到包后,一看 11.239.161.60是自己同一子网的IP,于是该arp广播找mac就广播,cache有就拿cache的,然后这个包才最终到达目的11.239.161.60上。

整个过程中目标mac地址每一跳都在变,IP地址不变,每经过一次MAC变化可以简单理解成一跳。

实际上可能要经过多个网关多次跳跃才能真正到达目标机器

目标机器收到这个icmp包后的回复过程一样,略过。

arp广播风暴和arp欺骗

广播风暴:如果一个子网非常大,机器非常多,每次arp查询都是广播的话,也容易因为N*N的问题导致广播风暴。

arp欺骗:同样如果一个子网中的某台机器冒充网关或者其他机器,当收到arp广播查询的时候总是把自己的mac冒充目标机器的mac发给你,然后你的包先走到他,再转发给真正的网关或者目标机器,所以在里面动点什么手脚,看看你发送的内容都还是很容易的。

讲完基础知识再来看开篇问题的答案

读完上面的基础知识相信现在我们已经能够回答 ping IP 后发生了什么,这些已经足够解决99%程序员日常网络中的网络为什么不通的问题了。但是前面问题比这个要稍微复杂一点,不过还是依靠这些基础知识就能解决的–这是基础知识的威力。

现场网络同学所做的一些其它测试:

  1. 怀疑不通的IP所使用的mac地址冲突,在交换机上清理了交换机的arp缓存,没有帮助,还是不通;
  2. 新拿出一台物理机配置上不通的容器的IP,这是通的,所以负责网络的同学坚持是容器网络的配置导致了问题。

对于1能通,我认为这个测试不严格,新物理机所用的mac不一样,并且所接的交换机口也不一样,影响了测试结果。

祭出万能手段–抓包

抓包在网络问题中是万能的,但是第一次容易被tcpdump抓包命令的众多参数吓晕,不去操作你永远上不了手,差距也就拉开了,你看差距有时候只是你对一条命令的执行

在物理机2上抓包:

image.png

tcpdump: listening on em1, link-type EN10MB (Ethernet), capture size 65535 bytes
f4:0f:1b:ae:15:fb > 18:66:da:f0:15:90, ethertype 802.1Q (0x8100), length 102: vlan 134, p 0, ethertype IPv4, (tos 0x0, ttl 63, id 5654, offset 0, flags [DF], proto ICMP (1), length 84)
10.159.43.162 > 10.159.43.1: ICMP echo request, id 6285, seq 1, length 64
18:66:da:f0:15:90 > 00:00:0c:9f:f0:86, ethertype 802.1Q (0x8100), length 102: vlan 134, p 0, ethertype IPv4, (tos 0x0, ttl 64, id 21395, offset 0, flags [none], proto ICMP (1), length 84)
10.159.43.1 > 10.159.43.162: ICMP echo reply, id 6285, seq 1, length 64

这个抓包能看到核心证据,ping包有到达物理机2,同时物理机2也正确回复了(mac、ip都对)

同时在物理机1上抓包(抓包截图略掉)只能看到ping包出去,回包没有到物理机1(所以回包肯定不会回到容器里了)

到这里问题的核心在交换机没有正确把物理机2的回包送到物理机1上面。

同时观察到的不正常延时都在网关那一跳:

image.png

最终的原因

最后在交换机上分析包没正确发到物理机1上的原因跟客户交换机使用了HSRP(热备份路由器协议,就是多个交换机HA高可用,也就是同一子网可以有多个网关的IP),停掉HSRP后所有IP容器都能通了,并且前面的某些容器延时也恢复正常了。

通俗点说就是HSRP把回包拐跑了,有些回包拐跑了又送回来了(延时200ms那些)

至于HSRP为什么会这么做,要厂家出来解释了。这里关键在于能让客户认同问题出现在交换机上还是前面的抓包证据充分,无可辩驳。实际中我们都习惯不给证据就说:我的程序没问题,就是你的问题。这样表述没有一点意义,我们是要拿着证据这么说,对方也好就着证据来反驳,这叫优雅地甩锅。

网络到底通不通是个复杂的问题吗?

讲这个过程的核心目的是除了真正的网络不通,有些是服务不可用了也怪网络。很多现场的同学根本讲不清自己的服务(比如80端口上的tomcat服务)还在不在,网络通不通,是网络不通呢还是服务出了问题。一看到SocketTimeoutException 就想把网络同学抓过来羞辱两句:网络不通了,网络抖动导致我的程序异常了(网络抖动是个万能的扛包侠)。

实际这里涉及到四个节点(以两个网关直连为例),srcIP -> src网关 -> dest网关 -> destIP。如果ping不通(也有特殊的防火墙限制ping包不让过的),那么在这四段中分段ping(二分查找程序员应该最熟悉了)。 比如前面的例子就是网关没有把包转发回来

抓包看ping包有没有出去,对方抓包看有没有收到,收到后有没有回复。

ping自己网关能不能通,ping对方网关能不能通。

接下来说点跟程序员日常相关的

如果网络能ping通,服务无法访问

那么尝试telnet IP port 看看你的服务是否还在监听端口,在的话再看看服务进程是否能正常响应新的请求。有时候是进程死掉了,端口也没人监听了;有时候是进程还在但是假死了,所以端口也不响应新的请求了;还有的是TCP连接队列满了不能响应新的连接

如果端口还在也是正常的话,telnet应该是好的:

$telnet 1.1.161.60 2376
Trying 1.1.161.60...
Connected to 1.1.161.60.
Escape character is '^]'.
^C
Connection closed by foreign host.

假如我故意换成一个不存在的端口,目标机器上的OS直接就拒绝了这个连接(抓包的话一般是看到reset标识):

$telnet 1.1.161.60 2379
Trying 1.1.161.60...
telnet: connect to address 11.239.161.60: Connection refused

一个SocketTimeoutException,程序员首先怀疑网络丢包的Case

当时的反馈应用代码抛SocketTimeoutException,怀疑网络问题:

  1. 业务应用连接Server 偶尔会出现超时异常;
  2. 有很多这样的异常日志:[Server SocketTimeoutException]

检查一下当时的网络状态非常好,出问题时间段的网卡的流量信息也非常正常:

image.png

上图是通过sar监控到的9号 v24d9e0f23d40 这个网卡的流量,看起来也是正常,流量没有出现明显的波动

为了监控网络到底有没有问题,接着在出问题的两个容器上各启动一个http server,然后在对方每1秒钟互相发一次发http get请求访问这个http server,基本认识告诉我们如果网络丢包、卡顿严重,那么我这个http server的监控日志时间戳也会跳跃,如果应用是因为网络出现异常那么我启动的http服务也会出现异常–宁愿写个工具都不背锅(主要是背了锅也不一定能解决掉问题)。

从实际监控来看,应用出现异常的时候我的http服务是正常的(写了脚本判断日志的连续性):

image.png

这也强有力地证明了网络没问题,所以写业务代码的同学一门心思集中火力查看应用的问题。后来的实际调查发现是应用假死掉了(内部线程太多,卡死了),服务端口不响应请求了。

如果基础知识缺乏一点那么甩过来的这个锅网络是扛不动的,同时也阻碍了问题的真正发现。

TCP建连接过程跟前面ping一样,只是把ping的icmp协议换成TCP协议,也是要先根据route,然后arp

总结

网络丢包,卡顿,抖动很容易做扛包侠,只有找到真正的原因解决问题才会更快,要不在错误的方向上怎么发力都不对。准确的方向要靠好的基础知识和正确的逻辑以及证据来支撑,而不是猜测

  • 基础知识是决定你能否干到退休的关键因素;
  • 有了基础知识不代表你能真正转化成生产力;
  • 越是基础,越是几十年不变的基础越是重要;
  • 知识到灵活运用要靠实践,同时才能把知识之间的联系建立起来;
  • 简而言之缺的是融会贯通和运用;
  • 做一个有礼有节的甩包侠;
  • 在别人不给证据愚昧甩包的情况下你的机会就来了。

留几个小问题

  1. server回复client的时候是如何确定回复包中的src-ip和dest-mac的?一定是请求包中的dest-ip当成src-ip吗?
  2. 上面问题中如果是TCP或者UDP协议,他们回复包中的src-ip和dest-mac获取会不一样吗?
  3. 既然局域网中都是依赖Mac地址来定位,那么IP的作用又是什么呢?

参考资料:

https://tools.ietf.org/html/rfc1180

《计算机基础》

如何追踪网络流量

如何追踪网络流量

背景

某些场景下通过监控发现了流量比较大,不太合理,需要知道这些流量都是哪些进程访问哪些服务触发的

方法

  1. 定位流量是由哪个进程触发的
  2. 定位流量主要是访问哪些ip导致的
  3. 定位具体的端口有较大的流量

工具

nethogs/iftop/tcptrack

定位进程

sudo nethogs 

image.png

从上图可以看到总的流量,以及每个进程的流量大小。这里可以确认流量主要是3820的java进程消耗的

定位ip

sudo iftop -p -n -B

image.png

通过上图可以看到流量主要是消耗在 10.0.48.1的ip上

定位端口

10.0.48.1 有可能是一个mapping ip,需要进一步查看具体

sudo tcptrack -r 5 -i eth0  //然后输入小写s,按流量排序
sudo tcptrack -r 5 -i eth0 host 10.0.48.1 //filter 语法和tcpdump一样

image.png

可以看到4355/4356端口上流量相对较大

软件安装

后续发布新镜像都会带上这三个软件的rpm安装包
目前可以手动下载这三个rpm安装包:

磁盘爆掉的几种情况

Docker宿主机磁盘爆掉的几种情况

磁盘爆掉的几种情况

  1. 系统磁盘没有空间,解决办法:删掉 /var/log/ 下边的带日期的日志,清空 /var/log/messages 内容
  2. 容器使用的大磁盘但是仍然空间不够,有三个地方会使用大量的磁盘
    • 容器内部日志非常大,处理办法见方法一
    • 容器内部产生非常多或者非常大的文件,但是这个文件的位置又通过volume 挂载到了物理机上,处理办法见方法二
    • 对特别老的部署环境,还有可能是容器的系统日志没有限制大小,处理办法见方法三

现场的同学按如下方法依次检查

方法零: 检查系统根目录下每个文件夹的大小

sudo du / -lh --max-depth=1 --exclude=overlay --exclude=proc

看看除了容器之外有没有其它目录使用磁盘特别大,如果有那么一层层进去通过du命令来查看,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#sudo du / -lh --max-depth=1 --exclude=overlay --exclude=proc
16K /dev
16K /lost+found
4.0K /media
17G /home
136M /boot
832K /run
1.9G /usr
75M /tmp
12K /log
8.5G /var
4.0K /srv
0 /proc
22M /etc
84G /root
4.0K /mnt
508M /opt
0 /sys
112G /

那么这个案例中应该查看 /root下为什么用掉了84G(总共用了112G), 先 cd /root 然后执行: sudo du . -lh –max-depth=1 –exclude=overlay 进一步查看 /root 目录下每个文件夹的大小

如果方法零没找到占用特别大的磁盘文件,那么一般来说是容器日志占用太多的磁盘空间,请看方法一

方法一: 容器内部日志非常大(请确保先按方法零检查过了)

在磁盘不够的物理机上执行如下脚本:

1
2
3
4
5
6
7
sudo docker ps -a -q >containers.list

sudo cat containers.list | xargs sudo docker inspect $1 | grep merged | awk -F \" '{ print $4 }' | sed 's/\/merged//g' | xargs sudo du --max-depth=0 $1 >containers.size

sudo paste containers.list containers.size | awk '{ print $1, $2 }' | sort -nk2 >real_size.log

sudo tail -10 real_size.log | awk 'BEGIN {print "\tcontainer size\tunit"} { print NR":\t" $0"\t kB" }'
执行完后会输出如下格式:
1
2
3
4
5
6
7
8
9
10
11
container     size	unit
1: 22690f16822f 3769980 kb
2: 82b4ae98eeed 4869324 kb
3: 572a1b7c8ef6 10370404 kb
4: 9f9250d98df6 10566776 kb
5: 7fab70481929 13745648 kb
6: 4a14b58e3732 29873504 kb
7: 8a01418b6df2 30432068 kb
8: 83dc85caaa5c 31010960 kb
9: 433e51df88b1 35647052 kb
10: 4b42818a8148 61962416 kb

第二列是容器id,第三列是磁盘大小,第四列是单位, 占用最大的排在最后面

然后进到容器后通过 du / –max-depth=2 快速发现大文件

方法二: 容器使用的volume使用过大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$sudo du -l /data/lib/docker/defaultVolumes --max-depth=1 | sort -rn
456012884 /data/lib/docker/defaultVolumes
42608332 /data/lib/docker/defaultVolumes/task_3477_g0_ark-metadb_miniDBPaaS-MetaDB_1
32322220 /data/lib/docker/defaultVolumes/task_3477_g0_dbpaas-metadb_dbpaas_1
27461120 /data/lib/docker/defaultVolumes/task_3001_g0_ark-metadb_miniDBPaaS-MetaDB_1
27319360 /data/lib/docker/defaultVolumes/task_36000_g0_ark-metadb_miniDBPaaS-MetaDB
27313836 /data/lib/docker/defaultVolumes/task_3600_g0_dbpaas-metadb_minidbpaas
27278692 /data/lib/docker/defaultVolumes/task_3604_g0_ark-metadb_miniDBPaaS-MetaDB_1
27277004 /data/lib/docker/defaultVolumes/task_3603_g0_ark-metadb_miniDBPaaS-MetaDB_1
27275736 /data/lib/docker/defaultVolumes/task_3542_g0_ark-metadb_miniDBPaaS-MetaDB
27271428 /data/lib/docker/defaultVolumes/task_3597_g0_ark-metadb_miniDBPaaS-MetaDB
27270840 /data/lib/docker/defaultVolumes/task_3603_g0_dbpaas-metadb_minidbpaas_1
27270492 /data/lib/docker/defaultVolumes/task_3603_g0_dbpaas-metadb_minidbpaas
27270468 /data/lib/docker/defaultVolumes/task_3600_g0_ark-metadb_miniDBPaaS-MetaDB
27270252 /data/lib/docker/defaultVolumes/task_3535_g0_ark-metadb_miniDBPaaS-MetaDB
27270244 /data/lib/docker/defaultVolumes/task_3538_g0_ark-metadb_miniDBPaaS-MetaDB
27270244 /data/lib/docker/defaultVolumes/task_3536_g0_ark-metadb_miniDBPaaS-MetaDB
25312404 /data/lib/docker/defaultVolumes/task_3477_g0_dncs-server_middleware-dncs_2

/data/lib/docker/defaultVolumes 参数是默认设置的volume存放的目录(一般是docker的存储路径下 –graph=/data/lib/docker) ,第一列是大小,后面是容器名

volume路径在物理机上也有可能是 /var/lib/docker 或者 /mw/mvdocker/ 之类的路径下,这个要依据安装参数来确定,可以用如下命令来找到这个路径:

sudo systemctl status docker -l | grep --color graph

结果如下,红色参数后面的路径就是docker 安装目录,到里面去找带volume的字眼:

找到 volume很大的文件件后同样可以进到这个文件夹中执行如下命令快速发现大文件:

du . --max-depth=2

方法三 容器的系统日志没有限制大小

这种情况只针对2017年上半年之前的部署环境,后面部署的环境默认都控制了这些日志不会超过150M

按照方法二的描述先找到docker 安装目录,cd 进去,然后 :

du ./containers --max-depth=2

就很快找到那个大json格式的日志文件了,然后执行清空这个大文件的内容:

echo '' | sudo tee 大文件名

一些其他可能占用空间的地方

  • 机器上镜像太多,可以删掉一些没用的: sudo docker images -q | xargs sudo docker rmi
  • 机器上残留的volume太多,删:sudo docker volume ls -q | xargs sudo docker volume rm
  • 物理文件被删了,但是还有进程占用这个文件句柄,导致文件对应的磁盘空间没有释放,检查: lsof | grep deleted  如果这个文件非常大的话,只能通过重启这个进程来真正释放磁盘空间

OverlayFS(overlay)的镜像分层与共享

OverlayFS使用两个目录,把一个目录置放于另一个之上,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作union mount。术语上,下层的目录叫做lowerdir,上层的叫做upperdir。对外展示的统一视图称作merged。   

如下图所示,Overlay在主机上用到2个目录,这2个目录被看成是overlay的层。 upperdir为容器层、lowerdir为镜像层使用联合挂载技术将它们挂载在同一目录(merged)下,提供统一视图。

图片

注意镜像层和容器层是如何处理相同的文件的:容器层(upperdir)的文件是显性的,会隐藏镜像层(lowerdir)相同文件的存在。容器映射(merged)显示出统一的视图。   overlay驱动只能工作在两层之上。也就是说多层镜像不能用多层OverlayFS实现。替代的,每个镜像层在/var/lib/docker/overlay中用自己的目录来实现,使用硬链接这种有效利用空间的方法,来引用底层分享的数据。注意:Docker1.10之后,镜像层ID和/var/lib/docker中的目录名不再一一对应。   创建一个容器,overlay驱动联合镜像层和一个新目录给容器。镜像顶层是overlay中的只读lowerdir,容器的新目录是可写的upperdir。

netstat timer keepalive RTO explain

netstat 等网络工具

netstat 和重传– timer

经常碰到一些断网环境下需要做快速切换,那么断网后需要多久tcp才能感知到这个断网,并断开连接触发上层的重连(一般会连向新的server)

netstat -st命令中,tcp: 部分取自/proc/net/snmp,而TCPExt部分取自/proc/net/netstat,该文件对TCP记录了更多的统计。sysstat包也会采集/proc/net/snmp

keepalive

from: https://superuser.com/questions/240456/how-to-interpret-the-output-of-netstat-o-netstat-timers

The timer column has two fields (from your o/p above):

1
2
keepalive     (6176.47/0/0)  
<1st field> <2nd field>

The 1st field can have values:
keepalive - when the keepalive timer is ON for the socket

on - when the retransmission timer is ON for the socket

off - none of the above is ON

on - #表示是重发(retransmission)的时间计时

off - #表示没有时间计时

timewait - #表示等待(timewait)时间计时

Probe zerowindow

keepalive 是指在连接闲置状态发送心跳包来检测连接是否还有效(比如对方掉电后肯定就无效了,tcp得靠这个keepalive来感知)。如果有流量在传输过程中对方掉电后会不停地 retransmission ,这个时候看到的就是 on,然后重传间隔和次数跟keepalive参数无关,只和 net.ipv4.tcp_retries1、net.ipv4.tcp_retries2相关了。

keepalive 状态下的连接:

image.png

1
2
3
4
5
6
7
8
9
10
11
12
#netstat -anto |grep -E ":3048|Q" | awk '{ if($3>0) print $0 }'
Proto Recv-Q Send-Q Local Address Foreign Address State Timer
tcp 0 22207 1.2.3.134:3048 4.3.44.45:40100 ESTABLISHED probe (0.05/0/0)
tcp 0 56960 1.2.3.134:3048 4.3.7.8:40057 ESTABLISHED probe (0.06/0/0)
tcp 0 59808 1.2.3.134:3048 4.3.9.10:40085 ESTABLISHED on (0.21/0/0)
tcp 0 64080 1.2.3.134:3048 4.3.7.8:40055 ESTABLISHED on (0.19/0/0)
tcp 0 4788 1.2.3.134:3048 4.3.0.1:40075 ESTABLISHED probe (0.01/0/0)
tcp 0 44144 1.2.3.134:3048 4.3.7.8:40049 ESTABLISHED probe (0.05/0/0)
tcp 0 52688 1.2.3.134:3048 4.3.34.35:40068 ESTABLISHED probe (0.06/0/0)
tcp 0 4788 1.2.3.134:3048 4.3.4.5:40073 ESTABLISHED probe (0.18/0/0)
tcp 0 31894 1.2.3.134:3048 4.3.17.18:40072 ESTABLISHED probe (0.17/0/0)
tcp 0 44144 1.2.3.134:3048 4.3.12.13:40099 ESTABLISHED probe (0.02/0/0)

The 2nd field has THREE subfields:

(6176.47/0/0) -> (a/b/c)
a=timer value (a=keepalive timer, when 1st field=“keepalive”; a=retransmission timer, when 1st field=“on”)

b=number of retransmissions that have occurred

c=number of keepalive probes that have been sent

/proc/sys/net/ipv4/tcp_keepalive_time
当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。

/proc/sys/net/ipv4/tcp_keepalive_intvl
当探测没有确认时,重新发送探测的频度。缺省是75秒。

/proc/sys/net/ipv4/tcp_keepalive_probes
在认定连接失效之前,发送多少个TCP的keepalive探测包。缺省值是9。这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之后可以有多少时间没有回应

For example, I had two sockets opened between a client & a server (not loopback). The keepalive setting are:

KEEPALIVE_IDLETIME 30
KEEPALIVE_NUMPROBES 4
KEEPALIVE_INTVL 10

And I did a shutdown of the client machine, so at …SHED on (2.47/254/2)

1
2
3
4
tcp        0    210 192.0.0.1:36483             192.0.68.1:43881            ESTABLISHED on (1.39/254/2)  
tcp 0 210 192.0.0.1:36483 192.0.68.1:43881 ESTABLISHED on (0.31/254/2)
tcp 0 210 192.0.0.1:36483 192.0.68.1:43881 ESTABLISHED on (2.19/255/2)
tcp 0 210 192.0.0.1:36483 192.0.68.1:43881 ESTABLISHED on (1.12/255/2)

As you can see, in this case things are a little different. When the client went down, my server started sending keepalive messages, but while it was still sending those keepalives, my server tried to send a message to the client. Since the client had gone down, the server couldn’t get any ACK from the client, so the TCP retransmission started and the server tried to send the data again, each time incrementing the retransmit count (2nd field) when the retransmission timer (1st field) expired.

Hope this explains the netstat –timer option well.

RTO 重传

1
2
#define TCP_RTO_MAX ((unsigned)(120*HZ)) //HZ 通常为1秒 
#define TCP_RTO_MIN ((unsigned)(HZ/5))

Linux 2.6+ uses HZ of 1000ms, so TCP_RTO_MIN is ~200 ms and TCP_RTO_MAX is ~120 seconds. Given a default value of tcp_retries set to 15, it means that it takes 924.6 seconds before a broken network link is notified to the upper layer (ie. application), since the connection is detected as broken when the last (15th) retry expires.

2018-04-27-linux-tcp-rto-retries2.png

The tcp_retries2 sysctl can be tuned via /proc/sys/net/ipv4/tcp_retries2 or the sysctl net.ipv4.tcp_retries2.

查看重传状态

重传状态的连接:

image.png

前两个 syn_sent 状态明显是 9031端口不work了,握手不上。

最后 established 状态的连接, 是22端口给53795发了136字节的数据但是没有收到ack,所以在倒计时准备重传中。

net.ipv4.tcp_retries1 = 3

放弃回应一个TCP 连接请求前﹐需要进行多少次重试。RFC 规定最低的数值是3﹐这也是默认值﹐根据RTO的值大约在3秒 - 8分钟之间。(注意:这个值同时还决定进入的syn连接)

(第二种解释:它表示的是TCP传输失败时不检测路由表的最大的重试次数,当超过了这个值,我们就需要检测路由表了)

从kernel代码可以看到,一旦重传超过阈值tcp_retries1,主要的动作就是更新路由缓存。
用以避免由于路由选路变化带来的问题。这个时候tcp连接没有关闭

net.ipv4.tcp_retries2 = 15

**在丢弃激活(已建立通讯状况)**的TCP连接之前﹐需要进行多少次重试。默认值为15,根据RTO的值来决定,相当于13-30分钟(RFC1122规定,必须大于100秒).(这个值根据目前的网络设置,可以适当地改小,我的网络内修改为了5)

(第二种解释:表示重试最大次数,只不过这个值一般要比上面的值大。和上面那个不同的是,当重试次数超过这个值,我们就必须关闭连接了)

from:Documentation/networking/ip-sysctl.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
tcp_retries1 - INTEGER
This value influences the time, after which TCP decides, that
something is wrong due to unacknowledged RTO retransmissions,
and reports this suspicion to the network layer.
See tcp_retries2 for more details.

RFC 1122 recommends at least 3 retransmissions, which is the
default.

tcp_retries2 - INTEGER
This value influences the timeout of an alive TCP connection,
when RTO retransmissions remain unacknowledged.
Given a value of N, a hypothetical TCP connection following
exponential backoff with an initial RTO of TCP_RTO_MIN would
retransmit N times before killing the connection at the (N+1)th RTO.

The default value of 15 yields a hypothetical timeout of 924.6
seconds and is a lower bound for the effective timeout.
TCP will effectively time out at the first RTO which exceeds the
hypothetical timeout.

RFC 1122 recommends at least 100 seconds for the timeout,
which corresponds to a value of at least 8.

img

retries限制的重传次数吗

咋一看文档,很容易想到retries的数字就是限定的重传的次数,甚至源码中对于retries常量注释中都写着”This is how many retries it does…”

1
2
3
4
5
6
7
8
9
10
11
12
13
#define TCP_RETR1       3   /*
* This is how many retries it does before it
* tries to figure out if the gateway is
* down. Minimal RFC value is 3; it corresponds
* to ~3sec-8min depending on RTO.
*/

#define TCP_RETR2 15 /*
* This should take at least
* 90 minutes to time out.
* RFC1122 says that the limit is 100 sec.
* 15 is ~13-30min depending on RTO.
*/

那就就来看看retransmits_timed_out的具体实现,看看到底是不是限制的重传次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* This function calculates a "timeout" which is equivalent to the timeout of a
* TCP connection after "boundary" unsuccessful, exponentially backed-off
* retransmissions with an initial RTO of TCP_RTO_MIN or TCP_TIMEOUT_INIT if
* syn_set flag is set.
*/
static bool retransmits_timed_out(struct sock *sk,
unsigned int boundary,
unsigned int timeout,
bool syn_set)
{
unsigned int linear_backoff_thresh, start_ts;
// 如果是在三次握手阶段,syn_set为真
unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;

if (!inet_csk(sk)->icsk_retransmits)
return false;

// retrans_stamp记录的是数据包第一次发送的时间,在tcp_retransmit_skb()中设置
if (unlikely(!tcp_sk(sk)->retrans_stamp))
start_ts = TCP_SKB_CB(tcp_write_queue_head(sk))->when;
else
start_ts = tcp_sk(sk)->retrans_stamp;

// 如果用户态未指定timeout,则算一个出来
if (likely(timeout == 0)) {
/* 下面的计算过程,其实就是算一下如果以rto_base为第一次重传间隔,
* 重传boundary次需要多长时间
*/
linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);

if (boundary <= linear_backoff_thresh)
timeout = ((2 << boundary) - 1) * rto_base;
else
timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
(boundary - linear_backoff_thresh) * TCP_RTO_MAX;
}
// 如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,则认为重传超于阈值了
return (tcp_time_stamp - start_ts) >= timeout;
}

从以上的代码分析可以看到,真正起到限制重传次数的并不是真正的重传次数。
而是以tcp_retries1或tcp_retries2为boundary,以rto_base(如TCP_RTO_MIN 200ms)为初始RTO,计算得到一个timeout值出来。如果重传间隔超过这个timeout,则认为超过了阈值。
上面这段话太绕了,下面举两个个例子来说明

1
2
3
4
5
6
7
8
以判断是否放弃TCP流为例,如果tcp_retries2=15,那么计算得到的timeout=924600ms。

1. 如果RTT比较小,那么RTO初始值就约等于下限200ms
由于timeout总时长是924600ms,表现出来的现象刚好就是重传了15次,超过了timeout值,从而放弃TCP流

2. 如果RTT较大,比如RTO初始值计算得到的是1000ms
那么根本不需要重传15次,重传总间隔就会超过924600ms。
比如我测试的一个RTT=400ms的情况,当tcp_retries2=10时,仅重传了3次就放弃了TCP流

一些重传的其它问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>> effective timeout指的是什么?  
<< 就是retransmits_timed_out计算得到的timeout值

>> 924.6s是怎么算出来的?
<< 924.6s = (( 2 << 9) -1) * 200ms + (15 - 9) * 120s

>> 为什么924.6s是lower bound?
<< 重传总间隔必须大于timeout值,即 (tcp_time_stamp - start_ts) >= timeout

>> 那RTO超时的间隔到底是不是源码注释的"15 is ~13-30min depending on RTO."呢?
<< 显然不是! 虽然924.6s(15min)是一个lower bound,但是它同时也是一个upper bound!
怎么理解?举例说明
1. 如果某个RTO值导致,在已经重传了14次后,总重传间隔开销是924s
那么它还需要重传第15次,即使离924.6s只差0.6s。这就是发挥了lower bound的作用
2. 如果某个RTO值导致,在重传了10次后,总重传间隔开销是924s
重传第11次后,第12次超时触发时计算得到的总间隔变为1044s,超过924.6s
那么此时会放弃第12次重传,这就是924.6s发挥了upper bound的作用
总的来说,在Linux3.10中,如果tcp_retres2设置为15。总重传超时周期应该在如下范围内
[924.6s, 1044.6s)

RTO重传案例

我们来看如下这个51432端口向9627端口上传过程,十分缓慢,重传包间隔基本是122秒,速度肯定没法快

image.png

上图中垂直方向基本都是发出3-5个包,然后休息120秒,继续发3-5个 包,速度肯定慢,下图可以看到具体的包:

image.png

来看下到9627的RTT,基本稳定在245秒或者122秒,这RTT也实在太大了。可以看到:

  1. 网络质量很不好,丢包有点多;

  2. rtt高得离谱,导致rto计算出来120秒了,所以一旦丢包就卡120秒以上。

下图是RTT图

image.png

两个原因一叠加,就出现了奇慢无比.

正常情况下RTO是从200ms开始翻倍,实际上OS层面限制了最小RTO 200ms、最大RTO 120秒,由于RTT都超过120秒了,计算所得的RTO必定也大于120秒,所以最终就是我们看到的一上来第一个RTO不是常见的200ms,直接干到了120秒。

netstat -s

netstat -s统计,有两个和timestamp stamp reject相关的。

1
2
3
netstat -st | grep stamp | grep reject
18 passive connections rejected because of time stamp
1453 packets rejects in established connections because of timestamp

丢包统计:

netstat -s |egrep -i “drop|route|overflow|filter|retran|fails|listen”

nstat -z -t 1 | egrep -i “drop|route|overflow|filter|retran|fails|listen”

netstat -st命令中,Tcp: 部分取自/proc/net/snmp,而TCPExt部分取自/proc/net/netstat,该文件对TCP记录了更多的统计。sysstat包也会采集/proc/net/snmp

nc 测试

1
nc -v -u -z -w 3 10.101.0.1 53 //测试server 的53端口上的udp服务能否通

nc 6.5 快速fin

img

nc -i 3 10.97.170.11 3306 -w 4 -p 1234

-i 3 表示握手成功后 等三秒钟nc退出(发fin)

nc 6.5 握手后立即发fin断开连接,导致可能收不到Greeting,换成7.5或者mysql client就OK了

也就是用nc 6.5来验证mysql 服务是否正常可能会碰到nc自己断的太快,实际mysql还是正常的,从而像是mysql没有回复Greeting,从而产生误判。

nc 7.5的抓包,明显可以看到nc在发fin前会先等3秒钟:

img

ping

sudo ping -f ip 大批量的icmp包

ping -D 带时间戳 或者:ping -i 5 google.com | xargs -L 1 -I ‘{}’ date ‘+%Y-%m-%d %H:%M:%S: {}’ 或者 ping www.google.fr | while read pong; do echo “$(date): $pong”; done

ping -O 不通的时候输出:no answer yet for icmp_seq=xxx

或者-D + awk

ping -D 114.114.114.114 | awk ‘{ if(gsub(/[|]/, “”, $1)) $1=strftime(“[%F %T]”, $1); print}’

Linux 下直接增加如下函数:

1
2
3
4
5
6
7
ping.ts(){
if [ -t 1 ]; then
ping -D "$@" | awk '{ if(gsub(/\[|\]/, "", $1)) $1=strftime("[\033[34m%F %T\033[0m]", $1); print; fflush()}'
else
ping -D "$@" | awk '{ if(gsub(/\[|\]/, "", $1)) $1=strftime("[%F %T]", $1); print; fflush()}'
fi
}

mtr

若需要将mtr的结果提供给第三方,建议可以使用-rc参数,r代表不使用交互界面,而是在最后给出一个探测结果报告;c参数指定需要作几次探测(一般建议是至少200个包,可以配合-i参数减少包间隔来加快得到结果的时间)。

traceroute

和mtr不同的是,traceroute默认使用UDP作为四层协议,下层还是依靠IP头的TTL来控制中间的节点返回ICMP差错报文,来获得中间节点的IP和延时。唯一的区别是,在达到目标节点时,若是ICMP协议,目标大概率是会回复ICMP reply;如果是UDP协议,按照RFC协议规定,系统是要回复ICMP 端口不可达的差错报文,虽然三大平台Windows/macOS/Linux都实现了这个行为,但出于某些原因,这个包可能还是会在链路上被丢弃,导致路由跟踪的结果无法显示出最后一跳。所以建议在一般的情况下,traceroute命令可以加上-I参数,让程序使用ICMP协议来发送探测数据包。

dstat

dstat 监控

image-20210425082343156

dstat -cdgilmnrsy –aio –fs –lock –raw

参考资料

http://perthcharles.github.io/2015/09/07/wiki-tcp-retries/

tcpping2

就是要你懂TCP--半连接队列和全连接队列

关于TCP 半连接队列和全连接队列

最近碰到一个client端连接异常问题,然后定位分析发现是因为全连接队列满了导致的。查阅各种资料文章和通过一系列的实验对TCP连接队列有了更深入的理解

查资料过程中发现没有文章把这两个队列以及怎么观察他们的指标说清楚,希望通过这篇文章能说清楚:

  1. 这两个队列是干什么用的;

2)怎么设置和观察他们的最大值;

3)怎么查看这两个队列当前使用到了多少;

4)一旦溢出的后果和现象是什么

问题描述

场景:JAVA的client和server,使用socket通信。server使用NIO。

1.间歇性的出现client向server建立连接三次握手已经完成,但server的selector没有响应到这连接。
2.出问题的时间点,会同时有很多连接出现这个问题。
3.selector没有销毁重建,一直用的都是一个。
4.程序刚启动的时候必会出现一些,之后会间歇性出现。

分析问题

正常TCP建连接三次握手过程:

image.png

  • 第一步:client 发送 syn 到server 发起握手;
  • 第二步:server 收到 syn后回复syn+ack给client;
  • 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)

从问题的描述来看,有点像TCP建连接的时候全连接队列(accept队列,后面具体讲)满了,尤其是症状2、4. 为了证明是这个原因,马上通过 netstat -s | egrep “listen” 去看队列的溢出统计数据:

667399 times the listen queue of a socket overflowed

反复看了几次之后发现这个overflowed 一直在增加,那么可以明确的是server上全连接队列一定溢出了

接着查看溢出后,OS怎么处理:

# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0

tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接队列满了那么 server 扔掉 client 发过来的ack(在server端认为连接还没建立起来)

为了证明客户端应用代码的异常跟全连接队列满有关系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的时候如果全连接队列满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来)。

接着测试,这时在客户端异常中可以看到很多connection reset by peer的错误,到此证明客户端错误是这个原因导致的(逻辑严谨、快速证明问题的关键点所在)

于是开发同学翻看java 源代码发现socket 默认的backlog(这个值控制全连接队列的大小,后面再详述)是50,于是改大重新跑,经过12个小时以上的压测,这个错误一次都没出现了,同时观察到 overflowed 也不再增加了。

到此问题解决,简单来说TCP三次握手后有个accept队列,进到这个队列才能从Listen变成accept,默认backlog 值是50,很容易就满了。满了之后握手第三步的时候server就忽略了client发过来的ack包(隔一段时间server重发握手第二步的syn+ack包给client),如果这个连接一直排不上队就异常了。

但是不能只是满足问题的解决,而是要去复盘解决过程,中间涉及到了哪些知识点是我所缺失或者理解不到位的;这个问题除了上面的异常信息表现出来之外,还有没有更明确地指征来查看和确认这个问题。

深入理解TCP握手过程中建连接的流程和队列

image.png

如上图所示,这里有两个队列:syns queue(半连接队列);accept queue(全连接队列)

三次握手中,在第一步server收到client的syn后,把这个连接信息放到半连接队列中,同时回复syn+ack给client(第二步);

题外话,比如syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,导致server上这个队列满其它正常请求无法进来

第三步的时候server收到client的ack,如果这时全连接队列没满,那么从半连接队列拿出这个连接的信息放入到全连接队列中,同时将连接状态从 SYN_RECV 改成 ESTABLISHED 状态,否则按tcp_abort_on_overflow指示的执行。

这时如果全连接队列满了并且tcp_abort_on_overflow是0的话,server会扔掉三次握手中第三步收到的ack(假装没有收到一样),过一段时间再次发送syn+ack给client(也就是重新走握手的第二步),如果client超时等待比较短,就很容易异常了。其实这个时候client认为连接已经建立了,可以发数据或者可以断开,而实际server上连接还没建立好(还没能力)。

在我们的os中retry 第二步的默认次数是2(centos默认是5次):

net.ipv4.tcp_synack_retries = 2

如果TCP连接队列溢出,有哪些指标可以看呢?

上述解决过程有点绕,听起来蒙,那么下次再出现类似问题有什么更快更明确的手段来确认这个问题呢?

通过具体的、可见的东西来强化我们对知识点的理解和吸收

netstat -s

[root@server ~]#  netstat -s | egrep "listen|LISTEN" 
667399 times the listen queue of a socket overflowed  //全连接队列溢出
//以下两行是一个意思, netstat 版本不同导致显示不同,新版本显示为 dropped
667399 SYNs to LISTEN sockets ignored                 //含全连接/半连接队列溢出+PAWSPassive 等
905080 SYNs to LISTEN sockets dropped                 //含全连接/半连接队列溢出+PAWSPassive 等
 
//和本文无关的一些其它指标 
919614 passive connections rejected because of time stamp //tcp_recycle 丢 syn 包,对应/proc/net/netstat 中 PAWSPassive
TCPTimeWaitOverflow: 65000                                //tcp_max_tw_buckets 溢出

比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了

ignored 和 dropped:

image-20240807112156872

这些指标都是从 /proc/net/netstat 中采集,含义可以参考 net-tool 工具(netstat 命令来源)中的源码

image-20240807112356787

ss 命令

[root@server ~]# ss -lnt
Recv-Q Send-Q Local Address:Port  Peer Address:Port 
0        50               *:3306             *:* 

上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全连接队列最大为50,第一列Recv-Q为全连接队列当前使用了多少

全连接队列的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数

《Unix Network Programming》中关于backlog的描述

The backlog argument to the listen function has historically specified the maximum value for the sum of both queues.

There has never been a formal definition of what the backlog means. The 4.2BSD man page says that it “defines the maximum length the queue of pending connections may grow to.” Many man pages and even the POSIX specification copy this definition verbatim, but this definition does not say whether a pending connection is one in the SYN_RCVD state, one in the ESTABLISHED state that has not yet been accepted, or either. The historical definition in this bullet is the Berkeley implementation, dating back to 4.2BSD, and copied by many others.

关于 somaxconn 终于在2019年将默认值从128调整到了2048, 这个调整合并到了kernel 5.17中

SOMAXCONN is /proc/sys/net/core/somaxconn default value.

It has been defined as 128 more than 20 years ago.

Since it caps the listen() backlog values, the very small value has
caused numerous problems over the years, and many people had
to raise it on their hosts after beeing hit by problems.

Google has been using 1024 for at least 15 years, and we increased
this to 4096 after TCP listener rework has been completed, more than
4 years ago. We got no complain of this change breaking any
legacy application.

Many applications indeed setup a TCP listener with listen(fd, -1);
meaning they let the system select the backlog.

Raising SOMAXCONN lowers chance of the port being unavailable under
even small SYNFLOOD attack, and reduces possibilities of side channel
vulnerabilities.

这个时候可以跟我们的代码建立联系了,比如Java创建ServerSocket的时候会让你传入backlog的值:

ServerSocket()
	Creates an unbound server socket.
ServerSocket(int port)
	Creates a server socket, bound to the specified port.
ServerSocket(int port, int backlog)
	Creates a server socket and binds it to the specified local port number, with the specified backlog.
ServerSocket(int port, int backlog, InetAddress bindAddr)
	Create a server with the specified port, listen backlog, and local IP address to bind to.

(来自JDK帮助文档:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)

半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。 不同版本的os会有些差异

我们写代码的时候从来没有想过这个backlog或者说大多时候就没给他值(那么默认就是50),直接忽视了他,首先这是一个知识点的忙点;其次也许哪天你在哪篇文章中看到了这个参数,当时有点印象,但是过一阵子就忘了,这是知识之间没有建立连接,不是体系化的。但是如果你跟我一样首先经历了这个问题的痛苦,然后在压力和痛苦的驱动自己去找为什么,同时能够把为什么从代码层推理理解到OS层,那么这个知识点你才算是比较好地掌握了,也会成为你的知识体系在TCP或者性能方面成长自我生长的一个有力抓手

netstat 命令

netstat跟ss命令一样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes;而 Send 则是发送队列中没有被远程主机确认的 bytes 数

$netstat -tn  
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address   Foreign Address State  
tcp    0  0 server:8182  client-1:15260 SYN_RECV   
tcp    0 28 server:22    client-1:51708  ESTABLISHED
tcp    0  0 server:2376  client-1:60269 ESTABLISHED

netstat -tn 看到的 Recv-Q 跟全连接半连接中的Queue没有关系,这里特意拿出来说一下是因为容易跟 ss -lnt 的 Recv-Q 搞混淆

所以ss看到的 Send-Q、Recv-Q是目前全连接队列使用情况和最大设置
netstat看到的 Send-Q、Recv-Q,如果这个连接是Established状态的话就是发出的bytes并且没有ack的包、和os接收到的bytes还没交给应用

我们看到的 Recv-Q、Send-Q获取源代码如下( net/ipv4/tcp_diag.c ):

static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
  void *_info)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    struct tcp_info *info = _info;
    
    if (sk->sk_state == TCP_LISTEN) {  //LISTEN状态下的 Recv-Q、Send-Q
	    r->idiag_rqueue = sk->sk_ack_backlog;
	    r->idiag_wqueue = sk->sk_max_ack_backlog; //Send-Q 最大backlog
    } else {						   //其它状态下的 Recv-Q、Send-Q
	    r->idiag_rqueue = max_t(int, tp->rcv_nxt - tp->copied_seq, 0);
	    r->idiag_wqueue = tp->write_seq - tp->snd_una;
    }
    if (info != NULL)
    	tcp_get_info(sk, info);
}

比如如下netstat -t 看到的Recv-Q有大量数据堆积,那么一般是CPU处理不过来导致的:

image.png

netstat看到的listen状态的Recv-Q/Send-Q

netstat 看到的listen状态下的Recv-Q/Send-Q意义跟 ss -lnt看到的完全不一样。上面的 netstat 对非listen的描述没问题,但是listen状态似乎Send-Q这个值总是0,这要去看netstat的代码了,实际上Listen状态它不是一个连接,所以肯定统计不到流量,netstat似乎只是针对连接的统计

从网上找了两个Case,server的8765端口故意不去读取对方发过来的2000字节,所看到的是:

$ netstat -ano | grep 8765  
tcp0  0 0.0.0.0:87650.0.0.0:*   LISTEN  off (0.00/0/0)  
tcp 2000  0 10.100.70.140:8765  10.100.70.139:43634 ESTABLISHED off (0.00/0/0)

第二个Case,8000端口的半连接满了(129),但是这个时候Send-Q还是看到的0

$ netstat -ntap | grep 8000 
tcp      129      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      1526/XXXXX- 
tcp        0      0 9.11.6.36:8000          9.11.6.37:48306         SYN_RECV    - 
tcp        0      0 9.11.6.36:8000          9.11.6.34:44936         SYN_RECV    - 
tcp      365      0 9.11.6.36:8000          9.11.6.37:58446         CLOSE_WAIT  -  

案列:如果TCP连接队列溢出,抓包是什么现象呢?

image.png

如上图server端8989端口的服务全连接队列已经满了(设置最大5,已经6了,通过后面步骤的ss -lnt可以验证), 所以 server尝试过一会假装继续三次握手的第二步,跟client说我们继续谈恋爱吧。可是这个时候client比较性急,忙着分手了,server觉得都没恋上那什么分手啊。所以接下来两边自说自话也就是都不停滴重传

通过ss和netstat所观察到的状态

image.png

image.png

另外一个案例,虽然最终的锅不是TCP全连接队列太小,但是也能从重传、队列溢出找到根因

实践验证一下上面的理解

上面是通过一些具体的工具、指标来认识全连接队列,接下来结合文章开始的问题来具体验证一下

把java中backlog改成10(越小越容易溢出),继续跑压力,这个时候client又开始报异常了,然后在server上通过 ss 命令观察到:

Fri May  5 13:50:23 CST 2017
Recv-Q Send-QLocal Address:Port  Peer Address:Port
11         10         *:3306               *:*

按照前面的理解,这个时候我们能看到3306这个端口上的服务全连接队列最大是10,但是现在有11个在队列中和等待进队列的,肯定有一个连接进不去队列要overflow掉,同时也确实能看到overflow的值在不断地增大。

能够进入全连接队列的 Socket 最大数量始终比配置的全连接队列最大长度 + 1,结合内核代码,发现内核在判断全连接队列是否满的情况下,使用的是 > 而非 >=

Linux下发SIGSTOP信号发给用户态进程,就可以让进程stop不再accept,模拟accept溢出的效果

kill -19 pid 即可; kill -18 pid 恢复暂停进程

1
2
3
#define SIGKILL     9    /* Kill, unblockable (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */

tsar监控accept队列的溢出

tsar –tcpx -s=lisove -li 1

Tomcat和Nginx中的Accept队列参数

Tomcat 默认短连接,backlog(Tomcat里面的术语是Accept count)Ali-tomcat默认是200, Apache Tomcat默认100.

#ss -lnt
Recv-Q Send-Q   Local Address:Port Peer Address:Port
0       100                 *:8080            *:*

Nginx默认是511

$sudo ss -lnt
State  Recv-Q Send-Q Local Address:PortPeer Address:Port
LISTEN    0     511              *:8085           *:*
LISTEN    0     511              *:8085           *:*

因为Nginx是多进程模式,所以看到了多个8085,也就是多个进程都监听同一个端口以尽量避免上下文切换来提升性能

image.png

进一步思考 client fooling 问题

如果client走完第三步在client看来连接已经建立好了,但是server上的对应的连接有可能因为accept queue满了而仍然是syn_recv状态,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,还是实践看看)

先来看一个例子:

image.png

如上图,图中3号包是三次握手中的第三步,client发送ack给server,这个时候在client看来握手完成,然后4号包中client发送了一个长度为238的包给server,因为在这个时候client认为连接建立成功,但是server上这个连接实际没有ready,所以server没有回复,一段时间后client认为丢包了然后重传这238个字节的包,等到server reset了该连接(或者client一直重传这238字节到超时,client主动发fin包断开该连接,如下图)

image.png

这个问题也叫client fooling,可以看这个patch在4.10后修复了:https://github.com/torvalds/linux/commit/5ea8ea2cb7f1d0db15762c9b0bb9e7330425a071 ,修复的逻辑就是,如果全连接队列满了就不再回复syn+ack了,免得client误认为这个连接建立起来了,这样client端收不到syn+ack就只能重发syn。

从上面的实际抓包来看不是reset,而是server忽略这些包,然后client重传,一定次数后client认为异常,然后断开连接。

如果这个连接已经放入了全连接队列但是应用没有accept(比如应用卡住了),那么这个时候client发过来的包是不会被扔掉,OS会先收下放到接收buffer中,知道buffer满了再扔掉新进来的。

过程中发现的一个奇怪问题

[root@server ~]# date; netstat -s | egrep "listen|LISTEN" 
Fri May  5 15:39:58 CST 2017
1641685 times the listen queue of a socket overflowed  # 全连接队列溢出
1641685 SYNs to LISTEN sockets ignored                 # 半连接队列溢出

[root@server ~]# date; netstat -s | egrep "listen|LISTEN" 
Fri May  5 15:39:59 CST 2017
1641906 times the listen queue of a socket overflowed
1641906 SYNs to LISTEN sockets ignored

如上所示:
overflowed 和 ignored 总是一样多,并且都是同步增加,overflowed 表示全连接队列溢出次数,SYNs to LISTEN socket ignored 表示半连接队列溢出等等指标的次数,没这么巧吧。

翻看内核源代码(https://github.com/torvalds/linux/blob/v6.13-rc6/net/ipv4/tcp_ipv4.c#L1849 ):

image.png

可以看到overflow的时候一定会drop++(socket ignored),也就是drop一定大于等于overflow。

同时我也查看了另外几台server的这两个值来证明drop一定大于等于overflow:

server1
150 SYNs to LISTEN sockets dropped

server2
193 SYNs to LISTEN sockets dropped

server3
16329 times the listen queue of a socket overflowed
16422 SYNs to LISTEN sockets dropped

server4
20 times the listen queue of a socket overflowed
51 SYNs to LISTEN sockets dropped

server5
984932 times the listen queue of a socket overflowed
988003 SYNs to LISTEN sockets dropped

总结:SYNs to LISTEN sockets dropped(ListenDrops)表示:全连接/半连接队列溢出以及PAWSPassive 等造成的 SYN 丢包

ListenDrops 表示: SYNs to LISTEN sockets dropped

那么全连接队列满了会影响半连接队列吗?

来看三次握手第一步的源代码(http://elixir.free-electrons.com/linux/v2.6.33/source/net/ipv4/tcp_ipv4.c#L1249):

image.png

TCP 三次握手第一步的时候如果全连接队列满了会影响第一步drop 半连接的发生,流程的如下:

tcp_v4_do_rcv->tcp_rcv_state_process->tcp_v4_conn_request
//如果accept backlog队列已满,且未超时的request socket的数量大于1,则丢弃当前请求  
  if(sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_yong(sk)>1)
      goto drop;

半连接队列的长度

半连接队列的长度由三个参数指定:

  • 调用 listen 时,传入的 backlog
  • /proc/sys/net/core/somaxconn 默认值为 128
  • /proc/sys/net/ipv4/tcp_max_syn_backlog* 默认值为 1024

假设 listen 传入的 backlog = 128,其他配置采用默认值,来计算下半连接队列的最大长度

1
2
3
4
5
6
7
backlog = min(somaxconn, backlog) = min(128, 128) = 128
nr_table_entries = backlog = 128
nr_table_entries = min(backlog, sysctl_max_syn_backlog) = min(128, 1024) = 128
nr_table_entries = max(nr_table_entries, 8) = max(128, 8) = 128
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) = 256
max_qlen_log = max(3, log2(nr_table_entries)) = max(3, 8) = 8
max_queue_length = 2^max_qlen_log = 2^8 = 256

可以得到半队列大小是 256,以上计算方法:

1
2
3
4
5
6
7
8
backlog = min(somaxconn, backlog)
nr_table_entries = backlog
nr_table_entries = min(backlog, sysctl_max_syn_backlog)
nr_table_entries = max(nr_table_entries, 8)
// roundup_pow_of_two: 将参数向上取整到最小的 2^n,注意这里存在一个 +1
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1)
max_qlen_log = max(3, log2(nr_table_entries))
max_queue_length = 2^max_qlen_log

没开启tcp_syncookies的话,到tcp_max_syn_backlog 75%水位就开始drop syn包了

总结

Linux内核就引入半连接队列(用于存放收到SYN,但还没收到ACK的连接)和全连接队列(用于存放已经完成3次握手,但是应用层代码还没有完成 accept() 的连接)两个概念,用于存放在握手中的连接。

全连接队列、半连接队列溢出这种问题很容易被忽视,但是又很关键,特别是对于一些短连接应用(比如Nginx、PHP,当然他们也是支持长连接的)更容易爆发。 一旦溢出,从cpu、线程状态看起来都比较正常,但是压力上不去,在client看来rt也比较高(rt=网络+排队+真正服务时间),但是从server日志记录的真正服务时间来看rt又很短。

另外就是jdk、netty等一些框架默认backlog比较小,可能有些情况下导致性能上不去,比如 @毕玄 碰到的这个 《netty新建连接并发数很小的case》
都是类似原因

希望通过本文能够帮大家理解TCP连接过程中的半连接队列和全连接队列的概念、原理和作用,更关键的是有哪些指标可以明确看到这些问题。

另外每个具体问题都是最好学习的机会,光看书理解肯定是不够深刻的,请珍惜每个具体问题,碰到后能够把来龙去脉弄清楚。

为什么 netstat 看到的 listen 状态的 SEND-Q 总是0

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e7073830cc8b52ef3df7dd150e4dac7706e0e104

1
2
3
4
5
6
#netstat -ntap | grep 8000
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 129 0 0.0.0.0:8000 0.0.0.0:* LISTEN 1526/XXXXX- //第三列总是0
tcp 0 0 9.11.6.36:8000 9.11.6.37:48306 SYN_RECV -
tcp 0 0 9.11.6.36:8000 9.11.6.34:44936 SYN_RECV -
tcp 365 0 9.11.6.36:8000 9.11.6.37:58446 CLOSE_WAIT -

如下图代码,最开始 2 边一起支持了 LISTEN socket 显示 accept 队列当前长度,后来右边支持显示最大长度时,左边没有加。netstat 是读取的 /proc/net/tcp,然后 ss 走了 diag 接口去拿的:

image-20240506134119413


也就是改了 tcp_diag_get_info ,但是忘了改 get_tcp4_sock,但是写 man netstat 自己也没验证过就以讹传讹

d31d2480d9840f0d88739941bed0654d5b581dcb ?

参考文章

星球同学最详细的实践(代码、抓包等等,共13篇,含演示代码) https://xiaodongq.github.io/2024/05/30/tcp_syn_queue/ https://xiaodongq.github.io/2024/06/26/libbpf-trace-tcp_connect/

X推友实验:https://wgzhao.github.io/notes/troubleshooting/deep-in-tcp-connect/

张师傅:https://juejin.cn/post/6844904071367753736

详细的实验以及分析,附Go 实验代码:https://www.51cto.com/article/687595.html

http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html

http://www.cnblogs.com/zengkefu/p/5606696.html

http://www.cnxct.com/something-about-phpfpm-s-backlog/

http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/

http://jin-yang.github.io/blog/network-synack-queue.html#

http://blog.chinaunix.net/uid-20662820-id-4154399.html

https://www.atatech.org/articles/12919

https://blog.cloudflare.com/syn-packet-handling-in-the-wild/

How Linux allows TCP introspection The inner workings of bind and listen on Linux.

https://www.cnblogs.com/xiaolincoding/p/12995358.html

从一次线上问题说起,详解 TCP 半连接队列、全连接队列–详细的实验验证各种溢出

案例三:诡异的幽灵连接,全连接队列满后4.10内核不再回复syn+ack, 但是3.10会回syn+ack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
commit 5ea8ea2cb7f1d0db15762c9b0bb9e7330425a071
Author: Eric Dumazet <edumazet@google.com>
Date: Thu Oct 27 00:27:57 2016

tcp/dccp: drop SYN packets if accept queue is full

Per listen(fd, backlog) rules, there is really no point accepting a SYN,
sending a SYNACK, and dropping the following ACK packet if accept queue
is full, because application is not draining accept queue fast enough.

This behavior is fooling TCP clients that believe they established a
flow, while there is nothing at server side. They might then send about
10 MSS (if using IW10) that will be dropped anyway while server is under
stress.

- /* Accept backlog is full. If we have already queued enough
- * of warm entries in syn queue, drop request. It is better than
- * clogging syn queue with openreqs with exponentially increasing
- * timeout.
- */
- if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
+ if (sk_acceptq_is_full(sk)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}

Client 不断地 connect 建新连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#define MAXLINE 4096

int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);

for (n = 0; n < 100; n++)
{
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
return 0;
}

//客户端不停的向服务端发起新连接,成功之后继续发,没成功会阻塞在这里 //--------------
if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
return 0;
}

printf("connected to server: %d\n", n);
close(sockfd);
}

return 0;
}

server 故意不accept:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>

#define MAXLINE 4096

int main(int argc, char* argv[])
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;

if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
{
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}

memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)
{
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 1;
}
//全连接队列设置为10 //--------------
if(listen(listenfd, 10) == -1)
{
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 2;
}

if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1)
{
printf("accept socket error: %s(errno: %d)", strerror(errno), errno);
return 3;
}

printf("accepet a socket\n");

//服务端仅accept一次,之后就不再accept,此时全连接队列会被堆满 //----------------------------------
sleep(1000);

close(connfd);
close(listenfd);
return 0;
}

就是要你懂TCP--握手和挥手

就是要你懂TCP–握手和挥手

看过太多tcp相关文章,但是看完总是不过瘾,似懂非懂,反复考虑过后,我觉得是那些文章太过理论,看起来没有体感,所以吸收不了。

希望这篇文章能做到言简意赅,帮助大家透过案例来理解原理

tcp的特点

这个大家基本都能说几句,面试的时候候选人也肯定会告诉你这些:

  • 三次握手
  • 四次挥手
  • 可靠连接
  • 丢包重传
  • 速度自我调整

但是我只希望大家记住一个核心的:tcp是可靠传输协议,它的所有特点都为这个可靠传输服务

那么tcp是怎么样来保障可靠传输呢?

tcp在传输过程中都有一个ack,接收方通过ack告诉发送方收到那些包了。这样发送方能知道有没有丢包,进而确定重传

tcp建连接的三次握手

来看一个java代码连接数据库的三次握手过程

image.png

三个红框表示建立连接的三次握手:

  • 第一步:client 发送 syn 到server 发起握手;
  • 第二步:server 收到 syn后回复syn+ack给client;
  • 第三步:client 收到syn+ack后,回复server一个ack表示收到了server的syn+ack(此时client的48287端口的连接已经是established)

握手的核心目的是告知对方seq(绿框是client的初始seq,蓝色框是server 的初始seq),对方回复ack(收到的seq+包的大小),这样发送端就知道有没有丢包了

握手的次要目的是告知和协商一些信息,图中黄框。

  • MSS–最大传输包
  • SACK_PERM–是否支持Selective ack(用户优化重传效率)
  • WS–窗口计算指数(有点复杂的话先不用管)

image.png

全连接队列(accept queue)的长度是由 listen(sockfd, backlog) 这个函数里的 backlog 控制的,而该 backlog 的最大值则是 somaxconn。somaxconn 在 5.4 之前的内核中,默认都是 128(5.4 开始调整为了默认 4096)

当服务器中积压的全连接个数超过该值后,新的全连接就会被丢弃掉。Server 在将新连接丢弃时,有的时候需要发送 reset 来通知 Client,这样 Client 就不会再次重试了。不过,默认行为是直接丢弃不去通知 Client。至于是否需要给 Client 发送 reset,是由 tcp_abort_on_overflow 这个配置项来控制的,该值默认为 0,即不发送 reset 给 Client。推荐也是将该值配置为 0

net.ipv4.tcp_abort_on_overflow = 0

这就是tcp为什么要握手建立连接,就是为了解决tcp的可靠传输

物理上没有一个连接的东西在这里,udp也类似会占用端口、ip,但是大家都没说过udp的连接。而本质上我们说tcp的连接是指tcp是拥有和维护一些状态信息的,这个状态信息就包含seq、ack、窗口/buffer,tcp握手就是协商出来这些初始值。这些状态才是我们平时所说的tcp连接的本质。

unres_qlen 和 握手

tcp connect 的本地流程是这样的:

1、tcp发出SYN建链报文后,报文到ip层需要进行路由查询

2、路由查询完成后,报文到arp层查询下一跳mac地址

3、如果本地没有对应网关的arp缓存,就需要缓存住这个报文,发起arp报文请求

4、arp层收到arp回应报文之后,从缓存中取出SYN报文,完成mac头填写并发送给驱动。

问题在于,arp层报文缓存队列长度默认为3。如果你运气不好,刚好赶上缓存已满,这个报文就会被丢弃。

TCP层发现SYN报文发出去3s(默认值)还没有回应,就会重发一个SYN。这就是为什么少数连接会3s后才能建链。

幸运的是,arp层缓存队列长度是可配置的,用 sysctl -a | grep unres_qlen 就能看到,默认值为3。

建连接失败经常碰到的问题

内核扔掉syn的情况(握手失败,建不上连接):

  • rp_filter 命中(rp_filter=1, 多网卡环境), troubleshooting: netstat -s | grep -i filter ;
  • snat/dnat的时候宿主机port冲突,内核会扔掉 syn包。 troubleshooting: sudo conntrack -S | grep insert_failed //有不为0的
  • 全连接队列满的情况
  • syn flood攻击
  • 若远端服务器的内核参数 net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps 的值都为 1,则远端服务器会检查每一个报文中的时间戳(Timestamp),若 Timestamp 不是递增的关系,不会响应这个报文。配置 NAT 后,远端服务器看到来自不同的客户端的源 IP 相同,但 NAT 前每一台客户端的时间可能会有偏差,报文中的 Timestamp 就不是递增的情况。nat后的连接,开启timestamp。因为快速回收time_wait的需要,会校验时间该ip上次tcp通讯的timestamp大于本次tcp(nat后的不同机器经过nat后ip一样,保证不了timestamp递增)
  • NAT 哈希表满导致 ECS 实例丢包 nf_conntrack full

tcp断开连接的四次挥手

再来看java连上mysql后,执行了一个SQL: select sleep(2); 然后就断开了连接

image.png

四个红框表示断开连接的四次挥手:

  • 第一步: client主动发送fin包给server
  • 第二步: server回复ack(对应第一步fin包的ack)给client,表示server知道client要断开了
  • 第三步: server发送fin包给client,表示server也可以断开了
  • 第四部: client回复ack给server,表示既然双发都发送fin包表示断开,那么就真的断开吧

image.png

除了 CLOSE_WAIT 状态外,其余两个状态都有对应的系统配置项来控制。

我们首先来看 FIN_WAIT_2 状态,TCP 进入到这个状态后,如果本端迟迟收不到对端的 FIN 包,那就会一直处于这个状态,于是就会一直消耗系统资源。Linux 为了防止这种资源的开销,设置了这个状态的超时时间 tcp_fin_timeout,默认为 60s,超过这个时间后就会自动销毁该连接。

至于本端为何迟迟收不到对端的 FIN 包,通常情况下都是因为对端机器出了问题,或者是因为太繁忙而不能及时 close()。所以,通常我们都建议将 tcp_fin_timeout 调小一些,以尽量避免这种状态下的资源开销。对于数据中心内部的机器而言,将它调整为 2s 足以:

net.ipv4.tcp_fin_timeout = 2

TIME_WAIT 状态存在的意义是:最后发送的这个 ACK 包可能会被丢弃掉或者有延迟,这样对端就会再次发送 FIN 包。如果不维持 TIME_WAIT 这个状态,那么再次收到对端的 FIN 包后,本端就会回一个 Reset 包,这可能会产生一些异常。

image.png

为什么握手三次、挥手四次

这个问题太恶心,面试官太喜欢问,其实大部分面试官只会背诵:因为TCP是双向的,所以关闭需要四次挥手……。

你要是想怼面试官的话可以问他握手也是双向的但是只需要三次呢?

我也不知道怎么回答。网上都说tcp是双向的,所以断开要四次。但是我认为建连接也是双向的(双向都协调告知对方自己的seq号),为什么不需要四次握手呢,所以网上说的不一定精准。

你再看三次握手的第二步发 syn+ack,如果拆分成两步先发ack再发syn完全也是可以的(效率略低),这样三次握手也变成四次握手了。

看起来挥手的时候多一次,主要是收到第一个fin包后单独回复了一个ack包,如果能回复fin+ack那么四次挥手也就变成三次了。 来看一个案例:

image.png

图中第二个红框就是回复的fin+ack,这样四次挥手变成三次了(如果一个包就是一次的话)。

我的理解:之所以绝大数时候我们看到的都是四次挥手,是因为收到fin后,知道对方要关闭了,然后OS通知应用层要关闭,这里应用层可能需要做些准备工作,可能还有数据没发送完,所以内核先回ack,等应用准备好了主动调close时再发fin 。 握手过程没有这个准备过程所以可以立即发送syn+ack(把这里的两步合成一步了)。 内核收到对方的fin后,只能ack,不能主动替应用来fin,因为他不清楚应用能不能关闭。

ack=seq+len

ack总是seq+len(包的大小),这样发送方明确知道server收到那些东西了

但是特例是三次握手和四次挥手,虽然len都是0,但是syn和fin都要占用一个seq号,所以这里的ack都是seq+1

image.png

看图中左边红框里的len+seq就是接收方回复的ack的数字,表示这个包接收方收到了。然后下一个包的seq就是前一个包的len+seq,依次增加,一旦中间发出去的东西没有收到ack就是丢包了,过一段时间(或者其他方式)触发重传,保障了tcp传输的可靠性。

三次握手中协商的其它信息

MSS 最大一个包中能传输的信息(不含tcp、ip包头),MSS+包头就是MTU(最大传输单元),如果MTU过大可能在传输的过程中被卡住过不去造成卡死(这个大小的包一直传输不过去),跟丢包还不一样

MSS的问题具体可以看我这篇文章: scp某个文件的时候卡死问题的解决过程

SACK_PERM 用于丢包的话提升重传效率,比如client一次发了1、2、3、4、5 这5个包给server,实际server收到了 1、3、4、5这四个包,中间2丢掉了。这个时候server回复ack的时候,都只能回复2,表示2前面所有的包都收到了,给我发第二个包吧,如果server 收到3、4、5还是没有收到2的话,也是回复ack 2而不是回复ack 3、4、5、6的,表示快点发2过来。

但是这个时候client虽然知道2丢了,然后会重发2,但是不知道3、4、5有没有丢啊,实际3、4、5 server都收到了,如果支持sack,那么可以ack 2的时候同时告诉client 3、4、5都收到了,这样client重传的时候只重传2就可以,如果没有sack的话那么可能会重传2、3、4、5,这样效率就低了。

来看一个例子:

image.png

图中的红框就是SACK。

知识点:ack数字表示这个数字前面的数据收到了

TIME_WAIT 和 CLOSE_WAIT

假设服务端和客户端跑在同一台机器上,服务端监听在 18080端口上,客户端使用18089端口建立连接。

如果client主动断开连接那么就会看到client端的连接在 TIME_WAIT:

1
2
3
# netstat -ant |grep 1808
tcp 0 0 0.0.0.0:18080 0.0.0.0:* LISTEN
tcp 0 0 192.168.1.79:18089 192.168.1.79:18080 TIME_WAIT

如果Server主动断开连接(也就是18080)那么就会看到client端的连接在CLOSE_WAIT 而Server在FIN_WAIT2:

1
2
3
# netstat -ant |grep 1808
tcp 0 0 192.168.1.79:18080 192.168.1.79:18089 FIN_WAIT2 --<< server
tcp 0 0 192.168.1.79:18089 192.168.1.79:18080 CLOSE_WAIT --<< client

TIME_WAIT是主动断连方出现的状态( 2MSL)

被动关闭方收到fin后有两种选择

如下描述是server端主动关闭的情况

1 如果client也立即断开,那么Server的这个连接会进入 TIME_WAIT状态

1
2
3
# netstat -ant |grep 1808
tcp 0 0 0.0.0.0:18080 0.0.0.0:* LISTEN --<< server还在
tcp 0 0 192.168.1.79:18080 192.168.1.79:18089 TIME_WAIT --<< server

2 client 坚持不断开过 Server 一段时间后(3.10:net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120, 4.19:net.ipv4.tcp_fin_timeout = 15)会结束这个连接但是client还是会 在CLOSE_WAIT 直到client进程退出

1
2
# netstat -ant |grep 1808
tcp 0 0 192.168.1.79:18089 192.168.1.79:18080 CLOSE_WAIT

CLOSE_WAIT

CLOSE_WAIT是被动关闭端在等待应用进程的关闭

通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,一般有如下几种可能:

  • 程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。
  • 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
  • BACKLOG 太大:此处的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的话,设想突然遭遇大访问量的话,即便响应速度不慢,也可能出现来不及消费的情况,导致多余的请求还在队列里就被对方关闭了。

如果你通过「netstat -ant」或者「ss -ant」命令发现了很多 CLOSE_WAIT 连接,请注意结果中的「Recv-Q」和「Local Address」字段,通常「Recv-Q」会不为空,它表示应用还没来得及接收数据,而「Local Address」表示哪个地址和端口有问题,我们可以通过「lsof -i:」来确认端口对应运行的是什么程序以及它的进程号是多少。

如果是我们自己写的一些程序,比如用 HttpClient 自定义的蜘蛛,那么八九不离十是程序问题,如果是一些使用广泛的程序,比如 Tomcat 之类的,那么更可能是响应速度太慢或者 timeout 设置太小或者 BACKLOG 设置过大导致的故障。

server端大量close_wait案例

看了这么多理论,下面用个案例来检查自己对close_wait理论(tcp握手本质)的掌握。同时也可以看看自己从知识到问题的推理能力(跟文章最后的知识效率呼应一下)。

问题描述:

服务端出现大量CLOSE_WAIT 个数正好 等于somaxconn(调整somaxconn后 CLOSE_WAIT 也会跟着变成一样的值)

根据这个描述先不要往下看,自己推理分析下可能的原因。

我的推理如下:

从这里看起来,client跟server成功建立了somaxconn个连接(somaxconn小于backlog,所以accept queue只有这么大),但是应用没有accept这个连接,导致这些连接一直在accept queue中。但是这些连接的状态已经是ESTABLISHED了,也就是client可以发送数据了,数据发送到server后OS ack了,并放在os的tcp buffer中,应用一直没有accept也就没法读取数据。client于是发送fin(可能是超时、也可能是简单发送数据任务完成了得结束连接),这时Server上这个连接变成了CLOSE_WAIT .

也就是从开始到结束这些连接都在accept queue中,没有被应用accept,很快他们又因为client 发送 fin 包变成了CLOSE_WAIT ,所以始终看到的是服务端出现大量CLOSE_WAIT 并且个数正好等于somaxconn(调整somaxconn后 CLOSE_WAIT 也会跟着变成一样的值)。

如下图所示,在连接进入accept queue后状态就是ESTABLISED了,也就是可以正常收发数据和fin了。client是感知不到server是否accept()了,只是发了数据后server的os代为保存在OS的TCP buffer中,因为应用没来取自然在CLOSE_WAIT 后应用也没有close(),所以一直维持CLOSE_WAIT 。

得检查server 应用为什么没有accept。

image.png

结论:

这个case的最终原因是因为OS的open files设置的是1024,达到了上限,进而导致server不能accept,但这个时候的tcp连接状态已经是ESTABLISHED了(这个状态变换是取决于内核收发包,跟应用是否accept()无关)。

同时从这里可以推断 netstat 即使看到一个tcp连接状态是ESTABLISHED也不能代表占用了 open files句柄。此时client可以正常发送数据了,只是应用服务在accept之前没法receive数据和close连接。

TCP连接状态图

image.png

总结下

tcp所有特性基本上核心都是为了可靠传输这个目标来服务的,然后有一些是出于优化性能的目的

三次握手建连接的详细过程可以参考我这篇: 关于TCP 半连接队列和全连接队列

后续希望再通过几个案例来深化一下上面的知识。


为什么要案例来深化一下上面的知识,说点关于学习的题外话

什么是工程效率,什么是知识效率

有些人纯看理论就能掌握好一门技能,还能举三反一,这是知识效率,这种人非常少;

大多数普通人都是看点知识然后结合实践来强化理解理论,要经过反反复复才能比较好地掌握一个知识,这就是工程效率,讲究技巧、工具来达到目的。

对于费曼(参考费曼学习法)这样的聪明人就是很容易看到一个理论知识就能理解这个理论知识背后的本质。

肯定知识效率最牛逼,但是拥有这种能力的人毕竟非常少。从小我们周边那种不怎么学的学霸型基本都是这类,这种学霸都还能触类旁通非常快地掌握一个新知识。剩下的绝大部分只能拼时间(刷题)+方法+总结等也能掌握一些知识

非常遗憾我就是工程效率型,只能羡慕那些知识效率型的学霸。但是这事又不能独立看待有些人在某些方向上是工程效率型,有些方向就又是知识效率型(有一种知识效率型是你掌握的实在太多也就比较容易触类旁通了,这算灰色知识效率型)

使劲挖掘自己在知识效率型方面的能力吧,即使灰色地带也行啊。

就是要你懂TCP--wireshark-dup-ack-issue

就是要你懂TCP–wireshark-dup-ack-issue

问题:

很多同学学会抓包后,经常拿着这样一个抓包来问我是怎么回事:

在wireshark中看到一个tcp会话中的两台机器突然一直互相发dup ack包,但是没有触发重传。每次重复ack都是间隔精确的20秒

如下截图:

client都一直在回复收到2号包(ack=2)了,可是server跟傻了一样居然还发seq=1的包(按理,应该发比2大的包啊)

系统配置:

net.ipv4.tcp_keepalive_time = 20
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 3

原因:

抓包不全的话wireshark有缺陷,把keepalive包识别成了dup ack包,看内容这种dup ack和keepalive似乎是一样的,flags都是0x010。keep alive的定义的是后退一格(seq少1)。

2、4、6、8……号包,都有一个“tcp acked unseen segment”。这个一般表示它ack的这个包,没有被抓到。Wirshark如何作出此判断呢?前面一个包是seq=1, len=0,所以正常情况下是ack = seq + len = 1,然而Wireshark看到的确是ack = 2, 它只能判断有一个seq =1, len = 1的包没有抓到。
dup ack也是类似道理,这些包完全符合dup ack的定义,因为“ack = ” 某个数连续多次出现了。

这一切都是因为keep alive的特殊性导致的。打开66号包的tcp层(见后面的截图),可以看到它的 next sequence number = 12583,表示正常情况下server发出的下一个包应该是seq = 12583。可是在下一个包,也就是68号包中,却是seq = 12582。keep alive的定义的确是这样,即后退一格。
Wireshark只有在抓到数据包(66号包)和keep alive包的情况下才有可能正确识别,前面的抓包中恰好在keep alive之前丢失了数据包,所以Wireshark就蒙了。

构造重现

如果用“frame.number >= 68” 过滤这个包,然后File–>export specified packets保存成一个新文件,再打开那个新文件,就会发现Wireshark又蒙了。本来能够正常识别的keep alive包又被错看成dup ack了,所以一旦碰到这种情况不要慌要稳

下面是知识点啦

Keepalive

TCP报文接收方必须回复的场景:

TCP携带字节数据
没有字节数据,携带SYN状态位
没有字节数据,携带FIN状态位

keepalive 提取历史发送的最后一个字节,充当心跳字节数据,依然使用该字节的最初序列号。也就是前面所说的seq回退了一个

对方收到后因为seq小于TCP滑动窗口的左侧,被判定为duplicated数据包,然后扔掉了,并回复一个duplicated ack

所以keepalive跟duplicated本质是一回事,就看wireshark能够正确识别了。

Duplication ack是指:

server收到了3和8号包,但是没有收到中间的4/5/6/7,那么server就会ack 3,如果client还是继续发8/9号包,那么server会继续发dup ack 3#1 ; dup ack 3#2 来向客户端说明只收到了3号包,不要着急发后面的大包,把4/5/6/7给我发过来

TCP Window Update

如上图,当接收方的tcp Window Size不足一个MSS的时候,为了避免 Silly Window Syndrome,Client不再发小包,而是发送探测包(跟keepalive一样,发一个回退一格的包,触发server ack同时server ack的时候会带过来新的window size)探测包间隔时间是200/400/800/1600……ms这样

正常的keep-alive Case:

keep-alive 通过发一个比实际seq小1的包,比如server都已经 ack 12583了,client故意发一个seq 12582来标识这是一个keep-Alive包

Duplication ack是指:

server收到了3和8号包,但是没有收到中间的4/5/6/7,那么server就会ack 3,如果client还是继续发8/9号包,那么server会继续发dup ack 3#1 ; dup ack 3#2 来向客户端说明只收到了3号包,不要着急发后面的大包,把4/5/6/7给我发过来

如何手动为docker daemon添加label

如何手动为docker daemon添加label

  1. 编辑或创建/etc/docker/daemon.json

  2. 将一个或多个lable以json格式写入文件,示例如下

1
2
# 为docker分配两个label,分别是nodetype和red
{"labels":["nodetype=dbpaas", "color=red"]}
  1. 重启docker daemon
1
service docker restart

4 /etc/docker/daemon.json 参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
"api-cors-header": "",
"authorization-plugins": [],
"bip": "",
"bridge": "",
"cgroup-parent": "",
"cluster-store": "",
"cluster-store-opts": {},
"cluster-advertise": "",
"debug": true,
"default-gateway": "",
"default-gateway-v6": "",
"default-runtime": "runc",
"default-ulimits": {},
"disable-legacy-registry": false,
"dns": [],
"dns-opts": [],
"dns-search": [],
"exec-opts": [],
"exec-root": "",
"fixed-cidr": "",
"fixed-cidr-v6": "",
"graph": "",
"group": "",
"hosts": [],
"icc": false,
"insecure-registries": [],
"ip": "0.0.0.0",
"iptables": false,
"ipv6": false,
"ip-forward": false,
"ip-masq": false,
"labels": ["nodetype=drds-server", "ark.ip=11.239.155.83"],
"live-restore": true,
"log-driver": "",
"log-level": "",
"log-opts": {},
"max-concurrent-downloads": 3,
"max-concurrent-uploads": 5,
"mtu": 0,
"oom-score-adjust": -500,
"pidfile": "",
"raw-logs": false,
"registry-mirrors": [],
"runtimes": {
"runc": {
"path": "runc"
},
"custom": {
"path": "/usr/local/bin/my-runc-replacement",
"runtimeArgs": [
"--debug"
]
}
},
"selinux-enabled": false,
"storage-driver": "",
"storage-opts": [],
"swarm-default-advertise-addr": "",
"tls": true,
"tlscacert": "",
"tlscert": "",
"tlskey": "",
"tlsverify": true,
"userland-proxy": false,
"userns-remap": ""
}

Daemon.json 指定 ulimit等参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cat >> /etc/docker/daemon.json <<EOF
{
"data-root": "/var/lib/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "200m",
"max-file": "5"
},
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 655360,
"Soft": 655360
},
"nproc": {
"Name": "nproc",
"Hard": 655360,
"Soft": 655360
}
},
"live-restore": true,
"oom-score-adjust": -1000,
"max-concurrent-downloads": 10,
"max-concurrent-uploads": 10,
"storage-driver": "overlay2",
"storage-opts": ["overlay2.override_kernel_check=true"],
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": [
"https://yssx4sxy.mirror.aliyuncs.com/"
]
}
EOF

docker、swarm的Label使用

docker、swarm的Label使用

需求背景

广发银行需要把方舟集群部署在多个机房(多个机房组成一个大集群),这样物理机和容器vlan没法互相完全覆盖,

也就是可能会出现A机房的网络subnet:192.168.1.0/24, B 机房的网络subnet:192.168.100.0/24 但是他们属于同一个vlan,要求如果容器在A机房的物理机拉起,分到的是192.168.1.0/24中的IP,B机房的容器分到的IP是:192.168.100.0/24

功能实现:

  • 本质就是对所有物理机打标签,同一个asw下的物理机用同样的标签,不同asw下的物理机标签不同;
  • 创建容器网络的时候也加标签,不同asw下的网络标签不一样,同时跟这个asw下的物理机标签匹配;
  • 创建容器的时候使用 –net=driver:vlan 来动态选择多个vlan网络中的任意一个,然后swarm根据网络的标签要和物理机的标签一致,从而把容器调度到正确的asw下的物理机上。

分为如下三个改造点

1:

daemon启动的时候增加标签(其中一个就行):

上联交换机组的名称,多个逗号隔开 com.alipay.acs.engine.asw.hostname

2:
创建网络的时候使用对应的标签:

网络域交换机组asw列表的名称,多个逗号隔开 com.alipay.acs.network.asw.hostname
该VLAN网络是否必须显式指定,默认为0即不必须,此时当传入–net driver:vlan时ACS会根据调度结果自行选择一个可用的VLAN网络并拼装到参数中 com.alipay.acs.network.explicit

3:

Swarm manager增加可选启动选项netarch.multiscope,值为true

功能实现逻辑

  1. Swarm manager增加可选启动选项netarch.multiscope,当为1时,network create时强制要求必须指定label描述配置VLAN的ASW信息
  2. Swarm manager在创建容器时检查网络类型,VLAN网络时则将网络ASW的label放入过滤器中,在调度时按照机器的ASW标签过滤
  3. 如果使用者如果不关心具体使用哪个VLAN,则可以指定–net=”driver:vlan”,会自动查找driver=vlan的network,并根据调度结果(Node所关联的ASW)自动选择合适的network填入Config.HostConfig.NetworkMode传递给Docker daemon.

如果是现存的环境,修改zk来更新网络标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
[zk: localhost:2181(CONNECTED) 21] get /Cluster/docker/network/v1.0/network/c79e533e4444294ac9cb7838608115c961c6e403d3610367ff4b197ef6b981fc 
{"addrSpace":"GlobalDefault","enableIPv6":false,"generic":{"com.docker.network.enable_ipv6":false,"com.docker.network.generic":{"VlanId":"192"}},"id":"c79e533e4444294ac9cb7838608115c961c6e403d3610367ff4b197ef6b981fc","inDelete":false,"internal":false,"ipamOptions":{"VlanId":"192"},"ipamType":"default","ipamV4Config":"[{\"PreferredPool\":\"192.168.8.0/24\",\"SubPool\":\"\",\"Gateway\":\"192.168.8.1\",\"AuxAddresses\":null}]","ipamV4Info":"[{\"IPAMData\":\"{\\\"AddressSpace\\\":\\\"\\\",\\\"Gateway\\\":\\\"192.168.8.1/24\\\",\\\"Pool\\\":\\\"192.168.8.0/24\\\"}\",\"PoolID\":\"GlobalDefault/192.168.8.0/24\"}]","labels":{},"name":"vlan192-8","networkType":"vlan","persist":true,"postIPv6":false,"scope":"global"}
cZxid = 0x4100008cce
ctime = Fri Mar 09 12:46:44 CST 2018
mZxid = 0x4100008cce
mtime = Fri Mar 09 12:46:44 CST 2018
pZxid = 0x4100008cce
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 716
numChildren = 0

//注意上面的网络还没有标签,修改如下:

1
2
[zk: localhost:2181(CONNECTED) 28] set /Cluster/docker/network/v1.0/network/c79e533e4444294ac9cb7838608115c961c6e403d3610367ff4b197ef6b981fc {"addrSpace":"GlobalDefault","enableIPv6":false,"generic":{"com.docker.network.enable_ipv6":false,"com.docker.network.generic":{"VlanId":"192"}},"id":"c79e533e4444294ac9cb7838608115c961c6e403d3610367ff4b197ef6b981fc","inDelete":false,"internal":false,"ipamOptions":{"VlanId":"192"},"ipamType":"default","ipamV4Config":"[{\"PreferredPool\":\"192.168.8.0/24\",\"SubPool\":\"\",\"Gateway\":\"192.168.8.1\",\"AuxAddresses\":null}]","ipamV4Info":"[{\"IPAMData\":\"{\\\"AddressSpace\\\":\\\"\\\",\\\"Gateway\\\":\\\"192.168.8.1/24\\\",\\\"Pool\\\":\\\"192.168.8.0/24\\\"}\",\"PoolID\":\"GlobalDefault/192.168.8.0/24\"}]",**"labels":{"com.alipay.acs.network.asw.hostname":"238"},**"name":"vlan192-8","networkType":"vlan","persist":true,"postIPv6":false,"scope":"global"}

example:

创建网络://–label=”com.alipay.acs.network.asw.hostname=vlan902-63”
docker network create -d vlan –label=”com.alipay.acs.network.asw.hostname=vlan902-63” –subnet=11.162.63.0/24 –gateway=11.162.63.247 –opt VlanId=902 –ipam-opt VlanId=902 hanetwork2
跟daemon中的标签:com.alipay.acs.engine.asw.hostname=vlan902-63 对应,匹配调度

1
2
3
4
$sudo cat /etc/docker/daemon.json
{"labels":["com.alipay.acs.engine.hostname=11.239.142.46","com.alipay.acs.engine.ip=11.239.142.46","com.alipay.acs.engine.device_type=Server","com.alipay.acs.engine.status=free","ark.network.vlan.range=vlan902-63","com.alipay.acs.engine.asw.hostname=vlan902-63","com.alipay.acs.network.asw.hostname=vlan902-63"]}
//不指定具体网络,有多个网络的时候自动调度 --net driver:vlan 必须是network打过标签了
docker run -d -it --name="udp10" --net driver:vlan --restart=always reg.docker.alibaba-inc.com/middleware.udp

方舟环境容器调度

方舟环境容器调度

主要功能

  • 恢复宿主机死机或者断网后上面需要调度的所有容器
  • 恢复非正常的容器状态到正常
  • 调度的容器能够支持vlan网络和Host模式
  • 调度容器本身通过Leader-Follower的模式保证高可用性
  • 调度容器支持cron定时任务(精确到秒级)
  • 查询哪个节点是Leader
  • 停止或者打开调度(方便容器维护、正常启停)

通过 ark-schedule 镜像启动调度

必须在swarm manager节点上以 docker 容器的方式来启动,下面的 -e 参数对应后面的 export 参数和作用注释

1
docker run -d --restart=always --name=ark-schedule -e ACS_CLUSTER_SECURITY_GROUP=false -e ACS_CLUSTER_SCHEME=tcp -e ACS_CLUSTER_ENDPOINT=11.239.155.112:3376 -e ACS_NETWORK_NAME=vlan701 -e ACS_CRONTAB="7 * * * * *" -e ACS_PORT=3375 -e ACS_ADVERTISE=11.239.155.112:3375 -e ACS_NETWORK_STORE_CLUSTER=zk://11.239.155.112:2181,11.239.155.103:2181,11.239.155.97:2181/Cluster -e affinity:container==swarm-manager --net=host reg.docker.alibaba-inc.com/ark/ark-schedule:0.6-20180530-68e7bed /ark-schedule/ark-schedule --debug start

如果需要调度容器本身高可以用,需要在不同的宿主机上启动多个 ark-schedule 容器, 同时可以给调度容器自己增加调度标签

环境变量参数说明

1
2
3
4
5
6
7
export ACS_CLUSTER_ENDPOINT=10.125.14.238:3376; //跟自己在同一台宿主机的swarm-manager
export ACS_NETWORK_NAME=vlan192; //方舟网络名称 docker network ls 看到vlan开头的名字
export ACS_NETWORK_STORE_CLUSTER=zk://10.125.26.108:2181,10.125.14.238:2181,10.125.1.45:2181/Cluster; //方舟zk集群,同部署的ark.properties中的
export ACS_CRONTAB="*/7 * * * * *"
export ACS_PORT="3375" //schedule 自身api暴露端口
export ACS_ADVERTISE="10.125.14.238:3375" //宿主机ip+自身api暴露端口 多个schedule容器唯一
./ark-schedule --debug start

ark-schedule 容器默认占用3375端口,如果要用别的端口需要通过 -e ACS_PORT 参数传入

-e ACS_CRONTAB="7 * * * * *" (秒 分 时 天 月 星期)

这个参数如果没有,那么需要外部来触发调度API(见下面)

ACS_ADVERTISE=”10.125.26.108:3375” 这个参数是多容器选举用的,每个容器用自己的IP+PORT来标识

容器日志主要在 /root/logs/ark-schedule-container-2017-12-12.log 中, 可以映射到宿主机上,查看更方便

镜像版本

0.1 带cron功能,自动定时扫描并恢复容器
0.2-election 有多个ark-schedule节点选举功能,抢到主的开始cron,没有抢到或者失去主的stop cron
0.3-election 在0.2的基础上修复了docker/libkv的bug,能够在弱网络、断网的条件下正常运行
0.4-switch 增加查询leader节点和cron是否开始的API,增加对Leader的cron启停的API
0.5-labels 增加对restart/recreate 标签的支持
0.6 去掉了对多个zk的支持,简化启动参数
0.7 修复了重复endpoint导致的容器的域名不通、inspect notfound(集群多个同名容器的时候)等各种问题

所有需要调度的容器增加调度标志标签

在docker run中增加一个标签: –label “ark.labels.schedule=haproxy”

详细命令:

1
sudo docker update --label-add="ark.labels.schedule=haproxy" --label-add="ark.enable_restart=true" --label-add="ark.enable_recreate=true" 容器名1 容器名2

上述命令不需要重启容器,但是要重新调snapshot API 做一次快照,让他们生效

ark-schedule容器在调度容器的时候,先检查快照中的容器,如果容器不见了或者状态不是up,又包含如上标签,就会重新在其它机器上把这个容器拉起来

  • ark.enable_restart
    是否允许通过重启来恢复容器(默认是true)。true为可以,false不可以

  • ark.enable_recreate
    是否允许将消失的容器在其他宿主机重建(默认是true)。true为可以,false不可以

API (如下ip:10.125.14.238 在现场换成客户物理机IP)

  1. 中间件部署完毕,并检查无误,调用: curl -v “http://10.125.14.238:3375/schedule/snapshot“ 对中间件做快照,将来会按快照的状态来进行恢复,执行一次就可以
  2. 手动恢复容器不见了,调用 curl -v “http://10.125.14.238:3375/schedule/snapshot/restore“ 会将所有异常容器恢复回来
  3. schedule 容器本身的健康检查接口 curl http://10.125.14.238:3375/schedule/leader http code 值是 200,说明schedule容器是健康的
  4. 查询哪个节点是Leader curl 以及是否是停止调度(维护时): “http://10.125.14.238:3375/schedule/leader
  5. 停止调度,先查询谁是leader,然后调: “http://leader-ip:3375/schedule/stop

维护状态

通过调度容器API停止调度,所有容器都不再被调度了,维护完毕再调snapshot、start API恢复调度。

如果只想对某个容器进行维护,其它容器还是希望被调度监控、调度可以通过下面的方式来实现:

docker update --label-rm="ark.labels.schedule=haproxy" 容器1 容器2 //还可以跟多个容器名
然后调 snapshot API让刚刚的update生效

运维完毕,恢复运维后的容器进入可以调度状态,具体命令如下:

docker update --label-add="ark.labels.schedule=haproxy" 容器1 容器2 //还可以跟多个容器名

然后调 snapshot API让刚刚的update生效

image.png

升级ark-schedule步骤:

下载并导入新镜像

下载镜像:http://fzpackages.oss-cn-shanghai.aliyuncs.com/ark%2Fpatch%2Fark-schedule-0.6-20180530-68e7bed.tgz
sudo docker load -i ark-schedule-0.6-20180530-68e7bed.tgz

停止原来的ark-schedule

停止两个crontab(新的ark-schedule自带crontab,每分钟执行一次调度)

停止两个ark-schedule容器

启动新的ark-schdule

在停止的两个ark-schedule的两台机器上启动两个新的ark-schedule容器,启动参数需要修改参考前面的描述(用现场环境信息替换下面的信息)

1
2
3
4
5
6
7
export ACS_CLUSTER_ENDPOINT=10.125.14.238:3376; //跟自己在同一台宿主机的swarm-manager
export ACS_NETWORK_NAME=vlan192; //方舟网络名称 docker network ls 看到vlan开头的名字
export ACS_NETWORK_STORE_CLUSTER=zk://10.125.26.108:2181,10.125.14.238:2181,10.125.1.45:2181/Cluster; //方舟zk集群,同部署的ark.properties中的
export ACS_CRONTAB="*/7 * * * * *" ----不需要改
export ACS_PORT="3375" //schedule 自身api暴露端口----不需要改
export ACS_ADVERTISE="10.125.14.238:3375" //宿主机ip+自身api暴露端口 多个schedule容器唯一
./ark-schedule --debug start //----不需要改

检查调度日志

检查两个ark-schedule 谁是主: curl http://ark-schedule所在的宿主机-ip:3375/schedule/leader

进到是主的ark-schedule容器中看日志:cat /root/logs/ark-schedule-2018-日期.log

参考资料

如何打标签 http://panama.alibaba-inc.com/qa/faq?id=1124

物理机磁盘空间都去哪里了

磁盘爆掉的几种情况

  1. 系统磁盘没有空间,解决办法:删掉 /var/log/ 下边的带日期的日志,清空 /var/log/messages 内容
  2. 容器使用的大磁盘空间不够,又有三个地方会使用大量的磁盘
    • 容器内部日志非常大,处理办法见方法一
    • 容器内部产生非常多或者非常大的文件,但是这个文件的位置又通过volume 挂载到了物理机上,处理办法见方法二
    • 对特别老的部署环境,还有可能是容器的系统日志没有限制大小,处理办法见方法三

现场的同学按如下方法依次检查

方法零: 检查系统根目录下每个文件夹的大小

sudo du / -lh --max-depth=1 --exclude=overlay --exclude=proc

看看除了容器之外有没有其它目录使用磁盘特别大,如果有那么一层层进去通过du命令来查看,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#sudo du / -lh --max-depth=1 --exclude=overlay --exclude=proc
16K /dev
16K /lost+found
4.0K /media
17G /home
136M /boot
832K /run
1.9G /usr
75M /tmp
12K /log
8.5G /var
4.0K /srv
0 /proc
22M /etc
84G /root
4.0K /mnt
508M /opt
0 /sys
112G /

那么这个案例中应该查看 /root下为什么用掉了84G(总共用了112G), 先 cd /root 然后执行: sudo du . -lh –max-depth=1 –exclude=overlay 进一步查看 /root 目录下每个文件夹的大小

如果方法零没找到占用特别大的磁盘文件,那么一般来说是容器日志占用太多的磁盘空间,请看方法一

方法一: 容器内部日志非常大(请确保先按方法零检查过了)

在磁盘不够的物理机上执行如下脚本:

1
2
3
4
5
6
7
8
sudo docker ps -a -q >containers.list

sudo cat containers.list | xargs sudo docker inspect $1 | grep merged | awk -F \" '{ print $4 }' | sed 's/\/merged//g' | xargs sudo du --max-depth=0 $1 >containers.size

sudo paste containers.list containers.size | awk '{ print $1, $2 }' | sort -nk2 >real_size.log

sudo tail -10 real_size.log | awk 'BEGIN {print "\tcontainer size\tunit"} { print NR":\t" $0"\t kB" }'

执行完后会输出如下格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
   	container     size	unit
1: 22690f16822f 3769980 kb
2: 82b4ae98eeed 4869324 kb
3: 572a1b7c8ef6 10370404 kb
4: 9f9250d98df6 10566776 kb
5: 7fab70481929 13745648 kb
6: 4a14b58e3732 29873504 kb
7: 8a01418b6df2 30432068 kb
8: 83dc85caaa5c 31010960 kb
9: 433e51df88b1 35647052 kb
10: 4b42818a8148 61962416 kb


第二列是容器id,第三列是磁盘大小,第四列是单位, 占用最大的排在最后面

然后进到容器后通过 du / –max-depth=2 快速发现大文件

方法二: 容器使用的volume使用过大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$sudo du -l /data/lib/docker/defaultVolumes --max-depth=1 | sort -rn
456012884 /data/lib/docker/defaultVolumes
42608332 /data/lib/docker/defaultVolumes/task_3477_g0_ark-metadb_miniDBPaaS-MetaDB_1
32322220 /data/lib/docker/defaultVolumes/task_3477_g0_dbpaas-metadb_dbpaas_1
27461120 /data/lib/docker/defaultVolumes/task_3001_g0_ark-metadb_miniDBPaaS-MetaDB_1
27319360 /data/lib/docker/defaultVolumes/task_36000_g0_ark-metadb_miniDBPaaS-MetaDB
27313836 /data/lib/docker/defaultVolumes/task_3600_g0_dbpaas-metadb_minidbpaas
27278692 /data/lib/docker/defaultVolumes/task_3604_g0_ark-metadb_miniDBPaaS-MetaDB_1
27277004 /data/lib/docker/defaultVolumes/task_3603_g0_ark-metadb_miniDBPaaS-MetaDB_1
27275736 /data/lib/docker/defaultVolumes/task_3542_g0_ark-metadb_miniDBPaaS-MetaDB
27271428 /data/lib/docker/defaultVolumes/task_3597_g0_ark-metadb_miniDBPaaS-MetaDB
27270840 /data/lib/docker/defaultVolumes/task_3603_g0_dbpaas-metadb_minidbpaas_1
27270492 /data/lib/docker/defaultVolumes/task_3603_g0_dbpaas-metadb_minidbpaas
27270468 /data/lib/docker/defaultVolumes/task_3600_g0_ark-metadb_miniDBPaaS-MetaDB
27270252 /data/lib/docker/defaultVolumes/task_3535_g0_ark-metadb_miniDBPaaS-MetaDB
27270244 /data/lib/docker/defaultVolumes/task_3538_g0_ark-metadb_miniDBPaaS-MetaDB
27270244 /data/lib/docker/defaultVolumes/task_3536_g0_ark-metadb_miniDBPaaS-MetaDB
25312404 /data/lib/docker/defaultVolumes/task_3477_g0_dncs-server_middleware-dncs_2

/data/lib/docker/defaultVolumes 参数是方舟默认volume存放的目录(一般是docker的存储路径下 –graph=/data/lib/docker) ,第一列是大小,后面是容器名

volume路径在物理机上也有可能是 /var/lib/docker 或者 /mw/mvdocker/ 之类的路径下,这个要依据安装参数来确定,可以用如下命令来找到这个路径:

sudo systemctl status docker -l | grep --color graph

结果如下,红色参数后面的路径就是docker 安装目录,到里面去找带volume的字眼:

image.png

找到 volume很大的文件件后同样可以进到这个文件夹中执行如下命令快速发现大文件:

du . --max-depth=2

方法三 容器的系统日志没有限制大小

这种情况只针对2017年上半年之前的部署环境,后面部署的环境默认都控制了这些日志不会超过150M

按照方法二的描述先找到docker 安装目录,cd 进去,然后 :

du ./containers --max-depth=2

就很快找到那个大json格式的日志文件了,然后执行清空这个大文件的内容:

echo '' | sudo tee 大文件名

一些其他可能占用空间的地方

  • 机器上镜像太多,可以删掉一些没用的: sudo docker images -q | xargs sudo docker rmi
  • 机器上残留的volume太多,删:sudo docker volume ls -q | xargs sudo docker volume rm
  • 物理文件被删了,但是还有进程占用这个文件句柄,导致文件对应的磁盘空间没有释放,检查: lsof | grep deleted 如果这个文件非常大的话,只能通过重启这个进程来真正释放磁盘空间

检查是否restart能支持只重启deamon,容器还能正常运行:

1
2
3
$sudo docker info | grep Restore
Live Restore Enabled: true

通过分析tcp包来确认服务调用的响应时间

通过分析tcp包来确认服务调用的响应时间

不需要在应用中打点,不限定于具体语言(php、cpp、java都可以), 分析服务调用的响应时间

案例

1
当时的问题,客户现场不管怎么样增加应用机器,tps就是上不去,同时增加应用机器后,增加的机器CPU还都能被用完,但是tps没有变化(这点比较奇怪,也就是cpu用的更多了,tps没变化),客户感觉 整体服务调用慢,数据库没有慢查询,不知道到具体时间花在哪里,各个环节都尝试过增加服务器(或提升配置),但是问题一直得不到解决

原因

数据库服务器网卡中断瓶颈导致rtt非常高,进一步导致每个Query的ResponseTime非常高(图中左边都是出问题、右边都是问题解决后的响应时间)

通过程序把每个请求、响应时间等数据分析出来并存入数据库中(缺一个图形展示界面,有图形展示界面后会更直观)

图一中是每一秒中的平均 rtt 时间(round trip time)

image

问题修复后数据库每个查询的平均响应时间从47毫秒下降到了4.5毫秒

图中的每一行都是是一个查询的数据库执行时间

image

从wireshark中也可以看到类似的rtt不正常(超过150ms的比较多)

image

从wireshark中也可以看到类似的rtt正常(99%都在10ms以内)

image

总结

实际上通过抓包发现所有发往后面的SQL查询(请求链路:app -> slb -> drds -> slb ->rds) ,在app上抓包发现每个请求发出去到收到结果平均需要差不多100ms(无论SQL复杂与否),通过统计网络往返时间(rtt)发现rtt非常高,好多都是50ms以上。
降低压力比较rtt,发现rtt降到了20ms以内,同时SQL响应时间也相应地减短了。
已经排除了drds到rds响应慢的问题,问题应该在slb或者drds上,进一步发现drds(16Core 16GMem)绑定网卡中断的cpu用到了95%以上,尝试绑定到多个cpu内核,似乎ecs不支持,接下来将配置,增加多个低配置的drds来解决问题。

简单来说ecs默认网卡中断只能用到一个核,如果ecs配置太高,网卡中断会成为瓶颈,导致rtt变高、不稳定

最牛B的Linux Shell命令

最牛B的Linux Shell命令

引言

Shell作为Unix系操作系统当中最有魅力且不可或缺的组件,经过数十载的洗礼不仅没有被淘汰,而且愈加变得成熟稳健,究其原因,大概因为它是个非常稳固的粘合剂,能够把大量功能强大的组件任意配搭,总能很好很快地完成用户的任务。

本文的一些命令很可能看起来是“雕虫小技”,我们只好仰慕一下Shell大牛了,但是有些细节我会稍加发掘加以说明,遇到有趣的地方希望能博您一笑了。

1.以sudo运行上条命令

1 $ sudo !!

大家应该都知sudo,不解释。但通常出现的情况是,敲完命令执行后报错才发现忘了sudo。这时候,新手用户就会:按上箭头,按左箭头,盯着光标回到开始处,输入sudo,回车;高手用户就蛋定多了,按Ctrl-p,按Ctrl-a,输入sudo,回车。

这里介绍这个是天外飞仙级别的,对,就直接sudo !!。

当然这几种解决方式效果是完全一样的,只是款不一样,嗯,不解释。

两个感叹号其实是bash的一个特性,称为事件引用符(event designators)。!!其实相当于!-1,引用前一条命令,当然也可以!-2,!-50。默认情况下bash会在~/.bash_history文件内记录用户执行的最近500条命令,history命令可以显示这些命令。

关于事件引用符的更多用法可以深入阅读The Definitive Guide to Bash Command Line History

2.以HTTP方式共享当前文件夹的文件

1 $ python -m SimpleHTTPServer 8080

这命令启动了Python的SimpleHTTPServer模块,考虑到Python在绝大多数的Linux发行版当中都默认安装,所以这个命令很可能是最简单的跨平台传文件的方法。

命令执行后将在本机8000端口开放HTTP服务,在其他能访问本机的机器的浏览器打开ttp://ip:8000即打开一个目录列表,点击即可下载。

python3的话

1
python3 -m http.server 8080

find

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#最近一天修改的md文档
find . -maxdepth 1 -type f -mtime -1 -name "*.md" -not -name "template.md" -not -name "temp.md" -exec ls -lh "{}" \;

find . -size 0 -type f -exec ls -lh "{}" \;

find . -maxdepth 1 -type f -mtime -2 -name "*margin*" -exec mv "{}" /tmp/img/ \;

#clean the big file, but exclude spill dir
sudo find /home/admin/ -not -path "*/spill/*" -type f -size +3G -exec cp /dev/null {} \;
sudo find /home/admin/ -type f -name "*.hprof" -mtime +1 -exec rm -f {} \;
#clean the spill temp file which before 7 days ago
sudo find /home/admin/ -type f -mtime +7 -exec cp /dev/null {} \;
sudo find /home/admin/logs/ -type f -mtime +7 -exec rm -f {} \;
sudo find /var/log/ -type f -size +500M -exec cp /dev/null {} \;

// -mindepth 1 可以忽略当前目录的"."
find . -mindepth 1 -maxdepth 1 -type d -mtime -50

#备份匹配的文件
find . -name '*.ibd' | grep tpcc1000 | grep -v mysql_global | xargs -I{} cp --path {} /tmp/bak/

#将yaml 备份,保留目录结构
find . -name '*.yaml' | xargs -I{} cp --path {} /tmp/


find $srcDir -maxdepth 1 -type f -mtime -$1 -name "*.md" -not -name "template.md" -not -name "temp.md" -exec ls -lh "{}" \;

find $srcDir -maxdepth 1 -type f -mtime -$1 -name "*.md" -not -name "template.md" -not -name "temp.md" -exec cp "{}" ./source/_posts/ \;

#sudo find /media/sf_D_DRIVE/case/ -maxdepth 1 -type f -mtime -$1 -name "*.md" -not -name "template.md" -print -exec cp "{}" ./source/_posts/ \;

cat的时候输出文件名:
find . -type f -print -exec cat {} \;

xargs 参数:

-I [replace-str]:将xargs的输出每一项参数,单独赋值给后面的命令,参数需要用指定的代替字符串replace-str代替,也就是说replace-str不可缺省,必须显示指明,可以使用{} $ @等符号,其主要作用是当xargs command后有多个参数时,调整参数位置

top

默认配置文件:/.toprc (on Ubuntu, it is */.config/procps/toprc*)

增加列:f (此时可以调整用 → 选择列并调整位置, 此时也有4个窗口可以选择)

按node展示cpu:2(3 选择需要展示的node)

按core展示cpu: 1

切换颜色:z (有4个窗口可以选择,按 g 可以选择1-4)

配置颜色: Z

V 切换成森林视图,也就是展示进程父子关系

保存配置: W

1
dG9wJ3MgQ29uZmlnIEZpbGUgKExpbnV4IHByb2Nlc3NlcyB3aXRoIHdpbmRvd3MpCklkOmksIE1vZGVfYWx0c2NyPTAsIE1vZGVfaXJpeHBzPTEsIERlbGF5X3RpbWU9My4wLCBDdXJ3aW49MApDcHUJZmllbGRzY3VyPaWmqLWztLu9wMS3urg5xScpKissLS4vMDEyNjw+P0FCQ0ZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWoKCXdpbmZsYWdzPTE5NTM4MCwgc29ydGluZHg9MTgsIG1heHRhc2tzPTAsIGdyYXBoX2NwdXM9MCwgZ3JhcGhfbWVtcz0wCglzdW1tY2xyPTQsIG1zZ3NjbHI9MSwgaGVhZGNscj0zLCB0YXNrY2xyPTQKTWVtCWZpZWxkc2N1cj2lu73AvMPBws3OJjk3uigzNEQnxSkqKywtLi8wMTI1Njg+P0ZHSElKS0xPUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqCgl3aW5mbGFncz0xOTUzODAsIHNvcnRpbmR4PTIxLCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdyYXBoX21lbXM9MAoJc3VtbWNscj02LCBtc2dzY2xyPTYsIGhlYWRjbHI9MywgdGFza2Nscj02ClNjaAlmaWVsZHNjdXI9pTo7PD0+P0BBTUJOQ7WztMfEtre5xcYmJygpKissLS4vMDEyOEhJSktMT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpagoJd2luZmxhZ3M9MTk0ODY4LCBzb3J0aW5keD0wLCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdyYXBoX21lbXM9MAoJc3VtbWNscj01LCBtc2dzY2xyPTUsIGhlYWRjbHI9MywgdGFza2Nscj01CkNncAlmaWVsZHNjdXI9paanqCowOTc6RCkrLC0uLzEyMzQ1Njg7PD0+P0BBQkNGR8hJSktMTU5P0NHS09TVxVZXWFlaW1xdXl9gYWJjZGVmZ2hpagoJd2luZmxhZ3M9MTk0ODY4LCBzb3J0aW5keD0wLCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdyYXBoX21lbXM9MAoJc3VtbWNscj0yLCBtc2dzY2xyPTMsIGhlYWRjbHI9MywgdGFza2Nscj0yCkZpeGVkX3dpZGVzdD0wLCBTdW1tX21zY2FsZT0wLCBUYXNrX21zY2FsZT0wLCBaZXJvX3N1cHByZXNzPTAKCnBpcGUJTmV0RmlsZXMJbHNvZiAtYSAtbCAtbiAtUCAtaTQgLXAgJWQgMj4mMQpwaXBlCU9wZW5GaWxlcwlsc29mIC1hIC1sIC1uIC1QIC1wICVkIDI+JjEKZmlsZQlOVU1BSW5mbwkvcHJvYy8lZC9udW1hX21hcHMK

xargs 传参数

ls /xx | xargs -t -I{} cp {} /tmp/{}

-t : 打印内容,去掉\n之后的字符串

-I : 后面定义占位符,上例子是{} ,后面命令行中可以多次使用占位符

挂载多台苹果的例子

idevice_id -l|xargs -t -I{} mkdir {};idevice_id -l |xargs -t -I{} ifuse {} {}

批量执行docker exec

1
ansible -i host.ini all -m shell -a "docker ps -a | grep tpcc | grep dn | cut -d ' ' -f 1 | xargs  -I{} docker exec {} bash -c \"myc -e 'shutdown'\""

批量推送镜像

1
docker images |grep "docker.io:5000" | awk '{ print $1":"$2 }' | xargs -I {} docker push {}

非贪婪匹配

vim中默认匹配:abc.*d 是贪婪匹配,也就是尽可能长地匹配,改用 abc.{-}d 匹配到第一个 d字符就结束

贪婪模式是: .*

非贪婪模式是: .\{-}

1
2
3
4
5
6
7
8
9
10
11
\{n,m} Matches n to m of the preceding atom, as many as possible
\{n} Matches n of the preceding atom
\{n,} Matches at least n of the preceding atom, as many as possible
\{,m} Matches 0 to m of the preceding atom, as many as possible
\{} Matches 0 or more of the preceding atom, as many as possible (like *)
*/\{-*
\{-n,m} matches n to m of the preceding atom, as few as possible
\{-n} matches n of the preceding atom
\{-n,} matches at least n of the preceding atom, as few as possible
\{-,m} matches 0 to m of the preceding atom, as few as possible
\{-} matches 0 or more of the preceding atom, as few as possibles

grep 非贪婪匹配

1
2
3
grep --color -P "agHost.*?," test.table  //匹配 agHost后带有多个任意字符直到第一个 逗号 结束,-P表示用 perl 的匹配语法,而perl默认是不支持贪婪的

grep --color -o -P "agHost.*?," test.table //-o 只打印匹配部分

匹配数字至少4次

1
2
grep -E ",rows=[0-9]{4,}"
grep -E "[0-9]{4,}ms" mongod.log

macOS sed 删除行

1
2
3
4
5
6
7
8
//查找匹配的行:|      |                               |
grep -E "\| [[:space:]]*\| [[:space:]]*\|" top_linux_commands.md -B3

//删除行 -i ".bak"是直接操作文件并添加.bak作为备份文件名称,如果不需要备份文件,则使用-i ""
sed -i '' -e '/\| [[:space:]]*\| [[:space:]]*\|/d' top_linux_commands.md

//先备份文件为.bak, 再删除行 -i ".bak"是添加.bak作为备份文件名称
sed -i '.bak' 's/\| [[:space:]]*\| [[:space:]]*\|/d' top_linux_commands.md

ps 查看进程

1
ps -Tfp pid // -T 展开进程下的线程 -f full -p pid

循环按行处理

1
while  read i ; do echo $i ; done <./prometheus.list

3.在以普通用户打开的vim当中保存一个root用户文件

1 :w !sudo tee %

这题目读起来纠结,其实是很常见的,常常忘记了sudo就直接用vim编辑/etc内的文件,(不过也不一定,vim发现保存的文件无法保存时候会提示)等编辑好了,保存时候才发现没权限。曲线方法是先保存个临时文件,退出后再sudo cp回去。不过实际上在vim里面可以直接完成这个过程的,命令就是如此。

查阅vim的文档(输入:help :w),会提到命令:w!{cmd},让vim执行一个外部命令{cmd},然后把当前缓冲区的内容从stdin传入。

tee是一个把stdin保存到文件的小工具。

而%,是vim当中一个只读寄存器的名字,总保存着当前编辑文件的文件路径。

所以执行这个命令,就相当于从vim外部修改了当前编辑的文件,好完工。

4.切换回上一个目录

1 $ cd -

应该不少人都知道这个,横杆-代表上一个目录的路径。

实际上cd -就是cd $OLDPWD的简写,bash的固定变量$OLDPWD总保存着之前一个目录的路径。

相对地,$PWD总保存着当前目录的路径。这些变量在编写shell脚本时候相当有用。

5.替换上一条命令中的一个短语

1 $ ^foo^bar^

又是另外一个事件引用符(event designator),可以把上一条命令当中的foo替换成bar。

在需要重复运行调试一道长长的命令,需要测试某个参数时候,用这个命令会比较实用;但多数人会首先选择按上箭头提出上道命令,再移动光标去修改某参数,这样更直观,但效率上就不够使用引用符高,而且在脚本中用这个方法可以简化很多。

这道命令的原始样式应该是这样的:

1 !!:s**/foo/bar/**

本文一开始介绍过!!,后面的一段大家应该很熟悉,vim、sed的替换操作都是这样的语法。

关于事件引用符的更多用法可以深入阅读The Definitive Guide to Bash Command Line History

6.快速备份一个文件

1 $ cp filename**{,.bak}**

这道命令把filename文件拷贝成filename.bak,大家应该在一些比较复杂的安装教程里面见过这样的用法。其原理就在于bash对大括号的展开操作,filename{,.bak}这一段会被展开成filename filename.bak再传给cp,于是就有了备份的命令了。

大括号在bash里面是一个排列的意义,可以试试这个:

1 $ echo {a,b,c}{a,b,c}{a,b,c}

将输出三个集合的全排列:

aaa aab aac aba abb abc aca acb acc

baa bab bac bba bbb bbc bca bcb bcc

caa cab cac cba cbb cbc cca ccb ccc

关于shell当中的集合操作,可深入阅读“Set Operations in the Unix Shell”

7.免密码ssh登录主机

1 $ ssh-copy-id remote-machine

这个命令把当前用户的公钥串写入到远程主机的~/.ssh/authorized_keys内,这样下次使用ssh登录的时候,远程主机就直接根据这串密钥完成身份校验,不再询问密码了。前提是你当前用户有生成了公钥,默认是没有的,先执行ssh-keygen试试吧!

这个命令如果用手工完成,是这样的:

1 2 3 your-machine$ scp ~/.ssh/identity.pub remote-machine: your-machine$ ssh remote-machine remote-machine$ cat identity.pub >> ~/.ssh/authorized_keys

如果你想删掉远程主机上的密钥,直接打开authorized_keys,搜索你的用户名,删除那行,即可。

8.抓取Linux桌面的视频

1 $ ffmpeg -f x11grab -s wxga -r 25 -i :0.0 -sameq **/tmp/**out.mpg

我们在一些视频网站上看到别人的3D桌面怎么怎么酷的视频,通常就是这么来的,ffmpeg可以直接解码X11的图形,并转换到相应输出格式。

ffmpeg的通常用法是,根据一堆参数,输出一个文件,输出文件通常放最后,下面解析下几个参数:

-f x11grab 指定输入类型。因为x11的缓冲区不是普通的视频文件可以侦测格式,必须指定后ffmpeg才知道如何获得输入。

-s wxga 设置抓取区域的大小。wxga是1366*768的标准说法,也可以换成-s 800×600的写法。

-r 25 设置帧率,即每秒抓取的画面数。

-i :0.0 设置输入源,本地X默认在0.0

-sameq 保持跟输入流一样的图像质量,以用来后期处理。

至于其他ffmpeg的用法,可以参考下面两篇文章:

· How to Extract Audio Tracks from YouTube Videos

· Converting YouTube Flash Videos to a Better Format with ffmpeg

后记

说Shell是一种编程语言,可能有些尴尬,虽然很多人每天都在用Shell,但从来没见它荣登TIOBE编程语言排行榜之类的,可以说毫无名分,因为很多用户没意识到它是一种语言,只当做这是一个能够很好完成任务的工具,基本得理所当然,就好像GUI程序的菜单、按钮一样。

掌握Shell,通常能够让任务在数秒钟内完成,这就让Shell跟C、Perl、Python这些语言区别开来,没人否认后者更能胜任更多的任务,但是他们是在不同的层面上去做,Shell依赖大量的系统组件黏合调用,而后者依赖各种库,各所擅长不同的应用领域,比喻就是,Shell是混凝土,可以很方便地粘合一些建筑组件而成为稳固的高楼大厦;但同样是粘合剂,粘玻璃窗、粘书报、粘皮鞋,混凝土是绝对不合适的,Shell并不擅长一些细致操作,比如它连浮点运算都不支持,更别提什么图形运算什么的。但这并不妨碍Shell来帮我们完成很多粗重任务。

Shell的工作方式,大多数入门用户会觉得枯燥难学,而所谓的经典教材也离不开《Advanced Bash-Scripting》、《Bash Guide for Beginners》,但类似本文这样的一些“雕虫小技”因为难登大雅之堂绝不会收录进去。这情况如果象国外一些unix用户比较多的地方会有很好改善,即使是新手,偶尔看看别人的操作都能“偷师”一手,我编译本系列文章其实也就希望稍微改善一下这个状况。

1.用你最喜欢的编辑器来敲命令

1 command <**CTRL-x CTRL-e**>

在已经敲完的命令后按,会打开一个你指定的编辑器(比如vim,通过环境变量$EDITOR指定),里面就是你刚输入的命令,然后爱怎么编辑就怎么编辑吧,特别是那些参数异常复杂的程序,比如mencoder/ffmpeg,一个命令动辄3、4行的,要修改其中的参数,这个方法最合适不过了,保存退出后自动执行这个程序。

实际上这是readline库的功能,在默认情况下,bash使用的是emacs模式的命令行操作方式,是调用这个功能的一个绑定。如果你习惯使用vi模式,按可以实现同样功能。

如果你喜欢别的编辑器,可以在~/.bashrc里面放上比如export EDITOR=nano的命令。

另外一个修改命令的方法是使用fc命令(Fix Command),在编辑器里面打开上一句命令。我们的第一辑连载提过一个^foo^bar^命令可以用fc来实现:fc -s foo=bar。

2.清空或创建一个文件

1 > file.txt

>在shell里面是标准输出重定向符,即把(前部个命令的)命令行输出转往一个文件内,但这里没有“前部命令”,输出为空,于是就覆盖(或创建)成一个空文件了。

有些脚本的写法是:>file.txt,因为:是bash默认存在的空函数。

单纯创建文件也可以用$touch file.txt,touch本来是用作修改文件的时间戳,但如果文件不存在,就自动创建了。

3.用ssh创建端口转发通道

1 ssh -N -L2001:remotehost:80 user**@**somemachine

这个命令在本机打开了2001端口,对本机2001端口的请求通过somemachine作为跳板,转到remotehost的80端口上。

实现效果跟术语反向代理是相似的,实际上就是端口转发,注意上面的描述涉及了3台主机,但当然somemachine可以变成localhost。

这个命令比较抽象,但有时候是很有用的,比如因为众所周知的原因国内的IP的80端口无法使用,又或者公司的防火墙只给外网开了ssh端口,需要访问内部服务器一个web应用,以及需要访问某些限定了来源IP的服务,就可以用上这个方法了。

举一个具体例子,运行:

1 2 ssh -f -N -L 0.0.0.0:443:twitter.com:443 shell.cjb.net ssh -f -N -L 0.0.0.0:80:twitter.com:80 shell.cjb.net

然后在/etc/hosts里面添加127.0.0.1 twitter.com,好吧剩下的你懂的。

当然通常做这个功能的反向代理,应该要用squid、nginx之类,ssh就算是轻量级的尝试吧!

4.重置终端

1 reset

如果你试过不小心cat了某个二进制文件,很可能整个终端就傻掉了,可能不会换行,没法回显,大堆乱码之类的,这时候敲入reset回车,不管命令有没有显示,就能回复正常了。

实际上reset命令只是输出了一些特殊字符,我们看BusyBox里面最简单的reset程序的实现:

1 printf(“\033c**\033**(K**\033**[J**\033**[0m**\033**[?25h”);

输出的这些字符对Shell是有特殊意义的:

· \033c: “ESC c” – 发送重置命令;

· \033(K: “ESC ( K” – 重载终端的字符映射;

· \033[J: “ESC [ J” – 清空终端内容;

· \033[0m: “ESC [ 0 m” – 初始化字符显示属性;

· \033[?25h: “ESC [ ? 25 h” – 让光标可见;

其中字符显示属性经常用来设定打印字符的颜色等,可参考这个博文

5.在午夜的时候执行某命令

1 echo cmd | at midnight

说的就是at这个组件,通常跟cron相提并论,不过at主要用于定时一次性任务,而cron定时周期性任务。

at的参数比较人性化,跟英语语法一样,可以tomorrow, next week之类的,详细的查看手册man at。

6.远程传送麦克风语音

1 dd if=**/dev/dsp | ssh username@host dd of=/dev/**dsp

没错就是实现一个喊话器的功能。

/dev/dsp是Linux下声卡的文件映射(Digital Signal Proccessor),从其中读数据就是录音,往里面写数据就是播放,相当简单!

dd是常用的数据拷贝程序,如果不同时指定if、of,就直接使用stdin/stdout来传输。

如果你没有远程主机,可以试试这样:

1 dd if=**/dev/dsp of=/dev/**dsp

直接回放麦克风的声音,只是有一点延时。

但是如果有别的程序正在使用声卡,这个方法就不凑效了,因为一般的声卡都不允许多个音频流同时处理,可以借用alsa组件的工具,arecord跟aplay:

1 arecord | ssh username**@**host aplay

本地回放就是:

1 arecord | aplay

如果你想吓吓别人:

1 cat **/dev/urandom | ssh username@**host aplay

7.映射一个内存目录

1 mount -t tmpfs -o size=1024m tmpfs **/mnt/**ram

这个命令开了一块1G内存来当目录用。不过放心,如果里面没文件,是不会占用内存的,用多少占多少。

不过一般来说没必要手动挂载,因为多数发行版都会在fstab内预留了一个内存目录,挂载在/dev/shm,直接使用即可;

最常见的用途是用内存空间来放Firefox的配置,可以让慢吞吞的FF快很多,参见Shellex的博文:用tmpfs让Firefox在内存中飞驰,以及后来的改进:用tmpfs让Firefox在内存中飞驰II,其中提到的脚本来自speeding up firefox with tmpfs and automatic rsync

那个破烂LinuxQQ也可以用这个方法,减少因为大量磁盘IO导致的问题。

8.用diff对比远程文件跟本地文件

1 ssh user**@**host cat **/path/to/**remotefile | diff **/path/to/**localfile -

diff通常的用法是从参数读入两个文件,而命令里面的-则是指从stdin读入了。

善用ssh可以让web开发减少很多繁琐,还有比如sshfs,可以从编辑**-上传-编辑-**上传的人工循环里面解脱出来。

9.查看系统中占用端口的进程

1 netstat -tulnp

Netstat是很常用的用来查看Linux网络系统的工具之一,这个参数可以背下来:

· -t: 显示TCP链接信息

· -u: 显示UDP链接信息

· -l: 显示监听状态的端口

· -n: 直接显示ip,不做名称转换

· -p: 显示相应的进程PID以及名称(要root权限)

如果要查看关于sockets更详细占用信息等,可以使用lsof工具。

1. 更友好的显示当前挂载的文件系统

| 1 | **mount** **|** column -t |
| —- | ————————— |

这条命令适用于任何文件系统,column 用于把输出结果进行列表格式化操作,这里最主要的目的是让大家熟悉一下 columnt 的用法。 下面是单单使用 mount 命令的结果:

1``2``3``4``5 $ **mount**`` ``**/**dev**/**root on **/** **type** ext3 **(**rw**)**``**/**proc on **/**proc **type** proc **(**rw**)**``**/**dev**/**mapper**/**lvmraid-home on **/**home **type** ext3 **(**rw,noatime**)**

而加了 column -t 命令后就成为这样了:

| 1``2``3``4``5 | $ **mount** **|** column -t`` ``**/**dev**/**root on **/** **type** ext3 **(**rw**)**``**/**proc on **/**proc **type** proc **(**rw**)**``**/**dev**/**mapper**/**lvmraid-home on **/**home **type** ext3 **(**rw,noatime**)** |
| ————— | ———————————————————— |

另外你可加上列名称来改善输出结果

| 1``2``3``4``5``6 | $ **(echo** "DEVICE - PATH - TYPE FLAGS" **&&** **mount)** **|** column -t`` ``DEVICE - PATH - TYPE FLAGS``**/**dev**/**root on **/** **type** ext3 **(**rw**)**``**/**proc on **/**proc **type** proc **(**rw**)**``**/**dev**/**mapper**/**lvmraid-home on **/**home **type** ext3 **(**rw,noatime**)** |
| —————— | ———————————————————— |

列2和列4并不是很友好,我们可以用 awk 来再处理一下

| 1``2``3``4``5``6 | $ **(echo** "DEVICE PATH TYPE FLAGS" **&&** **mount** **|** **awk** '$2=$4="";1'**)** **|** column -t`` ``DEVICE PATH TYPE FLAGS``**/**dev**/**root **/** ext3 **(**rw**)**``**/**proc **/**proc proc **(**rw**)**``**/**dev**/**mapper**/**lvmraid-home **/**home ext3 **(**rw,noatime**)** |
| —————— | ———————————————————— |

最后我们可以设置一个别名,为 nicemount

| 1 | $ nicemount**()** **{** **(echo** "DEVICE PATH TYPE FLAGS" **&&** **mount** **|** **awk** '$2=$4="";1'**)** **|** column -t; **}** |
| —- | ———————————————————— |

试一下

1``2``3``4``5``6 $ nicemount`` ``DEVICE PATH TYPE FLAGS``**/**dev**/**root **/** ext3 **(**rw**)**``**/**proc **/**proc proc **(**rw**)**``**/**dev**/**mapper**/**lvmraid-home **/**home ext3 **(**rw,noatime**)**

2. 运行前一个 Shell 命令,同时用 “bar” 替换掉命令行中的每一个 “foo”

1 **!!**:gs**/**foo**/**bar

!! 表示重复执行上一条命令,并用 :gs/foo/bar 进行替换操作。 关于 !! 这个用法在前一篇文章中已有详细的介绍。

3. 实时某个目录下查看最新改动过的文件

1 **watch** -d -n 1 'df; ls -FlAt /path'

watch 是实时监控工具,-d 参数会高亮显示变化的区域,-n 1 参数表示刷新间隔为 1 秒。 df; ls -FlAt /path 运行了两条命令,df 是输出磁盘使用情况,ls -FlAt 则列出 /path 下面的所有文件。 ls -FlAt 的参数详解:

· -F 在文件后面加一个文件符号表示文件类型,共有 /=>@| 这几种类型, 表示可执行文件,/ 表示目录,= 表示接口( sockets) ,> 表示门, @ 表示符号链接, | 表示管道。

· -l 以列表方式显示

· -A 显示 ...

· -t 根据时间排序文件

4. 通过 SSH 挂载远程主机上的文件夹

1 sshfs name**@**server:**/**path**/**to**/**folder **/**path**/**to**/**mount**/**point

这条命令可以让你通过 SSH 加载远程主机上的文件系统为本地磁盘,前提是你需要安装 FUSE 及 sshfs 这两个软件。 译者注:关于 sshfs 实际上我之前写过一篇文章介绍过,详见在 Ubuntu 上使用 sshfs 映射远程 ssh 文件系统为本地磁盘。 卸载的话使用 fusermount 或 umount 命令:

1``2 $ fusermount -u **/**path**/**to**/**mount**/**point``*# umount /path/to/mount/point*

5. 通过 DNS 来读取 Wikipedia 的词条

1 **dig** +short txt .wp.dg.cx

这也许是最有趣的一条技巧了,David Leadbeater 创建了一个 DNS 服务器,通过它当你查询一个 TXT 记录类型时,会返回一条来自于 Wikipedia 的简短的词条文字,这是他的介绍。 这里有一个样例,来查询 “hacker” 的含义:

1``2``3``4``5``6``7``8 $ **dig** +short txt hacker.wp.dg.cx`` ``"Hacker may refer to: Hacker (computer security), someone involved``in computer security/insecurity, Hacker (programmer subculture), a``programmer subculture originating in the US academia in the 1960s,``which is nowadays mainly notable for the free software/” “open``source movement, Hacker (hobbyist), an enthusiastic home computer``hobbyist http://a.vu/w:Hacker"

这里使用了 dig 命令,这是标准的用来查询 DNS 的系统管理工具,+short 参数是让其仅仅返回文字响应,txt 则是指定查询 TXT 记录类型。 更简单的做法是你可以为这个技巧创建一个函数:

1``2``3``4``5 wiki**()** **{** **dig** +short txt $1.wp.dg.cx; **}**``*#**然后试试吧:*``wiki hacker`` ``"Hacker may refer to: Hacker (computer security), …"

如果你不想用 dig ,也可以用 host 命令:

1 host -t txt hacker.wp.dg.cx

另外在Twitter上看过某人的创意,用普通的dns来作为程序版本更新的查询服务器:设定域名software-version-check.example.com的A记录为1.2.40.3,对比自己的版本号,嗯,有更新了!

6. 用 Wget 的递归方式下载整个网站

1
nohup wget --random-wait -nc -q -r -l 0 --reject=html -np -e robots=off -U Mozilla www.example.com &

参数解释: –random-wait 等待 0.5 1.5 秒的时间来进行下一次请求 -r 开启递归检索 -e robots=off 忽略 robots.txt -U Mozilla 设置 User-Agent 头为 Mozilla 其它一些有用的参数:

· –limit-rate=20K 限制下载速度为 20K

· -o logfile.txt 记录下载日志

· -l 0 删除深度(默认为5)

· -wait=1h 每下载一个文件后等待1小时

-np 不下载父目录

–reject=html 不下载html

-nc 本地已有的不再下载

7. 复制最后使用的命令中的参数

1
2
Ctrl + . or ESC + . 
command + . //macOS

这个快捷键只能工作于 shell 的 emacs 编辑模式,它可以从最后使用的命令行中复制参数到当前命令行中,下面是一个样例:

1``2``3``4``5 $ **echo** a b c``a b c`` ``$ **echo**``$ **echo** c

你可以重复执行该快捷键,以便获取自已需要的参数, 以下是样例:

1``2``3``4``5``6``7``8``9``10 $ **echo** 1 2 3``1 2 3``$ **echo** a b c``a b c`` ``$ **echo**``$ **echo** c`` ``$ **echo** again``$ **echo** 3

另外,假如你想指定第1个或第2个,或者是第 n 个参数的话,可以按 ALT + 1 (或 ESC + 1) 或 ALT + 2 (或 ESC +2) 这样形式的快捷键。 以下是样例:

1``2``3``4``5``6``7``8``9``10 $ **echo** a b c``a b c`` ``$ **echo**``$ **echo** a``a`` ``$ **echo**``$ **echo** b``b

查看Emacs Editing Mode Keyboard Shortcuts一文获取更多类似的快捷键。

8. 执行一条命令但不保存到 history 中

1 $ **command**

这条命令可运行于最新的 Bash shell 里,在其它 shell 中没测试过。 通过在命令行前面添加一个空格,就可以阻止这条命令被保存到 bash history (~/.bash_history) 文件中,这个行为可以通过 $HISTIGNORE shell 变量来控制。我的设置是 HISTIGNORE=”&:[ ]*” ,表示不保存重复的命令到 history 中,并且不保存以空格开头的命令行。$HISTIGNORE 中的值以冒号分隔。 如果你的命令内包含密码,比如mysqladmin,不把它记录在历史当中是好主义。 深入了解的话,可进一步看此文The Definitive Guide to Bash Command Line History

9. 显示当前目录中所有子目录的大小 du

sudo du –max-depth=1 -BG //单位 block-size G; or -BM MB du -h –max-depth=1

–max-depth=1 参数可以让 du 命令显示当前目录下 1 级子目录的统计信息,当然你也可以把 1 改为 2 ,进一步显示 2 级子目录的统计信息,可以灵活运用。而 -h 参数则是以 Mb 、G 这样的单位来显示大小。 译者注:在此推荐一个小工具 ncdu ,可以更方便的达到此效果。

按单位大小排序

1
2
3
4
5
6
7
8
9
10
11
#du -sh * | sort -hr | head
1.8T anolis_yum
1.6T u02
1.5T os
45G drds_image
23G polarx
8.3G src
7.9G drds.pcap
7.8G root
4.3G core.24086
3.5G core.112462

10. 显示消耗内存最多的 10 个运行中的进程,以内存使用量排序

| 1 | **ps** aux **|** **sort** -nk +4 **|** **tail** |
| —- | ————————————————- |

显然这并不是最好的方法,但它确实用起还不错。 这是一个典型的管道应用,通过 ps aux 来输出到 sort 命令,并用 sort 排序列出 4 栏,再进一步转到 tail 命令,最终输出 10 行显示使用内存最多的进程情况。 假如想要发现哪个进程使用了大量内存的话,我通常会使用 htop 或 top 而非 ps 。

11. 用 python 快速开启一个 SMTP 服务

1 python -m smtpd -n -c DebuggingServer localhost:1025

这是一个用 Python 标准库 smtpd (用 -m smtpd 指定) 实现在简易 SMTP 服务,运行于 1025 端口 。 另外三个参数的解释: -n 参数让 Python 不要进行 setuid ( 改变用户)为 “nobody” ,也就是说直接用你的帐号来运行 -c DebuggingServer 参数是让 Python 运行时在屏幕上输出调试及运行信息 * localhost:1025 参数则是让 Python 在本地的 1025 端口上开启 SMTP 服务 另外,假如你想让程序运行于标准的 25 的端口上的话,你必须使用 sudo 命令,因为只有 root 才能在 1-1024 端口上开启服务。如下:

1 **sudo** python -m smtpd -n -c DebuggingServer localhost:25

1.查看ascii码表

1 man 7 ascii

很多人初学编程都会接触到ascii码的概念,有时候为了查某个符号的ascii值,可能还得翻箱倒柜找出当年的课本?Linux Manpage里面其实包含了很多类似的实用资料,上述命令就能很详细的方式解释ascii编码,当然这里还有在线版

man命令的第二个参数是区域码,用来区分索引词的范围,比如printf,在C标准库里面的printf跟bash当中的printf是不同的,前者的查询是man 3 printf,后者是man 1 printf。如果这个区域码省略,就会从1开始搜索,直到找到为止。

命令man man可以看到详细的解释

manpages里面还有一些有趣而且实用的资料,可能鲜为人知:

· man 1 intro – 一篇对从未接触过Linux的用户的简明教程。

· man 2 syscalls – 内核系统请求的列表,按内核版本注释分类,系统编程必备。

· man 2 select_tut – 关于select()系统请求的教程。

· man 3 string – 在头文件内的所有函数。

· man 3 stdio – 关于头文件的使用,标准输入/输出库的说明。

· man 3 errno – 所有errorno的取值及说明。(C语言内类似其他语言的异常告知机制)

· man 4 console_codes – Linux的终端控制码及其使用解释。

· man 4 full – 介绍/dev/full这个总是处于“满”状态的磁盘。(对应/dev/null这个总是空的设备)

· man 5 proc – 介绍/proc下的文件系统。

· man 5 filesystems – 各种Linux文件系统。

第7区里面的资料通常最酷:

· man 7 bootparam – 详细解释内核启动参数。

· man 7 charsets – 解释各种语言的编码集。(gbk,gb2312等)

· man 7 glob – 解释glob文件名管理机制的工作过程。

· man 7 hier – 解释Linux文件系统结构各个部分的作用。

· man 7 operator – C语言的运算符的列表。

· man 7 regex – 介绍正则表达式。

· man 7 suffixes – 常见文件后缀名的列表跟解释。

· man 7 time – Linux的时钟机制解释。

· man 7 units – 数值单位及其数值的解释。

· man 7 utf8 – 描述UTF-8编码。

· man 7 url – 解释URL、URI、URN等的标准。

2.简易计时器

1 time read

运行命令开始算起,到结束时按一下Enter,就显示出整个过程的时间,精确到ms级别。

time是用来计算一个进程在运行到结束过程耗费多少时间的程序,它的输出通常有三项:

1 2 3 4 5 $ time ls /opt … real 0m0.008s user 0m0.003s sys 0m0.007s

real指整个程序对真实世界而言运行所需时间,user指程序在用户空间运行的时间,sys指程序对系统调用锁占用时间。

read本来是一个读取用户输入的命令,常见用法是read LINE,用户输入并回车后,键入的内容就被保存到$LINE变量内,但在键入回车前,这个命令是一直阻塞的。

可见time read这命令灵活地利用了操作系统的阻塞。用这个命令来测试一壶水多久煮滚应该是不错的。

3.远程关掉一台Windows机器

1 net rpc shutdown -I IP_ADDRESS -U username**%**password

Windows平台上的net命令是比较强大的,因为其后台是一个RPC类的系统服务,大家应该看过win下用net use \ip\ipc$ *这样一个命令建立IPC空连接,入侵主机的事情。

Linux下的net命令是samba组件的程序,通常包含在smbclient内,可以跟windows主机的文件、打印机共享等服务进行通讯,但是也支持rpc命令。

上述命令就是在远程Windows主机上执行了shutdown命令。当然这不一定成功,关系到win主机上面的安全设置。net命令能够控制到win主机就是了。

4.在一个子shell中运行一个命令

1 (cd **/**tmp && ls)

当然这只是演示,要查看目录当然可以ls /tmp。

好处就是不会改变当前shell的目录,以及如果命令中设计环境变量,也不会对当前shell有任何修改。

在Shell编程中还有很多使用上引号来括住一个命令:ls /tmp,这也是子shell过程。可是上引号的方法无法嵌套,而使用小括号的方法可以,一个比较纠结的例子是:

1 echo $(echo -e \x$(printf “%x” 65**))**

5.利用中间管道嵌套使用SSH

1 ssh -t host_A ssh host_B

如果目标机器host_B处于比较复杂的网络环境,本机无法直接访问,但另外一台host_A能够访问到host_B,而且也能被本机访问到,那上述命令就解决了方便登录host_B的问题。

但理论上这个过程是可以无限嵌套的,比如:

1 ssh -t host1 ssh -t host2 ssh -t host3 ssh -t host4 …

嗯那神马FBI CIA的,有本事来捉我吧~

6.清空屏幕

1 <**CTRL+l**>;

这个跟之前介绍的reset命令重置终端的作用有些类似,其实都只是发送一段控制序列,让终端的显示复位。

还可以这样运行:

1 tput clear

tput是专门用来控制终端的一个小工具,也挺强大的,详细信息运行man tput查看。

7.我想知道一台服务器什么时候重启完

1 ping -a IP

系统管理员最常做的事情是重启系统。但是服务器的重启过程往往得花上好几分钟,什么你的服务器4个scsi卡?16个硬盘?系统是Redhat?还完全安装所有组件?好吧,它重启的时间都够你吃顿饭了,所以我很想知道它什么时候回来。

ping命令有个audible ping参数,-a,当它终于ping通你的服务器时会让小喇叭叫起来。

8.列出你最常用的10条命令

1 history | awk ‘{a[$2]++}END{for(i in a){print a[i] “ “ i}}’ | sort -rn | head

这行命令组合得很妙:

history输出用户了命令历史;awk统计并输出列表;sort排序;head截出前10行。

9.检查Gmail新邮件

1 2 3 4 5 6 curl -u you**@**gmail.com –silent “https://mail.google.com/mail/feed/atom| perl -ne \ ‘ print “Subject: $1 “ if /(.+?)</title>/ && $title++; print “(from $1)\n” if /<email>(.+?)</email>/; ‘</th> </tr> </thead> </table> <p>Gmail的一个特色是支持Atom feed输出邮件列表,所以总是见到很多Gmail邮件提醒器之类的,因为开发特简单,atom很方便。</p> <p>这里只是利用了perl的正则来解析atom(sed/awk也能做到)。</p> <h2 id="10-用Telnet看《星球大战》"><a href="#10-用Telnet看《星球大战》" class="headerlink" title="10.用Telnet看《星球大战》"></a>10.用Telnet看《星球大战》</h2><table> <thead> <tr> <th>1</th> <th>telnet towel.blinkenlights.nl</th> </tr> </thead> </table> <p>没什么好解释的,就是ASCII艺术之一。如果你有ipv6连接,还能看到彩色版的。牛吧?</p> </div> <footer class="article-footer"> <a data-url="https://plantegg.github.io/2017/01/01/top_linux_commands/" data-id="cuidjZbLj6Ez7TJ-Gx7otLDye" data-title="最牛B的Linux Shell命令" class="article-share-link"><span class="fa fa-share">Teilen</span></a> <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Linux/" rel="tag">Linux</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/shell/" rel="tag">shell</a></li></ul> </footer> </div> </article> <article id="post-ss用法大全" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting"> <div class="article-meta"> <a href="/2016/10/12/ss%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/" class="article-date"> <time class="dt-published" datetime="2016-10-12T07:30:03.000Z" itemprop="datePublished">2016-10-12</time> </a> <div class="article-category"> <a class="article-category-link" href="/categories/network/">network</a> </div> </div> <div class="article-inner"> <header class="article-header"> <h1 itemprop="name"> <a class="p-name article-title" href="/2016/10/12/ss%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/">就是要你懂网络监控--ss用法大全</a> </h1> </header> <div class="e-content article-entry" itemprop="articleBody"> <h1 id="就是要你懂网络监控–ss用法大全"><a href="#就是要你懂网络监控–ss用法大全" class="headerlink" title="就是要你懂网络监控–ss用法大全"></a>就是要你懂网络监控–ss用法大全</h1><p>ss是Socket Statistics的缩写。</p> <p>netstat命令大家肯定已经很熟悉了,但是在2001年的时候netstat 1.42版本之后就没更新了,之后取代的工具是ss命令,是iproute2 package的一员。</p> <blockquote> <p>​ rpm -ql iproute | grep ss<br>​ /usr/sbin/ss</p> </blockquote> <p>netstat的替代工具是nstat,当然netstat的大部分功能ss也可以替代</p> <p>ss可以显示跟netstat类似的信息,但是速度却比netstat快很多,netstat是基于/proc/net/tcp获取 TCP socket 的相关统计信息,用strace跟踪一下netstat查询tcp的连接,会看到他open的是/proc/net/tcp的信息。ss快的秘密就在于它利用的是TCP协议的tcp_diag模块,而且是从内核直接读取信息,<strong>当内核不支持 tcp_diag 内核模块时,会回退到 /proc/net/tcp 模式</strong>。</p> <p>/proc/net/snmp 存放的是系统启动以来的累加值,netstat -s 读取它<br>/proc/net/tcp 是存放目前活跃的tcp连接的统计值,连接断开统计值清空, ss -it 读取它</p> <h2 id="ss-查看Buffer窗口"><a href="#ss-查看Buffer窗口" class="headerlink" title="ss 查看Buffer窗口"></a><a href="https://access.redhat.com/discussions/3624151">ss 查看Buffer窗口</a></h2><p>ss参数说明<a href="https://man7.org/linux/man-pages/man8/ss.8.html">权威参考</a></p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">-m, --memory //查看每个连接的buffer使用情况</span><br><span class="line"> Show socket memory usage. The output format is:</span><br><span class="line"></span><br><span class="line"> skmem:(r<rmem_alloc>,rb<rcv_buf>,t<wmem_alloc>,tb<snd_buf>,</span><br><span class="line"> f<fwd_alloc>,w<wmem_queued>,o<opt_mem>,</span><br><span class="line"> bl<back_log>,d<sock_drop>)</span><br><span class="line"></span><br><span class="line"> <rmem_alloc></span><br><span class="line"> the memory allocated for receiving packet</span><br><span class="line"></span><br><span class="line"> <rcv_buf></span><br><span class="line"> the total memory can be allocated for receiving</span><br><span class="line"> packet</span><br><span class="line"></span><br><span class="line"> <wmem_alloc></span><br><span class="line"> the memory used for sending packet (which has been</span><br><span class="line"> sent to layer 3)</span><br><span class="line"></span><br><span class="line"> <snd_buf></span><br><span class="line"> the total memory can be allocated for sending</span><br><span class="line"> packet</span><br><span class="line"></span><br><span class="line"> <fwd_alloc></span><br><span class="line"> the memory allocated by the socket as cache, but</span><br><span class="line"> not used for receiving/sending packet yet. If need</span><br><span class="line"> memory to send/receive packet, the memory in this</span><br><span class="line"> cache will be used before allocate additional</span><br><span class="line"> memory.</span><br><span class="line"></span><br><span class="line"> <wmem_queued></span><br><span class="line"> The memory allocated for sending packet (which has</span><br><span class="line"> not been sent to layer 3)</span><br><span class="line"></span><br><span class="line"> <ropt_mem></span><br><span class="line"> The memory used for storing socket option, e.g.,</span><br><span class="line"> the key for TCP MD5 signature</span><br><span class="line"></span><br><span class="line"> <back_log></span><br><span class="line"> The memory used for the sk backlog queue. On a</span><br><span class="line"> process context, if the process is receiving</span><br><span class="line"> packet, and a new packet is received, it will be</span><br><span class="line"> put into the sk backlog queue, so it can be</span><br><span class="line"> received by the process immediately</span><br><span class="line"></span><br><span class="line"> <sock_drop></span><br><span class="line"> the number of packets dropped before they are de-</span><br><span class="line"> multiplexed into the socket</span><br></pre></td></tr></table></figure> <p>The entire print format of <code>ss -m</code> is given in the source:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"> printf(" skmem:(r%u,rb%u,t%u,tb%u,f%u,w%u,o%u",</span><br><span class="line"> skmeminfo[SK_MEMINFO_RMEM_ALLOC],</span><br><span class="line"> skmeminfo[SK_MEMINFO_RCVBUF],</span><br><span class="line"> skmeminfo[SK_MEMINFO_WMEM_ALLOC],</span><br><span class="line"> skmeminfo[SK_MEMINFO_SNDBUF],</span><br><span class="line"> skmeminfo[SK_MEMINFO_FWD_ALLOC],</span><br><span class="line"> skmeminfo[SK_MEMINFO_WMEM_QUEUED],</span><br><span class="line"> skmeminfo[SK_MEMINFO_OPTMEM]);</span><br><span class="line"></span><br><span class="line"> if (RTA_PAYLOAD(tb[attrtype]) >=</span><br><span class="line"> (SK_MEMINFO_BACKLOG + 1) * sizeof(__u32))</span><br><span class="line"> printf(",bl%u", skmeminfo[SK_MEMINFO_BACKLOG]);</span><br><span class="line"></span><br><span class="line"> if (RTA_PAYLOAD(tb[attrtype]) >=</span><br><span class="line"> (SK_MEMINFO_DROPS + 1) * sizeof(__u32))</span><br><span class="line"> printf(",d%u", skmeminfo[SK_MEMINFO_DROPS]);</span><br><span class="line"></span><br><span class="line"> printf(")");</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">net/core/sock.c line:3095</span><br><span class="line">void sk_get_meminfo(const struct sock *sk, u32 *mem)</span><br><span class="line">{</span><br><span class="line"> memset(mem, 0, sizeof(*mem) * SK_MEMINFO_VARS);</span><br><span class="line"></span><br><span class="line"> mem[SK_MEMINFO_RMEM_ALLOC] = sk_rmem_alloc_get(sk);</span><br><span class="line"> mem[SK_MEMINFO_RCVBUF] = sk->sk_rcvbuf;</span><br><span class="line"> mem[SK_MEMINFO_WMEM_ALLOC] = sk_wmem_alloc_get(sk);</span><br><span class="line"> mem[SK_MEMINFO_SNDBUF] = sk->sk_sndbuf;</span><br><span class="line"> mem[SK_MEMINFO_FWD_ALLOC] = sk->sk_forward_alloc;</span><br><span class="line"> mem[SK_MEMINFO_WMEM_QUEUED] = sk->sk_wmem_queued;</span><br><span class="line"> mem[SK_MEMINFO_OPTMEM] = atomic_read(&sk->sk_omem_alloc);</span><br><span class="line"> mem[SK_MEMINFO_BACKLOG] = sk->sk_backlog.len;</span><br><span class="line"> mem[SK_MEMINFO_DROPS] = atomic_read(&sk->sk_drops);</span><br><span class="line">}</span><br></pre></td></tr></table></figure> <p><img src="/images/951413iMgBlog/image-20210604120011898.png" alt="image-20210604120011898"></p> <p>–memory/-m : 展示buffer窗口的大小</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#ss -m | xargs -L 1 | grep "ESTAB" | awk '{ if($3>0 || $4>0) print $0 }'</span><br><span class="line">tcp ESTAB 0 31 10.97.137.1:7764 10.97.137.2:41019 skmem:(r0,rb7160692,t0,tb87040,f1792,w2304,o0,bl0)</span><br><span class="line">tcp ESTAB 0 193 ::ffff:10.97.137.1:sdo-tls ::ffff:10.97.137.2:55545 skmem:(r0,rb369280,t0,tb87040,f1792,w2304,o0,bl0)</span><br><span class="line">tcp ESTAB 0 65 ::ffff:10.97.137.1:splitlock ::ffff:10.97.137.2:47796 skmem:(r0,rb369280,t0,tb87040,f1792,w2304,o0,bl0)</span><br><span class="line">tcp ESTAB 0 80 ::ffff:10.97.137.1:informer ::ffff:10.97.137.3:49279 skmem:(r0,rb369280,t0,tb87040,f1792,w2304,o0,bl0)</span><br><span class="line">tcp ESTAB 0 11 ::ffff:10.97.137.1:acp-policy ::ffff:10.97.137.2:41607 skmem:(r0,rb369280,t0,tb87040,f1792,w2304,o0,bl0)</span><br><span class="line"></span><br><span class="line">#ss -m -n | xargs -L 1 | grep "tcp EST" | grep "t[1-9]"</span><br><span class="line">tcp ESTAB 0 281 10.97.169.173:32866 10.97.170.220:3306 skmem:(r0,rb4619516,t2304,tb87552,f1792,w2304,o0,bl0)</span><br></pre></td></tr></table></figure> <p><img src="/images/oss/4a09503e6c6e84c25e026248a1b3ebb6.png" alt="image.png"></p> <p>如上图,tb指可分配的发送buffer大小,不够还可以动态调整(应用没有写死的话),w[The memory allocated for sending packet (which has not been sent to layer 3)]已经预分配好了的size,t[the memory used for sending packet (which has been sent to layer 3)] , 似乎 w总是等于大于t?</p> <p>example:</p> <p><img src="/images/oss/4ed3d8aab6ef3ee45decda75e534baab.png" alt="image.png"></p> <p>对172.16.210.17和172.16.160.1之间的带宽限速50MB后观察(带宽限制后,发送buffer就很容易被撑满了):</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$</span><span class="language-bash">ss -m | xargs -L 1 | grep <span class="string">"tcp EST"</span> | awk <span class="string">'{ if($3>0 || $4>0) print $0 }'</span></span></span><br><span class="line">Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port</span><br><span class="line">tcp ESTAB 1431028 0 172.16.210.17:30082 172.16.160.1:4847 skmem:(r2066432,rb2135508,t0,tb46080,f2048,w0,o0,bl0,d72)</span><br><span class="line">tcp ESTAB 1195628 0 172.16.210.17:30086 172.16.160.1:4847 skmem:(r1742848,rb1915632,t8,tb46080,f190464,w0,o0,bl0,d187)</span><br><span class="line">tcp ESTAB 86416 0 172.16.210.17:40470 172.16.160.1:4847 skmem:(r127232,rb131072,t0,tb46080,f3840,w0,o0,bl0,d16)</span><br><span class="line">tcp ESTAB 1909826 0 172.16.210.17:40476 172.16.160.1:4847 skmem:(r2861568,rb2933688,t2,tb46080,f26112,w0,o0,bl0,d15)</span><br><span class="line">tcp ESTAB 758312 0 172.16.210.17:40286 172.16.160.1:4847 skmem:(r1124864,rb1177692,t0,tb46080,f1536,w0,o0,bl0,d17)</span><br><span class="line">tcp ESTAB 2238720 0 172.16.210.17:40310 172.16.160.1:4847 skmem:(r3265280,rb3334284,t0,tb46080,f3328,w0,o0,bl0,d30)</span><br><span class="line">tcp ESTAB 88172 0 172.16.210.17:40508 172.16.160.1:4847 skmem:(r128000,rb131072,t0,tb46080,f3072,w0,o0,bl0,d16)</span><br><span class="line">tcp ESTAB 87700 0 172.16.210.17:41572 172.16.160.1:4847 skmem:(r130560,rb131072,t0,tb46080,f512,w0,o0,bl0,d10)</span><br><span class="line">tcp ESTAB 4147293 0 172.16.210.17:40572 172.16.160.1:4847 skmem:(r6064896,rb6291456,t2,tb46080,f75008,w0,o0,bl0,d27)</span><br><span class="line">tcp ESTAB 1610940 0 172.16.210.17:30100 172.16.160.1:4847 skmem:(r2358784,rb2533092,t6,tb46080,f82432,w0,o0,bl0,d304)</span><br><span class="line">tcp ESTAB 4216156 0 172.16.210.17:30068 172.16.160.1:4847 skmem:(r6091008,rb6291456,t0,tb46080,f3840,w0,o0,bl0,d112)</span><br><span class="line">tcp ESTAB 87468 0 172.16.210.17:40564 172.16.160.1:4847 skmem:(r127232,rb131072,t0,tb46080,f3840,w0,o0,bl0,d16)</span><br><span class="line">tcp ESTAB 0 84608 172.16.210.17:3306 10.100.7.27:43114 skmem:(r0,rb65536,t8352,tb131072,f75648,w92288,o0,bl0,d0)</span><br><span class="line">tcp ESTAB 4141872 0 172.16.210.17:40584 172.16.160.1:4847 skmem:(r6050560,rb6291456,t2,tb46080,f19712,w0,o0,bl0,d14)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">ss -itn</span></span><br><span class="line">State Recv-Q Send-Q Local Address:Port Peer Address:Port</span><br><span class="line">ESTAB 965824 0 172.16.210.17:19310 172.16.160.1:4847</span><br><span class="line"> cubic wscale:9,7 rto:215 rtt:14.405/0.346 ato:160 mss:1440 rcvmss:1460 advmss:1460 cwnd:10 bytes_acked:1324584 bytes_received:2073688144 segs_out:91806 segs_in:1461520 data_segs_out:4824 data_segs_in:1456130 send 8.0Mbps lastsnd:545583 lastrcv:545276 lastack:13173 pacing_rate 16.0Mbps delivery_rate 8.9Mbps app_limited busy:9071ms rcv_rtt:1.303 rcv_space:164245 minrtt:1.293</span><br><span class="line">ESTAB 0 84371 172.16.210.17:3306 10.100.7.147:59664</span><br><span class="line"> cubic wscale:7,7 rto:217 rtt:16.662/0.581 ato:40 mss:1448 rcvmss:976 advmss:1448 cwnd:375 ssthresh:19 bytes_acked:5087795046 bytes_received:1647 segs_out:3589314 segs_in:358086 data_segs_out:3589313 data_segs_in:8 send 260.7Mbps lastsnd:6 lastrcv:1177745 lastack:4 pacing_rate 312.8Mbps delivery_rate 32.9Mbps busy:1176476ms rwnd_limited:1717ms(0.1%) sndbuf_limited:159867ms(13.6%) unacked:37 retrans:0/214 rcv_space:14600 notsent:32055 minrtt:7.945</span><br><span class="line">ESTAB 0 83002 172.16.210.17:3306 10.100.7.28:34066</span><br><span class="line"> cubic wscale:7,7 rto:215 rtt:14.635/0.432 ato:40 mss:1448 rcvmss:976 advmss:1448 cwnd:144 ssthresh:144 bytes_acked:972464708 bytes_received:1466 segs_out:671667 segs_in:94369 data_segs_out:671666 data_segs_in:8 send 114.0Mbps lastsnd:1 lastrcv:453365 lastack:1 pacing_rate 136.8Mbps delivery_rate 24.0Mbps busy:453493ms sndbuf_limited:200ms(0.0%) unacked:23 rcv_space:14600 notsent:49698 minrtt:9.937</span><br><span class="line">ESTAB 1239616 0 172.16.210.17:41592 172.16.160.1:4847</span><br><span class="line"> cubic wscale:9,7 rto:216 rtt:15.754/0.775 ato:144 mss:1440 rcvmss:1460 advmss:1460 cwnd:10 bytes_acked:20321 bytes_received:1351071 segs_out:269 segs_in:1091 data_segs_out:76 data_segs_in:988 send 7.3Mbps lastsnd:339339 lastrcv:337401 lastack:10100 pacing_rate 14.6Mbps delivery_rate 1.0Mbps app_limited busy:1214ms rcv_rtt:227.156 rcv_space:55581 minrtt:11.38</span><br><span class="line">ESTAB 3415748 0 172.16.210.17:30090 172.16.160.1:4847</span><br><span class="line"> cubic wscale:9,7 rto:202 rtt:1.667/0.011 ato:80 mss:1440 rcvmss:1460 advmss:1460 cwnd:10 bytes_acked:398583 bytes_received:613824362 segs_out:28630 segs_in:437621 data_segs_out:1495 data_segs_in:435792 send 69.1Mbps lastsnd:1179931 lastrcv:1179306 lastack:12149 pacing_rate 138.2Mbps delivery_rate 7.2Mbps app_limited busy:2520ms rcv_rtt:1.664 rcv_space:212976 minrtt:1.601</span><br><span class="line">ESTAB 86480 0 172.16.210.17:41482 172.16.160.1:4847</span><br><span class="line"> cubic wscale:9,7 rto:215 rtt:14.945/1.83 ato:94 mss:1440 rcvmss:1460 advmss:1460 cwnd:10 bytes_acked:3899 bytes_received:93744 segs_out:73 segs_in:136 data_segs_out:20 data_segs_in:83 send 7.7Mbps lastsnd:449541 lastrcv:449145 lastack:19314 pacing_rate 15.4Mbps delivery_rate 964.2Kbps app_limited busy:296ms rcv_rtt:8561.27 rcv_space:14600 minrtt:11.948</span><br><span class="line">ESTAB 89136 0 172.16.210.17:40480 172.16.160.1:4847</span><br><span class="line"> cubic wscale:9,7 rto:213 rtt:12.11/0.79 ato:196 mss:1440 rcvmss:1460 advmss:1460 cwnd:10 bytes_acked:2510 bytes_received:95652 segs_out:102 segs_in:168 data_segs_out:16 data_segs_in:81send 9.5Mbps lastsnd:1099067 lastrcv:1098659 lastack:13686 pacing_rate 19.0Mbps delivery_rate 1.0Mbps app_limited busy:199ms rcv_rtt:2438.63 rcv_space:14600 minrtt:11.178</span><br><span class="line">ESTAB 0 84288 172.16.210.17:3306 10.100.7.26:51160</span><br><span class="line"> cubic wscale:7,7 rto:216 rtt:15.129/0.314 ato:40 mss:1448 rcvmss:976 advmss:1448 cwnd:157 ssthresh:157 bytes_acked:2954689465 bytes_received:1393 segs_out:2041403 segs_in:237797 data_segs_out:2041402 data_segs_in:8 send 120.2Mbps lastsnd:11 lastrcv:1103462 lastack:10 pacing_rate 144.2Mbps delivery_rate 31.3Mbps busy:1103503ms sndbuf_limited:3398ms(0.3%) unacked:24 retrans:0/7rcv_space:14600 notsent:49536 minrtt:9.551</span><br></pre></td></tr></table></figure> <p>推荐 -m -i 一起查看状态,比如 rcv_space 表示buffer达到过的最大水位</p> <blockquote> <p><strong>rcv_space</strong> is the high water mark of the rate of the local application reading from the receive buffer during any RTT. This is used internally within the kernel to adjust sk_rcvbuf.</p> </blockquote> <h2 id="ss-查看拥塞窗口、RTO"><a href="#ss-查看拥塞窗口、RTO" class="headerlink" title="ss 查看拥塞窗口、RTO"></a>ss 查看拥塞窗口、RTO</h2><blockquote> <p>//rto的定义,不让修改,每个ip的rt都不一样,必须通过rtt计算所得, HZ 一般是1秒<br>#define TCP_RTO_MAX ((unsigned)(120*HZ))<br>#define TCP_RTO_MIN ((unsigned)(HZ/5)) //在rt很小的环境中计算下来RTO基本等于TCP_RTO_MIN</p> </blockquote> <p>下面看到的rto和rtt单位都是毫秒,一般rto最小为200ms、最大为120秒</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#ss -itn |egrep "cwnd|rto" </span><br><span class="line">ESTAB 0 165 [::ffff:192.168.0.174]:48074 [::ffff:192.168.0.173]:3306</span><br><span class="line"> cubic wscale:7,7 rto:201 rtt:0.24/0.112 ato:40 mss:1448 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:1910206449 bytes_received:8847784416 segs_out:11273005 segs_in:22997562 data_segs_out:9818729 data_segs_in:13341573 send 482.7Mbps lastsnd:1 lastrcv:1 pacing_rate 963.8Mbps delivery_rate 163.2Mbps app_limited busy:2676463ms retrans:0/183 rcv_rtt:1.001 rcv_space:35904 minrtt:0.135</span><br><span class="line"></span><br><span class="line">ESTAB 0 0 [::ffff:192.168.0.174]:48082 [::ffff:192.168.0.173]:3306</span><br><span class="line"> cubic wscale:7,7 rto:201 rtt:0.262/0.112 ato:40 mss:1448 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:1852907381 bytes_received:8346503207 segs_out:10913962 segs_in:22169704 data_segs_out:9531411 data_segs_in:12796151 send 442.1Mbps lastsnd:2 lastack:2 pacing_rate 881.3Mbps delivery_rate 164.3Mbps app_limited busy:2736500ms retrans:0/260 rcv_rtt:1.042 rcv_space:31874 minrtt:0.133</span><br><span class="line"> </span><br><span class="line"> -----</span><br><span class="line"> skmem:(r0,rb131072,t0,tb133632,f0,w0,o0,bl0,d0) cubic wscale:8,7 rto:233 rtt:32.489/2.99 ato:40 mss:1380 rcvmss:536 advmss:1460 cwnd:11 ssthresh:8 bytes_acked:99862366 bytes_received:2943 segs_out:78933 segs_in:23388 data_segs_out:78925 data_segs_in:81 send 3.7Mbps lastsnd:1735288 lastrcv:1735252 lastack:1735252 pacing_rate 4.5Mbps delivery_rate 2.9Mbps busy:370994ms retrans:0/6479 reordering:5 rcv_space:14600 minrtt:27.984</span><br></pre></td></tr></table></figure> <h3 id="RTO计算算法"><a href="#RTO计算算法" class="headerlink" title="RTO计算算法"></a>RTO计算算法</h3><p>RTO的计算依赖于RTT值,或者说一系列RTT值。rto=f(rtt)</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">1.1. 在没有任何rtt sample的时候,RTO <- TCP_TIMEOUT_INIT (1s)</span><br><span class="line"> 多次重传时同样适用指数回避算法(backoff)增加RTO </span><br><span class="line"></span><br><span class="line">1.2. 获得第一个RTT sample后,</span><br><span class="line"> SRTT <- RTT</span><br><span class="line"> RTTVAR <- RTT/2</span><br><span class="line"> RTO <- SRTT + max(G, K * RTTVAR)</span><br><span class="line">其中K=4, G表示timestamp的粒度(在CONFIG_HZ=1000时,粒度为1ms)</span><br><span class="line"></span><br><span class="line">1.3. 后续获得更多RTT sample后,</span><br><span class="line"> RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R|</span><br><span class="line"> SRTT <- (1 - alpha) * SRTT + alpha * R</span><br><span class="line">其中beta = 1/4, alpha = 1/8</span><br><span class="line"></span><br><span class="line">1.4. Whenever RTO is computed, if it is less than 1 second, then the</span><br><span class="line"> RTO SHOULD be rounder up to 1 second.</span><br><span class="line"></span><br><span class="line">1.5. A maximum value MAY be placed on RTO provided it is at least 60 seconds.</span><br></pre></td></tr></table></figure> <p>RTTVAR表示的是平滑过的平均偏差,SRTT表示的平滑过的RTT。这两个值的具体含义会在后面介绍<br>具体实现的时候进一步的解释。<br>以上是计算一个初始RTO值的过程,当连续出现RTO超时后,<br>RTO值会用一个叫做指数回避的策略进行调整,下面来具体介绍。</p> <h2 id="从系统cache中查看-tcp-metrics-item"><a href="#从系统cache中查看-tcp-metrics-item" class="headerlink" title="从系统cache中查看 tcp_metrics item"></a>从系统cache中查看 tcp_metrics item</h2><pre><code>$sudo ip tcp_metrics show | grep 100.118.58.7 100.118.58.7 age 1457674.290sec tw_ts 3195267888/5752641sec ago rtt 1000us rttvar 1000us ssthresh 361 cwnd 40 ----这两个值对传输性能很重要 192.168.1.100 age 1051050.859sec ssthresh 4 cwnd 2 rtt 4805us rttvar 4805us source 192.168.0.174 ---这条记录有问题,缓存的ssthresh 4 cwnd 2都太小,传输速度一定慢 清除 tcp_metrics, sudo ip tcp_metrics flush all 关闭 tcp_metrics 功能,net.ipv4.tcp_no_metrics_save = 1 sudo ip tcp_metrics delete 100.118.58.7 </code></pre> <p>每个连接的ssthresh默认是个无穷大的值,但是内核会cache对端ip上次的ssthresh(大部分时候两个ip之间的拥塞窗口大小不会变),这样大概率到达ssthresh之后就基本拥塞了,然后进入cwnd的慢增长阶段。</p> <h2 id="ss-过滤地址和端口号,类似tcpdump的用法"><a href="#ss-过滤地址和端口号,类似tcpdump的用法" class="headerlink" title="ss 过滤地址和端口号,类似tcpdump的用法"></a>ss 过滤地址和端口号,类似tcpdump的用法</h2><p>过滤目标端口是80的或者源端口是1723的连接,dst后面要跟空格然后加“:”:</p> <pre><code># ss -ant dst :80 or src :1723 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 3 *:1723 *:* TIME-WAIT 0 0 172.31.23.95:37269 111.161.68.235:80 TIME-WAIT 0 0 172.31.23.95:37263 111.161.68.235:80 TIME-WAIT 0 0 172.31.23.95:37267 </code></pre> <p>or:</p> <pre><code>ss -ant dport = :80 or sport = :1723 </code></pre> <p>地址筛选,目标地址是111.161.68.235的连接</p> <pre><code>ss -ant dst 111.161.68.235 </code></pre> <p>端口大小筛选,源端口大于1024的端口:</p> <pre><code>ss sport gt 1024 </code></pre> <p>How Do I Compare Local and/or Remote Port To A Number?<br>Use the following syntax:</p> <pre><code>## Compares remote port to a number ## ss dport OP PORT ## Compares local port to a number ## sport OP PORT </code></pre> <p>Where OP can be one of the following:</p> <pre><code><= or le : Less than or equal to port >= or ge : Greater than or equal to port == or eq : Equal to port != or ne : Not equal to port < or gt : Less than to port > or lt : Greater than to port Note: le, gt, eq, ne etc. are use in unix shell and are accepted as well. ################################################################################### ### Do not forget to escape special characters when typing them in command line ### ################################################################################### ss sport = :http ss dport = :http ss dport \> :1024 ss sport \> :1024 ss sport \< :32000 ss sport eq :22 ss dport != :22 ss state connected sport = :http ss \( sport = :http or sport = :https \) ss -o state fin-wait-1 \( sport = :http or sport = :https \) dst 192.168.1/24 </code></pre> <h2 id="ss-查看-timer-状态"><a href="#ss-查看-timer-状态" class="headerlink" title="ss 查看 timer 状态"></a>ss 查看 timer 状态</h2><p>ss -atonp</p> <h2 id="按连接状态过滤"><a href="#按连接状态过滤" class="headerlink" title="按连接状态过滤"></a>按连接状态过滤</h2><p>Display All Established HTTP Connections</p> <pre><code>ss -o state established '( dport = :http or sport = :http )' </code></pre> <p>List all the TCP sockets in state -FIN-WAIT-1 for our httpd to network 202.54.1/24 and look at their timers:<br> ss -o state fin-wait-1 ‘( sport = :http or sport = :https )’ dst 202.54.1/24</p> <p>Filter Sockets Using TCP States</p> <pre><code>ss -4 state FILTER-NAME-HERE </code></pre> <p>Where FILTER-NAME-HERE can be any one of the following,</p> <pre><code>established syn-sent syn-recv fin-wait-1 fin-wait-2 time-wait closed close-wait last-ack listen closing all : All of the above states connected : All the states except for listen and closed synchronized : All the connected states except for syn-sent bucket : Show states, which are maintained as minisockets, i.e. time-wait and syn-recv. big : Opposite to bucket state. </code></pre> <h2 id="ss分析重传的包数量"><a href="#ss分析重传的包数量" class="headerlink" title="ss分析重传的包数量"></a>ss分析重传的包数量</h2><p>通过抓取ss命令,可以分析出来重传的包数量,然后将重传的流的数量和重传的包的数量按照对端IP:port的维度分段聚合,参考命令:</p> <pre><code>ss -itn |grep -v "Address:Port" | xargs -L 1 | grep retrans | awk '{gsub("retrans:.*/", "",$21); print $5, $21}' | awk '{arr[$1]+=$2} END {for (i in arr) {print i,arr[i]}}' | sort -rnk 2 </code></pre> <p>xargs <strong>-L 1</strong> 每一行处理一次,但是这个行如果是空格、tab结尾,那么会被认为是连续行,跟下一行合并</p> <p>高版本Linux内核的话,可以用systemtap或者bcc来获取每个连接的重传包以及发生重传的阶段</p> <h2 id="当前和最大全连接队列确认"><a href="#当前和最大全连接队列确认" class="headerlink" title="当前和最大全连接队列确认"></a>当前和最大全连接队列确认</h2><pre><code>$ss -lt State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.1:10248 *:* LISTEN 0 128 *:2376 *:* LISTEN 0 128 127.0.0.1:10249 *:* LISTEN 0 128 *:7337 *:* LISTEN 0 128 *:10250 *:* LISTEN 0 128 11.163.187.44:7946 *:* LISTEN 0 128 127.0.0.1:55631 *:* LISTEN 0 128 *:10256 *:* LISTEN 0 10 *:6640 *:* LISTEN 0 128 127.0.0.1:vmware-fdm *:* LISTEN 0 128 11.163.187.44:vmware-fdm *:* LISTEN 0 128 *:ssh *:* LISTEN 0 10 127.0.0.1:15772 *:* LISTEN 0 10 127.0.0.1:15776 *:* LISTEN 0 10 127.0.0.1:19777 *:* LISTEN 0 10 11.163.187.44:15778 *:* LISTEN 0 128 *:tr-rsrb-p2 *:* </code></pre> <h2 id="ss-s"><a href="#ss-s" class="headerlink" title="ss -s"></a>ss -s</h2><p>统计所有连接的状态</p> <h2 id="nstat"><a href="#nstat" class="headerlink" title="nstat"></a>nstat</h2><p>nstat -z -t 1 类似 netstat -s (ss –info 展示rto、拥塞算法等更详细信息; netstat -ant -o 展示keepalive是否)</p> <p>netstat<a href="http://perthcharles.github.io/2015/11/10/wiki-netstat-proc/">参考</a></p> <p>比如:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#nstat -az TcpExtTCPRcvCollapsed TcpExtTCPRcvCoalesce TcpExtTCPRcvQDrop</span><br><span class="line">#kernel</span><br><span class="line">TcpExtTCPRcvCollapsed 0 0.0 //类似对内存进行垃圾回收,慢</span><br><span class="line">TcpExtTCPRcvCoalesce 403679 0.0 //合并整理,较快</span><br><span class="line">TcpExtTCPRcvQDrop 0 0.0</span><br></pre></td></tr></table></figure> <p>参考 <a href="https://blog.cloudflare.com/when-the-window-is-not-fully-open-your-tcp-stack-is-doing-more-than-you-think">cloudflare 博客</a>:</p> <p><img src="/images/951413iMgBlog/image5-13.png" alt="img"></p> <p><img src="/images/951413iMgBlog/image8-4.png" alt="img"></p> <h2 id="knetstat"><a href="#knetstat" class="headerlink" title="knetstat"></a>knetstat</h2><p>最后给出的一个工具,knetstat(需要单独安装),也可以查看tcp的状态下的各种参数,需要单独安装</p> <p>example(3306是本地server,4192是后端MySQL):</p> <pre><code>Recv-Q Send-Q Local Address Foreign Address Stat Diag Options 0 0 0.0.0.0:3306 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 0.0.0.0:3406 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 127.0.0.1:8182 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 10.0.186.73:8182 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 0.0.0.0:22 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 0.0.0.0:8188 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 0 127.0.0.1:15778 0.0.0.0:* LSTN SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=0,TCP_NODELAY=0,TCP_FASTOPEN=0,TCP_DEFER_ACCEPT=0 0 138 10.0.186.73:51756 10.0.160.1:4192 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 0 10.0.186.73:3306 10.0.186.70:37428 ESTB SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVBUF=32768,SO_SNDBUF=65536,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 138 10.0.186.73:51476 10.0.160.1:4192 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 0 10.0.186.73:3306 10.0.186.70:37304 ESTB SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVBUF=32768,SO_SNDBUF=65536,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 0 10.0.186.73:51842 10.0.160.1:4192 ESTB SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 44 0 10.0.186.73:3306 10.0.186.70:36238 ESTB SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVBUF=32768,SO_SNDBUF=65536,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 44 0 10.0.186.73:3306 10.0.186.70:36160 ESTB SO_REUSEADDR=1,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVBUF=32768,SO_SNDBUF=65536,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 0 10.0.186.73:19030 10.0.171.188:8000 TIMW </code></pre> <p>3306对应的client上:</p> <pre><code>Recv-Q Send-Q Local Address Foreign Address Stat Diag Options 0 44 10.0.186.70:42428 10.0.186.73:3306 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVTIMEO=31536000000ms,SO_SNDTIMEO=31536000000ms,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 44 10.0.186.70:42298 10.0.186.73:3306 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVTIMEO=31536000000ms,SO_SNDTIMEO=31536000000ms,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 44 10.0.186.70:42296 10.0.186.73:3306 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVTIMEO=31536000000ms,SO_SNDTIMEO=31536000000ms,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 0 44 10.0.186.70:42322 10.0.186.73:3306 ESTB ># SO_REUSEADDR=0,SO_REUSEPORT=0,SO_KEEPALIVE=1,SO_RCVTIMEO=31536000000ms,SO_SNDTIMEO=31536000000ms,TCP_NODELAY=1,TCP_DEFER_ACCEPT=0 </code></pre> <p>Diag列的说明 <br> Indicator Meaning<br> >| The sender window (i.e. the window advertised by the remote endpoint) is 0. No data can be sent to the peer.<br> >|< The receiver window (i.e. the window advertised by the local endpoint) is 0. No data can be received from the peer.<br> ><br> ># There are unacknowledged packets and the last ACK was received more than one second ago. This may be an indication that there are network problems or that the peer crashed.</p> <h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://www.cyberciti.biz/tips/linux-investigate-sockets-network-connections.html">https://www.cyberciti.biz/tips/linux-investigate-sockets-network-connections.html</a></p> <p><a href="http://perthcharles.github.io/2015/11/10/wiki-netstat-proc/">http://perthcharles.github.io/2015/11/10/wiki-netstat-proc/</a></p> <p>源代码:<a href="https://github.com/sivasankariit/iproute2/blob/master/misc/ss.c">https://github.com/sivasankariit/iproute2/blob/master/misc/ss.c</a></p> <p><a href="https://github.com/veithen/knetstat/tree/master">https://github.com/veithen/knetstat/tree/master</a></p> <p><a href="https://access.redhat.com/discussions/782343">https://access.redhat.com/discussions/782343</a></p> <p><a href="https://perthcharles.github.io/2015/09/06/wiki-rtt-estimator/">RTO的计算方法(基于RFC6298和Linux 3.10)</a></p> </div> <footer class="article-footer"> <a data-url="https://plantegg.github.io/2016/10/12/ss%E7%94%A8%E6%B3%95%E5%A4%A7%E5%85%A8/" data-id="cuidy7fQfGBzY0y_DgfoDkO8g" data-title="就是要你懂网络监控--ss用法大全" class="article-share-link"><span class="fa fa-share">Teilen</span></a> <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Linux/" rel="tag">Linux</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/netstat/" rel="tag">netstat</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/socket/" rel="tag">socket</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/ss/" rel="tag">ss</a></li></ul> </footer> </div> </article> <article id="post-Linux tc qdisc的使用案例" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting"> <div class="article-meta"> <a href="/2016/08/24/Linux%20tc%20qdisc%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B/" class="article-date"> <time class="dt-published" datetime="2016-08-24T09:30:03.000Z" itemprop="datePublished">2016-08-24</time> </a> <div class="article-category"> <a class="article-category-link" href="/categories/Linux/">Linux</a> </div> </div> <div class="article-inner"> <header class="article-header"> <h1 itemprop="name"> <a class="p-name article-title" href="/2016/08/24/Linux%20tc%20qdisc%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B/">Linux tc qdisc的使用案例</a> </h1> </header> <div class="e-content article-entry" itemprop="articleBody"> <h1 id="Linux-tc-qdisc的使用案例"><a href="#Linux-tc-qdisc的使用案例" class="headerlink" title="Linux tc qdisc的使用案例"></a>Linux tc qdisc的使用案例</h1><p>在linux下通过tc qdisc 很容易对rt延时、丢包、带宽进行控制,这样的话方便重现各种网络问题</p> <h2 id="延时"><a href="#延时" class="headerlink" title="延时"></a>延时</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">1. give packets from eth0 a delay of 2ms</span><br><span class="line"><span class="meta prompt_">bash$ </span><span class="language-bash">tc qdisc add dev eth0 root netem delay 2ms</span></span><br><span class="line"> </span><br><span class="line">2.change the delay to 300ms</span><br><span class="line"><span class="meta prompt_">bash$ </span><span class="language-bash">tc qdisc change dev eth0 root netem delay 3ms</span></span><br><span class="line"></span><br><span class="line">3.display eth0 delay setting</span><br><span class="line"><span class="meta prompt_">bash$ </span><span class="language-bash">tc qdisc show dev eth0</span></span><br><span class="line"> </span><br><span class="line">4.stop the delay</span><br><span class="line"><span class="meta prompt_">bash$ </span><span class="language-bash">tc qdisc del dev eth0 root</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">corrupt</span></span><br><span class="line">The following rule corrupts 5% of the packets by introducing single bit error at a random offset in the packet:</span><br><span class="line">tc qdisc change dev eth0 root netem corrupt 5%</span><br></pre></td></tr></table></figure> <h2 id="模拟网络丢包"><a href="#模拟网络丢包" class="headerlink" title="模拟网络丢包"></a>模拟网络丢包</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tc qdisc add dev eth0 root netem loss 1%</span><br></pre></td></tr></table></figure> <p>指定ip 172.31.65.30延时17ms, 测试发现181和183这两句命令顺序无所谓。恢复正常:179行命令</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">179 tc qdisc del dev eth0 root</span><br><span class="line">180 tc qdisc add dev eth0 root handle 1: prio</span><br><span class="line">181 tc filter add dev eth0 parent 1:0 protocol ip pref 55 handle ::55 u32 match ip dst 172.31.65.30 flowid 2:1</span><br><span class="line">182 tc qdisc ls</span><br><span class="line">183 tc qdisc add dev eth0 parent 1:1 handle 2: netem delay 17ms</span><br></pre></td></tr></table></figure> <h2 id="指定ip和端口延时"><a href="#指定ip和端口延时" class="headerlink" title="指定ip和端口延时"></a>指定ip和端口延时</h2><p>指定 eth0 网卡,来源 ip 是 10.0.1.1,目的端口是 3306 的访问延迟 20ms,上下浮动 2ms 100.100.146.3</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"># 指定 eth0 网卡,来源 ip 是 10.0.1.1,目的端口是 3306 的访问延迟 20ms,上下浮动 2ms</span><br><span class="line">tc qdisc add dev eth0 root handle 1: prio bands 4</span><br><span class="line">tc qdisc add dev eth0 parent 1:4 handle 40: netem delay 20ms 2ms</span><br><span class="line">tc filter add dev bond0 parent 1: protocol ip prio 4 basic match "cmp(u16 at 2 layer transport eq 80)</span><br><span class="line"> and cmp(u8 at 16 layer network eq 100)</span><br><span class="line"> and cmp(u8 at 17 layer network eq 100)</span><br><span class="line"> and cmp(u8 at 18 layer network eq 146)</span><br><span class="line"> and cmp(u8 at 19 layer network eq 3)" flowid 1:4</span><br><span class="line"> </span><br><span class="line"># 删除过滤</span><br><span class="line">sudo tc filter del dev eth0 parent 1: prio 4 basic</span><br><span class="line">sudo tc qdisc del dev eth0 root </span><br></pre></td></tr></table></figure> <p>0 layer 代表 sport<br>2 layer 代表 dport</p> <h2 id="指定端口34001上,延时5ms"><a href="#指定端口34001上,延时5ms" class="headerlink" title="指定端口34001上,延时5ms"></a>指定端口34001上,延时5ms</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tc qdisc add dev eth0 root handle 1: prio</span><br><span class="line">tc qdisc add dev eth0 parent 1:3 handle 30: netem delay 5ms</span><br><span class="line">tc filter add dev eth0 protocol ip parent 1:0 u32 match ip sport 34001 0xffff flowid 1:3</span><br></pre></td></tr></table></figure> <h2 id="控制网卡的带宽、延时、乱序、丢包"><a href="#控制网卡的带宽、延时、乱序、丢包" class="headerlink" title="控制网卡的带宽、延时、乱序、丢包"></a>控制网卡的带宽、延时、乱序、丢包</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">sudo tc qdisc add dev bond0 root handle 1: netem delay 10ms reorder 25% 50% loss 0.2%</span><br><span class="line">sudo tc qdisc add dev bond0 parent 1: handle 2: tbf rate 1mbit burst 32kbit latency 10ms</span><br><span class="line"></span><br><span class="line">/sbin/tc qdisc add dev bond0 root tbf rate 500kbit latency 50ms burst 15kb</span><br><span class="line"></span><br><span class="line">// 同时模拟20Mbps带宽,50msRTT和0.1%丢包率 </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">tc qdisc add dev bond0 root handle 1:0 tbf rate 20mbit burst 10kb <span class="built_in">limit</span> 300000</span> </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">tc qdisc add dev bond0 parent 1:0 handle 10:0 netem delay 50ms loss 0.1 <span class="built_in">limit</span> 300000</span> </span><br><span class="line"></span><br><span class="line">tc qdisc change dev eth0 root netem reorder 50% gap 3 delay 1ms</span><br><span class="line">tc qdisc change dev eth0 root netem delay 1ms reorder 15%</span><br><span class="line"></span><br><span class="line">//在eth0上设置一个tbf队列,网络带宽为200kbit,延迟10ms以内,超出的包会被drop掉,缓冲区为1540个字节</span><br><span class="line">sudo /sbin/tc qdisc add dev eth0 root tbf rate 200kbit latency 10ms burst 15kb</span><br><span class="line">sudo /sbin/tc qdisc ls dev eth0</span><br></pre></td></tr></table></figure> <p>在eth0上设置一个tbf队列,网络带宽为200kbit,延迟10ms以内,超出的包会被drop掉,缓冲区为1540个字节</p> <blockquote> <p>rate表示令牌的产生速率, <em>sustained maximum rate</em><br>latency表示数据包在队列中的最长等待时间, <em>packets with higher latency get dropped</em><br>burst参数表示 maximum allowed burst:<br> burst means the maximum amount of bytes that tokens can be available for instantaneously.<br> 如果数据包的到达速率与令牌的产生速率一致,即200kbit,则数据不会排队,令牌也不会剩余<br> 如果数据包的到达速率小于令牌的产生速率,则令牌会有一定的剩余。<br> 如果后续某一会数据包的到达速率超过了令牌的产生速率,则可以一次性的消耗一定量的令牌。<br> burst就是用于限制这“一次性”消耗的令牌的数量的,以字节数为单位。</p> </blockquote> <p>tbf: <em>use</em> the <em>token buffer filter to manipulate traffic rates</em></p> <p>限制10MB,排队等待超过100ms就触发丢包,只限制了出去的流量,没有限制进来的流量:</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">tc qdisc ls dev eth0 // 查看eth0上的队列规则 </span><br><span class="line">sudo tc qdisc add dev eth0 root tbf rate 80mbit burst 1mbit latency 100ms </span><br><span class="line"></span><br><span class="line">//限制80MB</span><br><span class="line">sudo tc qdisc add dev eth0 root tbf rate 80mbps burst 1mbps latency 100ms</span><br></pre></td></tr></table></figure> <h3 id="乱序"><a href="#乱序" class="headerlink" title="乱序"></a>乱序</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1001 [2024-08-08 15:12:01] sudo tc qdisc add dev bond0 root handle 1: prio</span><br><span class="line">1004 [2024-08-08 15:12:44] sudo tc filter add dev bond0 parent 1: protocol ip prio 1 u32 match ip dst 1.2.3.7 flowid 1:1</span><br><span class="line">1005 [2024-08-08 15:13:17] tc qdisc add dev bond0 parent 1:1 handle 10: netem delay 10ms reorder 5% 10%</span><br></pre></td></tr></table></figure> <h2 id="两地三中心模拟"><a href="#两地三中心模拟" class="headerlink" title="两地三中心模拟"></a>两地三中心模拟</h2><p>针对不同的ip地址可以限制不同的带宽和网络延时,htb较prio多了一个带宽控制</p> <p>通过htb 只限制带宽和延时</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">//对10.0.3.228、229延时1ms,对 10.0.3.232延时30ms 两地三中心限制延时和带宽</span><br><span class="line">tc qdisc add dev eth0 root handle 1: htb</span><br><span class="line"></span><br><span class="line">tc class add dev eth0 parent 1: classid 1:1 htb rate 600Gbps</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:1 match ip dst 10.0.3.228</span><br><span class="line">tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 1ms</span><br><span class="line"></span><br><span class="line">tc class add dev eth0 parent 1: classid 1:2 htb rate 600Gbps</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:2 match ip dst 10.0.3.229</span><br><span class="line">tc qdisc add dev eth0 parent 1:2 handle 20: netem delay 1ms</span><br><span class="line"></span><br><span class="line">tc class add dev eth0 parent 1: classid 1:3 htb rate 600Gbps</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:3 match ip dst 10.0.3.232</span><br><span class="line">tc qdisc add dev eth0 parent 1:3 handle 30: netem delay 30ms</span><br></pre></td></tr></table></figure> <p><img src="/Users/ren/case/951413iMgBlog/image-20230607152951762-3791233.png" alt="image-20230607152951762"></p> <p><img src="/images/951413iMgBlog/TX_path_tc_mqprio-1.png" alt="img"></p> <p>通过prio 只限制延时</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">//两地三中心限制不同的延时,htb 才可以加带宽限制</span><br><span class="line">tc qdisc add dev eth0 root handle 1: prio</span><br><span class="line"></span><br><span class="line">//flowid 指定的是流量要去往的目标队列</span><br><span class="line">//10.0.3.228/10.0.3.229 延时1ms</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:1 match ip dst 10.0.3.228/31</span><br><span class="line">tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 1ms</span><br><span class="line"></span><br><span class="line">//以上两行可以改成如下三行单独为 10.0.3.228 和 10.0.3.229 添加延时 1ms</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:1 match ip dst 10.0.3.228</span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:1 match ip dst 10.0.3.229</span><br><span class="line">tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 1ms //表示多条 filter 命中的流量都走这条 qdisc 规则</span><br><span class="line"></span><br><span class="line">tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:2 match ip dst 10.0.3.232</span><br><span class="line">tc qdisc add dev eth0 parent 1:2 handle 20: netem delay 30ms</span><br></pre></td></tr></table></figure> <p>对多个 ip 进行不同的时延控制:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">tc qdisc add dev bond0 root handle 1: prio</span><br><span class="line">tc qdisc add dev bond0 parent 1:1 handle 10: netem delay 30ms</span><br><span class="line">tc filter add dev bond0 protocol ip parent 1:0 prio 1 u32 match ip dst 100.1.2.3 flowid 1:1</span><br><span class="line"></span><br><span class="line">tc qdisc add dev bond0 parent 1:2 handle 20: netem delay 30ms</span><br><span class="line">tc filter add dev bond0 protocol ip parent 1:0 prio 1 u32 match ip dst 11.1.2.3 flowid 1:2</span><br><span class="line"></span><br><span class="line">//查看</span><br><span class="line">tc qdisc show dev bond0</span><br><span class="line">tc filter show dev bond0</span><br></pre></td></tr></table></figure> <h2 id="qdisc的类别"><a href="#qdisc的类别" class="headerlink" title="qdisc的类别"></a><a href="https://cloud.tencent.com/developer/article/1409664">qdisc的类别</a></h2><p>QDisc(排队规则)是queueing discipline的简写,它是理解流量控制(traffic control)的基础。无论何时,内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。然后,内核会尽可能多地从qdisc里面取出数据包,把它们交给网络适配器驱动模块。最简单的QDisc是pfifo它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列。不过,它会保存网络接口一时无法处理的数据包。</p> <p>一个网络接口上如果没有设置QDisc,pfifo_fast就作为缺省的QDisc。</p> <p>CLASSFUL QDISC(分类QDisc),可分类的qdisc包括: </p> <ul> <li>CBQ: CBQ是Class Based Queueing(基于类别排队)的缩写。它实现了一个丰富的连接共享类别结构,既有限制(shaping)带宽的能力,也具有带宽优先级管理的能力。带宽限制是通过计算连接的空闲时间完成的。空闲时间的计算标准是数据包离队事件的频率和下层连接(数据链路层)的带宽。</li> <li>HTB: HTB是Hierarchy Token Bucket的缩写。通过在实践基础上的改进,它实现了一个丰富的连接共享类别体系。使用HTB可以很容易地保证每个类别的带宽,它也允许特定的类可以突破带宽上限,占用别的类的带宽。HTB可以通过TBF(Token Bucket Filter)实现带宽限制,也能够划分类别的优先级。</li> <li>PRIO: PRIO QDisc 不能限制带宽,因为属于不同类别的数据包是顺序离队的。使用PRIO QDisc可以很容易对流量进行优先级管理,只有属于高优先级类别的数据包全部发送完毕,才会发送属于低优先级类别的数据包。为了方便管理,需要使用iptables或者ipchains处理数据包的服务类型(Type Of Service,ToS)。</li> </ul> <h3 id="htb分类-qdisc"><a href="#htb分类-qdisc" class="headerlink" title="htb分类 qdisc"></a>htb分类 qdisc</h3><p>tbf 能对流量无差别控制,htb 可以进一步进行更精细的控制</p> <h4 id="针对IP、端口限速案例"><a href="#针对IP、端口限速案例" class="headerlink" title="针对IP、端口限速案例"></a>针对IP、端口限速案例</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$</span><span class="language-bash"><span class="built_in">cat</span> qdisc_bw.sh</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">针对不同的ip进行限速</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">清空原有规则</span></span><br><span class="line">tc qdisc del dev eth0 root</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">创建根序列</span></span><br><span class="line">tc qdisc add dev eth0 root handle 1: htb default 1</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">创建一个主分类绑定所有带宽资源(60M)</span></span><br><span class="line">tc class add dev eth0 parent 1:0 classid 1:1 htb rate 60Mbps burst 15k</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">到这里可以使用了,整机速度限制到了60M</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">创建子分类,ceil表示最大带宽</span></span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:10 htb rate 2Mbps ceil 1Mbps burst 15k</span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:20 htb rate 20Mbps ceil 30Mbps burst 15k</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">为了避免一个会话永占带宽,添加随即公平队列sfq.</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">perturb:是多少秒后重新配置一次散列算法,默认为10秒</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">sfq,他可以防止一个段内的一个ip占用整个带宽</span></span><br><span class="line">tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10</span><br><span class="line">tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">创建过滤器</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">对所有ip限速到1Mbps</span></span><br><span class="line">tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dst 0.0.0.0/0 flowid 1:10</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">对10.0.186.140限速在30Mbps</span></span><br><span class="line">tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 10.0.186.140 flowid 1:20</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">对端口进行filter限流</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip sport 22 flowid 1:10</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">查看以上规则</span></span><br><span class="line">sudo tc class show dev eth0</span><br><span class="line">sudo tc filter show dev eth0</span><br></pre></td></tr></table></figure> <p>限流100MB后的实际监控效果</p> <p><img src="/images/951413iMgBlog/image-20211031205539407.png" alt="image-20211031205539407"></p> <h2 id="tc-qdisc-示例"><a href="#tc-qdisc-示例" class="headerlink" title="tc qdisc 示例"></a>tc qdisc 示例</h2><p>通过 Linux tc filter/qdisc 的代码来对 10.0.3.228/229/230/231 这四个 ip 分别增加 delay1/2/3/4 ms,带宽限制在 1Mb/2Mb/4Mb/8Mb</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"># 1. 首先在网卡上添加一个根队列规程(qdisc)</span><br><span class="line">tc qdisc add dev eth0 root handle 1: htb default 10</span><br><span class="line"></span><br><span class="line"># 2. 创建主类</span><br><span class="line">tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit</span><br><span class="line"></span><br><span class="line"># 3. 为每个 IP 创建子类并设置带宽</span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:10 htb rate 1mbit</span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:20 htb rate 2mbit</span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:30 htb rate 4mbit</span><br><span class="line">tc class add dev eth0 parent 1:1 classid 1:40 htb rate 8mbit</span><br><span class="line"></span><br><span class="line"># 4. 为每个类添加延迟</span><br><span class="line">tc qdisc add dev eth0 parent 1:10 handle 10: netem delay 1ms</span><br><span class="line">tc qdisc add dev eth0 parent 1:20 handle 20: netem delay 2ms</span><br><span class="line">tc qdisc add dev eth0 parent 1:30 handle 30: netem delay 3ms</span><br><span class="line">tc qdisc add dev eth0 parent 1:40 handle 40: netem delay 4ms</span><br><span class="line"></span><br><span class="line"># 5. 添加过滤规则,将不同 IP 的流量导向不同的类</span><br><span class="line">tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dst 10.0.3.228/32 flowid 1:10</span><br><span class="line">tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dst 10.0.3.229/32 flowid 1:20</span><br><span class="line">tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dst 10.0.3.230/32 flowid 1:30</span><br><span class="line">tc filter add dev eth0 protocol ip parent 1: prio 1 u32 match ip dst 10.0.3.231/32 flowid 1:40</span><br></pre></td></tr></table></figure> <p>以上代码对应的层级结构:</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">eth0 [root]</span><br><span class="line">└── 1: htb (根队列规程)</span><br><span class="line"> └── 1:1 htb [rate 100mbit] (主类)</span><br><span class="line"> ├── 1:10 htb [rate 1mbit] (子类)</span><br><span class="line"> │ └── 10: netem [delay 1ms]</span><br><span class="line"> │ └── filter: dst 10.0.3.228/32</span><br><span class="line"> │</span><br><span class="line"> ├── 1:20 htb [rate 2mbit] (子类)</span><br><span class="line"> │ └── 20: netem [delay 2ms]</span><br><span class="line"> │ └── filter: dst 10.0.3.229/32</span><br><span class="line"> │</span><br><span class="line"> ├── 1:30 htb [rate 4mbit] (子类)</span><br><span class="line"> │ └── 30: netem [delay 3ms]</span><br><span class="line"> │ └── filter: dst 10.0.3.230/32</span><br><span class="line"> │</span><br><span class="line"> └── 1:40 htb [rate 8mbit] (子类)</span><br><span class="line"> └── 40: netem [delay 4ms]</span><br><span class="line"> └── filter: dst 10.0.3.231/32</span><br></pre></td></tr></table></figure> <h2 id="docker-中使用-tc"><a href="#docker-中使用-tc" class="headerlink" title="docker 中使用 tc"></a>docker 中使用 tc</h2><p>docker里无法使用的bug 可以参考 <a href="https://bugzilla.redhat.com/show_bug.cgi?id=1152231%EF%BC%8C%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95%E5%B0%B1%E6%98%AF%E5%8D%87%E7%BA%A7tc%E7%89%88%E6%9C%AC%EF%BC%8Ctc">https://bugzilla.redhat.com/show_bug.cgi?id=1152231,解决方法就是升级tc版本,tc</a> qdisc add 时加上direct_qlen参数</p> <h3 id="场景:"><a href="#场景:" class="headerlink" title="场景:"></a>场景:</h3><p>故障注入的docker: 10.1.1.149</p> <p>10.1.1.149上会模拟各种网络故障,但是中控机到该docker的连接需要不受影响</p> <p>DEVICE_NAME=eth0</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"># 根规则,direct_qlen 1000必须加,否则在docker的虚拟网络跑不了</span><br><span class="line">tc qdisc add dev ${DEVICE_NAME} root handle 1: htb default 1024 direct_qlen 1000</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 建立两个类继承root</span><br><span class="line">tc class add dev ${DEVICE_NAME} parent 1:0 classid 1:1 htb rate 10000mbit</span><br><span class="line">tc class add dev ${DEVICE_NAME} parent 1:0 classid 1:2 htb rate 10000mbit</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#新版本的tc在filter设置完后,所有网络都会断,类似黑名单,需要加qdisc才能恢复, 所以先让两个通道都能跑</span><br><span class="line"># 队列采用公平的调度算法,保证网络通畅,perturb参数是每隔10秒换一次hash,进一步保障平均</span><br><span class="line">tc qdisc add dev ${DEVICE_NAME} parent 1:1 sfq perturb 10</span><br><span class="line">tc qdisc add dev ${DEVICE_NAME} parent 1:2 sfq perturb 10</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 加过滤规则</span><br><span class="line">#1.队列1是和跳板机交互的网络,需要保持通畅</span><br><span class="line">tc filter add dev ${DEVICE_NAME} protocol ip parent 1: prio 10 u32 match ip dst 10.0.0.200/32 flowid 1:1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#2.其他所有主机走队列2,实现网络模拟</span><br><span class="line">tc filter add dev ${DEVICE_NAME} protocol ip parent 1: prio 10 u32 match ip dst 0.0.0.0/0 flowid 1:2</span><br><span class="line"></span><br><span class="line">#队列2 开始网络模拟</span><br><span class="line">#该命令将${DEVICE_NAME}网卡的耗时随机delay 100ms,延迟的尖刺在标准值的正负30ms, 最后的百分比数字是尖刺的相关系数</span><br><span class="line"></span><br><span class="line"># 这边用replace是因为之前已经用add加过规则了</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 netem delay 100ms 30ms 25%</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#该命令将 ${DEVICE_NAME} 网卡的传输设置为随机丢掉10%的数据包, 成功率为50%</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 netem loss 10% 50%</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#该命令将 ${DEVICE_NAME} 网卡的传输设置为随机产生10%的重复数据包。</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 netem duplicate 10%</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#该命令将 ${DEVICE_NAME} 网卡的传输设置为:有25%的数据包会被立即发送,其他的延迟10ms,相关性是10%,产生乱序</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 netem delay 10ms reorder 25% 10% </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#该命令将 ${DEVICE_NAME} 网卡的传输设置为随机产生9%的损坏的数据包</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 netem corrupt 9%</span><br></pre></td></tr></table></figure> <p>恢复网络</p> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">#让网络恢复正常</span><br><span class="line">tc qdisc replace dev ${DEVICE_NAME} parent 1:2 sfq perturb 10</span><br><span class="line"></span><br><span class="line"># =================== 查看规则 ======================</span><br><span class="line">tc filter show dev ${DEVICE_NAME}</span><br><span class="line">tc class show dev ${DEVICE_NAME}</span><br><span class="line">tc qdisc show dev ${DEVICE_NAME}</span><br><span class="line"></span><br><span class="line">#====================== 清理 ======================</span><br><span class="line">tc filter delete dev ${DEVICE_NAME} parent 1:0 protocol ip pref 10</span><br><span class="line">tc qdisc del dev ${DEVICE_NAME} parent 1:2 netem</span><br><span class="line">tc class del dev ${DEVICE_NAME} parent 1:0 classid 1:2</span><br><span class="line">tc class del dev ${DEVICE_NAME} parent 1:0 classid 1:1</span><br><span class="line">tc qdisc del dev ${DEVICE_NAME} root handle 1</span><br></pre></td></tr></table></figure> <h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p><a href="https://netbeez.net/blog/how-to-use-the-linux-traffic-control/">https://netbeez.net/blog/how-to-use-the-linux-traffic-control/</a></p> <p><a href="https://bootlin.com/blog/multi-queue-improvements-in-linux-kernel-ethernet-mvneta/">https://bootlin.com/blog/multi-queue-improvements-in-linux-kernel-ethernet-mvneta/</a></p> </div> <footer class="article-footer"> <a data-url="https://plantegg.github.io/2016/08/24/Linux%20tc%20qdisc%E7%9A%84%E4%BD%BF%E7%94%A8%E6%A1%88%E4%BE%8B/" data-id="cuidR28bCTDlkGD_eV7bynzQZ" data-title="Linux tc qdisc的使用案例" class="article-share-link"><span class="fa fa-share">Teilen</span></a> <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Linux/" rel="tag">Linux</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/network/" rel="tag">network</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/tc/" rel="tag">tc</a></li></ul> </footer> </div> </article> <nav id="page-nav"> <a class="extend prev" rel="prev" href="/page/8/">« zurück</a><a class="page-number" href="/">1</a><span class="space">…</span><a class="page-number" href="/page/7/">7</a><a class="page-number" href="/page/8/">8</a><span class="page-number current">9</span><a class="page-number" href="/page/10/">10</a><a class="extend next" rel="next" href="/page/10/">weiter »</a> </nav> </section> <aside id="sidebar"> <div class="widget-wrap"> <h3 class="widget-title">Kategorien</h3> <div class="widget"> <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Ansible/">Ansible</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/CPU/">CPU</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/DNS/">DNS</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java/">Java</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/LVS/">LVS</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Memory/">Memory</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/MySQL/">MySQL</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/SSH/">SSH</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/TCP/">TCP</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/docker/">docker</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/network/">network</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/others/">others</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/performance/">performance</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/tcpdump/">tcpdump</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/troubleshooting/">troubleshooting</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%85%B6%E5%AE%83/">其它</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%8A%80%E5%B7%A7/">技巧</a></li></ul> </div> </div> <div class="widget-wrap"> <h3 class="widget-title">Tags</h3> <div class="widget"> <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/tags/etc-hosts/" rel="tag">/etc/hosts</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/etc-nsswitch/" rel="tag">/etc/nsswitch</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ADM/" rel="tag">ADM</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/AMD/" rel="tag">AMD</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Ansible/" rel="tag">Ansible</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Ansible-Linux/" rel="tag">Ansible - Linux</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Apple/" rel="tag">Apple</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/AsyncAppender/" rel="tag">AsyncAppender</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/BCC/" rel="tag">BCC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/BDP/" rel="tag">BDP</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/BIOS/" rel="tag">BIOS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/CLOSE-WAIT/" rel="tag">CLOSE_WAIT</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/CPU/" rel="tag">CPU</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/CVE/" rel="tag">CVE</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/CWND/" rel="tag">CWND</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/DNS/" rel="tag">DNS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Die/" rel="tag">Die</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Docker/" rel="tag">Docker</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/DropWatch/" rel="tag">DropWatch</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/EDNS/" rel="tag">EDNS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/EPOLLEXCLUSIVE/" rel="tag">EPOLLEXCLUSIVE</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ET/" rel="tag">ET</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/FT2500/" rel="tag">FT2500</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/FullGC/" rel="tag">FullGC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/HTTP-Proxy/" rel="tag">HTTP Proxy</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/HeapDumpBeforeFullGC/" rel="tag">HeapDumpBeforeFullGC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/HugePage/" rel="tag">HugePage</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/" rel="tag">IO多路复用</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/IP/" rel="tag">IP</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/IPC/" rel="tag">IPC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Index/" rel="tag">Index</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Intel/" rel="tag">Intel</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/JDBC/" rel="tag">JDBC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Java/" rel="tag">Java</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/LT/" rel="tag">LT</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/LVM/" rel="tag">LVM</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/LVS/" rel="tag">LVS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Linux/" rel="tag">Linux</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/LoadBalance/" rel="tag">LoadBalance</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/M1/" rel="tag">M1</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/MSS/" rel="tag">MSS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/MTU/" rel="tag">MTU</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Mac/" rel="tag">Mac</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Memory/" rel="tag">Memory</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/MySQL/" rel="tag">MySQL</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/NIO/" rel="tag">NIO</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/NUMA/" rel="tag">NUMA</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/OSS/" rel="tag">OSS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/PageCache/" rel="tag">PageCache</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Pause/" rel="tag">Pause</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Proxy/" rel="tag">Proxy</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/RT/" rel="tag">RT</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/RTO/" rel="tag">RTO</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/RTT/" rel="tag">RTT</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SO-REUSEADDR/" rel="tag">SO_REUSEADDR</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SSD/" rel="tag">SSD</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SSH/" rel="tag">SSH</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SSH-Proxy/" rel="tag">SSH Proxy</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SSTHRESH/" rel="tag">SSTHRESH</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/S-IFCHR/" rel="tag">S_IFCHR</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/S-IFIFO/" rel="tag">S_IFIFO</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SocketTimeout/" rel="tag">SocketTimeout</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Socks5/" rel="tag">Socks5</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SpringMVC/" rel="tag">SpringMVC</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Sysbench/" rel="tag">Sysbench</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SystemStap/" rel="tag">SystemStap</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/TCP/" rel="tag">TCP</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/TCP-connection/" rel="tag">TCP connection</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/TCP-queue/" rel="tag">TCP queue</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/TCPWqueueTooBig/" rel="tag">TCPWqueueTooBig</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/TCP-USER-TIMEOUT/" rel="tag">TCP_USER_TIMEOUT</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ThreadPool/" rel="tag">ThreadPool</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Unix-Socket/" rel="tag">Unix-Socket</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Wireshark/" rel="tag">Wireshark</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Zen/" rel="tag">Zen</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/accept/" rel="tag">accept</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/accept-queue/" rel="tag">accept queue</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ansible/" rel="tag">ansible</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/arm/" rel="tag">arm</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/arp/" rel="tag">arp</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/arthas/" rel="tag">arthas</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/bash-posix/" rel="tag">bash posix</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/bpftrace/" rel="tag">bpftrace</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/branch-miss/" rel="tag">branch_miss</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/btrace/" rel="tag">btrace</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/buddyinfo/" rel="tag">buddyinfo</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/buffer/" rel="tag">buffer</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/bug/" rel="tag">bug</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/cache/" rel="tag">cache</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/cache-line/" rel="tag">cache_line</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/calico/" rel="tag">calico</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/capinfos/" rel="tag">capinfos</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/context/" rel="tag">context</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/contextswitch/" rel="tag">contextswitch</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/createrepo/" rel="tag">createrepo</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/crond/" rel="tag">crond</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/dd/" rel="tag">dd</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/debug/" rel="tag">debug</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/delay-ack/" rel="tag">delay ack</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/dns/" rel="tag">dns</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/docker/" rel="tag">docker</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/dropped/" rel="tag">dropped</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/druid/" rel="tag">druid</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/dtrace/" rel="tag">dtrace</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/du/" rel="tag">du</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/env/" rel="tag">env</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/epoll/" rel="tag">epoll</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/flannel/" rel="tag">flannel</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/forward/" rel="tag">forward</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/free/" rel="tag">free</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/gdb/" rel="tag">gdb</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/github/" rel="tag">github</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/glibc/" rel="tag">glibc</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hexo/" rel="tag">hexo</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hygon/" rel="tag">hygon</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ifconfig/" rel="tag">ifconfig</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iftop/" rel="tag">iftop</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/immediateFlush/" rel="tag">immediateFlush</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/innodb-spin-wait-delay/" rel="tag">innodb_spin_wait_delay</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/insmod/" rel="tag">insmod</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/intel/" rel="tag">intel</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ip-local-port-range/" rel="tag">ip_local_port_range</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ipmi/" rel="tag">ipmi</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ipmitool/" rel="tag">ipmitool</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iptables/" rel="tag">iptables</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/irqbalance/" rel="tag">irqbalance</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/isatty/" rel="tag">isatty</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/iso/" rel="tag">iso</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/java/" rel="tag">java</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/journald/" rel="tag">journald</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/keepalive/" rel="tag">keepalive</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/keepalived/" rel="tag">keepalived</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/kernel/" rel="tag">kernel</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/kill/" rel="tag">kill</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/kubernetes/" rel="tag">kubernetes</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/linux/" rel="tag">linux</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/listen/" rel="tag">listen</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/load/" rel="tag">load</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/localhost/" rel="tag">localhost</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/lock/" rel="tag">lock</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/log/" rel="tag">log</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/log4j2/" rel="tag">log4j2</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/logback/" rel="tag">logback</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/macOS/" rel="tag">macOS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/make/" rel="tag">make</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/memory/" rel="tag">memory</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/module/" rel="tag">module</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mysql/" rel="tag">mysql</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nagles/" rel="tag">nagles</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nbns/" rel="tag">nbns</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/net-write-timeout/" rel="tag">net_write_timeout</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nethogs/" rel="tag">nethogs</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/netstat/" rel="tag">netstat</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/network/" rel="tag">network</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/neverBlock/" rel="tag">neverBlock</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nginx/" rel="tag">nginx</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/no-login/" rel="tag">no-login</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/nslookup/" rel="tag">nslookup</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/numa/" rel="tag">numa</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/overflows/" rel="tag">overflows</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/pam/" rel="tag">pam</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/password/" rel="tag">password</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/perf/" rel="tag">perf</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/performance/" rel="tag">performance</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ping/" rel="tag">ping</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/pipe/" rel="tag">pipe</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/pipeline/" rel="tag">pipeline</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/queue/" rel="tag">queue</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/receiveBuffer/" rel="tag">receiveBuffer</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/recv-q/" rel="tag">recv-q</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/recvBuffer/" rel="tag">recvBuffer</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/reset/" rel="tag">reset</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/retransmission/" rel="tag">retransmission</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/reuseport/" rel="tag">reuseport</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rmem/" rel="tag">rmem</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rotate/" rel="tag">rotate</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/route/" rel="tag">route</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rp-filter/" rel="tag">rp_filter</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/rsyslogd/" rel="tag">rsyslogd</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/san/" rel="tag">san</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/scapy/" rel="tag">scapy</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/send-q/" rel="tag">send-q</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/sendBuffer/" rel="tag">sendBuffer</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/service/" rel="tag">service</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/shell/" rel="tag">shell</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/simultaneous/" rel="tag">simultaneous</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/socat/" rel="tag">socat</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/socket/" rel="tag">socket</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/softirq/" rel="tag">softirq</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/speed/" rel="tag">speed</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ss/" rel="tag">ss</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ssh/" rel="tag">ssh</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/strace/" rel="tag">strace</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/stream/" rel="tag">stream</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/syn-flood/" rel="tag">syn flood</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/syn-queue/" rel="tag">syn queue</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/sysbench/" rel="tag">sysbench</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tc/" rel="tag">tc</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tcp-windows-scale/" rel="tag">tcp windows scale</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tcp-metrics/" rel="tag">tcp_metrics</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tcpdump/" rel="tag">tcpdump</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tcpretrans/" rel="tag">tcpretrans</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tcptrack/" rel="tag">tcptrack</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/thread-pool/" rel="tag">thread pool</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/time-zone/" rel="tag">time_zone</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/timeout/" rel="tag">timeout</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/timer/" rel="tag">timer</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/timer-keepalive/" rel="tag">timer(keepalive)</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/troubleshooting/" rel="tag">troubleshooting</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tshark/" rel="tag">tshark</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/tuning/" rel="tag">tuning</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/ubuntu/" rel="tag">ubuntu</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/unres-qlen/" rel="tag">unres_qlen</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/vipclient/" rel="tag">vipclient</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/virtualbox/" rel="tag">virtualbox</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/vmtouch/" rel="tag">vmtouch</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/volume/" rel="tag">volume</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/vxlan/" rel="tag">vxlan</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/weibo/" rel="tag">weibo</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/windows/" rel="tag">windows</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/wireshark/" rel="tag">wireshark</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/wlc/" rel="tag">wlc</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/wmem/" rel="tag">wmem</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/wrr/" rel="tag">wrr</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/x86/" rel="tag">x86</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/yum/" rel="tag">yum</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/yum-utils/" rel="tag">yum-utils</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/zone-reclaim-mode/" rel="tag">zone_reclaim_mode</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E4%B8%BE%E4%B8%89%E5%8F%8D%E4%B8%80/" rel="tag">举三反一</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E4%B9%B1%E5%BA%8F/" rel="tag">乱序</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E4%BA%8B%E5%8A%A1/" rel="tag">事务</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E4%BF%9D%E9%99%A9/" rel="tag">保险</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%85%89%E7%BA%A4/" rel="tag">光纤</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%89%AF%E4%B8%9A/" rel="tag">副业</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%8D%8F%E7%A8%8B/" rel="tag">协程</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%8D%A1%E9%A1%BF/" rel="tag">卡顿</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%8F%82%E6%95%B0/" rel="tag">参数</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%8F%91%E9%80%81%E7%AA%97%E5%8F%A3/" rel="tag">发送窗口</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%90%8C%E6%AD%A5%E9%98%BB%E5%A1%9EIO/" rel="tag">同步阻塞IO</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%90%8C%E6%AD%A5%E9%9D%9E%E9%98%BB%E5%A1%9EIO/" rel="tag">同步非阻塞IO</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%90%91%E9%87%8F%E5%8C%96/" rel="tag">向量化</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%A4%8D%E7%9B%98/" rel="tag">复盘</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%AD%98%E5%82%A8/" rel="tag">存储</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%AE%9E%E8%B7%B5/" rel="tag">实践</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%B8%A6%E8%B4%A7/" rel="tag">带货</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E5%BB%BA%E7%AB%99/" rel="tag">建站</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%80%9D%E8%B7%AF/" rel="tag">思路</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%83%8A%E7%BE%A4/" rel="tag">惊群</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%8C%A5%E6%89%8B/" rel="tag">挥手</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%8E%A5%E6%94%B6%E7%AA%97%E5%8F%A3/" rel="tag">接收窗口</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%98%9F%E7%90%83/" rel="tag">星球</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%9D%A1%E4%BB%B6%E8%A7%A6%E5%8F%91/" rel="tag">条件触发</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%A1%88%E4%BE%8B/" rel="tag">案例</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%A1%88%E4%BE%8B%E5%AD%A6%E4%B9%A0/" rel="tag">案例学习</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%B0%9F%E6%AF%94%E6%B4%9B%E8%8A%AC%E5%87%9D%E8%83%B6/" rel="tag">氟比洛芬凝胶</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%B3%BD%E6%99%AE%E6%80%9D/" rel="tag">泽普思</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E6%B5%B7%E5%85%89/" rel="tag">海光</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%9F%A5%E8%AF%86%E7%A7%AF%E7%B4%AF/" rel="tag">知识积累</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%A2%8E%E7%89%87/" rel="tag">碎片</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%A3%81%E7%9B%98/" rel="tag">磁盘</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%A3%81%E7%9B%98%E6%80%A7%E8%83%BD/" rel="tag">磁盘性能</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%A7%92%E6%9D%80/" rel="tag">秒杀</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%BB%8F%E6%B5%8E/" rel="tag">经济</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%BD%91%E7%90%83%E8%82%98/" rel="tag">网球肘</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%87%AA%E8%BF%9E%E6%8E%A5/" rel="tag">自连接</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%AF%BB%E7%BB%8F%E5%85%B8%E4%B9%A6/" rel="tag">读经典书</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%B4%B7%E6%AC%BE/" rel="tag">贷款</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%B5%9A%E9%92%B1/" rel="tag">赚钱</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%B6%85%E7%BA%BF%E7%A8%8B/" rel="tag">超线程</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E8%BE%B9%E7%BC%98%E8%A7%A6%E5%8F%91/" rel="tag">边缘触发</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%80%BB%E8%BE%91/" rel="tag">逻辑</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%9B%B6%E6%8B%B7%E8%B4%9D/" rel="tag">零拷贝</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%A3%9E%E8%85%BE/" rel="tag">飞腾</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E9%BB%84%E5%A5%87%E5%B8%86/" rel="tag">黄奇帆</a></li></ul> </div> </div> <div class="widget-wrap"> <h3 class="widget-title">Tag Cloud</h3> <div class="widget tagcloud"> <a href="/tags/etc-hosts/" style="font-size: 10px;">/etc/hosts</a> <a href="/tags/etc-nsswitch/" style="font-size: 10px;">/etc/nsswitch</a> <a href="/tags/ADM/" style="font-size: 10.67px;">ADM</a> <a href="/tags/AMD/" style="font-size: 10.67px;">AMD</a> <a href="/tags/Ansible/" style="font-size: 11.33px;">Ansible</a> <a href="/tags/Ansible-Linux/" style="font-size: 10px;">Ansible - Linux</a> <a href="/tags/Apple/" style="font-size: 10px;">Apple</a> <a href="/tags/AsyncAppender/" style="font-size: 10px;">AsyncAppender</a> <a href="/tags/BCC/" style="font-size: 10px;">BCC</a> <a href="/tags/BDP/" style="font-size: 10.67px;">BDP</a> <a href="/tags/BIOS/" style="font-size: 10.67px;">BIOS</a> <a href="/tags/CLOSE-WAIT/" style="font-size: 10.67px;">CLOSE_WAIT</a> <a href="/tags/CPU/" style="font-size: 17.33px;">CPU</a> <a href="/tags/CVE/" style="font-size: 10px;">CVE</a> <a href="/tags/CWND/" style="font-size: 10px;">CWND</a> <a href="/tags/DNS/" style="font-size: 14px;">DNS</a> <a href="/tags/Die/" style="font-size: 10px;">Die</a> <a href="/tags/Docker/" style="font-size: 10px;">Docker</a> <a href="/tags/DropWatch/" style="font-size: 10px;">DropWatch</a> <a href="/tags/EDNS/" style="font-size: 10px;">EDNS</a> <a href="/tags/EPOLLEXCLUSIVE/" style="font-size: 10.67px;">EPOLLEXCLUSIVE</a> <a href="/tags/ET/" style="font-size: 10px;">ET</a> <a href="/tags/FT2500/" style="font-size: 10px;">FT2500</a> <a href="/tags/FullGC/" style="font-size: 10.67px;">FullGC</a> <a href="/tags/HTTP-Proxy/" style="font-size: 10px;">HTTP Proxy</a> <a href="/tags/HeapDumpBeforeFullGC/" style="font-size: 10.67px;">HeapDumpBeforeFullGC</a> <a href="/tags/HugePage/" style="font-size: 10px;">HugePage</a> <a href="/tags/IO%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8/" style="font-size: 10px;">IO多路复用</a> <a href="/tags/IP/" style="font-size: 10px;">IP</a> <a href="/tags/IPC/" style="font-size: 11.33px;">IPC</a> <a href="/tags/Index/" style="font-size: 10px;">Index</a> <a href="/tags/Intel/" style="font-size: 10.67px;">Intel</a> <a href="/tags/JDBC/" style="font-size: 10.67px;">JDBC</a> <a href="/tags/Java/" style="font-size: 10.67px;">Java</a> <a href="/tags/LT/" style="font-size: 10px;">LT</a> <a href="/tags/LVM/" style="font-size: 10px;">LVM</a> <a href="/tags/LVS/" style="font-size: 12.67px;">LVS</a> <a href="/tags/Linux/" style="font-size: 20px;">Linux</a> <a href="/tags/LoadBalance/" style="font-size: 10.67px;">LoadBalance</a> <a href="/tags/M1/" style="font-size: 10px;">M1</a> <a href="/tags/MSS/" style="font-size: 10px;">MSS</a> <a href="/tags/MTU/" style="font-size: 10px;">MTU</a> <a href="/tags/Mac/" style="font-size: 10px;">Mac</a> <a href="/tags/Memory/" style="font-size: 12px;">Memory</a> <a href="/tags/MySQL/" style="font-size: 16px;">MySQL</a> <a href="/tags/NIO/" style="font-size: 10.67px;">NIO</a> <a href="/tags/NUMA/" style="font-size: 10px;">NUMA</a> <a href="/tags/OSS/" style="font-size: 10px;">OSS</a> <a href="/tags/PageCache/" style="font-size: 10px;">PageCache</a> <a href="/tags/Pause/" style="font-size: 10.67px;">Pause</a> <a href="/tags/Proxy/" style="font-size: 10px;">Proxy</a> <a href="/tags/RT/" style="font-size: 11.33px;">RT</a> <a href="/tags/RTO/" style="font-size: 10px;">RTO</a> <a href="/tags/RTT/" style="font-size: 10px;">RTT</a> <a href="/tags/SO-REUSEADDR/" style="font-size: 10px;">SO_REUSEADDR</a> <a href="/tags/SSD/" style="font-size: 10px;">SSD</a> <a href="/tags/SSH/" style="font-size: 10.67px;">SSH</a> <a href="/tags/SSH-Proxy/" style="font-size: 10px;">SSH Proxy</a> <a href="/tags/SSTHRESH/" style="font-size: 10px;">SSTHRESH</a> <a href="/tags/S-IFCHR/" style="font-size: 10px;">S_IFCHR</a> <a href="/tags/S-IFIFO/" style="font-size: 10px;">S_IFIFO</a> <a href="/tags/SocketTimeout/" style="font-size: 10px;">SocketTimeout</a> <a href="/tags/Socks5/" style="font-size: 10px;">Socks5</a> <a href="/tags/SpringMVC/" style="font-size: 10px;">SpringMVC</a> <a href="/tags/Sysbench/" style="font-size: 10px;">Sysbench</a> <a href="/tags/SystemStap/" style="font-size: 10px;">SystemStap</a> <a href="/tags/TCP/" style="font-size: 18px;">TCP</a> <a href="/tags/TCP-connection/" style="font-size: 10.67px;">TCP connection</a> <a href="/tags/TCP-queue/" style="font-size: 12px;">TCP queue</a> <a href="/tags/TCPWqueueTooBig/" style="font-size: 10px;">TCPWqueueTooBig</a> <a href="/tags/TCP-USER-TIMEOUT/" style="font-size: 10px;">TCP_USER_TIMEOUT</a> <a href="/tags/ThreadPool/" style="font-size: 10px;">ThreadPool</a> <a href="/tags/Unix-Socket/" style="font-size: 10px;">Unix-Socket</a> <a href="/tags/Wireshark/" style="font-size: 10px;">Wireshark</a> <a href="/tags/Zen/" style="font-size: 12px;">Zen</a> <a href="/tags/accept/" style="font-size: 10px;">accept</a> <a href="/tags/accept-queue/" style="font-size: 11.33px;">accept queue</a> <a href="/tags/ansible/" style="font-size: 10px;">ansible</a> <a href="/tags/arm/" style="font-size: 10px;">arm</a> <a href="/tags/arp/" style="font-size: 11.33px;">arp</a> <a href="/tags/arthas/" style="font-size: 10px;">arthas</a> <a href="/tags/bash-posix/" style="font-size: 10px;">bash posix</a> <a href="/tags/bpftrace/" style="font-size: 10px;">bpftrace</a> <a href="/tags/branch-miss/" style="font-size: 10px;">branch_miss</a> <a href="/tags/btrace/" style="font-size: 10px;">btrace</a> <a href="/tags/buddyinfo/" style="font-size: 10px;">buddyinfo</a> <a href="/tags/buffer/" style="font-size: 10px;">buffer</a> <a href="/tags/bug/" style="font-size: 10px;">bug</a> <a href="/tags/cache/" style="font-size: 10px;">cache</a> <a href="/tags/cache-line/" style="font-size: 10.67px;">cache_line</a> <a href="/tags/calico/" style="font-size: 11.33px;">calico</a> <a href="/tags/capinfos/" style="font-size: 10px;">capinfos</a> <a href="/tags/context/" style="font-size: 10px;">context</a> <a href="/tags/contextswitch/" style="font-size: 10px;">contextswitch</a> <a href="/tags/createrepo/" style="font-size: 10.67px;">createrepo</a> <a href="/tags/crond/" style="font-size: 10px;">crond</a> <a href="/tags/dd/" style="font-size: 10px;">dd</a> <a href="/tags/debug/" style="font-size: 10px;">debug</a> <a href="/tags/delay-ack/" style="font-size: 10px;">delay ack</a> <a href="/tags/dns/" style="font-size: 10px;">dns</a> <a href="/tags/docker/" style="font-size: 16.67px;">docker</a> <a href="/tags/dropped/" style="font-size: 11.33px;">dropped</a> <a href="/tags/druid/" style="font-size: 10.67px;">druid</a> <a href="/tags/dtrace/" style="font-size: 10px;">dtrace</a> <a href="/tags/du/" style="font-size: 10px;">du</a> <a href="/tags/env/" style="font-size: 10px;">env</a> <a href="/tags/epoll/" style="font-size: 11.33px;">epoll</a> <a href="/tags/flannel/" style="font-size: 10px;">flannel</a> <a href="/tags/forward/" style="font-size: 10px;">forward</a> <a href="/tags/free/" style="font-size: 12.67px;">free</a> <a href="/tags/gdb/" style="font-size: 10.67px;">gdb</a> <a href="/tags/github/" style="font-size: 10px;">github</a> <a href="/tags/glibc/" style="font-size: 10.67px;">glibc</a> <a href="/tags/hexo/" style="font-size: 10px;">hexo</a> <a href="/tags/hygon/" style="font-size: 12.67px;">hygon</a> <a href="/tags/ifconfig/" style="font-size: 10.67px;">ifconfig</a> <a href="/tags/iftop/" style="font-size: 10px;">iftop</a> <a href="/tags/immediateFlush/" style="font-size: 10px;">immediateFlush</a> <a href="/tags/innodb-spin-wait-delay/" style="font-size: 10.67px;">innodb_spin_wait_delay</a> <a href="/tags/insmod/" style="font-size: 10px;">insmod</a> <a href="/tags/intel/" style="font-size: 10.67px;">intel</a> <a href="/tags/ip-local-port-range/" style="font-size: 10px;">ip_local_port_range</a> <a href="/tags/ipmi/" style="font-size: 10px;">ipmi</a> <a href="/tags/ipmitool/" style="font-size: 10px;">ipmitool</a> <a href="/tags/iptables/" style="font-size: 13.33px;">iptables</a> <a href="/tags/irqbalance/" style="font-size: 10px;">irqbalance</a> <a href="/tags/isatty/" style="font-size: 10px;">isatty</a> <a href="/tags/iso/" style="font-size: 10.67px;">iso</a> <a href="/tags/java/" style="font-size: 11.33px;">java</a> <a href="/tags/journald/" style="font-size: 10px;">journald</a> <a href="/tags/keepalive/" style="font-size: 10.67px;">keepalive</a> <a href="/tags/keepalived/" style="font-size: 10px;">keepalived</a> <a href="/tags/kernel/" style="font-size: 10.67px;">kernel</a> <a href="/tags/kill/" style="font-size: 10px;">kill</a> <a href="/tags/kubernetes/" style="font-size: 14.67px;">kubernetes</a> <a href="/tags/linux/" style="font-size: 10px;">linux</a> <a href="/tags/listen/" style="font-size: 10px;">listen</a> <a href="/tags/load/" style="font-size: 10px;">load</a> <a href="/tags/localhost/" style="font-size: 10px;">localhost</a> <a href="/tags/lock/" style="font-size: 10px;">lock</a> <a href="/tags/log/" style="font-size: 10.67px;">log</a> <a href="/tags/log4j2/" style="font-size: 10px;">log4j2</a> <a href="/tags/logback/" style="font-size: 10px;">logback</a> <a href="/tags/macOS/" style="font-size: 10px;">macOS</a> <a href="/tags/make/" style="font-size: 10px;">make</a> <a href="/tags/memory/" style="font-size: 10px;">memory</a> <a href="/tags/module/" style="font-size: 10px;">module</a> <a href="/tags/mysql/" style="font-size: 10.67px;">mysql</a> <a href="/tags/nagles/" style="font-size: 10px;">nagles</a> <a href="/tags/nbns/" style="font-size: 10px;">nbns</a> <a href="/tags/net-write-timeout/" style="font-size: 10.67px;">net_write_timeout</a> <a href="/tags/nethogs/" style="font-size: 10px;">nethogs</a> <a href="/tags/netstat/" style="font-size: 13.33px;">netstat</a> <a href="/tags/network/" style="font-size: 18.67px;">network</a> <a href="/tags/neverBlock/" style="font-size: 10px;">neverBlock</a> <a href="/tags/nginx/" style="font-size: 13.33px;">nginx</a> <a href="/tags/no-login/" style="font-size: 10px;">no-login</a> <a href="/tags/nslookup/" style="font-size: 10.67px;">nslookup</a> <a href="/tags/numa/" style="font-size: 10px;">numa</a> <a href="/tags/overflows/" style="font-size: 11.33px;">overflows</a> <a href="/tags/pam/" style="font-size: 10px;">pam</a> <a href="/tags/password/" style="font-size: 10px;">password</a> <a href="/tags/perf/" style="font-size: 14.67px;">perf</a> <a href="/tags/performance/" style="font-size: 19.33px;">performance</a> <a href="/tags/ping/" style="font-size: 11.33px;">ping</a> <a href="/tags/pipe/" style="font-size: 10px;">pipe</a> <a href="/tags/pipeline/" style="font-size: 10.67px;">pipeline</a> <a href="/tags/queue/" style="font-size: 10px;">queue</a> <a href="/tags/receiveBuffer/" style="font-size: 10px;">receiveBuffer</a> <a href="/tags/recv-q/" style="font-size: 10px;">recv-q</a> <a href="/tags/recvBuffer/" style="font-size: 10px;">recvBuffer</a> <a href="/tags/reset/" style="font-size: 11.33px;">reset</a> <a href="/tags/retransmission/" style="font-size: 10px;">retransmission</a> <a href="/tags/reuseport/" style="font-size: 10.67px;">reuseport</a> <a href="/tags/rmem/" style="font-size: 10px;">rmem</a> <a href="/tags/rotate/" style="font-size: 10px;">rotate</a> <a href="/tags/route/" style="font-size: 12px;">route</a> <a href="/tags/rp-filter/" style="font-size: 11.33px;">rp_filter</a> <a href="/tags/rsyslogd/" style="font-size: 10px;">rsyslogd</a> <a href="/tags/san/" style="font-size: 10.67px;">san</a> <a href="/tags/scapy/" style="font-size: 10px;">scapy</a> <a href="/tags/send-q/" style="font-size: 10px;">send-q</a> <a href="/tags/sendBuffer/" style="font-size: 10.67px;">sendBuffer</a> <a href="/tags/service/" style="font-size: 10.67px;">service</a> <a href="/tags/shell/" style="font-size: 10.67px;">shell</a> <a href="/tags/simultaneous/" style="font-size: 10px;">simultaneous</a> <a href="/tags/socat/" style="font-size: 10px;">socat</a> <a href="/tags/socket/" style="font-size: 10px;">socket</a> <a href="/tags/softirq/" style="font-size: 10px;">softirq</a> <a href="/tags/speed/" style="font-size: 10px;">speed</a> <a href="/tags/ss/" style="font-size: 12px;">ss</a> <a href="/tags/ssh/" style="font-size: 10px;">ssh</a> <a href="/tags/strace/" style="font-size: 10px;">strace</a> <a href="/tags/stream/" style="font-size: 10px;">stream</a> <a href="/tags/syn-flood/" style="font-size: 11.33px;">syn flood</a> <a href="/tags/syn-queue/" style="font-size: 11.33px;">syn queue</a> <a href="/tags/sysbench/" style="font-size: 10.67px;">sysbench</a> <a href="/tags/tc/" style="font-size: 10px;">tc</a> <a href="/tags/tcp-windows-scale/" style="font-size: 10px;">tcp windows scale</a> <a href="/tags/tcp-metrics/" style="font-size: 10px;">tcp_metrics</a> <a href="/tags/tcpdump/" style="font-size: 15.33px;">tcpdump</a> <a href="/tags/tcpretrans/" style="font-size: 10px;">tcpretrans</a> <a href="/tags/tcptrack/" style="font-size: 10px;">tcptrack</a> <a href="/tags/thread-pool/" style="font-size: 10px;">thread pool</a> <a href="/tags/time-zone/" style="font-size: 10px;">time_zone</a> <a href="/tags/timeout/" style="font-size: 10.67px;">timeout</a> <a href="/tags/timer/" style="font-size: 10px;">timer</a> <a href="/tags/timer-keepalive/" style="font-size: 10px;">timer(keepalive)</a> <a href="/tags/troubleshooting/" style="font-size: 14px;">troubleshooting</a> <a href="/tags/tshark/" style="font-size: 11.33px;">tshark</a> <a href="/tags/tuning/" style="font-size: 10px;">tuning</a> <a href="/tags/ubuntu/" style="font-size: 10px;">ubuntu</a> <a href="/tags/unres-qlen/" style="font-size: 10px;">unres_qlen</a> <a href="/tags/vipclient/" style="font-size: 10px;">vipclient</a> <a href="/tags/virtualbox/" style="font-size: 10.67px;">virtualbox</a> <a href="/tags/vmtouch/" style="font-size: 10px;">vmtouch</a> <a href="/tags/volume/" style="font-size: 10px;">volume</a> <a href="/tags/vxlan/" style="font-size: 10px;">vxlan</a> <a href="/tags/weibo/" style="font-size: 10px;">weibo</a> <a href="/tags/windows/" style="font-size: 10px;">windows</a> <a href="/tags/wireshark/" style="font-size: 12px;">wireshark</a> <a href="/tags/wlc/" style="font-size: 10px;">wlc</a> <a href="/tags/wmem/" style="font-size: 10px;">wmem</a> <a href="/tags/wrr/" style="font-size: 10px;">wrr</a> <a href="/tags/x86/" style="font-size: 10px;">x86</a> <a href="/tags/yum/" style="font-size: 10.67px;">yum</a> <a href="/tags/yum-utils/" style="font-size: 10.67px;">yum-utils</a> <a href="/tags/zone-reclaim-mode/" style="font-size: 10px;">zone_reclaim_mode</a> <a href="/tags/%E4%B8%BE%E4%B8%89%E5%8F%8D%E4%B8%80/" style="font-size: 10.67px;">举三反一</a> <a href="/tags/%E4%B9%B1%E5%BA%8F/" style="font-size: 10px;">乱序</a> <a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size: 10px;">事务</a> <a href="/tags/%E4%BF%9D%E9%99%A9/" style="font-size: 10px;">保险</a> <a href="/tags/%E5%85%89%E7%BA%A4/" style="font-size: 10.67px;">光纤</a> <a href="/tags/%E5%89%AF%E4%B8%9A/" style="font-size: 10px;">副业</a> <a href="/tags/%E5%8D%8F%E7%A8%8B/" style="font-size: 10px;">协程</a> <a href="/tags/%E5%8D%A1%E9%A1%BF/" style="font-size: 10px;">卡顿</a> <a href="/tags/%E5%8F%82%E6%95%B0/" style="font-size: 10px;">参数</a> <a href="/tags/%E5%8F%91%E9%80%81%E7%AA%97%E5%8F%A3/" style="font-size: 10px;">发送窗口</a> <a href="/tags/%E5%90%8C%E6%AD%A5%E9%98%BB%E5%A1%9EIO/" style="font-size: 10px;">同步阻塞IO</a> <a href="/tags/%E5%90%8C%E6%AD%A5%E9%9D%9E%E9%98%BB%E5%A1%9EIO/" style="font-size: 10px;">同步非阻塞IO</a> <a href="/tags/%E5%90%91%E9%87%8F%E5%8C%96/" style="font-size: 10px;">向量化</a> <a href="/tags/%E5%A4%8D%E7%9B%98/" style="font-size: 14px;">复盘</a> <a href="/tags/%E5%AD%98%E5%82%A8/" style="font-size: 10px;">存储</a> <a href="/tags/%E5%AE%9E%E8%B7%B5/" style="font-size: 14px;">实践</a> <a href="/tags/%E5%B8%A6%E8%B4%A7/" style="font-size: 10px;">带货</a> <a href="/tags/%E5%BB%BA%E7%AB%99/" style="font-size: 10px;">建站</a> <a href="/tags/%E6%80%9D%E8%B7%AF/" style="font-size: 10px;">思路</a> <a href="/tags/%E6%83%8A%E7%BE%A4/" style="font-size: 10.67px;">惊群</a> <a href="/tags/%E6%8C%A5%E6%89%8B/" style="font-size: 10px;">挥手</a> <a href="/tags/%E6%8E%A5%E6%94%B6%E7%AA%97%E5%8F%A3/" style="font-size: 10px;">接收窗口</a> <a href="/tags/%E6%98%9F%E7%90%83/" style="font-size: 12px;">星球</a> <a href="/tags/%E6%9D%A1%E4%BB%B6%E8%A7%A6%E5%8F%91/" style="font-size: 10px;">条件触发</a> <a href="/tags/%E6%A1%88%E4%BE%8B/" style="font-size: 10.67px;">案例</a> <a href="/tags/%E6%A1%88%E4%BE%8B%E5%AD%A6%E4%B9%A0/" style="font-size: 10px;">案例学习</a> <a href="/tags/%E6%B0%9F%E6%AF%94%E6%B4%9B%E8%8A%AC%E5%87%9D%E8%83%B6/" style="font-size: 10px;">氟比洛芬凝胶</a> <a href="/tags/%E6%B3%BD%E6%99%AE%E6%80%9D/" style="font-size: 10px;">泽普思</a> <a href="/tags/%E6%B5%B7%E5%85%89/" style="font-size: 12.67px;">海光</a> <a href="/tags/%E7%9F%A5%E8%AF%86%E7%A7%AF%E7%B4%AF/" style="font-size: 14.67px;">知识积累</a> <a href="/tags/%E7%A2%8E%E7%89%87/" style="font-size: 10px;">碎片</a> <a href="/tags/%E7%A3%81%E7%9B%98/" style="font-size: 10px;">磁盘</a> <a href="/tags/%E7%A3%81%E7%9B%98%E6%80%A7%E8%83%BD/" style="font-size: 11.33px;">磁盘性能</a> <a href="/tags/%E7%A7%92%E6%9D%80/" style="font-size: 10px;">秒杀</a> <a href="/tags/%E7%BB%8F%E6%B5%8E/" style="font-size: 10px;">经济</a> <a href="/tags/%E7%BD%91%E7%90%83%E8%82%98/" style="font-size: 10px;">网球肘</a> <a href="/tags/%E8%87%AA%E8%BF%9E%E6%8E%A5/" style="font-size: 10px;">自连接</a> <a href="/tags/%E8%AF%BB%E7%BB%8F%E5%85%B8%E4%B9%A6/" style="font-size: 10px;">读经典书</a> <a href="/tags/%E8%B4%B7%E6%AC%BE/" style="font-size: 10px;">贷款</a> <a href="/tags/%E8%B5%9A%E9%92%B1/" style="font-size: 10px;">赚钱</a> <a href="/tags/%E8%B6%85%E7%BA%BF%E7%A8%8B/" style="font-size: 13.33px;">超线程</a> <a href="/tags/%E8%BE%B9%E7%BC%98%E8%A7%A6%E5%8F%91/" style="font-size: 10px;">边缘触发</a> <a href="/tags/%E9%80%BB%E8%BE%91/" style="font-size: 14px;">逻辑</a> <a href="/tags/%E9%9B%B6%E6%8B%B7%E8%B4%9D/" style="font-size: 10px;">零拷贝</a> <a href="/tags/%E9%A3%9E%E8%85%BE/" style="font-size: 10px;">飞腾</a> <a href="/tags/%E9%BB%84%E5%A5%87%E5%B8%86/" style="font-size: 10px;">黄奇帆</a> </div> </div> <div class="widget-wrap"> <h3 class="widget-title">Archiv</h3> <div class="widget"> <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2117/06/">June 2117</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2025/01/">January 2025</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/11/">November 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/10/">October 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/09/">September 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/05/">May 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/04/">April 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/03/">March 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/02/">February 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2024/01/">January 2024</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/12/">December 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/10/">October 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/09/">September 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/08/">August 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/07/">July 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/06/">June 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/05/">May 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2023/04/">April 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/11/">November 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/10/">October 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/07/">July 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/06/">June 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/05/">May 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/03/">March 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2022/01/">January 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/11/">November 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/08/">August 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/07/">July 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/06/">June 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/05/">May 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/04/">April 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/03/">March 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/02/">February 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/01/">January 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/12/">December 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/11/">November 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/09/">September 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/08/">August 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/07/">July 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/05/">May 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/04/">April 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/03/">March 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/01/">January 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/12/">December 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/11/">November 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/10/">October 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/09/">September 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/08/">August 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/07/">July 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/06/">June 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/05/">May 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/04/">April 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/01/">January 2019</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/12/">December 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/11/">November 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/10/">October 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/09/">September 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/08/">August 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/07/">July 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/06/">June 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/05/">May 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/04/">April 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">March 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/02/">February 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/01/">January 2018</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/12/">December 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/10/">October 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/08/">August 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/06/">June 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/03/">March 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/01/">January 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/10/">October 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/08/">August 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/03/">March 2016</a></li></ul> </div> </div> <div class="widget-wrap"> <h3 class="widget-title">letzter Beitrag</h3> <div class="widget"> <ul> <li> <a href="/2117/06/07/%E5%85%B3%E4%BA%8E%E6%9C%AC%E5%8D%9A/">关于本博</a> </li> <li> <a href="/2025/01/29/%E8%B7%A8Die%E7%83%AD%E8%BF%81%E7%A7%BB%E8%BF%81%E7%A7%BB/">跨 Die 热迁移迁移导致的性能问题</a> </li> <li> <a href="/2025/01/09/CPU%20%E4%BD%BF%E7%94%A8%E7%8E%87%E9%AB%98%E5%B0%B1%E4%B8%80%E5%AE%9A%E6%9C%89%E6%95%88%E7%8E%87%E5%90%97/">CPU 使用率高就一定有效率吗?</a> </li> <li> <a href="/2024/11/02/tcp%E4%BC%9A%E5%81%B6%E5%B0%943%E7%A7%92timeout/">tcp会偶尔3秒timeout的分析以及如何用php规避这个问题</a> </li> <li> <a href="/2024/10/10/%E4%B8%80%E6%AC%A1%E6%8A%93%E5%8C%85%E5%88%86%E6%9E%90%E8%BF%87%E7%A8%8B/">一次抓包分析过程——Wireshark 新手上车</a> </li> </ul> </div> </div> </aside> </div> <footer id="footer"> <div class="outer"> <div id="footer-info" class="inner"> © 2025 twitter @plantegg<br> Powered by <a href="https://hexo.io/" target="_blank">Hexo</a> </div> </div> </footer> </div> <nav id="mobile-nav"> <a href="/" class="mobile-nav-link">Home</a> <a href="/archives" class="mobile-nav-link">Archives</a> </nav> <script src="/js/jquery-3.6.4.min.js"></script> <script src="/fancybox/jquery.fancybox.min.js"></script> <script src="/js/script.js"></script> </div> </body> </html>