go-backend-handbook 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/00-linux.md +389 -83
- package/01-go-basics.md +603 -174
- package/02-service-architecture.md +447 -101
- package/03-concurrency.md +542 -151
- package/04-mysql.md +1009 -228
- package/05-database-selection.md +608 -112
- package/06-redis.md +617 -140
- package/07-message-queue.md +493 -111
- package/08-api-and-rpc.md +448 -120
- package/09-observability.md +450 -110
- package/10-resilience.md +666 -112
- package/11-monitoring.md +1225 -115
- package/12-testing.md +514 -102
- package/13-performance-debug.md +450 -100
- package/14-security.md +430 -108
- package/15-development-process.md +618 -115
- package/16-api-docs.md +729 -196
- package/17-deploy-ops.md +848 -162
- package/18-ai-assisted.md +456 -85
- package/19-appendix.md +2 -0
- package/20-production-patterns.md +117 -31
- package/21-complete-project.md +484 -89
- package/22-database-deep-dive.md +557 -42
- package/23-debugging-and-performance.md +379 -97
- package/24-api-contract-and-testing.md +566 -98
- package/25-production-operations.md +739 -235
- package/CHANGELOG.md +18 -0
- package/README.md +7 -3
- package/code/mini-coin/README.md +22 -14
- package/code/mini-coin/api/openapi.yaml +192 -0
- package/code/mini-coin/docs/changes/CHG-001-recharge/spec.md +3 -1
- package/code/mini-coin/internal/httpapi/handler.go +145 -25
- package/code/mini-coin/internal/httpapi/handler_test.go +181 -0
- package/code/mini-coin/internal/model/account.go +31 -6
- package/code/mini-coin/internal/repository/memory.go +41 -19
- package/code/mini-coin/internal/security/sign_test.go +2 -2
- package/code/mini-coin/internal/service/recharge.go +47 -38
- package/code/mini-coin/internal/service/recharge_test.go +142 -7
- package/code/mini-coin/migrations/002_create_recharge_order.sql +1 -1
- package/code/mini-coin/migrations/003_add_channel.sql +1 -1
- package/code/mini-coin/scripts/e2e.sh +9 -4
- package/code/mini-coin/scripts/loadtest.js +8 -4
- package/package.json +1 -1
package/00-linux.md
CHANGED
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
# 第 0 章 开发机与 Linux 入门
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
> **JS 类比速查**(帮助建立直觉,不表示语义等价;差异列必须看)
|
|
3
|
+
> 目标:能连上远程开发机,看懂机器状态,磁盘/内存/CPU 出问题时知道先做什么,不把“机器卡了”当成玄学。
|
|
6
4
|
>
|
|
7
|
-
>
|
|
8
|
-
> |---|---|---|
|
|
9
|
-
> | 远程 Linux shell | 本机终端,只是换了台机器 | 代码、进程、依赖、环境变量全在远端 |
|
|
10
|
-
> | 进程 | `node app.js` 启动的进程 | 后端进程长期驻留,要管 PID、信号、日志轮转 |
|
|
11
|
-
> | systemd | `pm2` 的自动重启 | 系统级守护,开机自启、崩溃拉起 |
|
|
12
|
-
> | top / htop | Chrome 任务管理器 | 看整机 CPU/内存/磁盘 |
|
|
13
|
-
> | 磁盘满 | `node_modules` 占满硬盘 | 后端会导致日志写失败、DB 异常 |
|
|
14
|
-
> | 没有对应物 | — | nginx / Docker / k8s 是纯后端概念,不用硬套 |
|
|
5
|
+
> 本章用 `devbox.example.com`、`demo` 这类占位符。真实地址、账号、密钥不要写进公开文档。
|
|
15
6
|
|
|
7
|
+
---
|
|
16
8
|
|
|
17
|
-
## 0.1
|
|
9
|
+
## 0.1 为什么后端要有一台开发机
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
前端本地起一个 dev server 就能开发,因为依赖主要是 Node 和浏览器。后端通常不行:
|
|
20
12
|
|
|
21
|
-
- MySQL
|
|
13
|
+
- MySQL/Redis/Kafka 在机房或容器平台,本地连不上;
|
|
22
14
|
- 服务要访问测试环境、注册中心、配置中心;
|
|
23
|
-
-
|
|
15
|
+
- 编译、压测、抓包、看日志需要一台长期在线的 Linux;
|
|
16
|
+
- 本地 Mac/Windows 和线上 Linux 的环境差异会制造 bug。
|
|
17
|
+
|
|
18
|
+
所以团队一般给每人一台远程 Linux 开发机,代码、编译、联调都在上面做。
|
|
19
|
+
|
|
20
|
+
**“本地写代码、开发机编译”** 的意思是:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
你的电脑:编辑器(VS Code / GoLand)通过 SSH/Remote 编辑远程文件
|
|
24
|
+
开发机:真正执行 go build / go test / 启动服务 / 连数据库
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
不是把代码拷来拷去,而是编辑器直接操作远程工作区。
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
---
|
|
26
30
|
|
|
27
|
-
## 0.2
|
|
31
|
+
## 0.2 怎么连:SSH 四层排查
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
|
-
# 1. 看本机 SSH 配置里有没有目标主机
|
|
31
34
|
cat ~/.ssh/config
|
|
32
|
-
|
|
33
|
-
# 2. 连接(主机别名来自 ssh config)
|
|
34
35
|
ssh devbox
|
|
35
|
-
|
|
36
|
-
# 3. 首次连接会问 yes/no,输入 yes
|
|
37
|
-
# 4. 免密登录靠公钥:把 ~/.ssh/id_ed25519.pub 内容加到目标机 ~/.ssh/authorized_keys
|
|
38
36
|
```
|
|
39
37
|
|
|
40
|
-
`~/.ssh/config`
|
|
38
|
+
`~/.ssh/config` 示例:
|
|
41
39
|
|
|
42
40
|
```
|
|
43
41
|
Host devbox
|
|
@@ -47,31 +45,50 @@ Host devbox
|
|
|
47
45
|
IdentityFile ~/.ssh/id_ed25519_demo
|
|
48
46
|
IdentitiesOnly yes
|
|
49
47
|
ServerAliveInterval 30
|
|
48
|
+
ServerAliveCountMax 3
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
`ServerAliveInterval 30`
|
|
51
|
+
`ServerAliveInterval 30` 每 30 秒发心跳,防止长时间不操作被防火墙断开。
|
|
52
|
+
|
|
53
|
+
### 连不上,按四层排查
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
排查 SSH 不能靠反复重试。先判断失败发生在哪一层:DNS 是否能解析、目标端口是否可达、服务端是否接受认证、登录后 shell 是否正常。每层只保留一个能给出确定结论的命令,并把报错原文记录下来;否则很容易把“代理没开”误判成“密钥坏了”。下面四条命令分别验证网络、端口、认证和登录后环境,执行顺序不要跳。
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
```
|
|
58
|
+
网络通不通 → 端口开不开 → 认证过不过 → 登录后环境对不对
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
| 现象 | 命令 | 常见原因 |
|
|
57
62
|
|---|---|---|
|
|
58
|
-
| `Connection timed out` | `ping
|
|
63
|
+
| `Connection timed out` | `ping host`、`nc -vz host 22` | 不在内网/VPN、防火墙、安全组 |
|
|
59
64
|
| `Connection refused` | `nc -vz host 22` | sshd 没启动、端口错 |
|
|
60
|
-
| `Permission denied (publickey)` | `ssh -vvv devbox` |
|
|
61
|
-
| `Host key verification failed` | `ssh-keygen -R host` |
|
|
62
|
-
| 连上就断 | `ssh -vvv
|
|
63
|
-
| 卡在
|
|
65
|
+
| `Permission denied (publickey)` | `ssh -vvv devbox` | 公钥没放对、权限不对 |
|
|
66
|
+
| `Host key verification failed` | `ssh-keygen -R host` | 机器重装,指纹变了 |
|
|
67
|
+
| 连上就断 | `ssh -vvv`,服务端 `/var/log/auth.log` | 账号被锁、磁盘满、sshd 限制 |
|
|
68
|
+
| 卡在 Connecting | `curl -v telnet://host:22` | 代理/跳板机问题 |
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
一次性确认:
|
|
66
71
|
|
|
67
72
|
```bash
|
|
68
|
-
# 一次性确认四层
|
|
69
73
|
ping -c 2 devbox.example.com
|
|
70
74
|
nc -vz devbox.example.com 22
|
|
71
75
|
ssh -o BatchMode=yes devbox 'echo ok'
|
|
72
76
|
ssh devbox 'hostname; whoami; uptime'
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
### 公钥权限
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
chmod 700 ~/.ssh
|
|
83
|
+
chmod 600 ~/.ssh/id_ed25519_demo
|
|
84
|
+
chmod 644 ~/.ssh/id_ed25519_demo.pub
|
|
85
|
+
chmod 600 ~/.ssh/authorized_keys # 在开发机上
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
权限太宽,sshd 会拒绝使用密钥。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
75
92
|
## 0.3 登录后先看什么
|
|
76
93
|
|
|
77
94
|
```bash
|
|
@@ -84,34 +101,86 @@ uptime # 负载
|
|
|
84
101
|
top -b -n1 | head -20
|
|
85
102
|
```
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
### 负载怎么读
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
`uptime` 输出:
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
```
|
|
109
|
+
load average: 1.20, 0.80, 0.50
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
三个数分别是 1/5/15 分钟平均。经验:
|
|
113
|
+
|
|
114
|
+
- `load < 核数`:通常正常;
|
|
115
|
+
- `load > 核数`:有排队;
|
|
116
|
+
- `load > 2 × 核数`:明显过载。
|
|
117
|
+
|
|
118
|
+
但 load 包含不可中断 IO 等待,磁盘慢时 load 也会高。结合 `top` 的 `%wa` 和 `iostat` 看。
|
|
119
|
+
|
|
120
|
+
### CPU 怎么读
|
|
121
|
+
|
|
122
|
+
`%CPU` 是某个进程消耗的 CPU 时间比例,单核跑满大约是 100%,多核进程可能超过 100%;`load average` 表示处于可运行和不可中断等待状态的任务数,不等于 CPU 使用率。判断时要把两者和三段时间窗口一起看:1 分钟高、15 分钟低通常只是短突发;三个值一起高且 `nproc` 只有 2 时,机器已经持续过载;CPU 不高但 load 很高,常见原因是大量 IO 等待。
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
top
|
|
126
|
+
# 看 %us(用户态)、%sy(内核态)、%wa(IO 等待)、%id(空闲)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `%us` 高:业务计算多,查 CPU profile;
|
|
130
|
+
- `%sy` 高:系统调用/上下文切换多;
|
|
131
|
+
- `%wa` 高:磁盘 IO 慢;
|
|
132
|
+
- `%id` 低但 load 高:可能在等 IO。
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 0.4 磁盘满了怎么办
|
|
137
|
+
|
|
138
|
+
### 先定位
|
|
139
|
+
|
|
140
|
+
定位磁盘问题要同时看“文件系统使用率”和“文件归属”。`df -h` 只告诉你哪个挂载点满了,`du` 找出目录占用,`lsof +L1` 处理“文件已删除但进程仍持有句柄”的特殊情况。生产机不要立刻递归扫描整个 `/`,先限定到日志、容器、临时目录和 core dump,控制命令本身的 IO。
|
|
92
141
|
|
|
93
142
|
```bash
|
|
94
143
|
df -h # 哪个分区满
|
|
95
144
|
du -sh /var/* 2>/dev/null | sort -h # 一级目录谁大
|
|
96
145
|
du -sh /var/log/* | sort -h | tail # 日志大户
|
|
97
|
-
lsof +L1 #
|
|
146
|
+
lsof +L1 # 已删除但被进程占用的文件
|
|
98
147
|
```
|
|
99
148
|
|
|
100
|
-
|
|
149
|
+
`df` 显示 100% 但 `du` 找不到大文件,通常是**文件被删除但进程还开着**,用 `lsof +L1` 找。
|
|
150
|
+
|
|
151
|
+
### 处理顺序
|
|
101
152
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
153
|
+
先止血再找根因。生产上优先确认影响范围、是否有副本可承接、最近是否发布,再决定扩容、摘流量或回滚;不要一上来 `kill -9`,否则线程栈、buffer 和未完成请求都会丢失。下面的顺序可以当作一次最小应急演练。
|
|
154
|
+
|
|
155
|
+
1. **止血**:确认不是审计/事故证据后,清理日志或临时文件;
|
|
156
|
+
2. **找根因**:日志没轮转?core dump?临时文件没清?
|
|
157
|
+
3. **加防线**:logrotate、磁盘告警 80%/90%、容器日志大小限制。
|
|
105
158
|
|
|
106
159
|
```bash
|
|
107
|
-
#
|
|
160
|
+
# 安全清空正在写的日志(rm 后空间可能不释放)
|
|
108
161
|
truncate -s 0 /var/log/app/app.log
|
|
109
162
|
|
|
110
|
-
# 找 7
|
|
163
|
+
# 找 7 天前的临时文件,先看再删
|
|
111
164
|
find /tmp -type f -mtime +7 -ls | head
|
|
112
165
|
```
|
|
113
166
|
|
|
114
|
-
|
|
167
|
+
**不要直接 `rm -rf /var/log/*`**:可能删掉正在写的关键日志,空间还不释放。
|
|
168
|
+
|
|
169
|
+
### 磁盘满会导致什么
|
|
170
|
+
|
|
171
|
+
磁盘满不只影响写日志。Go 进程可能无法创建临时文件、MySQL 无法写 redo/binlog、容器运行时无法拉镜像,甚至 DNS 和日志采集也会异常。因此“接口 500”可能只是磁盘问题,排查时要主动看 `df -i` 和 `df -h`。
|
|
172
|
+
|
|
173
|
+
- 日志写失败;
|
|
174
|
+
- 数据库无法写入/WAL 卡住;
|
|
175
|
+
- 容器被驱逐;
|
|
176
|
+
- SSH 登录失败(无法写 session);
|
|
177
|
+
- 服务启动失败。
|
|
178
|
+
|
|
179
|
+
磁盘告警必须早于 100%。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 0.5 内存满了怎么办
|
|
115
184
|
|
|
116
185
|
```bash
|
|
117
186
|
free -h
|
|
@@ -119,69 +188,306 @@ ps aux --sort=-%mem | head
|
|
|
119
188
|
cat /proc/meminfo | head
|
|
120
189
|
```
|
|
121
190
|
|
|
122
|
-
Linux 会把空闲内存用作 page cache,`free` 的 `available`
|
|
191
|
+
Linux 会把空闲内存用作 page cache,`free` 的 `available` 才是真正可用。如果 `available` 很低且 swap 在涨,就是真紧张。
|
|
192
|
+
|
|
193
|
+
### 处理
|
|
194
|
+
|
|
195
|
+
内存问题先分清 `free`、`available`、缓存和 RSS。Linux 会把空闲内存用于 page cache,`free` 很小不一定危险;`available` 才是新进程大约还能拿到的内存。若 `available` 持续下降、swap 大量换入换出,或内核日志出现 OOM kill,才说明已经影响业务。处理时优先摘流量、回滚、限制并发和保留现场,不要用清理 page cache 伪装恢复。
|
|
196
|
+
|
|
197
|
+
先找到增长最快的目录和文件,确认它是否能安全清理,再处理进程仍持有的已删除文件。生产清理必须保留审计日志和数据库文件,不能直接 `rm -rf /` 或删除正在写入的 binlog。
|
|
198
|
+
|
|
199
|
+
1. 找最大进程:`ps aux --sort=-%mem | head`;
|
|
200
|
+
2. 看是否持续上涨(内存泄漏);
|
|
201
|
+
3. Go 服务用 pprof heap 分析;
|
|
202
|
+
4. 容器设置合理内存 limit 和 `GOMEMLIMIT`;
|
|
203
|
+
5. swap 只是缓冲,不是解决方案。
|
|
204
|
+
|
|
205
|
+
OOM 时内核会杀进程:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
dmesg -T | grep -i 'killed process'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 0.6 环境变量与 shell
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
export APP_ENV=dev
|
|
217
|
+
echo $APP_ENV
|
|
218
|
+
env | grep APP_
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
临时生效只影响当前 shell。永久生效写到 `~/.bashrc` 或 `~/.zshrc`,但**不要把密钥写进 shell 历史**。
|
|
222
|
+
|
|
223
|
+
查看命令来源:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
which go
|
|
227
|
+
type go
|
|
228
|
+
go version
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
PATH 配置:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
export PATH=$PATH:/usr/local/go/bin
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 常用的管道组合
|
|
238
|
+
|
|
239
|
+
管道把上一条命令的 stdout 交给下一条命令的 stdin,`grep` 过滤、`sort` 排序、`uniq -c` 计数、`awk` 取列。排障时要意识到每一段都可能改变结果:日志格式变化会让列号失效,`tail -f` 后接过滤会缓冲,统计大文件也会争抢磁盘。先在样本文件上验证输出,再对线上大日志执行。
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# 统计访问日志里各状态码数量
|
|
243
|
+
awk '{print $9}' access.log | sort | uniq -c | sort -nr
|
|
244
|
+
|
|
245
|
+
# 找监听 8080 的进程
|
|
246
|
+
ss -lntp | grep 8080
|
|
247
|
+
|
|
248
|
+
# 实时看错误日志
|
|
249
|
+
tail -f app.log | grep -i error
|
|
250
|
+
|
|
251
|
+
# 查文件里某个字符串
|
|
252
|
+
grep -rn 'panic' /var/log/app/
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
123
256
|
|
|
124
|
-
## 0.
|
|
257
|
+
## 0.7 必会命令
|
|
125
258
|
|
|
126
259
|
```bash
|
|
127
260
|
# 文件
|
|
128
|
-
ls -lah
|
|
261
|
+
ls -lah
|
|
262
|
+
cd /path
|
|
263
|
+
pwd
|
|
264
|
+
cp -r src dst
|
|
265
|
+
mv old new
|
|
266
|
+
mkdir -p /data/app
|
|
267
|
+
find . -name '*.go'
|
|
268
|
+
du -sh *
|
|
269
|
+
|
|
129
270
|
# 文本
|
|
130
|
-
cat
|
|
271
|
+
cat file
|
|
272
|
+
less file # /搜索,q退出
|
|
273
|
+
head -n 50 file
|
|
274
|
+
tail -f file
|
|
275
|
+
grep -rn 'keyword' .
|
|
276
|
+
sed -n '10,20p' file
|
|
277
|
+
|
|
131
278
|
# 进程
|
|
132
|
-
ps aux | grep app
|
|
279
|
+
ps aux | grep app
|
|
280
|
+
top
|
|
281
|
+
kill -15 PID # 优雅退出
|
|
282
|
+
kill -9 PID # 强杀,最后手段
|
|
283
|
+
|
|
133
284
|
# 端口
|
|
134
|
-
ss -lntp | grep 8080
|
|
285
|
+
ss -lntp | grep 8080
|
|
286
|
+
lsof -i:8080
|
|
287
|
+
|
|
135
288
|
# 网络
|
|
136
|
-
curl -v
|
|
289
|
+
curl -v https://example.com
|
|
290
|
+
curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' https://example.com
|
|
291
|
+
nc -vz host 443
|
|
292
|
+
|
|
137
293
|
# 压缩
|
|
138
|
-
tar -czf
|
|
139
|
-
|
|
140
|
-
chmod 600 key; chown user:group file
|
|
294
|
+
tar -czf app.tar.gz app/
|
|
295
|
+
tar -xzf app.tar.gz
|
|
141
296
|
```
|
|
142
297
|
|
|
143
|
-
`kill -15
|
|
298
|
+
### `kill -15` 和 `kill -9`
|
|
144
299
|
|
|
145
|
-
|
|
300
|
+
信号不是“关闭按钮”,而是通知进程采取动作。`SIGTERM` 给服务执行优雅退出的机会:停止接收新请求、等待在途请求、关闭连接池并 flush 日志;`SIGKILL` 由内核立即终止,进程没有清理机会。正确顺序是先发 TERM,观察进程是否在发布窗口内退出;只有确认卡死且已评估影响后才发 KILL。
|
|
301
|
+
|
|
302
|
+
- `SIGTERM(15)`:请求进程优雅退出,服务可以处理完在途请求、关闭连接;
|
|
303
|
+
- `SIGKILL(9)`:内核直接杀,进程没有机会清理。
|
|
304
|
+
|
|
305
|
+
先 15,等几秒不退再 9。
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 0.8 systemd:让进程常驻
|
|
310
|
+
|
|
311
|
+
systemd 负责拉起进程、收集日志和管理开机启动,但它不是业务健康检查器。一个 unit 至少要写清运行用户、工作目录、启动命令、重启策略、环境变量和文件描述符上限;配置错误时先 `systemctl daemon-reload`,再用 `systemctl status` 与 `journalctl -u` 看真实退出原因。`Restart=on-failure` 只能恢复偶发崩溃,如果进程因为配置错误反复退出,自动重启会制造日志风暴。
|
|
312
|
+
|
|
313
|
+
```ini
|
|
314
|
+
# /etc/systemd/system/minicoin.service
|
|
315
|
+
[Unit]
|
|
316
|
+
Description=minicoin
|
|
317
|
+
After=network.target
|
|
318
|
+
|
|
319
|
+
[Service]
|
|
320
|
+
User=app
|
|
321
|
+
WorkingDirectory=/opt/minicoin
|
|
322
|
+
ExecStart=/opt/minicoin/server
|
|
323
|
+
Restart=on-failure
|
|
324
|
+
RestartSec=3
|
|
325
|
+
Environment=APP_ENV=prod
|
|
326
|
+
LimitNOFILE=65535
|
|
327
|
+
|
|
328
|
+
[Install]
|
|
329
|
+
WantedBy=multi-user.target
|
|
330
|
+
```
|
|
146
331
|
|
|
147
332
|
```bash
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
333
|
+
sudo systemctl daemon-reload
|
|
334
|
+
sudo systemctl start minicoin
|
|
335
|
+
sudo systemctl status minicoin
|
|
336
|
+
sudo systemctl restart minicoin
|
|
337
|
+
journalctl -u minicoin -f
|
|
152
338
|
```
|
|
153
339
|
|
|
154
|
-
|
|
340
|
+
`Restart=on-failure` 让进程崩溃后自动拉起,但**不要用它掩盖反复崩溃**,要看日志找根因。
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 0.9 开发机常见问题
|
|
345
|
+
|
|
346
|
+
这张表用于建立第一反应:先区分网络、认证、DNS、端口和本机环境,不要看到“连不上”就重装客户端。每次排查都记录失败发生在哪一层,后续才能复用命令。
|
|
347
|
+
|
|
348
|
+
| 现象 | 可能原因 | 处理 |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| 连不上 | VPN/防火墙/sshd | 按四层排查 |
|
|
351
|
+
| 登录后很卡 | CPU/内存/磁盘满 | top/free/df |
|
|
352
|
+
| `go: command not found` | PATH 没配 | `which go`、配 PATH |
|
|
353
|
+
| 拉代码失败 | 凭证/网络 | `ssh -T git@host`、检查代理 |
|
|
354
|
+
| 端口被占 | 旧进程没退 | `ss -lntp`、kill |
|
|
355
|
+
| 磁盘满 | 日志/core dump | df/du/lsof |
|
|
356
|
+
| 服务起不来 | 配置/端口/依赖 | 看日志、`journalctl` |
|
|
357
|
+
|
|
358
|
+
---
|
|
155
359
|
|
|
156
360
|
## 习题
|
|
157
361
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
362
|
+
### 基础题
|
|
363
|
+
|
|
364
|
+
1. 为什么后端需要远程开发机?
|
|
365
|
+
2. SSH 连不上的四层排查顺序是什么?
|
|
366
|
+
3. `load average` 三个数分别代表什么?load 高一定是 CPU 忙吗?
|
|
367
|
+
4. `df` 显示磁盘满但 `du` 找不到大文件,可能是什么原因?
|
|
368
|
+
5. `free` 的 `available` 和 `free` 有什么区别?
|
|
369
|
+
6. `kill -15` 和 `kill -9` 的区别?
|
|
370
|
+
7. systemd 的 `Restart=on-failure` 有什么作用和风险?
|
|
371
|
+
8. 为什么不能把密钥写进 `~/.bashrc` 或命令历史?
|
|
372
|
+
|
|
373
|
+
### 动手题
|
|
374
|
+
|
|
375
|
+
9. 写一组命令:查看主机名、CPU 核数、内存、磁盘、负载。
|
|
376
|
+
10. 写命令找出 `/var/log` 下最大的 10 个文件。
|
|
377
|
+
11. 写命令找出监听 8080 的进程并优雅停止。
|
|
378
|
+
12. 写一个 systemd unit,让服务崩溃后 3 秒重启。
|
|
379
|
+
|
|
380
|
+
### 综合题
|
|
381
|
+
|
|
382
|
+
13. 开发机突然很卡,SSH 勉强能连上。写出你的前 10 个排查命令和判断逻辑。
|
|
383
|
+
|
|
384
|
+
---
|
|
166
385
|
|
|
167
386
|
## 参考答案
|
|
168
387
|
|
|
169
388
|
<details>
|
|
170
389
|
<summary>展开</summary>
|
|
171
390
|
|
|
172
|
-
1.
|
|
391
|
+
1. 后端依赖数据库、缓存、消息队列和内网服务,还需要一台长期在线、与线上 Linux 接近的机器做编译、联调、压测、抓包和看日志。前端本地 dev server 能替代一部分工作,但不能替代后端依赖环境。
|
|
392
|
+
|
|
393
|
+
2. 按“DNS → TCP 端口 → SSH 认证 → 登录后 shell”排查。DNS 用 `getent hosts devbox.example.com`;TCP 用 `nc -vz devbox.example.com 22`;认证用 `ssh -vvv devbox`;登录后用 `hostname; whoami; uptime`。每层成功后再进下一层,不要把网络超时误判成密钥错误。
|
|
394
|
+
|
|
395
|
+
3. 三个数分别是最近 1、5、15 分钟的可运行任务和不可中断等待任务的平均数量。load 高不一定是 CPU 忙:磁盘 IO 阻塞也会把 load 拉高,要同时看 `top` 的 `%wa`、`iostat` 和进程状态。
|
|
396
|
+
|
|
397
|
+
4. 常见原因是文件已经被删除,但进程仍持有打开句柄,inode 空间没有释放。执行:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
df -h
|
|
401
|
+
sudo lsof +L1 | head -30
|
|
173
402
|
```
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
403
|
+
|
|
404
|
+
确认是哪个进程后,重启或让进程重新打开文件;不要直接删除不认识的文件。
|
|
405
|
+
|
|
406
|
+
5. `free` 是完全没有被使用的内存;`available` 是估算后可分配给新进程的内存,包含可回收的 page cache。判断是否快 OOM 主要看 `available`、swap 和进程 RSS。
|
|
407
|
+
|
|
408
|
+
6. `SIGTERM(15)` 请求进程优雅退出,服务可以停止接新请求、等待在途请求并关闭连接池;`SIGKILL(9)` 由内核立即终止。先发 15,等待发布窗口仍不退出并评估影响后才发 9。
|
|
409
|
+
|
|
410
|
+
7. `Restart=on-failure` 能在进程异常退出后自动拉起,减少短时故障;风险是配置错误、端口冲突或持续 panic 时反复重启,制造日志风暴并掩盖根因。必须同时看 `systemctl status` 和 `journalctl -u`。
|
|
411
|
+
|
|
412
|
+
8. 写进 `.bashrc`、命令历史或进程命令行会进入文件、shell history、备份、截图和 `ps` 输出,泄露后很难彻底清理。使用密钥管理或平台注入的环境变量,并确保日志脱敏。
|
|
413
|
+
|
|
414
|
+
9.
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
hostnamectl
|
|
418
|
+
nproc
|
|
419
|
+
free -h
|
|
420
|
+
df -h
|
|
421
|
+
uptime
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
预期输出依次包含主机信息、CPU 核数、内存、磁盘和三个 load 值。命令缺失时先查 `command -v`,不要用一个命令的输出推断全部资源状态。
|
|
425
|
+
|
|
426
|
+
10.
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
sudo du -ah /var/log 2>/dev/null | sort -rh | head -10
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
这是一次性排查,不要在生产高峰对整个 `/` 扫。用 `sudo` 是为了读取受限日志;如果 `du` 很慢,先缩小到具体业务日志目录。
|
|
433
|
+
|
|
434
|
+
11.
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
pid=$(ss -lntp 'sport = :8080' | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
|
438
|
+
test -n "$pid"
|
|
439
|
+
kill -TERM "$pid"
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
先确认 PID 对应的是预期进程,再发 SIGTERM。若进程 10 秒后仍不退出,先用 `ps -o pid,ppid,stat,etime,cmd -p "$pid"` 和日志判断是否卡在关闭流程;只有确认必须强杀时才用 `kill -KILL`。
|
|
443
|
+
|
|
444
|
+
12. 完整的 unit 文件:
|
|
445
|
+
|
|
446
|
+
```ini
|
|
447
|
+
[Unit]
|
|
448
|
+
Description=minicoin
|
|
449
|
+
After=network.target
|
|
450
|
+
|
|
451
|
+
[Service]
|
|
452
|
+
User=app
|
|
453
|
+
WorkingDirectory=/opt/minicoin
|
|
454
|
+
ExecStart=/opt/minicoin/server
|
|
455
|
+
Restart=on-failure
|
|
456
|
+
RestartSec=3
|
|
457
|
+
Environment=APP_ENV=prod
|
|
458
|
+
LimitNOFILE=65535
|
|
459
|
+
|
|
460
|
+
[Install]
|
|
461
|
+
WantedBy=multi-user.target
|
|
179
462
|
```
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
463
|
+
|
|
464
|
+
启用并验证:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
sudo systemctl daemon-reload
|
|
468
|
+
sudo systemctl enable --now minicoin
|
|
469
|
+
sudo systemctl status minicoin
|
|
470
|
+
journalctl -u minicoin -n 50 --no-pager
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
预期进程处于 `active (running)`,日志中没有配置或端口错误。配置错时先修 unit 再 reload;端口占用时先查监听者,不要靠反复 restart。
|
|
474
|
+
|
|
475
|
+
13. 先保留 SSH 会话,按下面顺序取证据:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
uptime
|
|
479
|
+
nproc
|
|
480
|
+
free -h
|
|
481
|
+
df -h
|
|
482
|
+
top -b -n1 | head -30
|
|
483
|
+
ps -eo pid,ppid,stat,%cpu,%mem,rss,etime,cmd --sort=-%cpu | head -20
|
|
484
|
+
ps -eo pid,ppid,stat,%cpu,%mem,rss,etime,cmd --sort=-%mem | head -20
|
|
485
|
+
iostat -xz 1 3
|
|
486
|
+
ss -s
|
|
487
|
+
dmesg -T | tail -50
|
|
488
|
+
journalctl -n 100 --no-pager
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
判断逻辑:`load` 高且 `%us` 高,查 CPU 消耗进程;`%wa` 高且 `iostat` 的 `%util`/await 高,查磁盘 IO;`available` 低且 RSS 高,查内存泄漏或被回收的缓存;`df` 满,查日志和已删除但未释放的文件;`dmesg` 出现 OOM killer,先保留证据再考虑扩容或重启。命令权限不足时记录 `sudo` 限制,不能把“没权限”误写成“机器正常”。
|
|
492
|
+
|
|
187
493
|
</details>
|