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

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

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
# 发送 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

# 查看公网 IP
$ curl cip.cc

date

1
2
3
4
5
6
7
8
9
10
11
12
# 该时间戳格式为 13 位毫秒级别
$ echo "`date -d '2017-04-21 10:00:00' +%s`000"

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

# 解析时间戳
$ 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

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
# 文件大小
$ du -h /home/ --max-depth=1
3.1G /home/eagle
40.8G /home/

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

# 同理,使用 `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

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
# https://stedolan.github.io/jq/download/ 下载 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

lsof

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

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

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` 公式来计算即可

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

ssh

1
2
3
4
5
# 登录远程机器,执行命令
$ ssh 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 也可以实现)

标准 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
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
# 通过 `-k1` 指定 针对第一列进行排序
# 通过 `-n` 指定 针对数字进行排序,避免 9 排在了 10 前面
# 当然也可以通过增加 `-r`,使得排序变成降序
# 如果,列与列之间的分隔符不是默认的 \t,则需要通过 `-t` 参数进行指定

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

uniq

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

exit 0

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

实用技巧

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

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

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`

从 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

Blog

Tool

Memory Test

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

人工智能 1020982(高级)& 1217710(进阶)| BigData 1670647

  • 本文作者: Benedict Jin
  • 本文链接: https://yuzhouwan.com/posts/15691/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
显示 Gitment 评论