Linux 实战技巧

常用命令

adduser

1
2
3
4
5
6
7
8
9
10
11
12
# 以创建 Apache Eagle 用户为例
$ adduser eagle
$ passwd eagle # ur password for eagle user
# 赋予用户可以 sudo 的权限
$ chmod u+w /etc/sudoers
$ vim /etc/sudoers
# 找到 `root ALL=(ALL) ALL` 这行,并在下面添加 eagle 用户
eagle ALL=(ALL) ALL
$ chmod u-w /etc/sudoers

# 切换到 eagle 用户
$ su - eagle

awk

求和

1
2
3
4
$ echo '1' >> sum
$ echo '2' >> sum
$ echo '3' >> sum
$ cat sum
1
2
3
1
2
3
1
$ cat sum | awk '{s+=$1} END {print s}'
1
6
1
$ awk '{s+=$1} END {print s}' sum
1
6

过滤

1
2
3
4
5
6
7
8
9
10
# 清空文件
$ > test_awk.txt

# 测试数据集
$ echo "a 1 3" >> test_awk.txt
$ echo "b 1 2" >> test_awk.txt
$ echo "c 2 1" >> test_awk.txt

# 过滤出第二列大于 1 的记录
$ cat test_awk.txt | awk '{if($2>1) print}'
1
c 2 1
1
2
# 过滤出第三列大于 2 的记录
$ cat test_awk.txt | awk '{if($3>2) print}'
1
a 1 3

bash

1
2
# 下载并一键执行 Shell 脚本,更多相关内容:https://yuzhouwan.com/posts/666/
$ bash -c "$(curl -L https://raw.githubusercontent.com/asdf2014/algorithm/master/first_commit.sh)"

cat

1
2
3
# 查看系统版本
$ cat /etc/issue
Yuzhouwan Group Enterprise Linux Server release 7.2

chown

1
2
3
4
# 软链接
$ chown -h superset:superset superset
# 所有子目录及文件
$ chown -R superset:superset superset-0.15.4

cp

1
2
3
4
5
6
7
8
# -r 拷贝目录
# -p 保留源文件或目录的属性,包括所有者、所属组、权限与时间
# -u 只会在源文件的修改时间,相比目的文件的修改时间更新时,或是对应的目的文件不存在,才执行复制
# -v 打印拷贝了哪些文件
$ cp -r -p -u -v ../* /

# 通过如下方式,可以避免源文件不存在而导致的报错
$ cp ~/does_not_exist ~/somewhere 2>/dev/null | true && echo "always get here"
为了避免别名的影响,可以使用 /bin/cp 原生的命令,或者 unalias cp 消除别名

crontab

1
2
3
4
5
6
7
# 查看脚本是否执行
$ tail -f /var/log/cron

# 防止 nohup 的定时任务发邮件给 root,导致 var 目录堆积(/var/spool/cron)
$ crontab -e
# 加在文件开头
MAILTO=""

curl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 发送 Post 请求
$ curl -H "Content-Type:application/json" -X POST --data '{"blog":"yuzhouwan"}' localhost:8080/api/query

# 发送的 Put 请求中带时间戳
for (( i=1; i<=100; i++ )); do echo '[{"ts": "yuzhouwan_'`date "+%s"`'"}]' > id.data; curl -s -X POST -H "Content-Type: application/json" http://yuzhouwan:8081/api/put -d "@id.data" ; done

# 发送 gzip 压缩的请求
$ gzip raw
$ curl -X POST --data-binary "@raw.gz" --header "Content-Type: application/json" --header "Content-Encoding: gzip" http://yuzhouwan:8888/gzip_api

# 查看公网 IP
$ curl cip.cc

# 链接 Proxy
$ curl xxx --proxy "socks5://127.0.0.1:9876"

date

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
# 该时间戳格式为 13 位毫秒级别
$ echo "`date -d '2017-04-21 10:00:00' +%s`000"

# 打印时间戳
$ date +%s

# 格式化时间戳
$ date '+%Y-%m-%d %H:%M:%S'

# 打印纳秒
$ date "+%s%N"
1539065478653578171

# 打印当前日期
$ date -R
Sun, 12 Mar 2023 15:28:54 +0800

# 打印一天前的相同时刻
$ date -R -d '-1day'
Sun, 12 Mar 2023 15:28:54 +0800

# 解析时间戳
$ date -d@1545149326
Wed Dec 19 00:08:46 CST 2018
$ date -d@1543210000 "+%Y-%m-%d %H:%M:%S"
2018-11-26 13:26:40

# For MacOS
$ date -r 1577808000
2020年 1月 1日 星期三 00时00分00秒 CST
$ date -r 1577808000 "+%Y-%m-%d %H:%M:%S"
2020-01-01 00:00:00

df

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 磁盘信息
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/systemvg-rootlv 7.9G 388M 7.1G 6% /
tmpfs 3.9G 12K 3.9G 1% /dev/shm
/dev/vda1 485M 39M 421M 9% /boot
/dev/mapper/systemvg-homelv 30G 11G 18G 38% /home
/dev/mapper/systemvg-optlv 30G 187M 28G 1% /opt
/dev/mapper/systemvg-tmplv 2.0G 68M 1.9G 4% /tmp
/dev/mapper/systemvg-usrlv 9.9G 2.7G 6.7G 29% /usr
/dev/mapper/systemvg-varlv 6.0G 352M 5.3G 7% /var
/dev/mapper/datavg-datalv 98G 4.1G 89G 5% /data

# 找到 device 对应的 disk
$ ls -trl /dev/mapper/
crw-rw---- 1 root root 10, 58 May 27 2017 control
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-lv_swap -> ../dm-0
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-optlv -> ../dm-4
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-tmplv -> ../dm-5
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-usrlv -> ../dm-6
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-varlv -> ../dm-3
lrwxrwxrwx 1 root root 7 May 27 2017 systemvg-rootlv -> ../dm-1
lrwxrwxrwx 1 root root 7 Jun 1 2017 datavg-datalv -> ../dm-7
lrwxrwxrwx 1 root root 7 Jun 1 2017 systemvg-homelv -> ../dm-2

diff

1
2
3
4
5
6
7
8
9
10
11
$ echo "a" > a
$ echo "b" > b
$ diff a b
1c1
< a
---
> b

# 1c1:表示 a 文件的第 1 行 和 b 文件的第 1 行不一致
# < a:展示的是 a 文件中不一致的那一行的内容
# > b:展示的是 b 文件中不一致的那一行的内容

du

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 文件大小
$ du -h /home/ --max-depth=1
3.1G /home/eagle
40.8G /home/

$ du -sh *
64K logs
276M software
28K zkdata

# 指定以 MB 为单位展示大小,并排序
$ du -sm * | sort -nr

# 另外,使用 `ll -h` 也可以得到文件的大小
$ ll -h
总用量 546M
-rw-rw-r-- 1 zookeeper zookeeper 41M 8月 31 10:08 zookeeper.log
-rw-rw-r-- 1 zookeeper zookeeper 101M 8月 31 00:06 zookeeper.log.1
-rw-rw-r-- 1 zookeeper zookeeper 101M 8月 29 11:39 zookeeper.log.2
-rw-rw-r-- 1 zookeeper zookeeper 101M 8月 27 16:01 zookeeper.log.3
-rw-rw-r-- 1 zookeeper zookeeper 101M 8月 25 20:38 zookeeper.log.4
-rw-rw-r-- 1 zookeeper zookeeper 101M 8月 24 00:34 zookeeper.log.5
-rw-rw-r-- 1 zookeeper zookeeper 4.2M 8月 31 09:35 zookeeper.out

echo

1
2
3
4
5
6
7
8
9
# 不换行
$ echo -e "yuzhouwan.com\c" >> blogs.txt

# 除法结果保留小数位
$ echo "scale=2;1234/1000" | bc
1.23
$ res=$(echo "scale=2;1234/1000" | bc)
$ echo $res
1.23

find

1
2
3
4
5
# 找到最占用磁盘空间的十个文件
$ find / -type f -print0 | xargs -0 du -h | sort -rh | head -n 10

# 当需要删除的文件列表过长时,rm 会报错 Argument list too long,此时则可以使用 find 命令进行查找删除
$ find . -name "*.log" -delete

firefox

1
2
$ yum install firefox
$ firefox yuzhouwan.html

grep

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
# 或操作
$ grep -E 'bin|etc'
$ egrep 'bin|etc'
$ awk '/bin|etc/'

# 与操作
$ grep bin | grep etc

# 不区分大小写
$ grep -i BIN # (bin/sbin)

# 全词匹配
$ grep -w bin # (bin)

# 匹配,并指定显示多少行上下文
$ grep -C 1 bin # (bin/boot root/sbin/script)

# 过滤脚本输出(|& 相当于 stdout + stderr)
$ zkServer.sh status |& grep Mode
Mode: follower

# 特殊字符,需要增加反斜杠(\),进行转义
$ grep \$

# 根据字段 str 匹配到某一行,并获取这一行随后的 n 行(grep -A <n> <str>)
$ vim yuzhouwan.txt
1
2
3
4
5
6
$ cat yuzhouwan.txt | grep -A 2 3
3
4
5

hostname

1
2
3
# 查看机器的 IP 地址
$ hostname -i
10.10.10.1

iostat

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
# 查看磁盘 I/O
$ iostat -x 1 10
Linux 2.6.32-504.3.3.el6.centos.plus.x86_64 (yuzhouwan01.com) 01/19/2018 _x86_64_ (24 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
17.28 0.00 2.02 0.08 0.00 80.62

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
sdb 0.01 159.15 2.51 9.62 493.46 1350.14 151.96 0.02 1.77 0.28 0.34
sdd 0.01 162.24 2.49 9.69 493.13 1375.44 153.42 0.02 1.85 0.28 0.34
sdc 0.01 162.57 2.49 9.83 487.72 1379.15 151.58 0.02 2.02 0.27 0.34
sde 0.01 161.54 2.54 9.80 500.08 1370.68 151.61 0.02 1.87 0.28 0.34
sdg 0.01 162.06 2.53 9.83 498.98 1375.10 151.64 0.02 1.93 0.28 0.34
sdf 0.01 163.34 2.53 9.74 496.43 1384.64 153.22 0.02 1.92 0.28 0.35
sdh 0.01 161.44 2.56 9.74 502.61 1369.43 152.20 0.02 1.90 0.28 0.34
sdi 0.01 162.58 2.54 9.78 498.87 1378.82 152.48 0.02 1.99 0.28 0.35
sda 0.29 83.48 0.29 146.49 14.22 1146.37 7.91 0.04 0.28 0.05 0.69
sdj 0.01 164.20 2.60 9.77 512.11 1391.74 153.90 0.02 1.97 0.28 0.35
sdk 0.01 161.76 2.60 9.70 505.17 1371.69 152.56 0.03 2.04 0.28 0.35
sdl 0.01 168.31 2.50 9.84 490.59 1425.20 155.30 0.03 2.57 0.28 0.35
sdm 0.01 162.34 2.52 9.69 486.76 1376.20 152.68 0.02 1.97 0.28 0.34
dm-0 0.00 0.00 0.02 0.31 0.70 2.45 9.64 0.00 6.04 0.43 0.01
dm-1 0.00 0.00 0.39 0.42 3.10 3.32 8.00 0.00 3.42 0.27 0.02
dm-2 0.00 0.00 0.10 173.34 7.42 1035.66 6.01 0.02 0.04 0.03 0.55
dm-3 0.00 0.00 0.01 7.47 0.22 59.05 7.93 0.00 0.56 0.07 0.05
dm-4 0.00 0.00 0.00 4.10 0.00 32.78 8.00 0.00 0.56 0.04 0.02
dm-5 0.00 0.00 0.04 0.81 1.26 6.51 9.07 0.00 0.59 0.17 0.01
dm-6 0.00 0.00 0.01 0.82 1.51 6.59 9.68 0.00 2.02 0.16 0.01

jq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# https://stedolan.github.io/jq/download/ 下载 jq-linux64
$ wget https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
$ mv jq-linux64 ~/software/
$ cd ~/software/
$ chmod +x jq-linux64
$ ln -s jq-linux64 jq
$ vim ~/.bashrc
alias jq='/home/connect/software/jq'

$ source ~/.bashrc
$ curl localhost:8083/ | jq
{
"version": "0.11.0.0-cp1",
"commit": "5cadaa94d0a69e0d"
}

# 获取某一个元素
$ jq ".element"

# 获取数组中的某一个
$ jq ".element[0]"

# 重新把 JSON 压缩成一行
$ jq -c

jstack

1
2
# 线程 dump,可以判断进程是否是假死状态
$ jstack -m <pid>

jstat

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看 Java 进程的 GC 情况
# Usage: jstat -gcutil <pid> <interval_ms>
$ jstat -gcutil 24909 1000
S0 S1 E O P YGC YGCT FGC FGCT GCT
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 98.56 6.38 46.86 2 0.147 0 0.000 0.147
40.77 0.00 99.16 6.38 46.87 2 0.147 0 0.000 0.147

lsblk

1
$ lsblk -d -o name,rota
1
2
3
4
5
6
# rota,全称 rotational,表示磁盘是否可以旋转
# 其中,0 表示不可以旋转,而 1 表示可以旋转
# 前者不可以旋转的说明是 SSD 盘,反之,后者可以旋转则不是 SSD 盘,有可能是 SDA 盘等
NAME ROTA
vda 1
vdb 1

lsof

1
2
3
4
5
6
# 系统级 监控 & 诊断工具
# 指定进程号,可以查看该进程打开的文件
$ lsof -p <pid>

# 针对打开的文件数,对进程进行排序
$ lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr

nc

1
2
3
4
5
6
7
8
9
# 模拟暴露 8080 端口的服务器
$ nc -l 8080

# 最简聊天室
# Windows1
$ nc -l 8080
# Windows2
$ nc localhost 8080
# 然后,可以在各自的窗口输入信息,另一个窗口就可以接收到

nmon

生成 nmon 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 获得帮助文档
$ nmon_x86_64_centos6 -h

# 不同的操作系统,可能 nmon 命令不一样
# -f 使得 xxx.nmon 文件名包含文件创建的时间
# -N 指定需要对 NFS 活动情况进行监控
# -m 指定生成的 xxx.nmon 存放的目录
# -s 指定相隔多少秒,做一次监控
# -c 指定采集多少次监控数据,生成一个 xxx.nmon 文件
$ /nmon/nmon_x86_64_rhel6 -f -N -m /nmon -s 60 -c 1440
$ /nmon/nmon_x86_64_centos6 -f -N -m /nmon -s 60 -c 1440

# 转换 .nmon 文件为 .csv 文件
$ sort yuzhouwan-prd3_170831_0001.nmon > yuzhouwan-prd3_170831_0001.csv

# 查看监控数据
# 在 https://www.ibm.com/developerworks/community/wikis/home?lang=en#!/wiki/Power%20Systems/page/nmon_analyser 页面,下载 nmon analyser v52_1.xlsm 文件
# 打开后,会提示“启用宏定义”,点击确定
# 一共会有 Analyser / Settings / Release Notes 三个 sheet,跳转到第一个 Analyser 里
# 点击 “Analyze nmon data” 按钮,选择 yuzhouwan-prd3_170831_0001.csv 文件
# 会生成一个 yuzhouwan-prd3_170831_0001.nmon.xlsx,并直接打开

系统资源实时监控

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
$ /nmon/nmon_x86_64_centos6
# 常用组合:nml
lnmonq14g-------------------Hostname=yuzhouwan-prd3-Refresh= 2secs ---11:04.35-----------|
| CPU +-------------------------------------------------------------------------+ |
|100%-| | |
| 95%-| | | # 为方便展示,此处省去 25% ~ 90%
| 20%-| | |
| 15%-| | |
| 10%-| ss | |
| 5%-|UUUUsUUUUsUUsUUUUwUUssUssUUssUUUsUUsUUUUUsU| |
| +--------------------User---------System----+----Wait---------------------+ |
| Memory Stats --------------------------------------------------------------------------|
| RAM High Low Swap Page Size=4 KB |
| Total MB 129013.3 -0.0 -0.0 10240.0 |
| Free MB 584.1 -0.0 -0.0 10240.0 |
| Free Percent 0.5% 100.0% 100.0% 100.0% |
| MB MB MB |
| Cached= 76656.7 Active= 54649.3 |
| Buffers= 4908.1 Swapcached= 0.0 Inactive = 68809.5 |
| Dirty = 131.6 Writeback = 0.0 Mapped = 75.1 |
| Slab = 4059.3 Commit_AS = 47663.8 PageTables= 96.6 |
| Network I/O ---------------------------------------------------------------------------|
|I/F Name Recv=KB/s Trans=KB/s packin packout insize outsize Peak->Recv Trans |
| lo 1044.0 1044.0 335.5 335.5 3186.8 3186.8 8639.4 8639.4 |
| eth0 5262.7 1168.7 5999.0 2762.1 898.3 433.3 10531.6 43312.1 |
| eth1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 |
|---------Warning: Some Statistics may not shown-----------------------------------------|

nohup

1
2
3
4
5
6
7
8
9
10
11
12
13
# 启动一个后台进程
$ nohup sleep 1000 &
[1] 30408
# 列出所有的后台进程
$ jobs
[1]+ Running nohup sleep 1000 &
# 将后台进程 [1] 提到前台,并 Ctrl+C 停止进程
$ fg %1
nohup sleep 1000
# 检查后台进程已经被停止
$ jobs
# 不输出任何内容
$ nohup bash yuzhouwan.sh >/dev/null 2>&1 &

make

1
2
3
4
5
$ make -j<thread_num>
# 查看 CPU 核数
$ cat /proc/cpuinfo | grep processor | wc -l
# 如果是两个处理器的话,一般 `-j2` 可以达到最高效率(某些进程主要耗时是在 I/O 上,并不能充分利用单个 cpu 的时间,则可以考虑 -j4)
# 一般的,使用 `thread_num = number_of_cores + 1` 公式来计算即可

mount

重命名挂载点

1
2
3
4
5
6
7
# 这里举例将 old 目录更名为 yuzhouwan
$ sudo su root
$ mkdir -p /yuzhouwan
$ vim /etc/fstab
/dev/mapper/vg_os-lv_app /yuzhouwan ext4 defaults 1 2
$ umount /old
$ mount /yuzhouwan

locale

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ locale 
LANG=en_US.UTF-8 # 未设置任何 LC_xxx 变量时所使用的默认值
LC_CTYPE=zh_CN.UTF-8 # 指定使用某区域的字符分类及处理方式
LC_NUMERIC="en_US.UTF-8" # 指定使用某区域的非货币的数字格式
LC_TIME="en_US.UTF-8" # 指定使用某区域的日期和时间格式
LC_COLLATE="en_US.UTF-8" # 指定使用某区域的排序规则
LC_MONETARY="en_US.UTF-8" # 指定使用某区域的货币格式
LC_MESSAGES="en_US.UTF-8" # 指定使用某区域的响应与信息的格式
LC_PAPER="en_US.UTF-8" # 指定使用某区域的纸张大小
LC_NAME="en_US.UTF-8" # 指定使用某区域的姓名书写方式
LC_ADDRESS="en_US.UTF-8" # 指定使用某区域的地址格式和位置信息
LC_TELEPHONE="en_US.UTF-8" # 指定使用某区域的电话号码格式
LC_MEASUREMENT="en_US.UTF-8" # 指定使用某区域的度量衡规则
LC_IDENTIFICATION="en_US.UTF-8" # 对 locale 自身信息的概述
LC_ALL= # 用来覆盖掉所有其他 LC_xxx 变量的值

tar

1
2
3
4
# 压缩
$ tar zcvf gc.tar.gz gc
# 解压
$ tar zxvf gc.tar.gz

top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在 top 展示页面中
# 按 CPU 排序:Shift + P
# 按内存排序:Shift + M

# 查看进程内,各线程的资源消耗
$ top -Hp <pid>

# 进一步,可以根据线程 pid 在 jstack 中定位出对应的线程栈
# 拿到 thread_pid 对应的 16 进制数
$ printf '%x\n' <thread_pid>
$ jstack <pid> | less

# 在 batch 模式(-b),并控制命令执行迭代次数为 1 次(-n),达到生成一次 CPU 使用情况的快照
$ top -b -n 1

tree

1
2
3
4
5
6
$ yum install tree -y
$ tree -L 1 /
/
├── bin
├── usr
└── var

rpm

1
$ rpm -i --badreloc --relocate /usr/java=/home/eagle/software/java jdk-7u80-linux-x64.rpm

rsync

1
2
# 软链接、隐藏文件等特殊文件的复制,需要用 `rsync` 命令而不能用 `scp`
$ rsync -avuz -e ssh eagle/ root@eagle:/home/eagle

sed

1
2
# 直接修改文件
$ sed -in-place -e 's/blog/yuzhouwan.com/g' yuzhouwan.json

scp

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
# 后台运行 scp 传输任务
$ scp yuzhouwan root@192.168.1.1:/
root@192.168.1.1's password:
yuzhouwan 0% 11MB 1.3MB/s 5:24:10 ETA^Z

# Ctrl+Z 暂停 scp 任务
[1]+ Stopped scp yuzhouwan root@192.168.1.1:/

# 查看任务已经被暂停
$ jobs
[1]+ Stopped scp yuzhouwan root@192.168.1.1:/

# 使得其在后台恢复运行
$ bg %1
[1]+ scp yuzhouwan root@192.168.1.1:/ &

# 已恢复运行
$ jobs
[1]+ Running scp yuzhouwan root@192.168.1.1:/ &

# 忽略 HUP 信号(终端断线的中断信号)
$ disown -h %1

# 退出
$ exit

# 重新登录后,查看 scp 后台任务是否正常运行
$ ps -ef | grep scp
root 6189 1 0 18:04 ? 00:00:00 scp yuzhouwan root@192.168.1.1:/
root 6190 6189 0 18:04 ? 00:00:00 /usr/bin/ssh -x -oForwardAgent=no -oPermitLocalCommand=no -oClearAllForwardings=yes -l root -- 192.168.1.1 scp -t /
root 7723 7643 0 18:05 pts/3 00:00:00 grep --color=auto scp

split

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
# 写入三行数据
$ echo "1" >> yuzhouwan
$ echo "2" >> yuzhouwan
$ echo "3" >> yuzhouwan
$ cat yuzhouwan
1
2
3

# 按照数据行数切分
$ split -l 1 yuzhouwan tmp_
# 按照数据大小切分
# 默认不指定单位是 B,支持 K, M, G, T, P, E, Z, Y
$ split -b 2 yuzhouwan tmp_

# 成功切分
$ ll tmp_*
-rw-r--r-- 1 root root 2 Aug 2 19:35 tmp_aa
-rw-r--r-- 1 root root 2 Aug 2 19:35 tmp_ab
-rw-r--r-- 1 root root 2 Aug 2 19:35 tmp_ac

# 两种切分方式,得到的结果应该是一样的
$ cat tmp_aa
1
$ cat tmp_ab
2
$ cat tmp_ac
3

# 合并为新文件
$ cat tmp_* > yuzhouwan_new
$ cat yuzhouwan_new
1
2
3

ssh

1
2
3
4
5
6
7
8
# 登录远程机器,执行命令
$ ssh bj@192.168.1.101 "df -h"

# 免密登录远程机器,并执行命令
$ ssh -i ~/.ssh/id_rsa bj@192.168.1.101 "df -h"

# 使用逗号分隔多个执行命令
$ ssh bj@192.168.1.101 "pwd; ls"

sz

1
2
3
4
5
6
7
8
# 安装
$ yum install lrzsz -y
# 将服务器上文件,拉取到本机
$ sz <file name>

# 如果下载出现乱码,可以增加 `-e` 参数,同理 `rz` 也可以通过这个参数来解决乱码问题
-e, --escape
Force sender to escape all control characters; normally XON, XOFF, DLE, CR-@-CR, and Ctrl-X are escaped.

ping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如果遇到 ZooKeeper 报警网络延迟,不能只看延迟,还需要关注丢包率(packet loss)
# 如果出现网络丢包率过高,可能是交换机的负载过大,接口松动,或者是网线质量问题
$ ping yuzhouwan01
PING yuzhouwan01.com (192.168.1.101) 56(84) bytes of data.
64 bytes from yuzhouwan01.com (192.168.1.101): icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from yuzhouwan01.com (192.168.1.101): icmp_seq=2 ttl=64 time=0.066 ms
64 bytes from yuzhouwan01.com (192.168.1.101): icmp_seq=3 ttl=64 time=0.064 ms
64 bytes from yuzhouwan01.com (192.168.1.101): icmp_seq=4 ttl=64 time=0.083 ms
^C
--- yuzhouwan01.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3443ms
rtt min/avg/max/mdev = 0.055/0.067/0.083/0.010 ms

# 控制 ping 的次数 和 timeout 时间(单位:秒)
$ ping -c 4 yuzhouwan01 -W 5

ps

1
2
3
4
5
6
# 查看进程的 “启动时间” 和 “已运行时长”
$ ps -eo pid,lstart,etime | grep <pid>

# 查看进程分配内存大小
# RSS(Resident Set Size)常驻内存集,表示该进程分配的内存大小
$ ps -e -o pid,rss

ulimit

1
2
3
4
5
6
7
8
# 当进程报错 `java.io.IOException: Too many open files` 时,可以检查是不是没有修改默认的打开文件数(1024)
$ ulimit -n
1024

# 可以通过下面这行命令进行修改
$ ulimit -HSn 4096

# 想要重启系统后,保证修改仍然生效,则可以把上面的命令,添加到 ~/.bash_profile 或者 /etc/profile

uname

1
2
3
4
5
6
7
8
$ uname -a
Linux yuzhouwan 2.6.32-504.3.3.el6.centos.plus.x86_64 #1 SMP Wed Dec 17 01:21:03 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

# kernel version
$ uname -r
2.6.32-504.3.3.el6.centos.plus.x86_64

# 具体含义:<内核主版本>.<偶数:稳定版本/奇数:开发中版本>.<错误修补的次数>-<?>

umask

1
2
3
4
5
6
7
8
9
10
11
# umask 全称 user file-creatiopn mode mask,用来控制用户权限的
# 查看 umask
$ umask
0022
$ umask -S
u=rwx,g=rx,o=rx
# 修改 umask
$ umask 0022
# 存在 umask 之后,会减少用户的 umask 权限(例如,0777 - 0022 = 0755)
# 所以,部署安装包的时候,如果 *.sh / *.py 脚本的执行权限分配不足的话,同时 umask 设置较大时,解压安装包后,会出现文件执行权限不足的情况
# 这时候,需要考虑分配适当的文件和文件夹权限

vim

按照单词跳转

1
2
# w       跳到下一个单词的开头
# get 跳到上一个单词的开头

撤销

1
2
3
# u       撤销上一步的操作
# U 恢复当前行(即一次撤销对当前行的全部操作)
# Ctrl+r 恢复上一步被撤销的操作

翻页

1
2
# Ctrl+U   向上翻半页
# Ctrl+D 向下翻半页

退出并保存

1
# esc + ZZ   (esc 是确保 vim 已经退出了编辑模式)

wget

从 ftp 服务器下载文件

1
$ wget -nd -m --ftp-user=<user> --ftp-password=<password> ftp://<ftp_server>/zookeeper/zookeeper-3.4.6.4.tar.gz

输出到控制台

1
2
3
4
5
6
# -q:quiet 模式,屏蔽 request header 信息的回显
# -O:指定输出文件,后面加 - 横杠,就定向为 console
$ wget -q -O- <url>

# 简写
$ wget -qO- <url>

Tips: 另外还有一个好处,wget 可以实现跳转。以 Apache Druid 为例,用 curl 命令去访问 http://standby-overlord:8090/druid/indexer/v1/runningTasks 会因为 overlord 是 standby 结点,导致调用接口失败。而如果用 wget 命令则可以自动跳转到 active 结点,同时再配合使用 -qO- 命令,就能达到和 curl 命令一样,将接口调用结果直接输出到控制台的效果(当然 curl -L 也可以实现)

xargs

1
2
# 获取 Java 进程的 PID,并使用 jstat 命令观察 GC 情况
$ jps -ml | grep yuzhouwan | awk '{print $1}' | xargs -i jstat -gcutil {} 1000

标准 I/O

1
2
3
4
# 只输出 “脚本执行异常信息”
$ ./commands.sh 1>/dev/null 2>./error.log
# 完全不作输出
$ ./commands.sh 1>/dev/null 2>&1

Shell 编程

.bashrc

1
2
# 在执行脚本开始的时候,载入环境变量
source ~/.bash_profile

dirname

1
2
# 跳转到,当前脚本的文件目录
cd `dirname $0`

cut string

1
2
3
# 将 string 的前后各删除一个字符
arr="[leader, election, zookeeper]"
arr=`echo ${arr:1:${#arr}-2}` # leader, election, zookeeper

command

1
2
3
4
# 通过 `command` 命令,可以在脚本开始执行前,对需要的命令进行 check
command -v nc >/dev/null 2>&1 || {
echo >&2 "I require nc but it's not installed!"; exit 1;
}

for loop

遍历文件

1
2
3
for line in `cat yuzhouwan.txt`; do
echo $line
done

进阶用法

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
arr="leader, election, zookeeper"
OLD_IFS="$IFS"
IFS=$", "
arr=(${arr})
for a in ${arr[@]}; do
echo ${a}
done
IFS="$OLD_IFS"

# 设置 IFS,前文 "实战技巧 - IFS 设置" 部分已经提到了
# 下面介绍一种 `fori` 的遍历方式

# 0 /election
# 0 /leader
# 2 /zookeeper
sorted_num=`echo ${sorted} | wc -l`
if [ ${sorted_num} -gt ${top_n} ]; then
sorted_num=${top_n}
fi
json="{"
for (( i=1; i<=${sorted_num}; i++ )); do
line=`echo "${sorted}" | sed -n "${i} p"`
json="${json}`echo ${line} | awk '{print $1}'`: `echo ${line} | awk '{print $2}'`"
if [ ${i} -lt ${sorted_num} ]; then
json="${json}, "
fi
done
json="${json}}"

# 当然,也有其他的实现方法,比如下面 `seq` 这种
# 但是,`seq` 这种方式需要注意两点
# 第一点,$(seq 1 1 10) 里面的 初始值、步长、最大值 都必须要是 Integer 类型的,如果是通过 shell 外部传参进来的,需要做类型转换
# 第二点,`seq` 方式在 console 里面执行没问题,但是,写在 shell 脚本里面,可能会失效(原因尚不明确)
for i in $(seq 1 2 10); do echo "skip by 2 step, value is $i"; done

function

1
2
3
4
5
6
7
8
# 定义函数的时候,不需要表示入参,直接用 $1 / $2 获取入参的值
function f() {
echo "$1.$2"
}

# 函数定义需要在调用逻辑之前
# 多个入参,依次空格隔开传入,即可
f "yuzhouwan" "com"

if

1
2
3
4
5
6
7
8
9
10
11
# 判断字符串是否一致
if [[ ${context}_x == "_x" ]]; then
echo "OK"
fi

# 数值比较大小(这种写法还支持浮点数,而 `lt` / `gt` 的写法则不行)
if [[ $(echo "${origin_cost_time} >= ${compact_cost_time}"|bc) -eq 1 ]]; then
echo "True" >> time.txt
else
echo "False" >> time.txt
fi

paste

1
2
3
# 针对某一列进行 sum 求和
$ cat c | awk '{print $1}' | paste -sd+ - | bc
160
1
2
3
# 获取到文件
$ readlink -f logs/zookeeper.log
/home/zookeeper/logs/zookeeper.log

sed

替换

1
2
3
# 先后从 zk_con 里面,将 ":" 冒号后面 和 "/" 斜线之前的都删掉
zk_con=" /192.168.1.1:35632[1](queued=0,recved=146,sent=146,sid=0x25dd0c02a0a02a7,lop=PING,est=1503994626336,to=40000,lcxid=0x0,lzxid=0xffffffffffffffff,lresp=1503996561355,llat=0,minlat=0,avglat=0,maxlat=3)"
zk_con=`echo $zk_con | sed 's/:.*//g' | sed 's/.*\///g'` # 192.168.1.1

删除匹配行

1
2
# 从 txt 文件中,删除包含 yuzhouwan 字符串的行
$ sed -e '/yuzhouwan/d' txt

删除空白行

1
2
3
4
# 删除没有任何字符的行
$ sed -i 's/^$//g' log
# 删除只有空格的行
$ sed -i '/^\s*$/d' log

sort

指定某一列进行排序

1
2
3
4
5
6
7
8
9
10
# 通过 `-k1` 参数指定针对第一列进行排序
# 通过 `-n` 参数指定针对数字进行排序,避免 9 排在了 10 前面
# 通过 `-V` 参数指定针对字段中包含的数字进行排序,例如按照 yuzhouwan-com-0、yuzhouwan-com-1、yuzhouwan-com-2 中的 0、1、2 数字进行排序
# 当然也可以通过增加 `-r`,使得排序变成降序
# 如果,列与列之间的分隔符不是默认的 \t,则需要通过 `-t` 参数进行指定

# 0 /election
# 0 /leader
# 2 /zookeeper
sorted=`echo ${result} | sort -k1 -n`

指定多个列进行排序

1
2
3
4
5
6
7
8
# 清空文件
$ > test_sort.txt

# 测试集
$ echo "a 1 3" >> test_sort.txt
$ echo "b 1 2" >> test_sort.txt
$ echo "c 2 1" >> test_sort.txt
cat test_sort.txt
1
2
3
a 1 3
b 1 2
c 2 1
1
2
# 先按照第二列进行排序,随后再按照第三列进行排序
$ cat test_sort.txt | sort -k2,2 -k3,3 -V
1
2
3
b 1 2
a 1 3
c 2 1
1
2
# 先按照第三列进行排序,随后再按照第二列进行排序
$ cat test_sort.txt | sort -k3,3 -k2,2 -V
1
2
3
c 2 1
b 1 2
a 1 3

uniq

1
2
3
4
# 计数
echo -e "1\n1\n0" | uniq -c
# 2 1
# 1 0

exit 0

1
2
# 养成一个,脚本结尾,添加 `exit 0` 的好习惯
exit 0

实用技巧

确定操作系统类型

1
$ cat /etc/redhat-release
1
2
# 输出类似如下的信息,说明操作系统是 CentOS 类型的
CentOS Linux release 7.9.2009 (Core)

IFS 设置

 Shell 脚本中 IFS 变量全称 Internal Field Seprator,内部域分隔符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ echo $IFS

$ echo "$IFS" | od -b
0000000 040 011 012 012
0000004

# 遍历多行文本之前,设置 IFS 为 \n
pids=`ps -ef | grep "${PROCESS_NAME}" | grep -v grep | grep -v "${SELF_NAME}" | awk '{print $2}'`
IFS=$'\n' read -rd '' -a pids <<<"$pids"
echo "pids: ${pids}"
for pid in "${pids}"; do
echo "kill -9 ${pid}"
kill -9 "${pid}"
done

每秒执行一次

1
$ while true; do sleep 1; <command>; done

批量后台运行

1
2
# 使用 & done 的方式,会异步打印出后台命令的执行状态
$ for i in $(seq 3); do echo $i ; sleep 2 & done
1
2
3
4
5
6
7
8
9
10
1
[2] 44066
2
[3] 44067
3
[4] 44068

[3] - 44067 done sleep 2
[4] + 44068 done sleep 2
[2] + 44066 done sleep 2
1
2
3
# 使用 (&) 的方式,则不会打印出后台命令的执行状态
# 但是如果是需要发送命令到虚拟机中去执行的话,这种使用 & 符号的方式都不行
$ for i in $(seq 3); do echo $i ; (sleep 2 &) ; done
1
2
3
1
2
3

自启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 以 OpenTSDB 为例
$ vim keep_alive.sh
#!/usr/bin/env bash

while true; do
process_num=`ps -ef | grep -v grep | grep opentsdb | wc -l`
echo ${process_num}
if [[ ${process_num} -gt 0 ]]; then
echo "health"
else
cd /usr/local/opentsdb
nohup ./build/tsdb tsd >/dev/null 2>&1 &
fi
sleep 10;
done

$ nohup sh keep_alive.sh >/dev/null 2>&1 &

获得当前机器的资源使用情况

CPU

1
2
idle_cpu_percent=`top -b -n 1 | grep Cpu | awk '{print $5}' | cut -f 1 -d "."`
used_cpu_percent=$(echo "100-${idle_cpu_percent}" | bc)

Memory

1
2
3
4
5
free_m=`free -m`
free_m_detail=`echo ${free_m} | sed 's/.*Mem://g' | sed 's/-\/+.*//g'`
total_mem=$(echo "${free_m_detail}" | awk '{print $1}')
used_mem=$(echo "${free_m_detail}" | awk '{print $2}')
used_mem_percent=$[${used_mem} * 100 / ${total_mem}]

Disk

空间使用率
1
used_disk_percent=`df -P | grep "home" | awk '{print $5}'`
I/O 使用率
1
2
3
virtual_device=`df -h | grep "home" | awk '{print $1}' | cut -f 4 -d '/'`
device=`ls -trl /dev/mapper/ | grep ${virtual_device} | sed 's/.*\///g'`
device_io=`iostat -x 1 1 | grep ${device} | awk '{print $12}'`

Network

获得当前机器的 IP
1
2
3
4
now_ip=`ifconfig | grep "inet addr" | grep -v "127.0.0.1" | awk '{print $2}' | sed 's/.*://g'`
if [ "${now_ip}" == "" ]; then
now_ip=`hostname -i | awk '{print $1}'`
fi
从机器列表中获取目标 IP
1
2
3
4
5
# 这里以 ZooKeeper 为例
aim_ip=`cat ${zk_home}/conf/zoo.cfg | grep server | sed 's/.*=//g' | cut -d ':' -f 1 | grep -v ${now_ip} | tail -1`
if [ "${aim_ip}" == "" ]; then
aim_ip="127.0.0.1"
fi
网络延迟和丢包率
1
2
3
ping_result=`ping -c 4 ${aim_ip} -W 5`
ping_packet_loss=`echo ${ping_result} | sed 's/.*received, //g' | sed 's/ packet loss.*//g'`
ping_avg_ms=`echo ${ping_result} | sed 's/.*rtt //g' | awk '{print $3}' | cut -d '/' -f 2`

通用工具

tsar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 安装
$ wget -O tsar.zip https://github.com/alibaba/tsar/archive/master.zip --no-check-certificate
$ unzip tsar.zip
$ cd tsar-master/
$ yum install gcc -y
$ make
$ make install

# 使用
# 指标含义
# cpu - util: CPU 总使用的时间百分比
# mem - util: 内存使用率
# tcp - retran: 系统的重传率
# traffic - bytin: 入口流量 Byte/s
# traffic - bytout: 出口流量 Byte/s
# vda - util: 虚拟磁盘 vda 的 I/O
# vda1 - util: 虚拟磁盘 vda1 的 I/O
# load - load1: 一分钟的系统平均负载
$ tsar -l -i 1
Time ---cpu-- ---mem-- ---tcp-- -----traffic---- --vda--- --vda1-- ---load-
Time util util retran bytin bytout util util load1
09/04/19-19:59:44 0.00 0.71 0.00 126.00 222.00 0.00 0.00 0.16
09/04/19-19:59:45 0.02 0.71 0.00 66.00 338.00 0.00 0.00 0.16
09/04/19-19:59:46 0.00 0.71 0.00 66.00 242.00 0.00 0.00 0.16

从 JVM 进程中,找到占用 CPU 最高的 Java 线程

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
# 使用命令 top -p <pid>,显示 Java 进程的内存情况
$ top -p 15108

# 按大写的 H,获取每个线程的内存情况
# 找到内存和 CPU 占用最高的线程 pid
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
15133 druid 20 0 24.9g 1.1g 7776 S 244.9 0.9 0:07.36 java
15108 druid 20 0 24.9g 1.1g 7776 S 0.0 0.9 0:00.57 java
15114 druid 20 0 24.9g 1.1g 7776 S 0.0 0.9 0:08.96 java
15115 druid 20 0 24.9g 1.1g 7776 S 0.0 0.9 0:07.26 java
15116 druid 20 0 24.9g 1.1g 7776 S 0.0 0.9 0:07.25 java
15117 druid 20 0 24.9g 1.1g 7776 S 0.0 0.9 0:07.19 java

# 获得线程 pid 的十六进制
$ printf 0x%x 15133
0x3b1d

# 从线程堆栈信息中找到 3b1d 这个线程,并展示所在行的后面 10 行
$ jstack 15108 | grep -A 10 3b1d
"qtp2028042905-211-acceptor-3@41374390-ServerConnector@2faa55bb{HTTP/1.1}{0.0.0.0:8090}" #211 daemon prio=4 os_prio=0 tid=0x00007f789bc47800 nid=0x3b1d runnable [0x00007f74e29ec000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
- locked <0x00000007122b1fa0> (a java.lang.Object)
at org.eclipse.jetty.server.ServerConnector.accept(ServerConnector.java:377)
at org.eclipse.jetty.server.AbstractConnector$Acceptor.run(AbstractConnector.java:500)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:620)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:540)
at java.lang.Thread.run(Thread.java:745)
# 查看对应的堆栈信息,找出可能存在问题的代码

Windows 相关

Windows 下执行 shell 脚本

1
2
3
4
5
6
7
# 安装 git
$ doskey bash="%GIT_HOME%\bin\bash.exe" $*
$ doskey sh="%GIT_HOME%\bin\sh.exe" $*
$ bash shell.sh

# 类似的可以设置 `np` 为 `notepad++` 的快捷启动命令
$ doskey np=D:\apps\Notepad++\notepad++.exe $*

Cmd 设置 Proxy

1
2
3
4
$ set http_proxy=http://your_proxy:your_port
$ set http_proxy=http://username:password@your_proxy:your_port
$ set https_proxy=https://your_proxy:your_port
$ set https_proxy=https://username:password@your_proxy:your_port

Cygwin

安装

 在官网找到 setup-x86_64.exe 安装包,下载并执行(安装的过程中,如果提示有冲突,选择 contiune 而不能是 retry,否则,将会全部重新开始)

使用
1
2
3
# 切换到想要的盘符下
$ cd /cygdrive/e
$ cd e:/

防火墙

CentOS

CentOS 6
1
2
3
4
5
6
7
# 查看状态
$ service iptable status

# 临时关闭
$ servcie iptables stop
# 永久关闭
$ chkconfig iptables off
CentOS 7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看默认防火墙状态(CentOS 7.0)
$ firewall-cmd --state

# 查看默认防火墙状态(CentOS 7.x)
$ systemctl list-unit-files | grep firewalld.service

# 启动 firewall
$ systemctl start firewalld.service
# 重启 firewall
$ systemctl restart firewalld.service
# 停止 firewall
$ systemctl stop firewalld.service
# 查看 firewall 状态
$ systemctl status firewalld.service
# 是否 / 开启 / 禁止 firewall 的开机自启
$ systemctl is-enabled/enable/disable firewalld.service

Ubuntu

1
2
3
4
5
6
7
# 开启防火墙
$ ufw enable
# 关闭防火墙
$ ufw disable

# 卸载 iptables 服务
$ apt-get remove iptables

优化实战

关闭 Swappiness

1
2
3
4
5
6
7
8
9
10
$ cat /proc/sys/vm/swappiness
# default: 60
# memory first: 0
# swap first: 100

# 使得 swappiness 设置永久生效
$ vim /etc/sysctl.conf
vm.swappiness=10

# 可以配合 JVM 中的 `-XX:+AlwaysPreTouch` 参数,在进程启动的时候,让 jvm 通过 demand-zeroed 方式将内存一次分配到位,提高 daemon 常驻进程性能

控制 Overcommit

1
2
3
4
5
$ cat /proc/sys/vm/overcommit_memory
# 0: 表示内核将检查是否有足够的可用内存供应用进程使用
# 如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程
# 1: 表示内核允许分配所有的物理内存,而不管当前的内存状态如何
# 2: 表示内核允许分配超过所有物理内存和交换空间总和的内存

禁用透明巨页

Page Size

1
2
$ getconf PAGE_SIZE
4096

Huge Page Size

1
2
$ cat /proc/meminfo | grep Hugepagesize
Hugepagesize: 2048 kB

Transparent Huge Pages

1
2
3
4
5
6
7
$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

$ echo never > /sys/kernel/mm/transparent_hugepage/enabled
$ echo never > /sys/kernel/mm/transparent_hugepage/defrag

# Redhat 的相关目录: /sys/kernel/mm/redhat_transparent_hugepage/

清理 Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ free -g && sync && echo 3 > /proc/sys/vm/drop_caches && free -g
# before
total used free shared buffers cached
Mem: 3833 3726 106 0 107 272
-/+ buffers/cache: 3346 486
Swap: 10239 0 10239
# after
total used free shared buffers cached
Mem: 3833 3345 487 0 1 31
-/+ buffers/cache: 3312 520
Swap: 10239 0 10239

# To free pagecache:
$ echo 1 > /proc/sys/vm/drop_caches
# To free dentries and inodes:
$ echo 2 > /proc/sys/vm/drop_caches
# To free pagecache, dentries and inodes:
$ echo 3 > /proc/sys/vm/drop_caches

# 执行完清空 Cache 的命令之后,需要重新设置为 0,否则 Cache 机制就相当于被 disable 了
$ echo 0 > /proc/sys/vm/drop_caches

增大预留空闲内存

1
2
3
4
5
6
7
# 系统默认预留内存空间,只有 88 MB
$ cat /proc/sys/vm/min_free_kbytes
90112

# 为了系统稳定性,可以增加到 1 GB
$ vim /proc/sys/vm/min_free_kbytes
1048576

系统架构

Cache & Buffer

定义

 在 Linux 的内存管理中,Cache(Page Cache)属于 页面缓存,Buffer(Buffer Cache)则属于 缓冲区缓存。前者,针对 页(Page)内存进行管理,后者,则针对 块(Block)内存进行管理

分析 Cache

fincore
安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 安装两个 perl 依赖包(大小仅 336 KB)
$ yum install perl-Inline perl-Parse-RecDescent -y

# 下载编译 fincore
$ wget https://github.com/david415/linux-ftools/archive/master.zip
$ unzip linux-ftools-master.zip
$ cd linux-ftools-master
$ chmod 777 configure
$ ./configure --prefix=/usr/local/ && make -j2 && make -j2 install

# 如果报错 WARNING: `automake-1.10` is missing on your system.
$ ln -sf /usr/bin/aclocal /usr/bin/aclocal-1.10
$ ln -sf /usr/bin/automake /usr/bin/automake-1.10

# 如果需要在线上环境,进行离线安装
$ mkdir -p /tmp/fincore
$ yum reinstall --downloadonly --downloaddir=/tmp/fincore perl-Inline perl-Parse-RecDescent -y
$ ll /tmp/fincore
-rw-r--r-- 1 root root 151484 Jul 22 2010 perl-Inline-0.46-1.el6.noarch.rpm
-rw-r--r-- 1 root root 193348 Sep 25 2011 perl-Parse-RecDescent-1.965-1.el6.noarch.rpm
编码
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
#!/bin/sh

command -v fincore >/dev/null 2>&1 || {
echo >&2 "I require fincore but it's not installed. Try install..." ; exit 1;
}

pids_tmp=/tmp/cache.pids
cache_tmp=/tmp/cache.files
fincore_tmp=/tmp/cache.fincore
result_tmp=/tmp/result.fincore

echo "The top 3 cache pid:"
ps -e -o pid,rss | sort -nk2 -r | grep -vw 1 | head -3 | awk '{print $1}' > ${pids_tmp}
echo -e "`cat ${pids_tmp}`\n"

for pid in `cat ${pids_tmp}`; do
ps -ef | grep ${pid}
done
echo -e "\n"

if [ -f ${cache_tmp} ]; then
rm -f ${cache_tmp}
fi

while read line; do
echo "Pid: ${line}, Files:"
file=`lsof -p ${line} 2>/dev/null | awk '{print $9}'`
echo "${file}" >> ${cache_tmp}
echo -e "${file}\n" | grep -vw "NAME"
done<${pids_tmp}

if [ -f ${fincore_tmp} ]; then
rm -f ${fincore_tmp}
fi

for i in `cat ${cache_tmp}`; do
if [ -f ${i} ]; then
echo ${i} >> ${fincore_tmp}
fi
done

fincore --pages=false `cat ${fincore_tmp} | grep -v /usr/sbin/sshd | grep -v /lib64/ld-2.12.so | grep -v /bin/bash | grep -v /bin/login` > ${result_tmp}

echo "filename size total pages cached pages cached size cached percentage"

len=`cat ${result_tmp} | wc -l`
cat ${result_tmp} | tail -$(echo "${len}-1" | bc) | sort -nk4 -r

exit 0

Tips: Full code is here.

System Tap

 依赖包太多,线上环境不适合

pcstat

 卡在了安装的第一步 go get golang.org/x/sys/unix

回收 Cache

操作

 具体操作,请看上文 “优化实战 - 清理 Cache” 部分

成本

 清理 Cache 之前,需要保证 Cache 中的数据,和对应文件中的数据一致,才能对 Cache 进行释放。因此,清除 Cache 之前,内核会检查 Cache 数据与对应磁盘文件数据一致性,并将不一致的 Cache 写回磁盘。这一过程,将消耗大量的磁盘 I/O 资源

无法被回收的情况
tmpfs

 Linux 提供一种 “临时”文件系统(重启之后数据不会被保存),拿出一部分的内存空间当做文件系统使用,可以动态调整文件系统的大小,并且相比磁盘文件系统的读写速度,会快上很多倍。在 tmpfs 系统里面的内存,是不会被回收的,除非 tmpfs 里面的文件被删除

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
# 操作系统默认就创建了一个 `/dev/shm` 的 tmpfs 目录,可以用来做缓存,进行提速(Automatic Memory Management、Web 缓存等)
$ ll -h /dev/shm
total 76K
-rw-r--r-- 1 root root 46 Aug 13 11:05 10694
-r-------- 1 root root 65M May 25 14:43 pulse-shm-17791060
-r-------- 1 root root 65M May 25 14:43 pulse-shm-31652098

# 当然,也可以创建自己的 tmpfs
$ mkdir /tmp/tmpfs
$ mount -t tmpfs -o size=3G none /tmp/tmpfs/
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 63G 0 63G 0% /dev/shm
none 3.0G 0 3.0G 0% /tmp/tmpfs

$ free -g
total used free shared buffers cached
Mem: 125 79 46 0 0 0
-/+ buffers/cache: 79 46
Swap: 0 0 0

$ dd if=/dev/zero of=/tmp/tmpfs/yuzhouwan bs=1G
dd: writing '/tmp/tmpfs/yuzhouwan': No space left on device
4+0 records in
3+0 records out
3221225472 bytes (3.2 GB) copied, 2.57476 s, 1.3 GB/s

$ free -g
total used free shared buffers cached
Mem: 125 83 42 0 0 3
-/+ buffers/cache: 80 45
Swap: 0 0 0

$ echo 3 > /proc/sys/vm/drop_caches
$ free -g
total used free shared buffers cached
Mem: 125 84 41 0 0 3
-/+ buffers/cache: 80 44
Swap: 0 0 0

$ rm /tmp/tmpfs/yuzhouwan
$ free -g
total used free shared buffers cached
Mem: 125 86 39 0 0 0
-/+ buffers/cache: 85 40
Swap: 0 0 0
ipcs

 进程间通信(IPC)方式,只需要在 “输入文件” 到 “共享内存区” 之间进行两次数据的拷贝。但是,如果通过 shmget 创建完共享内存之后,没有在结束的时候,使用 shmctl 删除共享内存,那么,这块内存将一直无法被清理出去

1
2
3
# 可以通过以下两个命令,查看共享内存空间,并删除
$ ipcs -m
$ ipcrm -m <shmid>
mmap

 进程之间还可以通过 mmap 方法,映射同一个普通文件,来实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用 read / write 方法。同样地,如果没有在进程退出时,使用 munmap 方法解除映射关系,也会存在共享内存无法被清除的情况

资源

Book

Tool

欢迎加入我们的技术群,一起交流学习

群名称 群号
人工智能(高级)
人工智能(进阶)
大数据
算法
数据库