-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
533 lines (277 loc) · 263 KB
/
atom.xml
File metadata and controls
533 lines (277 loc) · 263 KB
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Lsmg的大学之路</title>
<link href="/atom.xml" rel="self"/>
<link href="http://blog.lsmg.xyz/"/>
<updated>2025-11-29T09:26:42.707Z</updated>
<id>http://blog.lsmg.xyz/</id>
<author>
<name>Lsmg</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>K8S-istio</title>
<link href="http://blog.lsmg.xyz/2025/11/%E4%BA%91-istio/"/>
<id>http://blog.lsmg.xyz/2025/11/%E4%BA%91-istio/</id>
<published>2025-11-15T10:38:08.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="istio安装"><a href="#istio安装" class="headerlink" title="istio安装"></a>istio安装</h1><pre><code class="shell">mkdir -r /k8s/istio && cd /k8s/istiocurl -L https://istio.io/downloadIstio | sh -echo 'export PATH="$PATH:/k8s/istio/istio-1.28.0/bin"' >> ~/.bashrc && source ~/.bashrcistioctl x precheckcd istio-1.28.0istioctl installkubectl label namespace default istio-injection=enabledkubectl set resources deployment/istiod -n istio-system --containers=discovery --requests=cpu=100m,memory=500Mi --limits=cpu=400m,memory=1Gikubectl set resources deployment/istio-ingressgateway -n istio-system --containers=istio-proxy --requests=cpu=100m,memory=256Mi --limits=cpu=500m,memory=512Mi</code></pre><h1 id="istio-nginx例子"><a href="#istio-nginx例子" class="headerlink" title="istio-nginx例子"></a>istio-nginx例子</h1><p>istio要求调用方同样位于istio环境的容器中,nginx-master.yaml用于调用测试</p><pre><code class="yaml"># nginx-master.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment-masterspec: replicas: 1 selector: matchLabels: app: nginx-master template: metadata: labels: app: nginx-master spec: containers: - name: nginx-container-master image: nginx:latest ports: - containerPort: 80</code></pre><p>nginx-online表示正式环境</p><pre><code class="yaml"># nginx-online.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deploymentspec: replicas: 1 selector: matchLabels: app: nginx isCanary: 'false' template: metadata: labels: app: nginx isCanary: 'false' spec: containers: - name: nginx-container image: nginx:latest ports: - containerPort: 80 lifecycle: postStart: exec: command: - /bin/sh - -c - sed -i "s/Welcome to nginx\!/Welcome to ${HOSTNAME}\!/g" /usr/share/nginx/html/index.html</code></pre><p>nginx-canary-1表示金丝雀1</p><pre><code class="yaml"># nginx-canary-1.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment-canary-1spec: replicas: 1 selector: matchLabels: app: nginx isCanary: 'true' canaryId: '1' template: metadata: labels: app: nginx isCanary: 'true' canaryId: '1' spec: containers: - name: nginx-container-canary-1 image: nginx:latest ports: - containerPort: 80 lifecycle: postStart: exec: command: - /bin/sh - -c - sed -i "s/Welcome to nginx!/Welcome to ${HOSTNAME}!/g" /usr/share/nginx/html/index.html</code></pre><p>nginx-canary-2表示金丝雀2</p><pre><code class="yaml"># nginx-canary-2.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment-canary-2spec: replicas: 1 selector: matchLabels: app: nginx isCanary: 'true' canaryId: '2' template: metadata: labels: app: nginx isCanary: 'true' canaryId: '2' spec: containers: - name: nginx-container-canary-2 image: nginx:latest ports: - containerPort: 80 lifecycle: postStart: exec: command: - /bin/sh - -c - sed -i "s/Welcome to nginx!/Welcome to ${HOSTNAME}!/g" /usr/share/nginx/html/index.html</code></pre><p>nginx的Service,通配nginx匹配</p><pre><code class="yaml"># nginx-service.yamlapiVersion: v1kind: Servicemetadata: name: nginxspec: selector: app: nginx ports: - name: web protocol: TCP port: 5000 targetPort: 80 type: ClusterIP</code></pre><p>istio配置</p><pre><code class="yaml"># nginx-vs.yamlapiVersion: networking.istio.io/v1kind: VirtualServicemetadata: name: nginxspec: hosts: - nginx.default.svc.cluster.local http: - match: - headers: canary_id: exact: '1' route: - destination: host: nginx.default.svc.cluster.local subset: canary-1 - match: - headers: canary_id: exact: '2' route: - destination: host: nginx.default.svc.cluster.local subset: canary-2 - route: - destination: host: nginx.default.svc.cluster.local subset: online---apiVersion: networking.istio.io/v1kind: DestinationRulemetadata: name: nginxspec: host: nginx.default.svc.cluster.local subsets: - name: online labels: isCanary: 'false' - name: canary-1 labels: isCanary: 'true' canaryId: '1' - name: canary-2 labels: isCanary: 'true' canaryId: '2' exportTo: - '*'</code></pre><h1 id="istio-nginx测试"><a href="#istio-nginx测试" class="headerlink" title="istio-nginx测试"></a>istio-nginx测试</h1><pre><code class="bash">kubectl exec -it nginx-deployment-master-d9dfb9976-x28s5 -- bashcurl -s nginx:5000 -H "canary_id: 1" | grep "Welcome to"<title>Welcome to nginx-deployment-canary-1-d4d4f9577-9dlvp!</title><h1>Welcome to nginx-deployment-canary-1-d4d4f9577-9dlvp!</h1>curl -s nginx:5000 -H "canary_id: 2" | grep "Welcome to"<title>Welcome to nginx-deployment-canary-2-558b489475-d6ppx!</title><h1>Welcome to nginx-deployment-canary-2-558b489475-d6ppx!</h1>curl -s nginx:5000 -H "canary_id: 3" | grep "Welcome to"<title>Welcome to nginx-deployment-748c545c49-mnsnq!</title><h1>Welcome to nginx-deployment-748c545c49-mnsnq!</h1>curl -s nginx:5000 | grep "Welcome to"<title>Welcome to nginx-deployment-748c545c49-mnsnq!</title><h1>Welcome to nginx-deployment-748c545c49-mnsnq!</h1></code></pre><p>配置的1和2都前往了对应的分组,未配置的3和空则走了默认的分组。</p><h1 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h1><ol><li>创建一个Service,得到一个域名nginx.default.svc.cluster.local</li><li>POD客户端(这个客户端也必须被注入了Istio)访问这个域名</li><li>流量被转发到POD客户端的istio-proxy(Envoy),访问nginx.default.svc.cluster.local不再是直达经过标签选择的pod,而是被转发到Envoy</li><li>Envoy查询VS,使用hosts比对得到上面的VS规则,按断http协议的请求是否存在canary_id这个header,都是则继续查找nginx.default.svc.cluster.local的canary这个subset </li><li>Envoy查询DestinationRule,判断nginx.default.svc.cluster.local的canary这个subset的规则,得到规则为存在canaryId: ‘1’这个标签 </li><li>最终转发到存在canaryId: ‘1’标签的nginx-deployment-canary-1</li></ol><p>Istio+Envoy的组合只会拦截TCP和UDP流量,不会拦截ping的ICMP流量</p><p><strong>注册和配置收集</strong></p><ol><li>pod被注入istio-proxy后,Envoy进程会自动启动</li><li>Envoy尝试连接Istio控制面,报告自己的身份</li><li>Istio控制面收集VS,DR等配置,计算这个Envoy的相关配置<ol><li>默认情况下本命名空间的服务都被认为相关</li><li>所有被注入了Envoy的服务</li></ol></li><li>Istio控制面根据身份信息推送对应的Envoy配置</li></ol><h1 id="kiali安装"><a href="#kiali安装" class="headerlink" title="kiali安装"></a>kiali安装</h1><pre><code class="bash">kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.28/samples/addons/kiali.yamlkubectl -n istio-system get svc kialikubectl -n istio-system port-forward svc/kiali 20001:20001 --address="0.0.0.0"kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/prometheus.yamlkubectl -n istio-system get pod,svc | grep -i prometheus</code></pre><pre><code class="bash">kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \ kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/experimental-install.yaml</code></pre>]]></content>
<summary type="html">
<h1 id="istio安装"><a href="#istio安装" class="headerlink" title="istio安装"></a>istio安装</h1><pre><code class="shell">mkdir -r /k8s/istio &amp;&am
</summary>
<category term="云" scheme="http://blog.lsmg.xyz/categories/%E4%BA%91/"/>
</entry>
<entry>
<title>K8S基础概念</title>
<link href="http://blog.lsmg.xyz/2025/11/%E4%BA%91-K8S/"/>
<id>http://blog.lsmg.xyz/2025/11/%E4%BA%91-K8S/</id>
<published>2025-11-01T10:38:08.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="入门操作"><a href="#入门操作" class="headerlink" title="入门操作"></a>入门操作</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><pre><code class="sh"># install helmcurl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash# install kubectlcurl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"sudo install kubectl /usr/local/bin/kubectl# install minikubecurl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64sudo install minikube-linux-amd64 /usr/local/bin/minikube && rm minikube-linux-amd64# startminikube start# start dashboardminikube dashboard# forwardkubectl proxy --address 0.0.0.0 --disable-filter=true# fast createhelm create mychart# install unstallhelm install mychart ./mycharthelm uninstall mychart# only renderhelm install --debug --dry-run goodly-guppy ./mychart</code></pre><h2 id="真机安装"><a href="#真机安装" class="headerlink" title="真机安装"></a>真机安装</h2><pre><code class="shell"># ubuntuswapoff /swap.imgvim /etc/fstabsudo apt-get install -y apt-transport-https ca-certificates curl gpgcurl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpgecho 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.listapt-get updateapt-get install -y kubelet kubeadm kubectlapt-mark hold kubelet kubeadm kubectlsed -i '/^\s*net\.ipv4\.ip_forward/s/^/#/' /etc/sysctl.d/99-sysctl.confsed -i '/^\s*net\.ipv4\.ip_forward/s/^/#/' /etc/sysctl.confcat <<EOF | sudo tee /etc/modules-load.d/k8s.confbr_netfilterEOFcat <<EOF | sudo tee /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1EOFmodprobe br_netfiltersysctl --systemapt updateapt install -y containerdmkdir -p /etc/containerdcontainerd config default | sudo tee /etc/containerd/config.tomlsudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.tomlsystemctl restart containerdsystemctl enable containerd</code></pre><p>master</p><pre><code class="yaml"># rm -rf /etc/cni/net.dkubeadm init --pod-network-cidr=10.100.0.0/16mkdir -p $HOME/.kubecp -i /etc/kubernetes/admin.conf $HOME/.kube/configkubectl taint node master node-role.kubernetes.io/control-plane-wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yaml -O calico_new.yamlsed -i 's/192.168.0.0\/16/10.100.0.0\/16/g' calico_new.yamlsed -i 's/# - name: CALICO_IPV4POOL_CIDR/- name: CALICO_IPV4POOL_CIDR/' calico_new.yamlsed -i 's/# value: "10.100.0.0\/16"/ value: "10.100.0.0\/16"/' calico_new.yamlkubectl apply -f calico_new.yamlsystemctl restart containerdkubeadm token create --print-join-command# systemctl restart kubelet</code></pre><pre><code class="node"></code></pre><pre><code class="yaml"># /etc/crictl.yamlruntime-endpoint: unix:///run/containerd/containerd.sockimage-endpoint: unix:///run/containerd/containerd.sock</code></pre><h3 id="自动补全"><a href="#自动补全" class="headerlink" title="自动补全"></a>自动补全</h3><pre><code class="bash">apt-get install bash-completionecho 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrcecho 'source <(kubectl completion bash)' >> ~/.bashrc</code></pre><h1 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h1><p><a href="https://kubernetes.io/zh-cn/docs/tutorials/hello-minikube/" target="_blank" rel="noopener">文档</a></p><p><a href="https://kubernetes.io/zh-cn/docs/concepts/overview/" target="_blank" rel="noopener">文档</a></p><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h2><ol><li><strong>服务发现和负载均衡</strong>: Kubernetes可以通过DNS名称或IP地址暴露容器,并自动进行负载均衡,确保流量均匀分配,保持部署稳定。</li><li><strong>存储编排</strong>: Kubernetes支持自动挂载各种存储系统,如本地存储和公共云存储,简化存储管理。</li><li><strong>自动部署和回滚</strong>: Kubernetes允许描述容器的期望状态,并自动调整实际状态以匹配期望状态,支持自动化部署和回滚。</li><li><strong>自动完成装箱计算</strong>: Kubernetes根据容器的CPU和内存需求,智能调度容器到集群中的节点,优化资源利用。</li><li><strong>自我修复</strong>: Kubernetes会自动重启失败的容器、替换不健康的容器,并在服务准备好之前不将其通告给客户端。</li><li><strong>密钥与配置管理</strong>: Kubernetes可以安全地存储和管理敏感信息,如密码和密钥,支持在不重建镜像的情况下更新配置。</li><li><strong>批处理执行</strong>: Kubernetes不仅管理服务,还支持批处理和CI工作负载,自动替换失败的容器。</li><li><strong>水平扩缩</strong>: Kubernetes支持通过简单命令、用户界面或根据CPU使用率自动扩缩应用。</li><li><strong>IPv4/IPv6双栈</strong>: Kubernetes为Pod和Service分配IPv4和IPv6地址,支持双栈网络。</li><li><strong>为可扩展性设计</strong>: Kubernetes允许在不改变上游源代码的情况下添加功能,支持集群的可扩展性。</li></ol><h2 id="Ingress"><a href="#Ingress" class="headerlink" title="Ingress"></a>Ingress</h2><p>控制Web流量达到工作负载,可以当做集群入口点。</p><h2 id="Endpoint"><a href="#Endpoint" class="headerlink" title="Endpoint"></a>Endpoint</h2><p>云原生服务发现</p><h2 id="Yaml文件"><a href="#Yaml文件" class="headerlink" title="Yaml文件"></a>Yaml文件</h2><p>使用yaml定义一个pod,此时不会被Deployment控制器所管理,出现问题后不会重新创建。</p><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: my-image:latest ports: - containerPort: 80</code></pre><p>使用yaml定义一个Deployment,当pod崩溃或者被删除时,会自动创建来保证存在指定数量的pod。</p><pre><code class="yaml">apiVersion: apps/v1kind: Deployment # 这里不一样metadata: name: my-deploymentspec: replicas: 3 # Pod副本数量 selector: matchLabels: app: my-app # 要与template.metadata.labels匹配,保证Deployment创建的pod能被正确管理。 template: metadata: labels: app: my-app spec: containers: - name: my-container image: my-image:latest ports: - containerPort: 80</code></pre><h2 id="K8S组件"><a href="#K8S组件" class="headerlink" title="K8S组件"></a>K8S组件</h2><p>Node:工作机器<br>Container:容器<br>Pod:部署单元,一个应用可能有多个部署单元,一个部署单元可能包含一个或多个容器,容器共享相同的网络和存储单元。先有的部署单元,部署单元需要创建容器,容器位于节点上。<em>一个Node上不同的Container之间怎么做的共享和隔离?</em><br>Deployment:应用控制器,检测Pod的运行创建,出问题时自动创建。</p><p>K8S集群由控制平面和一个或多个工作节点组成。</p><ol><li>控制平面组件:管理集群的整体状态,负责资源调度,检测和响应集群事件<ol><li>kube-apiserver:公开 Kubernetes HTTP API 的核心组件服务器</li><li>etcd:一致性和高可用的键值存储,用于API服务器的数据存储</li><li>kube-scheduler:监听新创建的未指定运行Node的Pods,将Pod分配给合适的Node。<strong>资源需求,软硬件,策略约束,亲和,反亲和,数据位置,工作覆盖干扰,最后时限。东西还挺多,每个感觉都能看看</strong></li><li>kube-controller-manager:运行控制器来实现 Kubernetes API 行为。(<em>不太懂</em>)<ol><li>Node控制器,在节点出现故障时进行响应</li><li>Job控制器,啥玩意</li><li>EndpointSlice控制器,填充EndpointSlice对象,提供Service和Pod之间的链接。</li><li>ServiceAccount控制器,啥玩意</li></ol></li></ol></li><li>Node 组件:运行在节点上,<em>维护pod并提供k8s运行时环境</em><ol><li>kubelet:确保pod及其容器正常运行。保证容器都运行在pod中,啥玩意</li><li>kube-proxy:维护节点网络规则来实现service功能,(Kubernetes中Service是将运行在一个或一组Pod上的应用可被客户端访问,Service提供一个虚拟的IP地址和端口,以及选择器selector如<code>k8s-app: kube-dns</code>带有此标签的服务A会被注册到这个Service实现转发的作用)。</li></ol></li><li>插件<ol><li>DNS</li><li>Web界面</li><li>容器资源监控</li><li>日志</li></ol></li></ol><h2 id="K8S对象"><a href="#K8S对象" class="headerlink" title="K8S对象"></a>K8S对象</h2><p>描述了K8S中的一个实体,对应一个yaml文件。其中描述了实体的名称,期望状态等内容。</p><p><strong>对象管理</strong></p><ul><li><strong>指令式命令</strong>:<code>kubectl create deployment nginx --image nginx</code></li><li><strong>指令式对象配置</strong>:指定一个对象 <code>kubectl create -f nginx.yaml</code></li><li><strong>声明式对象配置</strong>:制定目录</li></ul><p>Name定义了一个对象名,对应的实体使用UID标识。对象删除重建后,对象名不变,应用名会变。</p><p>label标签,可以通过标签进行选择和过滤操作。<code>"metadata": { "labels": {"K1": "value"} }</code></p><p>annotations注解,用于存储非标识数据,不用于选择和过滤附加信息,<code>"metadata": { "annotations": {"K1": "value"} }</code></p><p>namespace,将同一集群中的资源划分为互相隔离的组,供不同用户来使用。</p><h3 id="回收机制"><a href="#回收机制" class="headerlink" title="回收机制"></a>回收机制</h3><p>pods的管理和回收机制,Finalizers,属主,附属</p><h3 id="K8S-API"><a href="#K8S-API" class="headerlink" title="K8S API"></a>K8S API</h3><p>K8S使用API查询和操纵其中对象的状态,kubectl这类命令行工具也是在调用API。kubectl获取并缓存API规范<strong>实现命令行补全</strong>。</p><p>两个机制,<em>发现API,OpenAPI文档</em></p><h2 id="K8S架构"><a href="#K8S架构" class="headerlink" title="K8S架构"></a>K8S架构</h2><h3 id="节点和控制面之间的通信"><a href="#节点和控制面之间的通信" class="headerlink" title="节点和控制面之间的通信"></a>节点和控制面之间的通信</h3><p>节点的API调用终止于API服务器</p><p>API服务器到各个节点上的kubelet进程存在连接,作用:获取pod日志,挂接?,端口转发。</p><h3 id="控制器"><a href="#控制器" class="headerlink" title="控制器"></a>控制器</h3><p>对象中存在spec字段,表示对象期望达到的状态,控制器负责确保其当前状态接近于期望状态。</p><h2 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h2><h3 id="镜像"><a href="#镜像" class="headerlink" title="镜像"></a>镜像</h3><p>Kubernetes 可以使用的一些镜像名称示例包括:</p><table><thead><tr><th>名字</th><th>说明</th></tr></thead><tbody><tr><td>busybox</td><td>仅包含镜像名称,没有标签或摘要,Kubernetes 将使用 Docker 公共镜像仓库和 latest 标签。 (例如 docker.io/library/busybox:latest)</td></tr><tr><td>busybox:1.32.0</td><td>带标签的镜像名称,Kubernetes 将使用 Docker 公共镜像仓库。 (例如 docker.io/library/busybox:1.32.0)</td></tr><tr><td>registry.k8s.io/pause:latest</td><td>带有自定义镜像仓库和 latest 标签的镜像名称。</td></tr><tr><td>registry.k8s.io/pause:3.5</td><td>带有自定义镜像仓库和非 latest 标签的镜像名称。</td></tr><tr><td>registry.k8s.io/pause@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07</td><td>带摘要的镜像名称。</td></tr><tr><td>registry.k8s.io/pause:3.5@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07</td><td>带有标签和摘要的镜像名称,镜像拉取仅参考摘要。</td></tr></tbody></table><p>镜像拉取策略imagePullPolicy</p><ul><li>IfNotPresent:本地不存在才拉取(未指定且使用非latest标签时的默认项)</li><li>Always:查询镜像摘要,如果本地不存在对应摘要则拉取,存在并不会拉取。(未指定且使用latest标签时的默认项)(未指定且无标签时的默认项)</li><li>Never:只使用本地</li></ul><p>策略在对象初次创建时设置,此后更新Deployment的镜像标签时,拉取策略不会发生变化。</p><h3 id="容器环境"><a href="#容器环境" class="headerlink" title="容器环境"></a>容器环境</h3><p>pod中定义的环境变量可在容器中使用</p><p>Foo服务器的环境变量,用于访问FOO服务器</p><pre><code class="txt">FOO_SERVICE_HOST=主机FOO_SERVICE_PORT=端口</code></pre><p><a href="https://github.com/kubernetes/kubernetes/tree/v1.32.0/cluster/addons/dns/" target="_blank" rel="noopener">DNS插件</a></p><h3 id="容器回调"><a href="#容器回调" class="headerlink" title="容器回调"></a>容器回调</h3><p>回调:无参数,执行失败会杀掉容器</p><ul><li><p>PostStart:容器创建之后被立即执行。执行时间过长或挂起,可能容器无法进入running状态。失败会发出FailedPostStartHook事件。</p></li><li><p>PreStop:容器被终止前调用。需要在限定时间内执行完成,时间一到即刻上路。失败会发出FailedPreStopHook事件。</p></li></ul><p>注册方式(执行方式)</p><p>Exec,执行命令,HTTP,Sleep</p><p><code>kubectl describe pod lifecycle-demo</code></p><pre><code class="yaml">apiVersion: apps/v1kind: Deploymentmetadata: name: nginx namespace: defaultspec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25-alpine ports: - containerPort: 80 lifecycle: postStart: exec: command: - /bin/sh - -c - sed -i 's/Welcome to nginx!/Welcome to nginx-1!/g' /usr/share/nginx/html/index.html</code></pre><h2 id="工作负载"><a href="#工作负载" class="headerlink" title="工作负载"></a>工作负载</h2><p>工作负载是运行程序,pod是一组运行状态的容器集合。一般不会直接管理pod,而是通过工作负载配置+控制器,自动管理pod</p><p>工作负载配置类型</p><ul><li>Deployment,ReplicaSet:管理无状态应用,pod都是等价的</li><li>StatefulSet:部署有状态服务,将Pod与PersistentVolume对应起来。<em>可以将数据复制到同一StatefulSet中其他Pod来提高整体服务的可靠性,怎么复制?</em></li><li>DaemonSet:基础负载,添加新Node时,会自动在新Node部署一个pod</li><li>Job:定义一直运行到结束并停止的任务,CronJob:根据排期表,多次运行同一个job</li><li>定制:CRD。如果你希望运行一组 Pod,但要求所有 Pod 都可用时才执行操作 (比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展, 并将其安装到集群中运行。<em>怎么定义感觉可以看看,CRD这个东西</em></li></ul><h3 id="Pod"><a href="#Pod" class="headerlink" title="Pod"></a>Pod</h3><h4 id="pod生命周期"><a href="#pod生命周期" class="headerlink" title="pod生命周期"></a>pod生命周期</h4><p>Pod:定义的一个逻辑主机,容器的运行时环境,包含一个或多个应用容器,容器对应的紧密耦合在一起(共享网络(可以通过localhost通信)和存储(共享内存也行?))。Pod 类似于共享名字空间并共享文件系统卷的一组容器。</p><p>除了应用容器:pod还可以在Pod启动期间运行init容器,还可以注入临时性容器来调试正在运行的pod。</p><p>直接创建POD,不推荐:<code>kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml</code></p><p><em>POD名称DNS子域值(这是啥?)DNS标签规则</em></p><p>livenessProbe:容器存活探针<br>readinessProbe:就绪探针,探测失败时从Server/Endpoint/负责均衡中摘除,不分配流量。<br>startupProbe:启动检测探针。容器启动时间过长时,会被上面两个探针误伤。当启用了启动检查探针时,存活和就绪探针将会在启动检查探针检测成功后启用。</p><p><strong>Pod.status.phase整体POD生命周期</strong>:起始于Pending,至少有一个主要容器正常启动后进入Running,之后有容器失败进入failed,没有则进入Succeeded。</p><p>Pending:Pod被保存到了Etcd中,但是Pod中的容器不能被顺利创建<br>Running:Pod已经创建成功,和Node进行绑定。所有容器都创建成功,至少有一个正在运行中<br>Succeeded:所有容器正常运行完毕,并且退出了。常见于运行一次性任务。<br>Failed:至少有一个容器以非0错误码退出<br>Unknown:通信出现问题</p><p><strong>Pod.status.conditionsPOD健康条件</strong>:展示<strong>pod</strong>关键阶段和健康性。<br>Ready:所有容器已经就绪,可以对外提供服务<br>ContainersReady:所有业务容器就绪(不含init容器)<br>PodScheduled:是否被调度到某一个Node上</p><p><strong>Pod.status.containerStatuses.state单个容器状态</strong>:直观展示<strong>容器</strong>的状态,不同于phase。waiting等待需要结合reason查看,running,反复启动失败可能会有CrashLoopBackOff,被删除会有Terminating<br>livenessProbe失败会导致state切换到Terminated,之后按照restartPolicy处理。</p><p>绑定:pod分配到特定节点</p><p>调度:选择使用哪个节点</p><p><code>kubectl describe pod <pod 名称></code>:查看pod的状态和时间</p><p><code>kubectl logs <pod名称></code>: 查看pod的日志</p><h4 id="Init容器和Sidecar边车容器"><a href="#Init容器和Sidecar边车容器" class="headerlink" title="Init容器和Sidecar边车容器"></a>Init容器和Sidecar边车容器</h4><p>Init按顺序执行完毕之后退出,应用容器才会运行</p><p>应用</p><ol><li>运行shell检查并等待一个Service完成创建(Init运行结束之后,应用容器才会运行)</li><li>curl注册POD到远程服务器</li><li>sleep应用容器启动之前,等待一段时间。</li><li>克隆git仓库到<strong>卷(后面会有一大章节介绍)</strong>中</li><li>渲染配置文件的模板,如模板需要POD的ip</li></ol><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: myapp-pod labels: app.kubernetes.io/name: MyAppspec: containers: - name: myapp-container image: busybox:1.28 command: ['sh', '-c', 'echo The app is running! && sleep 3600'] initContainers: - name: init-myservice image: busybox:1.28 command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"] - name: init-mydb image: busybox:1.28 # 指定成Always会变成边车容器 restartPolicy: Always command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]</code></pre><pre><code class="shell"># 启动kubectl apply -f myapp.yaml# 检查状态kubectl get -f myapp.yaml# 查看pod内Init容器的日志kubectl logs myapp-pod -c init-myservice</code></pre><p>pod重启,init容器必须重新执行</p><p>Init容器的restartPolicy设置成Always,就会在pod生命周期内持续运行</p><h4 id="临时容器"><a href="#临时容器" class="headerlink" title="临时容器"></a>临时容器</h4><p>Pod是一次性可替换的,一旦创建完成,无法添加新的容器到其中。</p><p>缺少对资源或者执行的保证,永远不会自动重启,不用于构建应用程序,没有端口配置,临时容器被添加到pod后,不能更改或删除临时容器。</p><h4 id="干扰,QoS类,命名空间-没怎么看,跳过了算是"><a href="#干扰,QoS类,命名空间-没怎么看,跳过了算是" class="headerlink" title="干扰,QoS类,命名空间 没怎么看,跳过了算是"></a>干扰,QoS类,命名空间 没怎么看,跳过了算是</h4><h4 id="Downward-API"><a href="#Downward-API" class="headerlink" title="Downward API"></a>Downward API</h4><p>将pod和容器字段暴露给运行中的容器</p><ol><li>作为环境变量</li><li>作为downwardAPI卷中的文件</li></ol><h3 id="工作负载管理"><a href="#工作负载管理" class="headerlink" title="工作负载管理"></a>工作负载管理</h3><p>Deployments:封装的ReplicaSet,提供了更新、回滚、恢复和暂停等能力。更新时创建新的RS用其中的POD逐步替换旧RS的POD。修改模板之后直接使用apply应用,会触发自动应用</p><p>ReplicaSet:提供了维持POD数量的能力,修改POD模板之后需要删除旧POD才能触发新POD创建。</p><p>StatefulSet:比起Deployments为它们的每个Pod维护了一个有粘性的永远不变的ID。</p><p>Deployments vs StatefulSet</p><p><strong>身份标识</strong></p><ul><li>Deployments的POD名称随机生成(nginx-deploy-5d5b64cf8-xj7pw),重启后变化。无固定网络标识,IP动态分配,服务依赖Service负载均衡</li><li>StatefulSete的POD名称固定(mysql-0,mysql-1),重建后保持不变。提供稳定的DNS名称<code>(<pod-name>.<svc-name>.namespace.svc.cluster.local)</code></li></ul><p><strong>存储管理</strong></p><ul><li>Deployments默认使用临时存储(emptyDir),POD删除后数据丢失,持久化需要手动配置PVC,无法绑定到特定POD。</li><li>StatefulSet为每个POD绑定独立PVC,与POD生命周期解耦</li></ul><p><strong>生命周期管理</strong></p><table><thead><tr><th>操作</th><th>Deployment</th><th>StatefulSet</th></tr></thead><tbody><tr><td>启动/停止顺序</td><td>并行创建/删除,无顺序要求</td><td>严格按序号顺序(如先启动 <code>web-0</code>,再 <code>web-1</code>)</td></tr><tr><td>滚动更新</td><td>无序更新,支持多 Pod 同时替换</td><td>逆序更新(从最高序号 Pod 开始逐个更新)</td></tr><tr><td>扩缩容</td><td>随机创建/删除 Pod</td><td>扩容:顺序新增 Pod(如新增 <code>web-2</code>); 缩容:逆序删除(先删 <code>web-2</code>)</td></tr></tbody></table><h4 id="Deployments"><a href="#Deployments" class="headerlink" title="Deployments"></a>Deployments</h4><pre><code class="yaml">apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment # 命名 labels: # Deployment的标签 app: nginxspec: replicas: 3 # 创建一个ReplicaSet,标明pod副本数量 selector: # 定义ReplicaSet会管理所有带有后面标签的pod。如果先前通过其他方式创建过带有此标签的pod,且pod符合后面image和port的定义,也会被同时管理,不过此时相关NAME可能规范不同(如没有相关的HASH或者前缀)不建议这样做。假定你在ReplicaSet已经被部署之后创建Pod,并且你已经在ReplicaSet中设置了其初始的Pod副本数以满足其副本计数需要,新的Pod会被该ReplicaSet获取,并立即被ReplicaSet终止, 因为它们的存在会使得ReplicaSet中Pod个数超出其期望值。 matchLabels: app: nginx template: metadata: labels: # 在模板中定义标签,这个是pod的标签 app: nginx spec: 指定运行一个nginx容器并命名为nginx containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80</code></pre><p><code>kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml</code></p><pre><code class="txt">kubectl get deploymentsNAME READY UP-TO-DATE AVAILABLE AGEnginx-deployment 0/3 0 0 1s</code></pre><ul><li>NAME: Deployment名称</li><li>READY:就绪个数/期望个数</li><li>UP-TO-DATE:为了达到期望状态已经更新的副本数</li><li>AVAILABLE:可用用户使用的副本数</li><li>AGE:运行时间</li></ul><pre><code class="txt">kubectl get rsNAME DESIRED CURRENT READY AGEnginx-deployment-75675f5897 3 3 3 18s</code></pre><ul><li>NAME: ReplicaSet名称,75675f5897是哈希,由<code>Deployment名称-哈希</code>组合而成</li><li>DESIRED:期望副本数</li><li>CURRENT:实际副本数</li><li>READY:可以为用户提供服务的副本数</li><li>AGE:显示应用运行时间的长度</li></ul><pre><code class="txt">kubectl get pods --show-labelsNAME READY STATUS RESTARTS AGE LABELSnginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897</code></pre><p>NAME:Pod名称,<code>ReplicaSet名称-哈希</code></p><p>Deployment控制器将pod-template-hash标签添加到<code>Deployment所创建或收留的每个ReplicaSet</code>此标签可确保Deployment的子ReplicaSet不重叠。标签是通过对ReplicaSet的PodTemplate进行哈希处理。所生成的哈希值被添加到ReplicaSet选择算符、Pod模板标签,并存在于在ReplicaSet可能拥有的任何现有<code>Pod中</code>。</p><h5 id="更新Deployment"><a href="#更新Deployment" class="headerlink" title="更新Deployment"></a>更新Deployment</h5><p><code>kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1</code></p><p><code>kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1</code></p><p><code>kubectl edit deployment/nginx-deployment</code></p><p><strong>查看上线状态</strong></p><p><code>kubectl rollout status deployment/nginx-deployment</code></p><pre><code class="txt">kubectl get rsNAME DESIRED CURRENT READY AGEnginx-deployment-1564180365 3 3 3 6s 这个是新的RS,已经创建完毕nginx-deployment-2035384211 0 0 0 36s 这个是旧的,已经全部归0</code></pre><h5 id="翻转-滚动更新"><a href="#翻转-滚动更新" class="headerlink" title="翻转 滚动更新"></a>翻转 滚动更新</h5><pre><code class="txt">kubectl describe deployments nginx-deploymentNormal ScalingReplicaSet 2m deployment-controller Scaled up replica set nginx-deployment-2035384211 to 3 # 旧的创建到3个Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 1 # 新的先创建1个Normal ScalingReplicaSet 22s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 2 # 旧的缩容到2个Normal ScalingReplicaSet 22s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 2 # 新的扩容到2个Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 1 # 旧的缩容到1个Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 3Normal ScalingReplicaSet 14s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 0</code></pre><p>更新了Deployment,控制标签匹配.sepc.selector, 模板不匹配.sepc.template。会进行缩容和扩容进行更新</p><h5 id="回滚更新"><a href="#回滚更新" class="headerlink" title="回滚更新"></a>回滚更新</h5><p>检查上线状态</p><pre><code class="txt">kubectl rollout status deployment/nginx-deploymentWaiting for rollout to finish: 1 out of 3 new replicas have been updated...</code></pre><p>获取rs信息</p><pre><code class="txt">kubectl get rsNAME DESIRED CURRENT READY AGEnginx-deployment-1564180365 3 3 3 25snginx-deployment-2035384211 0 0 0 36snginx-deployment-3066724191 1 1 0 6s</code></pre><p>获取pod信息</p><pre><code class="txt">kubectl get podsNAME READY STATUS RESTARTS AGEnginx-deployment-1564180365-70iae 1/1 Running 0 25snginx-deployment-1564180365-jbqqo 1/1 Running 0 25snginx-deployment-1564180365-hysrc 1/1 Running 0 25snginx-deployment-3066724191-08mng 0/1 ImagePullBackOff 0 6s # 新的RS中的新的POD卡在镜像拉取</code></pre><p>检查上线历史</p><pre><code class="txt">kubectl rollout history deployment/nginx-deploymentdeployments "nginx-deployment"REVISION CHANGE-CAUSE1 kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml2 kubectl set image deployment/nginx-deployment nginx=nginx:1.16.13 kubectl set image deployment/nginx-deployment nginx=nginx:1.161</code></pre><p>查看修订历史的详细信息</p><pre><code class="txt">kubectl rollout history deployment/nginx-deployment --revision=2deployments "nginx-deployment" revision 2 Labels: app=nginx pod-template-hash=1159050644 Annotations: kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 Containers: nginx: Image: nginx:1.16.1 Port: 80/TCP QoS Tier: cpu: BestEffort memory: BestEffort Environment Variables: <none> No volumes</code></pre><p>撤销当前上线,回滚到上一个版本<br>kubectl rollout undo deployment/nginx-deployment</p><p>回滚到指定的版本<br>kubectl rollout undo deployment/nginx-deployment –to-revision=2</p><h5 id="缩放"><a href="#缩放" class="headerlink" title="缩放"></a>缩放</h5><p>kubectl scale deployment/nginx-deployment –replicas=10</p><h5 id="暂停和恢复更新"><a href="#暂停和恢复更新" class="headerlink" title="暂停和恢复更新"></a>暂停和恢复更新</h5><p>kubectl rollout pause deployment/nginx-deployment</p><p>kubectl rollout resume deployment/nginx-deployment</p><p>监视上线状态</p><p>kubectl get rs –watch</p><h5 id="Deployment状态,清理策略,金丝雀部署,规范-简单过了下"><a href="#Deployment状态,清理策略,金丝雀部署,规范-简单过了下" class="headerlink" title="Deployment状态,清理策略,金丝雀部署,规范 简单过了下"></a>Deployment状态,清理策略,金丝雀部署,规范 简单过了下</h5><h4 id="ReplicaSet"><a href="#ReplicaSet" class="headerlink" title="ReplicaSet"></a>ReplicaSet</h4><p>维持在任何给定时间运行的一组稳定的设置数量且完全相同的副本Pod,通常用Deployment来自动管理</p><p>会在pod上添加metadata.ownerReferences字段,来标注属于哪个ReplicaSet。如果POD上没有ownerReference或者其ownerReference不是一个控制器,匹配到某ReplicaSet的选择运算符,则该pod会被RS获得。</p><pre><code class="yaml">apiVersion: apps/v1kind: ReplicaSetmetadata: name: frontend labels: app: guestbook tier: frontendspec: # 按你的实际情况修改副本数 replicas: 3 selector: matchLabels: tier: frontend template: metadata: labels: tier: frontend spec: containers: - name: php-redis image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5</code></pre><p>除了<code>kind字段</code>和Deployment章节的例子几乎完全一样,但是指定成ReplicaSet后,会失去Deployment的自动扩容和回滚等功能。ReplicaSet主要用于保证指定数量的pod副本运行。</p><pre><code class="shell">kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yamlkubectl get rsNAME DESIRED CURRENT READY AGEfrontend 3 3 3 6skubectl describe rs/frontendkubectl get podsNAME READY STATUS RESTARTS AGEfrontend-gbgfx 1/1 Running 0 10mfrontend-rwz57 1/1 Running 0 10mfrontend-wkl7w 1/1 Running 0 10m</code></pre><p>可以看到pods的命名里少了一节Deployment中RS的段,因为<code>kind:ReplicaSet</code>模式下定义的name直接就是RS的,而不是定义的Deployment,由Deployment来创建RS。</p><p><strong>假定你在ReplicaSet已经被部署之后创建Pod,并且你已经在ReplicaSet中设置了其初始的Pod副本数以满足其副本计数需要,新的Pod会被该ReplicaSet 获取,并立即被ReplicaSet终止, 因为它们的存在会使得ReplicaSet中Pod个数超出其期望值。</strong></p><h5 id="删除相关的过了一下"><a href="#删除相关的过了一下" class="headerlink" title="删除相关的过了一下"></a>删除相关的过了一下</h5><h4 id="StatefulSet"><a href="#StatefulSet" class="headerlink" title="StatefulSet"></a>StatefulSet</h4><p>当个Web-0的phase进入Running,Condition变成Ready前,Web-0一直是Pending状态。</p><p>和Deployment不同的是,StatefulSet为它们的每个Pod维护了一个有粘性的永远不变的ID。<em>使用存储卷,这个存储卷是啥?</em></p><ul><li>稳定的、唯一的网络标识符。</li><li>稳定的、持久的存储。</li><li>有序的、优雅的部署和扩缩。</li><li>有序的、自动的滚动更新。</li></ul><p>给定Pod的存储必须由PersistentVolume Provisioner基于所请求的storage class来制备,或者由管理员预先制备。删除或者扩缩StatefulSet并不会删除它关联的存储卷。这样做是为了保证数据安全,它通常比自动清除StatefulSet所有相关的资源更有价值。</p><pre><code class="yaml">apiVersion: v1kind: Servicemetadata: name: nginx labels: app: nginxspec: ports: - port: 80 name: web clusterIP: None selector: app: nginx---apiVersion: apps/v1kind: StatefulSetmetadata: name: webspec: selector: matchLabels: app: nginx # 必须匹配 .spec.template.metadata.labels serviceName: "nginx" replicas: 3 # 默认值是 1 updateStrategy: RollingUpdate # 默认的滚动更新 OnDelete手动删除pod后控制器才会创建新的pod,这时才能响应.sepc.template的变动 minReadySeconds: 10 # 默认值是 0 template: metadata: labels: app: nginx # 必须匹配 .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: registry.k8s.io/nginx-slim:0.24 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: # 这个字段是创建PersistentVolumeClaim, PersistentVolume制备和PersistentVolumes是啥?卷相关的还比较多,后面有单独的章节,不急。 - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "my-storage-class" resources: requests: storage: 1Gi</code></pre><table><thead><tr><th>集群域名</th><th>服务(名字空间/名字)</th><th>StatefulSet(名字空间/名字)</th><th>StatefulSet 域名</th><th>Pod DNS</th><th>Pod 主机名</th></tr></thead><tbody><tr><td>cluster.local</td><td>default/nginx</td><td>default/web</td><td>nginx.default.svc.cluster.local</td><td>web-{0..N-1}.nginx.default.svc.cluster.local</td><td>web-{0..N-1}</td></tr><tr><td>cluster.local</td><td>foo/nginx</td><td>foo/web</td><td>nginx.foo.svc.cluster.local</td><td>web-{0..N-1}.nginx.foo.svc.cluster.local</td><td>web-{0..N-1}</td></tr></tbody></table><p>web-{0..N-1}扩容按顺序进行。缩容会逆序执行。当前执行完毕之后,才会处理下一个。</p><p>按照与Pod终止相同的顺序,Kubernetes控制平面会等到被更新的Pod进入Running和Ready状态,然后再更新其前身。如果有minReadySeconds则还会进行额外等待。</p><p><strong>分区滚动更新</strong></p><p>.spec.updateStrategy.rollingUpdate.partition</p><p>当StatefulSet的.spec.template被更新时,所有序号大于等于该分区序号的Pod都会被更新。所有序号小于该分区序号的Pod都不会被更新,并且即使它们被删除也会依据之前的版本进行重建。如果StatefulSet的.spec.updateStrategy.rollingUpdate.partition大于.spec.replicas,则对它的.spec.template的更新将不会传递到它的Pod。</p><p><em>这里还是实操一下比较好</em></p><p><strong>稳定的存储 PersistentVolumeClaim保留</strong></p><p>对于StatefulSet中定义的每个VolumeClaimTemplate,每个Pod会收到基于<code>storage class: my-storage-class</code>分配的1GiB的PersistentVolume。当pod被调度(重新调度)到节点时,volumeMounts会挂载与PersistentVolumeClaim关联的PersistentVolume。pod或StatefulSet被删除时,与PersistentVolumeClaim关联的PersistentVolume不会被一起自动删除,只能手动删除。</p><p>.spec.persistentVolumeClaimRetentionPolicy可以控制是否保留等,简单过了下,默认都是保留。</p><p>StatefulSet的.spec.volumeClaimTemplates会自动创建PVC,而POD就有的.spec.volumes需要指定已经创建好的PVC</p><pre><code class="yaml"># pvapiVersion: v1kind: PersistentVolumemetadata: name: nginx-pvspec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: /mnt/data# pvc POD需要创建好的PVCapiVersion: v1kind: PersistentVolumeClaimmetadata: name: nginx-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi</code></pre><h4 id="DaemonSet"><a href="#DaemonSet" class="headerlink" title="DaemonSet"></a>DaemonSet</h4><p>提供节点本地设施,新Node加入时会自动部署对应的pod。</p><ul><li>节点上运行集群守护进程</li><li>节点上</li></ul><h4 id="JOB"><a href="#JOB" class="headerlink" title="JOB"></a>JOB</h4><pre><code class="yaml">apiVersion: batch/v1kind: Jobmetadata: name: pispec: # 这里没有selector了 template: spec: containers: - name: pi image: resouer/ubuntu-bc command: ["sh", "-c", "echo 'scale=10000; 4*a(1)' | bc -l "] restartPolicy: Never # 只能是Never或者OnFailure backoffLimit: 4 # 默认是6</code></pre><p>当restartPolicy指定Never时,会不断创建新的POD,间隔(10S,20S,40S,80S)重试4次。<br>指定OnFailure时,会不断重启现有的容器,不会创建新的</p><p>可以控制并行进行</p><pre><code class="yaml">apiVersion: batch/v1kind: Jobmetadata: name: pispec: parallelism: 2 # 最大并行数量 completions: 4 # 最小完成数量 template: spec: containers: - name: pi image: resouer/ubuntu-bc command: ["sh", "-c", "echo 'scale=5000; 4*a(1)' | bc -l "] restartPolicy: Never backoffLimit: 4</code></pre><h5 id="外部管理器-Job模板"><a href="#外部管理器-Job模板" class="headerlink" title="外部管理器+Job模板"></a>外部管理器+Job模板</h5><pre><code class="yaml">apiVersion: batch/v1kind: Jobmetadata: name: process-item-$ITEM labels: jobgroup: jobexamplespec: template: metadata: name: jobexample labels: jobgroup: jobexample spec: containers: - name: c image: busybox command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"] restartPolicy: Never</code></pre><p>使用工具对$ITEM进行替换后执行<br>KubeFlow使用的这种方式</p><p><strong>拥有固定任务数目的并行 Job</strong>。没太看懂</p><p><strong>指定并行度(parallelism),但不设置固定的 completions 的值。</strong></p><h5 id="CronJob"><a href="#CronJob" class="headerlink" title="CronJob"></a>CronJob</h5><pre><code class="yaml">apiVersion: batch/v1beta1kind: CronJobmetadata: name: hellospec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure</code></pre><p>像是Deploymet管理RS。Cronjob管理job</p><p>spec.concurrencyPolicy,控制上一个Job未完成的时候,新Job开始时的动作</p><ol><li>concurrencyPolicy=Allow,默认,Job 可以同时存在;</li><li>concurrencyPolicy=Forbid,不会创建新的 Pod,该创建周期被跳过;</li><li>concurrencyPolicy=Replace,新产生的 Job 会替换旧的、没有执行完的 Job。</li></ol><p>一次Job失败miss+1,当miss达到100就会停止创建Job。统计时间段可以由startingDeadlineSeconds指定。</p><h3 id="管理工作负载"><a href="#管理工作负载" class="headerlink" title="管理工作负载"></a>管理工作负载</h3><p>—可以分割多个负载放入一个文件中。</p><h3 id="自动扩缩"><a href="#自动扩缩" class="headerlink" title="自动扩缩"></a>自动扩缩</h3><p>水平扩缩:运行多个实例,通过HPA实现。垂直扩缩:调整容器的CPU和内存资源、VPA不过是插件形式。</p><h2 id="服务,负载均衡,联网"><a href="#服务,负载均衡,联网" class="headerlink" title="服务,负载均衡,联网"></a>服务,负载均衡,联网</h2><p>POD会获得此集群下的一个唯一IP,POD内部容器可以通过localhost通信, POD之间可以相互通信。</p><p>ServiceAPI可以为一个或多个后端POD提供一个稳定的IP地址或主机名。K8S自动管理EndpointSlice对象,提供Service的POD信息。服务代理实现通过操作系统或云平台API来拦截或重写数据包,监视Service和EndpointSlice对象集,在数据平面编程将服务流量路由到其后端。</p><p>Gateway API(前身是Ingress):使得集群外能够访问Service</p><p>NetworkPolic:控制POD之间的流量,POD和外部的流量</p><h3 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h3><pre><code class="yaml">apiVersion: v1kind: Servicemetadata: name: my-servicespec: selector: app: my-app ports: - name: http protocol: TCP port: 80 targetPort: 8080 type: ClusterIP</code></pre><p>对集群中其他服务暴露80端口,转到selector筛选后pod的端口8080。K8S会为Service分配一个集群IP。</p><p>Pod的定义</p><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: nginx labels: app.kubernetes.io/name: nginx-proxyspec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 name: http-web-svc # 这里给80端口起了一个名字</code></pre><p>ClusterIP</p><pre><code class="yaml">apiVersion: v1kind: Servicemetadata: name: nginx-clusterspec: type: ClusterIP # ClusterIP是默认值,只能在集群内访问,可以使用Ingress或者GatewayAPI向外暴露服务。NodePort:通过Node上的IP和端口公开。ExternalName:映射到主机名 selector: app.kubernetes.io/name: nginx-proxy ports: - name: name-of-service-port protocol: TCP port: 80 targetPort: http-web-svc # 直接使用名字</code></pre><p>NodePort</p><pre><code class="yaml">apiVersion: v1kind: Servicemetadata: name: nginx-nodeportspec: type: NodePort # NodePort:每个Node的kube-proxy监听nodePort并转发到符合selector的Pod的targetPort selector: app.kubernetes.io/name: nginx-proxy ports: - name: name-of-service-port protocol: TCP port: 80 targetPort: http-web-svc # 直接使用名字 nodePort: 30000</code></pre><p>lb</p><pre><code class="yaml">apiVersion: v1kind: Servicemetadata: name: nginx-lbspec: type: LoadBalancer # 云厂商的CCM监听到后,会自动创建负载均衡,分配EXTERNAL-IP,通过EXTERNAL-IP调用时,会自动负载均衡到某一个pod selector: app.kubernetes.io/name: nginx-proxy ports: - name: http protocol: TCP port: 80 targetPort: http-web-svc</code></pre><h3 id="Ingress-1"><a href="#Ingress-1" class="headerlink" title="Ingress"></a>Ingress</h3><p>Ingress Controller通过Service(LoadBalance或者NodePort)对外暴露入口,根据其中设置的规则,将流量转发到指定的pod。</p><pre><code class="yaml">apiVersion: extensions/v1beta1kind: Ingressmetadata: name: cafe-ingressspec: rules: - host: cafe.example.com # IngressRule的Key http: paths: - path: /tea backend: serviceName: tea-svc servicePort: 80 - path: /coffee backend: serviceName: coffee-svc servicePort: 80</code></pre><p>Ingress对象更新后nginx-ingress-controller就会根据内容生成一个nginx配置</p><h3 id="Service和Ingress"><a href="#Service和Ingress" class="headerlink" title="Service和Ingress"></a>Service和Ingress</h3><p>Service工作在四层,Ingress工作在七层,涉及TLS等HTTP内容是只能使用ingress</p><h3 id="EndpointSlic"><a href="#EndpointSlic" class="headerlink" title="EndpointSlic"></a>EndpointSlic</h3><p>EndpointSlic对象是某个Service的后端网络断点的子集</p><h3 id="DNS"><a href="#DNS" class="headerlink" title="DNS"></a>DNS</h3><p>cluster-domain默认为cluster.local.</p><p>同一集群可以省略<code><cluster-domain></code>, 同一命名空间可以省略<code><namespace></code></p><p>服务的DNS名称格式为 <code><service-name>.<namespace>.svc.<cluster-domain></code>,短域名<code><service-name>.<namespace></code>,同一命名空间<code><service-name></code></p><p>Pod的DNS名称格式为 <code><pod-ip>.<namespace>.pod.<cluster-domain></code><br>同时还有端口的SRV记录<code>_<port-name>._<protocol>.<service-name>.<namespace>.svc.cluster.local</code></p><p>记录 A SRV PTR</p><pre><code class="shell">nslookup -type=A paifuServer: 9.166.175.254Address: 9.166.175.254#53Name: paifu.gamematrix.svc.cluster.localAddress: 9.166.174.123nslookup -type=SRV _http-metrics._tcp.cgi-common-sendsysmail.happygameServer: 9.166.175.254Address: 9.166.175.254#53_http-metrics._tcp.cgi-common-sendsysmail.happygame.svc.cluster.local service = 0 100 9100 cgi-common-sendsysmail.happygame.svc.cluster.local.nslookup -type=PTR 9.166.169.229Server: 9.166.175.254Address: 9.166.175.254#53229.169.166.9.in-addr.arpa name = 9-166-169-229.paifu.gamematrix.svc.cluster.local.229.169.166.9.in-addr.arpa name = 9-166-169-229.service-hlsvr.gamematrix.svc.cluster.local.229.169.166.9.in-addr.arpa name = 9-166-169-229.service-configagent.gamematrix.svc.cluster.local.</code></pre><p>Cluster IP的service<br><code><svc>.<namespace>.svc.cluster.local</code> 指向Service的ClusterIP(VIP)</p><p>Headless Service<br><code><svc>.<namespace>.svc.cluster.local</code> 返回所有后端的Endpoints的IP列表</p><p>Pod的记录<br><code><pod-ip-with-dashes>.<namespace>.pod.cluster.local</code>, pod-ip-with-dashes: 将.替换成-,10.1.2.3 -> 10-1-2-3</p><p>StatefulSet的格式<br><code><hostname>.<subdomain>.<namespace>.svc.cluster.local</code>, subdomain一般是0,1,2,3</p><h3 id="IPV6支持,感知路由,Windows,Cluster-IP分配,跳过"><a href="#IPV6支持,感知路由,Windows,Cluster-IP分配,跳过" class="headerlink" title="IPV6支持,感知路由,Windows,Cluster IP分配,跳过"></a>IPV6支持,感知路由,Windows,Cluster IP分配,跳过</h3><h3 id="同一Node中Pod的通信"><a href="#同一Node中Pod的通信" class="headerlink" title="同一Node中Pod的通信"></a>同一Node中Pod的通信</h3><p>service中.spec.internalTrafficPolicy设置成Local,只会选择本Node中的Pod</p><h2 id="存储"><a href="#存储" class="headerlink" title="存储"></a>存储</h2><h3 id="卷"><a href="#卷" class="headerlink" title="卷"></a>卷</h3><p>卷为POD中的容器提供了通过文件系统访问和共享数据的方式,可以进行数据的持久存储和共享。</p><h4 id="hostPath-不能迁移到其他Node"><a href="#hostPath-不能迁移到其他Node" class="headerlink" title="hostPath 不能迁移到其他Node"></a>hostPath 不能迁移到其他Node</h4><pre><code class="yaml">spec: volumes: - hostPath: path: /data/corefile/ # 给Node的此目录命名 type: '' name: corefiles containers: - volumeMounts: - name: corefiles mountPath: /data/corefile/ # 将Node目录挂载到容器的此目录</code></pre><h4 id="emptyDir-POD移除时内容消失"><a href="#emptyDir-POD移除时内容消失" class="headerlink" title="emptyDir POD移除时内容消失"></a>emptyDir POD移除时内容消失</h4><pre><code class="yaml">spec: volumes: - name: share_empty # 从节点临时存储创建一个卷 emptyDir: {} containers: - volumeMounts: - name: share_empty # 挂载卷,POD崩溃时卷内容依然存在,POD删除时内容移除,适合用来做缓存 mountPath: /etc/config </code></pre><h3 id="持久卷"><a href="#持久卷" class="headerlink" title="持久卷"></a>持久卷</h3><p>存储如何制备的细节从其如何被使用中抽象出来</p><p>持久卷(PersistentVolume,PV): 集群中的存储,可手动制备或使用存储类动态制备,拥有独立于任何使用PV的POD的生命周期。将物理存储方式隐藏起来,对外提供存储</p><p>持久卷申领(PersistentVolumeClaim,PVC):POD会消耗Node资源,PVC可以设定大小或访问模式来消耗PV资源。</p><p>保护:删除被某Pod使用的PVC对象,PVC申领不会被立即移除。PVC对象的移除会被推迟,直至其不再被任何Pod使用。删除已绑定到某PVC申领的PV卷,该PV卷也不会被立即移除。PV对象的移除也要推迟到该PV不再绑定到PVC。可以使用<code>kubectl describe pvc hostpath</code>查看</p><p>PVC可以自动绑定到匹配的PV。也可以手动指定未通过claimRef预留给其他PVC的PV。</p><pre><code class="yaml">apiVersion: v1kind: PersistentVolumeClaimmetadata: name: foo-pvc namespace: foospec: storageClassName: "" # 此处须显式设置空字符串,否则会被设置为默认的 StorageClass volumeName: foo-pv---apiVersion: v1kind: PersistentVolumemetadata: name: foo-pvspec: storageClassName: "" claimRef: name: foo-pvc namespace: foo</code></pre><h4 id="创建PV和PVC,POD申领PVC"><a href="#创建PV和PVC,POD申领PVC" class="headerlink" title="创建PV和PVC,POD申领PVC"></a>创建PV和PVC,POD申领PVC</h4><pre><code class="yaml">apiVersion: v1kind: PersistentVolumemetadata: name: pv0003spec: capacity: storage: 5Gi volumeMode: Filesystem # Filesystem文件系统,可以被直接挂载。Block块,POD和卷之间不存在文件系统,原始块卷 accessModes: - ReadWriteOnce # ReadWriteOnce被Node中多个Pod读写挂载,ReadOnlyMany,ReadWriteMany,ReadWriteOncePod被Node中一个POD读写挂载 persistentVolumeReclaimPolicy: Retain # PVC被删除时PV依然保留,对应数据卷视为已经释放,卷存在此前申领人的数据,不能用于其他申领。 storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp server: 172.17.0.2</code></pre><pre><code class="yaml">apiVersion: v1kind: PersistentVolumeClaimmetadata: name: myclaimspec: accessModes: - ReadWriteOnce volumeMode: Filesystem # volumeName: 指定要绑定的PV,不指定则会自动创建 resources: requests: storage: 5Gi storageClassName: slow # PVC指定时,只有存在相同指定slow的PV,申领才能成功 selector: # 选择算符,设置之后不会动态制备PV卷 matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]}</code></pre><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: mypodspec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/var/www/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim</code></pre><h4 id="节点亲和性"><a href="#节点亲和性" class="headerlink" title="节点亲和性"></a>节点亲和性</h4><p>限制那些节点可以访问此卷,使用这些卷的POD只会被调度到规则选择的节点上执行。<code>.spec.nodeAffinity</code></p><h4 id="阶段"><a href="#阶段" class="headerlink" title="阶段"></a>阶段</h4><p>持久卷阶段:Available,Bound,Release(已经被删除,关联的存储资源未被回收),Failed。</p><p><code>kubectl describe persistentvolume <name></code> 查看绑定到PV的PVC的名称</p><h3 id="StorageClass"><a href="#StorageClass" class="headerlink" title="StorageClass"></a>StorageClass</h3><p>可能会存在大量的PVC,如果每个PV都手动创建则非常繁琐。<br>自动PV的机制核心是StorageClass。<br>StorageClass实际是PV的模板。定义了PV的属性:存储类型,Volume大小。创建PV用到的存储插件。这样K8S就可以根据PVC的创建请求,找到StorageClass创建出PV。</p><p>对于支持动态供应的存储系统,可以用来创建PV。<br>使用不支持动态供应的节点本地存储,需要手动创建PV表示具体的存储资源,但此时StorageClass仍能用来定义一些内容。</p><p>provisioner,parameters,reclaimPolicy是必须的字段</p><pre><code class="yaml">apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: low-latency annotations: storageclass.kubernetes.io/is-default-class: "false" # true 设置成默认的,当PVC未指定时会使用provisioner: csi-driver.example-vendor.example # 指定制作PV的卷插件reclaimPolicy: Retain # 默认值是 DeleteallowVolumeExpansion: true # 允许部分类型的卷调整PVC对象大小,申请新的,大容量的存储卷。mountOptions: - discard # 这可能会在块存储层启用 UNMAP/TRIMvolumeBindingMode: WaitForFirstConsumer # 默认的Immediate是立刻绑定,可能导致PV与POD的可用区不同,速度降低。WaitForFirstConsumer将PVC的绑定从立刻绑定到PV,延迟到确定POD调度位置之后进行。parameters: guaranteedReadWriteLatency: "true" # 这是服务提供商特定的</code></pre><h3 id="存储类,PV,PVC,使用存储类进行动态卷制备"><a href="#存储类,PV,PVC,使用存储类进行动态卷制备" class="headerlink" title="存储类,PV,PVC,使用存储类进行动态卷制备"></a>存储类,PV,PVC,使用存储类进行动态卷制备</h3><p>存储管理实际是PV负责抽象物理存储,PVC负责消费。存储类负责PVC消耗时匹配对应的PV,如果PV不存在且可以动态制备,存储类会创建PV,但此时也是PV负责抽象的物理存储</p><p>PV和PVC也实现了职责分离,PVC更像是接口调用,PV提供接口。开发人员只需要调用接口,接口PV的维护则是运维处理。</p><p>PVC:Pod想要的大小,权限<br>PV:描述Volume的属性,Volume类型,挂载目录,远程服务器地址<br>StorageClass:PV的模板。同属一个StorageClass的PV和PVC才能进行绑定</p><h3 id="本地存储"><a href="#本地存储" class="headerlink" title="本地存储"></a>本地存储</h3><p>PV,PVC,StorageClass使用的大多是远程存储来保证和节点无关,方便一个StorageClass在不同Node上使用。<br>然而部分服务器需要高性能的本地存储,如部分服务会将数据落地磁盘缓存。而本地存储这个是和Node绑定的(通过 PV 的 nodeAffinity 实现)。不同Node的本地存储是互相隔离的。且由于数据在Node上,一旦Node出现问题,数据就会丢失。所以本地存储要求具备定时备份能力。</p><p><strong>延迟绑定</strong>:现在假设创建一个PVC,PVC会向Storageclass申请创建一个PV,此时由于没有Pod的信息,可能创建到了Node1上。Pod创建且使用此PVC时,会被直接约束在Node1上。延迟到POD创建时绑定,则可以根据POD的要求和选择更合适的PV进行绑定。避免被调度到不合适的Node上。</p><p><strong>不应该将宿主机的目录当做PV使用</strong>:宿主机目录不可控,PVC申请的资源可能会被侵占。缺乏配额保证,缺乏隔离会被其他容器修改。</p><p><strong>本地存储多为静态制备</strong>:PVC创建时会立刻与PV绑定,如果使用的是远程目录,立即绑定不会出现问题。使用本地目录则可能绑定到预期外的Node上,导致后续POD也被创建到此Node上。不过目前已经有provisioner支持动态制备。</p><pre><code class="yaml">apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: local-storageprovisioner: kubernetes.io/no-provisioner # 不支持动态制备volumeBindingMode: WaitForFirstConsumer---apiVersion: v1kind: PersistentVolumemetadata: name: local-pvspec: capacity: storage: 1Ti accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /data nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - 1.1.1.1---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: local-pvcspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Ti storageClassName: local-storage---apiVersion: v1kind: Podmetadata: name: my-podspec: containers: - name: my-container image: nginx volumeMounts: - mountPath: /data name: my-volume volumes: - name: my-volume persistentVolumeClaim: claimName: local-pvc</code></pre><h3 id="投射卷"><a href="#投射卷" class="headerlink" title="投射卷"></a>投射卷</h3><p>将多个不同内容来源,聚合到一个挂载目录。支持在一个目录下挂载多个来源</p><p><strong>支持的投射类型</strong></p><ul><li>configMap</li><li>secret</li><li>downwardAPI</li><li>serviceAccountToken</li></ul><h5 id="Secret"><a href="#Secret" class="headerlink" title="Secret"></a>Secret</h5><p>将etcd中的内容,映射到容器的某个目录上,提供了加密手段</p><h5 id="configMap"><a href="#configMap" class="headerlink" title="configMap"></a>configMap</h5><p>于Secret几乎相同,不过是不需要加密的数据</p><pre><code class="yaml">spec: volumes: - name: config-vol configMap: name: log-config # 声明一个卷config-vol,log-config中log_level条目会 只读 的保存到log_level.conf中 items: - Key: log_level path: log_level.conf containers: - volumeMounts: - name: config-vol # 将卷config-vol挂载到容器中此目录,目录中存在log_level.conf文件 mountPath: /etc/config </code></pre><h5 id="downwardAPI"><a href="#downwardAPI" class="headerlink" title="downwardAPI"></a>downwardAPI</h5><p>提供访问容器属性的方法</p><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: test-downwardapi-volume labels: zone: us-est-coast cluster: test-cluster1 rack: rack-22spec: containers: - name: client-container image: k8s.gcr.io/busybox command: ["sh", "-c"] args: - while true; do if [[ -e /etc/podinfo/labels ]]; then echo -en '\n\n'; cat /etc/podinfo/labels; fi; sleep 5; done; volumeMounts: - name: podinfo mountPath: /etc/podinfo readOnly: false volumes: - name: podinfo projected: sources: - downwardAPI: items: - path: "labels" fieldRef: fieldPath: metadata.labels</code></pre><p>使用kubectl logs查看</p><p><strong>serviceAccountToken</strong><br>K8S为每个pod都默认挂载了serviceAccountToken到固定目录。为pod提供访问K8S API Server的token。容器中的进程如果需要使用K8S API Server就需要加载token。<br>算是特殊的secret</p><h3 id="临时卷"><a href="#临时卷" class="headerlink" title="临时卷"></a>临时卷</h3><p>和POD保持统一生命周期,随POD创建和删除</p><pre><code class="yaml">kind: PodapiVersion: v1metadata: name: my-appspec: containers: - name: my-frontend image: busybox:1.28 volumeMounts: - mountPath: "/scratch" name: scratch-volume command: [ "sleep", "1000000" ] volumes: - name: scratch-volume ephemeral: # 定义一个临时卷 volumeClaimTemplate: # 使用模板可以自动创建PVC,使用persistentVolumeClaim则需要手动指定一个创建好的PVC metadata: labels: type: my-frontend-volume spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "scratch-storage-class" resources: requests: storage: 1Gi</code></pre><h3 id="卷属性类,Beta功能跳过"><a href="#卷属性类,Beta功能跳过" class="headerlink" title="卷属性类,Beta功能跳过"></a>卷属性类,Beta功能跳过</h3><h3 id="卷快照,卷快照类,CSI卷克隆,先跳过"><a href="#卷快照,卷快照类,CSI卷克隆,先跳过" class="headerlink" title="卷快照,卷快照类,CSI卷克隆,先跳过"></a>卷快照,卷快照类,CSI卷克隆,先跳过</h3><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><h3 id="ConfigMap"><a href="#ConfigMap" class="headerlink" title="ConfigMap"></a>ConfigMap</h3><pre><code class="yaml">apiVersion: v1kind: ConfigMapmetadata: name: game-demodata: # 类属性键;每一个键都映射到一个简单的值 player_initial_lives: "3" # 类文件键 game.properties: | enemy.types=aliens,monsters player.maximum-lives=5 apiVersion: v1kind: Podmetadata: name: configmap-demo-podspec: containers: env: # 定义环境变量 - name: PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的 valueFrom: configMapKeyRef: name: game-demo # 这个值来自 ConfigMap key: player_initial_lives # 需要取值的键 volumeMounts: - name: config mountPath: "/config" readOnly: true volumes: # 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中 - name: config configMap: # 提供你想要挂载的 ConfigMap 的名字 name: game-demo # 来自 ConfigMap 的一组键,将被创建为文件 items: - key: "game.properties" path: "game.properties"</code></pre><h3 id="Secret-1"><a href="#Secret-1" class="headerlink" title="Secret"></a>Secret</h3><pre><code class="yaml">apiVersion: v1kind: Secretmetadata: name: dotfile-secretdata: .secret-file: dmFsdWUtMg0KDQo=---apiVersion: v1kind: Podmetadata: name: secret-dotfiles-podspec: volumes: - name: secret-volume secret: secretName: dotfile-secret containers: - name: dotfile-test-container image: registry.k8s.io/busybox command: - ls - "-l" - "/etc/secret-volume" volumeMounts: - name: secret-volume readOnly: true mountPath: "/etc/secret-volume" # 最终表现为/etc/secret-volume/.secret-file文件</code></pre><h3 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h3><p>为POD指定资源的Request后,kube-scheduler就会决定将POD调度到哪个节点上,如果节点有大于Request的剩余量,POD可以多使用一些。但是指定limit后确保资源不超过设定值。</p><p>CPU限制:通过CPU节流机制强制执行</p><p>CPU单位:1CPU = 1个物理核或者1个虚拟核,1 CPU=1000m CPU,即500m为半个核心。最小为1m。</p><p>Memory限制:使用OOM终止机制执行</p><p>Memory单位:400m表示0.4字节,400Mi表示400M直接</p><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: frontendspec: containers: - name: app image: images.my-company.example/app:v4 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"</code></pre><h2 id="安全-暂时跳过"><a href="#安全-暂时跳过" class="headerlink" title="安全-暂时跳过"></a>安全-暂时跳过</h2><h2 id="策略"><a href="#策略" class="headerlink" title="策略"></a>策略</h2><h3 id="范围限制-LimitRange"><a href="#范围限制-LimitRange" class="headerlink" title="范围限制 LimitRange"></a>范围限制 LimitRange</h3><p>指定一个命名空间</p><ol><li>设置POD或者Container的最小和最大的资源使用量</li><li>PVC最大和最小的存储空间</li></ol><p>如果新创建的POD或者container不满足要求则会创建失败</p><h3 id="资源配额"><a href="#资源配额" class="headerlink" title="资源配额"></a>资源配额</h3><p>设定一个命名空间下,各种资源的上限或者下限</p><h3 id="节点资源管理器"><a href="#节点资源管理器" class="headerlink" title="节点资源管理器"></a>节点资源管理器</h3><p>可以设置CPU调度相关,比如将POD绑定到某个核心上提高缓存使用效率。</p><h2 id="调度,强占和驱逐"><a href="#调度,强占和驱逐" class="headerlink" title="调度,强占和驱逐"></a>调度,强占和驱逐</h2><p>调度:确保POD匹配到合适的节点,以便kubelet(Node上的运行的代理,保证容器运行在POD中)能够运行POD。<br>强占:终止低优先级的POD,以便运行高优先级的POD<br>驱逐:资源匮乏的节点上,主动让一个或多个POD失效</p><h3 id="调度器"><a href="#调度器" class="headerlink" title="调度器"></a>调度器</h3><p>通过Watch机制发现集群中新创建且未被调度到节点上的POD,将其调度到合适的节点上运行</p><p>可调度节点:满足一个POD调度请求的节点。没有节点满足时,POD将停留在未调度状态</p><p>绑定:可调度节点中打分,选出最高分的节点,通知kube-apiserver。</p><h3 id="特定节点上运行POD"><a href="#特定节点上运行POD" class="headerlink" title="特定节点上运行POD"></a>特定节点上运行POD</h3><ol><li>节点标签,nodeSelector</li><li>亲和性<ol><li>requiredDuringSchedulingIgnoredDuringExecution:规则满足时执行调度,是语法表达能力更强的nodeSelector</li><li>preferredDuringSchedulingIgnoredDuringExecution:找不到匹配节点时,仍会调度</li></ol></li><li>反亲和性<ol><li>requiredDuringSchedulingIgnoredDuringExecution:满足规则是不执行调度</li><li>preferredDuringSchedulingIgnoredDuringExecution:最好不调度到匹配的节点上</li></ol></li></ol><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: with-node-affinityspec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: # 必须有 nodeSelectorTerms: - matchExpressions: - key: topology.kubernetes.io/zone operator: In values: - antarctica-east1 - antarctica-west1 preferredDuringSchedulingIgnoredDuringExecution: # 最好有 - weight: 1 # 最好有的里面存在多项时,每项的权重,权重越高得分越高 preference: matchExpressions: - key: another-node-label-key operator: In values: - another-node-label-value</code></pre><p>topologyKey:拓扑域,hostname 节点级别,zone可用区级别,region区域级别。</p><pre><code class="yaml">apiVersion: v1kind: Podmetadata: name: with-pod-affinityspec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: # 不调度到这个主机上 - labelSelector: matchExpressions: - key: app # 符合标签描述的POD operator: In values: - my-app topologyKey: "kubernetes.io/hostname" # 符合标签描述的POD,其所属的节点(hostname域)会被反亲和,同节点的无法调度。 preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: security operator: In values: - S2 topologyKey: topology.kubernetes.io/zone</code></pre><h3 id="POD开销"><a href="#POD开销" class="headerlink" title="POD开销"></a>POD开销</h3><p>POD除了内部容器消耗请求的资源,POD本身也会消耗一些资源,调度时会将二者资源相加去寻找节点。</p><p><code>kind: RuntimeClass</code>进行配置</p><h3 id="POD调度就绪态"><a href="#POD调度就绪态" class="headerlink" title="POD调度就绪态"></a>POD调度就绪态</h3><p>通过yaml中的schedulingGates将就绪的POD放入等待列表,不是立即进行调度。删除后可以进行调度</p><h3 id="POD拓扑分布约束,内容很多,简单过了下"><a href="#POD拓扑分布约束,内容很多,简单过了下" class="headerlink" title="POD拓扑分布约束,内容很多,简单过了下"></a>POD拓扑分布约束,内容很多,简单过了下</h3><p>将POD分布到不同的拓扑中以提高可用性,或者使得客户端就近调度</p><h3 id="污点和容忍度"><a href="#污点和容忍度" class="headerlink" title="污点和容忍度"></a>污点和容忍度</h3><p>污点使得节点能够排斥一类特定的POD</p><h3 id="调度框架"><a href="#调度框架" class="headerlink" title="调度框架"></a>调度框架</h3><p>调度是串行的,绑定是并行。调度+绑定称为调度上下文</p><h4 id="调度框架扩展点"><a href="#调度框架扩展点" class="headerlink" title="调度框架扩展点"></a>调度框架扩展点</h4><p>EnqueueExtension:接口,插件实现此接口根据集群变化通过或者拒绝</p><p>QueueingHint:集群中发生变化时,此回调函数被执行,能控制POD放入活跃或回退队列。</p><ol><li>进入队列前:<ol><li>PreEnqueue:在POD被添加到内部活动队列之前调用,此队列中的POD被标记为<strong>准备好进行调度</strong>。</li></ol></li><li>队列排序:<ol><li>PreFilter:预处理POD或者检查相关条件</li><li>Filter:标记不符合此POD的节点</li><li>PostFilter:没有可用节点时调度</li><li>PreScore:生成一个可共享状态给Score插件使用</li><li>Score:评分</li><li>NormalizeScore:可以修改分数</li><li>Reserve:防止调度器等待绑定成功是发生竞争情况</li><li>Permit:批准,拒绝,等待POD的绑定</li></ol></li><li>绑定:<ol><li>PreBind:执行POD绑定前的工作</li><li>Bind:进行绑定</li><li>PostBind:收尾清理相关资源</li></ol></li></ol><h3 id="动态资源分配如GPU,跳过"><a href="#动态资源分配如GPU,跳过" class="headerlink" title="动态资源分配如GPU,跳过"></a>动态资源分配如GPU,跳过</h3><h3 id="调度器性能调优"><a href="#调度器性能调优" class="headerlink" title="调度器性能调优"></a>调度器性能调优</h3><p>通过配置减少调度流程中的操作项目,比如有一个可调度的Node就直接用,不用评估其他的Node</p><h3 id="资源装箱,控制节点资源权重进而控制分数"><a href="#资源装箱,控制节点资源权重进而控制分数" class="headerlink" title="资源装箱,控制节点资源权重进而控制分数"></a>资源装箱,控制节点资源权重进而控制分数</h3><h3 id="POD优先级和抢占,通过PriorityClass使高优先级POD驱逐低优先POD"><a href="#POD优先级和抢占,通过PriorityClass使高优先级POD驱逐低优先POD" class="headerlink" title="POD优先级和抢占,通过PriorityClass使高优先级POD驱逐低优先POD"></a>POD优先级和抢占,通过PriorityClass使高优先级POD驱逐低优先POD</h3><h3 id="节点压力驱逐"><a href="#节点压力驱逐" class="headerlink" title="节点压力驱逐"></a>节点压力驱逐</h3><p>根据节点压力驱逐</p><h3 id="API发起的驱逐"><a href="#API发起的驱逐" class="headerlink" title="API发起的驱逐"></a>API发起的驱逐</h3><p>API 发起的驱逐是一个先调用 Eviction API 创建 Eviction 对象,再由该对象体面地中止 Pod 的过程。</p><h2 id="集群管理"><a href="#集群管理" class="headerlink" title="集群管理"></a>集群管理</h2><ol><li>自己机器上尝试K8S还是构建高可用节点,或者是参与开发</li><li>使用开箱即用的集群还是管理自己的</li><li>本地还是云上</li><li>本地配置时要选取合适的网络模型</li><li>裸机还是虚拟机运行</li><li>熟悉运行所需的相关组件</li></ol><h3 id="节点关闭"><a href="#节点关闭" class="headerlink" title="节点关闭"></a>节点关闭</h3><p>基于优先级的体面关闭,非体面关闭</p><h3 id="Node自动缩容和扩容"><a href="#Node自动缩容和扩容" class="headerlink" title="Node自动缩容和扩容"></a>Node自动缩容和扩容</h3><p>Node不足时创建新的Node,足够时关闭Node。配合负载自动缩容和扩容使用</p><h3 id="集群网络系统"><a href="#集群网络系统" class="headerlink" title="集群网络系统"></a>集群网络系统</h3><ol><li>高度耦合容器间通信:POD和localhost解决</li><li>POD间通信</li><li>POD与Service通信</li><li>外部和Service通信</li></ol><h3 id="日志架构"><a href="#日志架构" class="headerlink" title="日志架构"></a>日志架构</h3><p>K8S支持将标准输入和输出定向到文件,也可以用过kubectl logs查看</p>]]></content>
<summary type="html">
<h1 id="入门操作"><a href="#入门操作" class="headerlink" title="入门操作"></a>入门操作</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h
</summary>
<category term="云" scheme="http://blog.lsmg.xyz/categories/%E4%BA%91/"/>
</entry>
<entry>
<title>HELM基础概念</title>
<link href="http://blog.lsmg.xyz/2025/11/%E4%BA%91-HELM/"/>
<id>http://blog.lsmg.xyz/2025/11/%E4%BA%91-HELM/</id>
<published>2025-11-01T10:38:08.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="Helm"><a href="#Helm" class="headerlink" title="Helm"></a>Helm</h1><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h2><p>包管理工具,定义一个k8s应用</p><ol><li>简化部署:通过Helm,你可以使用预定义的配置文件来部署应用,而不需要手动编写和管理大量的Kubernetes YAML文件。</li><li>版本控制:Helm支持版本控制,你可以轻松地回滚到以前的版本。</li><li>依赖管理:Helm可以管理应用的依赖关系,确保所有依赖的服务都正确部署。</li><li>模板化:Helm使用Go模板引擎,可以根据不同的环境和需求生成动态的Kubernetes配置文件。</li></ol><p>功能</p><ol><li>chart创建,chart中含有K8S的全部模板信息</li><li>config,包含了配置信息,用于和模板结合后生成最终K8S配置</li><li>release,chart和config结合后的运行实例</li><li>打包chart为tgz</li><li>与存储chart的仓库交互</li><li>安装和卸载chart</li><li>管理helm安装的chart的生命周期</li></ol><h2 id="Chart"><a href="#Chart" class="headerlink" title="Chart"></a>Chart</h2><p>软件包</p><ol><li>Chart.yaml:元数据文件,包含名称、版本和描述等信息。</li><li>values.yaml:配置文件,用来控制部署。</li><li>templates/:K8S资源模板文件,可以在配置项中引用values.yaml中的Key。<ol><li>deployment.yaml:定义Deployment资源的模板</li><li>service.yaml:定义Service资源的模板</li></ol></li></ol><p>Chart:类似yum的rpm包</p><p>Repository:存放Chart的地方,包含一个或多个打包的chart,存在index.yaml的文件,包含完整的包列表,用于检索和验证元数据。helm可以使用repo添加仓库,不过helm不提供上传chart到仓库的功能。</p><p>Release:运行的chart的示例,每次install chart都会产生一个Release</p><pre><code class="shell">helm show valueshelm repo add bitnami https://charts.bitnami.com/bitnamihelm search repo bitnamihelm repo update# 每次执行都会创建一个新的发布版本,一个chart可以被多次安装,独立管理# helm生成名称helm install bitnami/mysql --generate-name# 指定名称helm install mysql_test bitnami/mysql# 查看发布的版本helm listNAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSIONmysql-1612624192 default 1 2021-02-06 16:09:56.283059 +0100 CET deployed mysql-8.3.0 8.0.23helm status mysql-1612624192helm get values mysql-1612624192helm upgrade -f x.yaml mysql-1612624192 bitnami/mysqlhelm history mysql-1612624192helm rollback mysql-1612624192 1# 会删除所有相关资源,service,Deployment,pod,版本历史helm uninstall mysql-1612624192</code></pre><p>使用helm管理chart</p><pre><code class="shell">helm create chart-ahelm package chart-ax.tar.gzhelm install chart-a ./x.tar.gz</code></pre><h2 id="Chart-1"><a href="#Chart-1" class="headerlink" title="Chart"></a>Chart</h2><h3 id="Chart文件结构"><a href="#Chart文件结构" class="headerlink" title="Chart文件结构"></a>Chart文件结构</h3><pre><code class="shell">wordpress/ Chart.yaml # 包含了chart信息的YAML文件 LICENSE # 可选: 包含chart许可证的纯文本文件 README.md # 可选: 可读的README文件 values.yaml # chart 默认的配置值 values.schema.json # 可选: 一个使用JSON结构的values.yaml文件 charts/ # 包含chart依赖的其他chart,dependencies中定义的 crds/ # 自定义资源的定义 templates/ # 模板目录, 当和values 结合时,可生成有效的Kubernetes manifest文件 templates/NOTES.txt # 可选: 包含简要使用说明的纯文本文件</code></pre><pre><code class="yaml"># Chart.yamlapiVersion: chart API 版本 (必需)name: chart名称 (必需)version: 语义化2 版本(必需)kubeVersion: 兼容Kubernetes版本的语义化版本(可选)description: 一句话对这个项目的描述(可选)type: chart类型 (可选)keywords: - 关于项目的一组关键字(可选)home: 项目home页面的URL (可选)sources: - 项目源码的URL列表(可选)dependencies: # chart 必要条件列表 (可选) helm dep up foochart将依赖的文件下载到charts目录 - name: chart名称 (nginx) version: chart版本 ("1.2.3") repository: (可选)仓库URL ("https://example.com/charts") 或别名 ("@repo-name") condition: (可选) 解析为布尔值的yaml路径,用于启用/禁用chart (e.g. subchart1.enabled ) tags: # (可选) - 用于一次启用/禁用 一组chart的tag import-values: # (可选) - ImportValue 保存源值到导入父键的映射。每项可以是字符串或者一对子/父列表项 alias: (可选) chart中使用的别名。当你要多次添加相同的chart时会很有用maintainers: # (可选) - name: 维护者名字 (每个维护者都需要) email: 维护者邮箱 (每个维护者可选) url: 维护者URL (每个维护者可选)icon: 用做icon的SVG或PNG图片URL (可选)appVersion: 包含的应用版本(可选)。不需要是语义化,建议使用引号deprecated: 不被推荐的chart (可选,布尔值)annotations: example: 按名称输入的批注列表 (可选).</code></pre><h2 id="Chart含dependencies的结构"><a href="#Chart含dependencies的结构" class="headerlink" title="Chart含dependencies的结构"></a>Chart含dependencies的结构</h2><pre><code class="yaml">hlsjsvr/├── Chart.yaml├── values.yaml├── charts/│ └── hlsvr-base/│ ├── Chart.yaml│ ├── values.yaml│ └── templates/│ └── ...(实际的K8s资源模板)# Chart.yaml 定义了dependenciesdependencies:- name: hlsvr-base repository: http://helm.bkrepo.oa.com/public-cluster/public-cluster/ version: 1.30.5# values.yamlhlsvr-base: # 这里的只会传递给dependencies的hlsvr-base中 replicaCount: 2 image: tag: "1.2.3"</code></pre><p>渲染时如果指定了外部values,会先覆盖hlsjsvr/values.yaml。发现charts中含有依赖下载依赖到charts目录,使用覆盖后的values.yaml再覆盖hlsvr-base的values.yaml,最终的values去渲染templates的内容</p><h3 id="模板文件和values文件和values-schema-json"><a href="#模板文件和values文件和values-schema-json" class="headerlink" title="模板文件和values文件和values.schema.json"></a>模板文件和values文件和values.schema.json</h3><p>模板文件位于chart的templates目录中,helm渲染chart时,会遍历其中的每个文件</p><pre><code class="yaml">apiVersion: v1kind: ReplicationControllermetadata: name: deis-database namespace: deis labels: app.kubernetes.io/managed-by: deisspec: replicas: 1 selector: app.kubernetes.io/name: deis-database template: metadata: labels: app.kubernetes.io/name: deis-database spec: serviceAccount: deis-database containers: - name: deis-database image: {{ .Values.imageRegistry }}/postgres:{{ .Values.dockerTag }} imagePullPolicy: {{ .Values.pullPolicy }} ports: - containerPort: 5432 env: - name: DATABASE_STORAGE value: {{ default "minio" .Values.storage }}</code></pre><p>values文件可以为chart的依赖项目提供基础值。values.schema.json中可以定义Values文件字段的格式和规范</p><pre><code class="yaml">title: "My WordPress Site" # Sent to the WordPress template# 相当于在每个chart中增加这个基础值。mysql的模板中可以使用{{.Values.global.app}}访问此内容global: app: MyWordPressmysql: # charts中的MySQL可以访问这两项内容,无法访问title和Apache的内容 max_connections: 100 # Sent to MySQL password: "secret"apache: port: 8080 # Passed to Apache</code></pre><h3 id="crd"><a href="#crd" class="headerlink" title="crd"></a>crd</h3><p>可以声明自定义资源类型,位于crds目录中。无法使用模板,只能是普通的yaml文档。</p><p>创建出一个类似pod的资源类型,不过实际作用还是不太清楚。</p><pre><code class="txt">crontabs/ Chart.yaml crds/ crontab.yaml templates/ mycrontab.yaml</code></pre><pre><code class="yaml"># crontab.yamlkind: CustomResourceDefinitionmetadata: name: crontabs.stable.example.comspec: group: stable.example.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: crontabs singular: crontab kind: CronTab</code></pre><pre><code class="yaml">apiVersion: stable.example.comkind: CronTabmetadata: name: {{ .Values.name }}spec:</code></pre><p>crd的安装是全局的,会受到以下的限制</p><ol><li>不会重现安装,helm确认crds目录已经存在的时候(忽略版本),helm不会安装或升级。</li><li>不会在升级或回滚中安装,只会在安装时创建crd</li><li>不会被删除,自动删除crd会删除集群中所有命名空间中所有的crd内容,所以helm不会删除crd</li></ol><h2 id="chart-Hook"><a href="#chart-Hook" class="headerlink" title="chart Hook"></a>chart Hook</h2><p>Helm提供了hook机制允许chart开发者在发布生命周期的某些点干预。</p><ol><li>安装时在加载其他chart之前加载配置映射或者秘钥。</li><li>安装新chart之前执行备份数据库的任务,升级之后执行第二个任务用于存储数据</li><li>删除发布之前执行一个任务以便在删除服务之前退出滚动。</li></ol><p>pre-install 在模板渲染之后,Kubernetes资源创建之前执行<br>post-install 在所有资源加载到Kubernetes之后执行<br>pre-delete 在Kubernetes删除之前,执行删除请求<br>post-delete 在所有的版本资源删除之后执行删除请求<br>pre-upgrade 在模板渲染之后,资源更新之前执行一个升级请求<br>post-upgrade 所有资源升级之后执行一个升级请求<br>pre-rollback 在模板渲染之后,资源回滚之前,执行一个回滚请求<br>post-rollback 在所有资源被修改之后执行一个回滚请求<br>test 调用Helm test子命令时执行 ( test文档)</p><ol><li>helm install foo</li><li>helm库调用安装API</li><li>安装cards目录中的cad</li><li>验证后渲染foo模板</li><li>准备执行pre-install</li><li>按权重对钩子进行排序,资源种类排序,名称正序排列</li><li>加载最小权重的钩子(可以使用负数)</li><li>等到钩子READY状态,资源是JOB或者POD类型时,Helm会等到其运行完成。</li><li>加载资源到K8S中,设置–wait时会等到所有资源ready,且所有资源准备就绪后才会继续</li><li>执行post-install钩子</li><li>等到钩子ready状态</li><li>反馈发布对象到客户端</li><li>客户端退出</li></ol><h3 id="钩子"><a href="#钩子" class="headerlink" title="钩子"></a>钩子</h3><pre><code class="yaml">apiVersion: batch/v1kind: Jobmetadata: name: "{{ .Release.Name }}" labels: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" annotations: # 在这里定义模板是钩子,否则是一个普通资源 "helm.sh/hook": post-install "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeeded # before-hook-creation:新钩子启动前删除之前的资源 (默认) hook-succeeded:钩子成功执行之后删除资源 hook-failed:如果钩子执行失败,删除资源spec: template: metadata: name: "{{ .Release.Name }}" labels: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" spec: restartPolicy: Never containers: - name: post-install-job image: "alpine:3.3" command: ["/bin/sleep","{{ default "10" .Values.sleepyTime }}"]</code></pre><h2 id="chart-test"><a href="#chart-test" class="headerlink" title="chart test"></a>chart test</h2><p>验证chart安装,帮助用户理解chart的功能</p><pre><code class="shell">helm create demo.├── charts├── Chart.yaml├── templates│ ├── deployment.yaml│ ├── _helpers.tpl│ ├── hpa.yaml│ ├── ingress.yaml│ ├── NOTES.txt│ ├── serviceaccount.yaml│ ├── service.yaml│ └── tests│ └── test-connection.yaml└── values.yaml</code></pre><p>include的内容在_helper.tpl中定义</p><pre><code class="yaml">{{- define "demo.fullname" -}}{{- if .Values.fullnameOverride }}{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}{{- else }}{{- $name := default .Chart.Name .Values.nameOverride }}{{- if contains $name .Release.Name }}{{- .Release.Name | trunc 63 | trimSuffix "-" }}{{- else }}{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}{{- end }}{{- end }}{{- end }}</code></pre><pre><code class="yaml"># test-connection.yamlapiVersion: v1kind: Podmetadata: name: "{{ include "demo.fullname" . }}-test-connection" labels: {{- include "demo.labels" . | nindent 4 }} annotations: "helm.sh/hook": testspec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "demo.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never</code></pre><pre><code class="shell">$ sudo helm install demo demoNAME: demoLAST DEPLOYED: Mon May 12 16:34:29 2025NAMESPACE: defaultSTATUS: deployedREVISION: 1NOTES:$ sudo helm test demoNAME: demoLAST DEPLOYED: Mon May 12 16:34:29 2025NAMESPACE: defaultSTATUS: deployedREVISION: 1TEST SUITE: demo-test-connectionLast Started: Mon May 12 16:34:49 2025Last Completed: Mon May 12 16:34:55 2025Phase: SucceededNOTES:</code></pre><p>部署时会使用values和tpl来渲染test-connection.yaml。values和tpl只是提供一些内容,实际起作用的是yaml文件</p><h2 id="库类型chart"><a href="#库类型chart" class="headerlink" title="库类型chart"></a>库类型chart</h2><p>可以定义一些tpl模板放到库中,其他chart可以引用这些库中预先定义的内容。</p><p>模板库的Chart.yaml中type为library</p><p>引用时在yaml文件中使用include,同时在chart.yaml中添加dependencies</p><h2 id="helm来源和完整性"><a href="#helm来源和完整性" class="headerlink" title="helm来源和完整性"></a>helm来源和完整性</h2><p>helm package –sign –key进行签名,helm verify进行验证</p><h2 id="chart仓库,OCI"><a href="#chart仓库,OCI" class="headerlink" title="chart仓库,OCI"></a>chart仓库,OCI</h2><p><code>https://example.com/charts/index.yaml</code> 其中包含chart的信息和下载地址</p><h2 id="helm渲染模板和values的合成覆盖流程"><a href="#helm渲染模板和values的合成覆盖流程" class="headerlink" title="helm渲染模板和values的合成覆盖流程"></a>helm渲染模板和values的合成覆盖流程</h2><ol><li>chart包中的values.yaml是默认值</li><li>使用-f(–values)指定外部的values文件,会覆盖默认值中的同名配置</li><li>–set,–set-file会再覆盖前面的同名配置</li><li>合成的最终values用来渲染templates目录中的yaml文件</li></ol><h2 id="基于角色的访问控制"><a href="#基于角色的访问控制" class="headerlink" title="基于角色的访问控制"></a>基于角色的访问控制</h2><h2 id="Helm插件"><a href="#Helm插件" class="headerlink" title="Helm插件"></a>Helm插件</h2><p>Helm可以设置一个插件目录,插件放于目录中,运行helm的插件时会自动在其中寻找。</p><h3 id="惯例"><a href="#惯例" class="headerlink" title="惯例"></a>惯例</h3><ol><li>chart名称是小写字母和数字,单词之间使用<code>-</code>分割,如nginx-hello</li><li>yaml使用双空格缩进而不是tab</li><li>values<ol><li>变量使用小写字母开头,单词驼峰区分。<code>helloWorld: true</code></li><li>Helm内置变量使用大写字母开头</li><li>foo: false和foo: “false”不同。规避类型转换,最好统一使用字符串,即打引号。</li></ol></li><li>模板</li></ol><h1 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h1><h2 id="日志查询"><a href="#日志查询" class="headerlink" title="日志查询"></a>日志查询</h2><pre><code class="shell">kubectl logs riichipersonalpanel-0 -n riichi -c istio-proxy</code></pre><h2 id="文件同步"><a href="#文件同步" class="headerlink" title="文件同步"></a>文件同步</h2>]]></content>
<summary type="html">
<h1 id="Helm"><a href="#Helm" class="headerlink" title="Helm"></a>Helm</h1><h2 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h
</summary>
<category term="云" scheme="http://blog.lsmg.xyz/categories/%E4%BA%91/"/>
</entry>
<entry>
<title>BT内网穿透</title>
<link href="http://blog.lsmg.xyz/2025/09/NAS-bt/"/>
<id>http://blog.lsmg.xyz/2025/09/NAS-bt/</id>
<published>2025-09-23T19:48:22.000Z</published>
<updated>2025-11-29T09:26:42.705Z</updated>
<content type="html"><![CDATA[<h1 id="总"><a href="#总" class="headerlink" title="总"></a>总</h1><p>Lucky配置STUN内网穿透</p><p>STUN穿透得到外网端口A,外网端口A转发到机器的40000端口</p><p>iptables将40000端口转发到内网端口A</p><p>BT监听内网端口A</p><p><img src="https://blog-1300231177.cos.ap-guangzhou.myqcloud.com/img/20250923182442.png" alt="20250923182442"><span class="img-alt">20250923182442</span></p><pre><code class="sh">#!/bin/bash# 用法: ./port_forward.sh <目标端口>if [ $# -ne 1 ]; then echo "Usage: $0 <target_port>" exit 1fiTARGET_PORT=$1SOURCE_PORT=40000public_port=$1# qBittorrent.qb_username=""qb_password=""qb_addr=""# Update qBittorrent listen port.qb_cookie=$(curl -s -i --header "Referer: http://$qb_addr" --data "username=$qb_username&password=$qb_password" http://$qb_addr/api/v2/auth/login | grep -i set-cookie | cut -c13-48)curl -X POST -b "$qb_cookie" -d 'json={"listen_port":"'$public_port'"}' "http://$qb_addr/api/v2/app/setPreferences"# 清除已有的 40000 转发规则(如果存在)EXISTING_RULE=$(sudo iptables -t nat -S PREROUTING | grep "dport $SOURCE_PORT" | grep "DNAT")if [ ! -z "$EXISTING_RULE" ]; then # 删除规则 sudo iptables -t nat -D PREROUTING $(echo "$EXISTING_RULE" | sed 's/^-A //')fi# 添加新的转发规则sudo iptables -t nat -A PREROUTING -p tcp --dport $SOURCE_PORT -j REDIRECT --to-port $TARGET_PORTecho "Port forwarding set: $SOURCE_PORT -> $TARGET_PORT"</code></pre><h1 id="所求所有用户执行-vol1-1000-py-proxy-sh时都不需要输入sudo密码,但是自动是管理权执行"><a href="#所求所有用户执行-vol1-1000-py-proxy-sh时都不需要输入sudo密码,但是自动是管理权执行" class="headerlink" title="所求所有用户执行/vol1/1000/py/proxy.sh时都不需要输入sudo密码,但是自动是管理权执行"></a>所求所有用户执行/vol1/1000/py/proxy.sh时都不需要输入sudo密码,但是自动是管理权执行</h1><p>sudo visudo</p><p>ALL ALL=(ALL) NOPASSWD: /vol1/1000/py/proxy.sh</p>]]></content>
<summary type="html">
<h1 id="总"><a href="#总" class="headerlink" title="总"></a>总</h1><p>Lucky配置STUN内网穿透</p>
<p>STUN穿透得到外网端口A,外网端口A转发到机器的40000端口</p>
<p>iptables将40
</summary>
<category term="NAS" scheme="http://blog.lsmg.xyz/categories/NAS/"/>
<category term="NAS" scheme="http://blog.lsmg.xyz/tags/NAS/"/>
</entry>
<entry>
<title>Windows下Unity内存泄露排查</title>
<link href="http://blog.lsmg.xyz/2024/11/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-Windows%E4%B8%8BUnity%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E6%8E%92%E6%9F%A5/"/>
<id>http://blog.lsmg.xyz/2024/11/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-Windows%E4%B8%8BUnity%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E6%8E%92%E6%9F%A5/</id>
<published>2024-11-14T19:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>项目使用Unity开发,使用过程中可以明显内存有异常增长,所以排查了一下原因,做一下记录。</p><p>分三步描述</p><ol><li>寻找异常内存分配时的地址</li><li>寻找异常内存分配的堆栈</li><li>寻找对应的项目代码</li></ol><h1 id="寻找异常内存分配时的地址"><a href="#寻找异常内存分配时的地址" class="headerlink" title="寻找异常内存分配时的地址"></a>寻找异常内存分配时的地址</h1><h2 id="后台用的工具"><a href="#后台用的工具" class="headerlink" title="后台用的工具"></a>后台用的工具</h2><p>使用massif可以详细打印内存分配时的堆栈信息,但是没有Windows版本,询问GPT后得到了下面几个工具。</p><ol><li>Unity自带的Profile</li><li>vs2022</li><li>vmmap64</li></ol><h2 id="工具一,Unity自带的Profile"><a href="#工具一,Unity自带的Profile" class="headerlink" title="工具一,Unity自带的Profile"></a>工具一,Unity自带的Profile</h2><p>拍摄两个快照,发现内存异常增长的部分被划分到了Unknown,也不会显示堆栈所以需要换工具。</p><h2 id="工具二,vs2022"><a href="#工具二,vs2022" class="headerlink" title="工具二,vs2022"></a>工具二,vs2022</h2><p>Attach到Unity之后使用<strong>堆快照</strong>无法发现明显的内存增长,换工具。</p><h2 id="工具三vmmap64"><a href="#工具三vmmap64" class="headerlink" title="工具三vmmap64"></a>工具三vmmap64</h2><p>附加到Unity进程之后会自动拍摄快照,之后隔一段时间手动F5刷新一下。之后点击Timeline,选择对应的时间范围,可以看到这期间的内存新增申请。可以明显看到不是Heap在增长,而是Private Data在增长, 且都是相同的524284K,符合内存泄露的特征。</p><p>所以下一步需要找到不断申请的524284K的Private Data是谁申请的。</p><p>观察一下分配的虚拟内存是524284K,但这并不是实际占用的物理内存,最终占用的物理内存是258000K左右(另一部分保留了)。可以理解为这一块内存是逐渐从0K占用逐渐增长到了258000K。</p><p>每次F5刷新时,可以看到新的524284K大小的虚拟内存申请和对应的起始地址如0xA0000,之后Unity会顺序写入直到0xA0000+258000K。</p><p>了解到这一点之后,可以使用硬件断点设置写内存断点,当向指定地址写入时触发断点,进而查看堆栈。</p><p>所以需要一个内存地址,从上文可以看到内存是逐渐从0xA0000到0xA0000+258000K,所以拍摄快照时,如果内存使用到了0xA0000+1000K,则其必定会访问到0xA0000+10000K,所以对0xA0000+10000K下断点等待Unity的访问。</p><h1 id="寻找异常内存分配的堆栈"><a href="#寻找异常内存分配的堆栈" class="headerlink" title="寻找异常内存分配的堆栈"></a>寻找异常内存分配的堆栈</h1><h2 id="Windbg"><a href="#Windbg" class="headerlink" title="Windbg"></a>Windbg</h2><p>Attach到Unity进程,之后恢复Unity运行,返回vmmap64拍摄快照之后,立刻返回Windbg将Unity暂停。</p><p>回到vmmap64,查看最新的524284K的起始地址和已经使用大小,计算一个将来会访问的地址0xA123456。</p><p>回到windbg使用ba w 4 0xA123456 设置硬件断点,之后恢复Unity的运行,等到Unity访问到524284K内存块中的0xA123456时就会触发断点。此时就获得了向这块异常内存块写入数据时的堆栈。</p><h1 id="寻找对应的项目代码"><a href="#寻找对应的项目代码" class="headerlink" title="寻找对应的项目代码"></a>寻找对应的项目代码</h1><h2 id="查看Unity源码"><a href="#查看Unity源码" class="headerlink" title="查看Unity源码"></a>查看Unity源码</h2><p>找到对应的相关代码,是有开关的功能,查找开关的开启和关闭的接口。回到项目中找使用接口的代码,找到问题代码。</p><p>最终是因为开始Profile后没有关闭,导致Unity的Profile一直运行,内存一直增长。</p><h1 id="存在问题"><a href="#存在问题" class="headerlink" title="存在问题"></a>存在问题</h1><p>vmmap64中显示这块内存分配到了PrivateData, vs2022的堆快照无法发现问题, 暂不清楚Unity是如何使用内存的。</p>]]></content>
<summary type="html">
<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>项目使用Unity开发,使用过程中可以明显内存有异常增长,所以排查了一下原因,做一下记录。</p>
<p>分三步描述</p>
<ol>
<l
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
</entry>
<entry>
<title>CMake项目常见的编译加速方式汇总</title>
<link href="http://blog.lsmg.xyz/2024/11/%E4%BC%98%E5%8C%96-%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F2/"/>
<id>http://blog.lsmg.xyz/2024/11/%E4%BC%98%E5%8C%96-%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F2/</id>
<published>2024-11-13T14:48:22.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>本文主要介绍CMake项目,在不改动或较小改动项目代码的情况下,如何减少编译时间。同时简要介绍其原理和使用场景。包含ccache,编译集群,CMake依赖优化,避免动态编译宏和UnityBuild。</p><h1 id="加快编译速度"><a href="#加快编译速度" class="headerlink" title="加快编译速度"></a>加快编译速度</h1><p>分通解,增量编译和全量编译三个方面来介绍。通解开启简单方便,增量编译减小项目重编范围。全量编译减少编译数量。</p><h2 id="通解"><a href="#通解" class="headerlink" title="通解"></a>通解</h2><h3 id="使用ccache"><a href="#使用ccache" class="headerlink" title="使用ccache"></a>使用ccache</h3><h4 id="原理及使用场景"><a href="#原理及使用场景" class="headerlink" title="原理及使用场景"></a>原理及使用场景</h4><p>ccache会缓存编译结果,在编译文件和编译宏未发生变化时,直接返回缓存的编译结果。</p><p>使用场景:在文件内容未变化时加速效果近乎100%,开启简单方便。</p><p>单机器,搭配make使用时,效果有限。</p><p>2024-12-02更新:了解到存在远程ccache。AB机器相同代码的情况下,如果A机器编译过一次传到远端,B机器编译时make判定全量编译,ccache会从远端下载A机器的编译结果。这时效果就很明显。</p><h4 id="开启方式"><a href="#开启方式" class="headerlink" title="开启方式"></a>开启方式</h4><p>使用<code>yum install ccache</code>安装</p><p>在CMake中添加如下内容开启ccache</p><pre><code class="CMake">find_program(CCACHE_PROGRAM ccache)if(CCACHE_PROGRAM) message(STATUS "Found ccache: ${CCACHE_PROGRAM}") set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})else() message(WARNING "ccache not found, proceeding without ccache")endif()</code></pre><p><strong>验证方式</strong></p><p><code>ccache -s</code>直接查看命中缓存次数。</p><pre><code class="sh">$ ccache -s cache directory /root/.ccachecache hit (direct) 467cache hit (preprocessed) 2cache miss 1009files in cache 1701 这里是缓存命中的次数cache size 656.0 Mbytesmax cache size 1.0 Gbytes</code></pre><h3 id="使用编译集群"><a href="#使用编译集群" class="headerlink" title="使用编译集群"></a>使用编译集群</h3><h4 id="原理及使用场景-1"><a href="#原理及使用场景-1" class="headerlink" title="原理及使用场景"></a>原理及使用场景</h4><p>cpp项目编译时,每个源码文件都是一个编译单元,所以可以使用多线程并行编译。编译集群简单粗暴增加编译的并行数量。</p><p>使用场景:大规模编译时效果明显,收费。</p><h4 id="开启方式-1"><a href="#开启方式-1" class="headerlink" title="开启方式"></a>开启方式</h4><p>公司内部<a href="https://devops.woa.com/console/turbo/card-matrix/task/init" target="_blank" rel="noopener">devops</a>中就有编译加速,接入很简单。</p><h2 id="增量编译"><a href="#增量编译" class="headerlink" title="增量编译"></a>增量编译</h2><p>介绍如何减少不必要的编译项目。</p><h3 id="如何发现不必要的编译项目?"><a href="#如何发现不必要的编译项目?" class="headerlink" title="如何发现不必要的编译项目?"></a>如何发现不必要的编译项目?</h3><p>CMake生成后,使用Make编译时,会打印一些日志,如下所示。</p><pre><code class="txt">$ make MyExecutable Scanning dependencies of target MyExecutable[ 33%] Building CXX object CMakeFiles/MyExecutable.dir/src/main.cpp.o[ 66%] Building CXX object CMakeFiles/MyExecutable.dir/src/Foo.cpp.o[100%] Linking CXX executable MyExecutable[100%] Built target MyExecutable</code></pre><p>从日志可以看到在编译MyExecutable时, Make将main.cpp.o和Foo.cpp.o重新编译,说明这两个文件所依赖的文件发生了变化。</p><p>将在<strong>依赖优化原理</strong>处介绍如何查看其依赖内容。这里只需要知道通过日志,可以观察本次编译的内容是否符合预期,如果发现不该编译的文件进行了编译,则可以查看其依赖内容,判断是否可以进行优化。</p><h3 id="CMake依赖优化"><a href="#CMake依赖优化" class="headerlink" title="CMake依赖优化"></a>CMake依赖优化</h3><h4 id="依赖优化原理"><a href="#依赖优化原理" class="headerlink" title="依赖优化原理"></a>依赖优化原理</h4><p>CMake生成之后会生成如下所示的depend.make文件</p><pre><code class="txt"># depend.makeCMakeFiles/MyExecutable.dir/src/Foo.cpp.o: ../src/Foo.cppCMakeFiles/MyExecutable.dir/src/Foo.cpp.o: src/version.hCMakeFiles/MyExecutable.dir/src/main.cpp.o: ../src/Foo.hCMakeFiles/MyExecutable.dir/src/main.cpp.o: ../src/main.cpp</code></pre><p>depend.make文件中描述了文件间的依赖情况。Foo.cpp.o依赖Foo.cpp和version.h。当version.h的<strong>修改时间戳</strong>变化时,会导致Foo.cpp.o重新生成。</p><p>如不想让冒号左侧的文件重新生成,则需要保证冒号右侧文件的<strong>修改时间戳</strong>不发生变化。如果内容无变化,但是<strong>修改时间戳</strong>发生变化,同样会导致左侧文件重新生成。</p><p>项目中存在的协议文件,需要转换后生成代码文件才能在代码中使用。如果转换时使用批量处理,会导致协议文件未变化,但代码文件发生了变化。代码文件发生变化后,所有依赖此代码文件的目标都会重新生成。</p><p>正确的做法应该是在协议文件发生变化时,再进行代码文件生成。手动识别较繁琐,正好CMake提供了此功能。</p><h4 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h4><p>编译时存在动态生成文件时</p><p>弊:需要理清生成文件的依赖关系</p><h4 id="开启方式-2"><a href="#开启方式-2" class="headerlink" title="开启方式"></a>开启方式</h4><pre><code class="CMake">set(XML_SRCS)file(GLOB CFG_FILES "*.xml")set(GameResFile GameRes.h)# 主要命令,定义了产出文件GameResFile,生成脚本ConvertGameRes.sh,生成GameResFile所需的文件CFG_FILESadd_custom_command(OUTPUT ${GameResFile} COMMAND bash ConvertGameRes.sh DEPENDS ${CFG_FILES} WORKING_DIRECTORY ${RES}/script/one)# 将所有动态生成文件添加到一个目标如XmlHeaders中list(APPEND XML_SRCS ${GameResFile})add_custom_target(XmlHeaders ALL DEPENDS ${XML_SRCS})# 需要使用GameRes.h的服务添加XmlHeaders为依赖add_dependencies(AServer XmlHeaders)</code></pre><p>编译AServer时会检查其依赖XmlHeaders是否有变化,XmlHeaders检查XML_SRCS即GameResFile是否需要重新生成。</p><p>当GameResFile的依赖文件CFG_FILES发生变化或产物GameResFile不存在时,调用ConvertGameRes.sh进行生成。</p><h3 id="避免使用频繁变化的编译宏"><a href="#避免使用频繁变化的编译宏" class="headerlink" title="避免使用频繁变化的编译宏"></a>避免使用频繁变化的编译宏</h3><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><p>在CMake中可以使用add_definitions添加编译宏,之后在代码文件中即可使用此宏获取对应的内容。如下所示打印编译时间。</p><pre><code class="txt"># 定义BUILD_TIME宏add_definitions(-DBUILD_TIME=\"${BUILD_TIME}\")# 使用宏std::cout << "Current machine IP address: " << BUILD_TIME << std::endl;</code></pre><p>编译宏会存储到如下CMake生成的flags.make中</p><pre><code class="make"># CMAKE generated file: DO NOT EDIT!# Generated by "Unix Makefiles" Generator, CMake Version 3.17# compile CXX with /usr/bin/c++CXX_DEFINES = -DBUILD_TIME=123</code></pre><p>每当编译宏发生变化时flags.make的修改时间也会发生变化。在build目录中搜索依赖此文件的内容,如下所示。</p><pre><code class="txt">$ grep -R "flags.make" ./* ./build.make:include CMakeFiles/MyExecutable.dir/flags.make./build.make:CMakeFiles/MyExecutable.dir/src/main.cpp.o: CMakeFiles/MyExecutable.dir/flags.make./build.make:CMakeFiles/MyExecutable.dir/src/Foo.cpp.o: CMakeFiles/MyExecutable.dir/flags.make</code></pre><p>可以看到main.cpp.o和Foo.cpp.o都依赖了flags.make。</p><p>所以每当cmake重新生成,宏内容发生变化时,flags.make也会发生变化,导致依赖flags.make的文件重新生成。</p><h4 id="使用场景-1"><a href="#使用场景-1" class="headerlink" title="使用场景"></a>使用场景</h4><p>项目存在变化的编译宏时使用。</p><h4 id="开启方式-3"><a href="#开启方式-3" class="headerlink" title="开启方式"></a>开启方式</h4><pre><code class="txt">// 文件CompileVersion.cpp.in#define _BUILDDATE "@BUILDDATE@"#define _BUILDIP "@BUILDIP@"#include "CompileVersion.h"std::string GetCompileVersionStr(){ std::string strVersion; strVersion += "\033[40;32mSo Build Date \033[0m: "_BUILDDATE"\n"; strVersion += "\033[40;32mSo Build Host \033[0m: "_BUILDIP"\n"; return strVersion;}// 文件 CompileVersion.h#pragma once#include <string>std::string GetCompileVersionStr();// 文件 CMakeconfigure_file(CompileVersion.cpp.in CompileVersion.cpp)</code></pre><p>configure_file会将模板CompileVersion.cpp.in中@BUILDDATE@替换成CMake中BUILDDATE变量。同时将产出放在cpp中,对外提供h文件。这样当版本信息发生变化时仅会编译CompileVersion.cpp</p><h2 id="全量编译"><a href="#全量编译" class="headerlink" title="全量编译"></a>全量编译</h2><h3 id="使用UnityBuild"><a href="#使用UnityBuild" class="headerlink" title="使用UnityBuild"></a>使用UnityBuild</h3><h4 id="原理及使用场景-2"><a href="#原理及使用场景-2" class="headerlink" title="原理及使用场景"></a>原理及使用场景</h4><p>默认的编译方式会逐个处理每个编译文件,如果A.cpp和B.cpp都include了common.h,编译A.cpp和B.cpp时common.h会被处理两次。将A.cpp和B.cpp合并为一个编译单元后,common.h仅会被处理一次。</p><p>弊:命名空间污染,重新编译范围扩大,同时手动组织比自动组织效果更好。</p><h4 id="自动组织开启方式-cmake3-16添加"><a href="#自动组织开启方式-cmake3-16添加" class="headerlink" title="自动组织开启方式 cmake3.16添加"></a>自动组织开启方式 cmake3.16添加</h4><pre><code class="CMake"># CMake中打开UnityBuildSET(CMAKE_UNITY_BUILD OFF)# 设置成一个文件所需的数量set(CMAKE_UNITY_BUILD_BATCH_SIZE 16)# 关闭指定文件的UnityBuild,使其单独编译set_source_files_properties(A.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)</code></pre><h4 id="手动组织开启方式-cmake3-18添加"><a href="#手动组织开启方式-cmake3-18添加" class="headerlink" title="手动组织开启方式 cmake3.18添加"></a>手动组织开启方式 cmake3.18添加</h4><p>这里项目未实际使用,直接粘贴的<a href="https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD_MODE.html#prop_tgt:UNITY_BUILD_MODE" target="_blank" rel="noopener">文档</a>的内容。</p><pre><code class="CMake">add_library(example_library source1.cxx source2.cxx source3.cxx source4.cxx)set_target_properties(example_library PROPERTIES UNITY_BUILD_MODE GROUP )set_source_files_properties(source1.cxx source2.cxx source3.cxx PROPERTIES UNITY_GROUP "bucket1" )set_source_files_properties(source4.cxx PROPERTIES UNITY_GROUP "bucket2" )</code></pre><h1 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h1><p>项目中未使用的内容</p><ol><li>编译速度-使用PCH,使用后未发现明显的加速效果。</li><li>链接速度-使用多线程链接器,加快链接速度,目前链接很快,倾向于不做改动。</li></ol><h1 id="加快部署速度"><a href="#加快部署速度" class="headerlink" title="加快部署速度"></a>加快部署速度</h1><p>简单的快速部署方式:常用的SSH工具由于权限一般不能使用,问GPT生成一个带账号密码以及能够接受文件的HTTPServer,功能只需要在接受文件并校验后,替换文件,执行服务器重启脚本。可以将脚本输出放到HTTP的回包中,上传文件和接收文件时进行压缩和解压缩,降低对带宽的需求。</p><p>需要部署服务器时可以通过curl命令直接上传文件后触发部署。</p>]]></content>
<summary type="html">
<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>本文主要介绍CMake项目,在不改动或较小改动项目代码的情况下,如何减少编译时间。同时简要介绍其原理和使用场景。包含ccache,编译集群,
</summary>
<category term="优化" scheme="http://blog.lsmg.xyz/categories/%E4%BC%98%E5%8C%96/"/>
<category term="编译加速" scheme="http://blog.lsmg.xyz/tags/%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F/"/>
</entry>
<entry>
<title>CMAKE&Make</title>
<link href="http://blog.lsmg.xyz/2024/07/CPP-CMAKE/"/>
<id>http://blog.lsmg.xyz/2024/07/CPP-CMAKE/</id>
<published>2024-07-02T17:07:45.000Z</published>
<updated>2025-11-29T09:26:42.704Z</updated>
<content type="html"><![CDATA[<h1 id="aux-source-directory"><a href="#aux-source-directory" class="headerlink" title="aux_source_directory"></a>aux_source_directory</h1><p>今天clion写项目 遇到一个问题 明明写了aux_source_directory 却依然提示没有加入. 后来查了下, 并不推荐使用那个命令.<br>可能会存在一些问题</p><p><a href="https://cmake.org/cmake/help/latest/command/aux_source_directory.html" target="_blank" rel="noopener">https://cmake.org/cmake/help/latest/command/aux_source_directory.html</a></p><pre><code class="cmake"># 必须片段# CMake 最低版本号要求cmake_minimum_required (VERSION 2.8)# 项目信息project (Demo1)# 指定生成目标add_executable(Demo main.cc)# 多文件# 如果一味地在add_executable中添加源文件, 会导致太长了# 将dir目录中所有源文件保存在变量中aux_source_directory(. DIR_SOURCE)# 将变量赋值给Demoadd_executable(Demo ${DIR_SOURCE})# 多文件多目录# 需要在主目录和子文件夹中都编写CMakeLists.txt文件# 主文件添加子目录add_subdirectory(dir1)# 添加链接库target_link_libraries(Demo Foo)# dir1目录中aux_source_directory(. DIR1_SOURCE)# 生成链接库 Foo 在主文件中添加即可add_library(Foo ${DIR1_SOURCE})</code></pre><h1 id="CMAKE和MAKE之间的区别"><a href="#CMAKE和MAKE之间的区别" class="headerlink" title="CMAKE和MAKE之间的区别"></a>CMAKE和MAKE之间的区别</h1><p><a href="https://my.oschina.net/xunxun/blog/86781" target="_blank" rel="noopener">博客原文</a></p><p>自己的理解<br>通过为cmake编写CMakeList文件, cmake即可按照规则生成相应的Makefile文件<br>然后make读取Makefile文件就可以按照规则将源代码编译</p><p>总的来说cmake就是为make生成Makefile文件, (Makefile文件可以自己编写, 也可以用Cmake生成)<br>应该是编写CMakeList的代码量少于或者简单于Makefile, 简化了操作</p><h1 id="find-package"><a href="#find-package" class="headerlink" title="find_package"></a>find_package</h1><p><a href="https://zhuanlan.zhihu.com/p/97369704" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/97369704</a></p><p><a href="https://www.jianshu.com/p/a0915895dbbc" target="_blank" rel="noopener">https://www.jianshu.com/p/a0915895dbbc</a></p><p>find_package 存在两个模式</p><ul><li>module模式</li><li>config模式</li></ul><pre><code class="cmake">find_package(GLEW REQUIRED) # 先module模式再config模式find_package(GLEW REQUIRED CONFIG) # 使用config模式</code></pre><p><strong>module模式</strong></p><pre><code class="cmake">find_package(GLEW REQUIRED)if (GLEW_FOUND) message("cannot find glew")endif()</code></pre><p>通过上述命令查找glew库,首先会去cmake的modules目录下查找对应的cmake文件。</p><p>glew对应的cmake文件为<code>FindGLEW.cmake</code></p><pre><code class="txt">C:/Application/Code/cmake-3.19.0-rc2-win64-x64/share/cmake-3.19/Modules/FindGLEW.cmake</code></pre><p><strong>config模式</strong></p><p>如果module模式对应的cmake文件不存在则启动config模式。</p><p>config模式会在如下目录搜索对应的配置文件<code>glew-config.cmake</code>或<code>GLEWConfig.cmake</code></p><p>W代表windows平台 U代表unix平台</p><pre><code class="txt"><prefix>/ (W)<prefix>/(cmake|CMake)/ (W)<prefix>/<name>*/ (W)<prefix>/<name>*/(cmake|CMake)/ (W)<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/ (U)<prefix>/(lib/<arch>|lib*|share)/<name>*/ (U)<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (U)<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/ (W/U)<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/ (W/U)<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (W/U)</code></pre><p>prefix生成规则如下</p><ol><li>查找<code>GLEW_ROOT</code>的cmake变量</li><li>使用命令行<code>cmake -DCMAKE_PREFIX_PATH=/tmp/test</code></li><li>特定的cmake变量如<code>GLEW_DIR</code> <code>CMAKE_PREFIX_PATH</code></li></ol><pre><code class="cmake"># glew-config.cmakefind_path(GLEW_INCLUDE_DIR glew/include/)find_library(GLEW_LIBRARY NAMES glew32 PATHS glew/lib/Release/x64)if (GLEW_INCLUDE_DIR AND GLEW_LIBRARY) set(GLEW_FOUND TRUE)endif()</code></pre><h1 id="相关原理"><a href="#相关原理" class="headerlink" title="相关原理"></a>相关原理</h1><pre><code class="shell">## CMake根目录生成Makefile# 使用all走这里all: cmake_check_build_system $(CMAKE_COMMAND) -E cmake_progress_start /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles/progress.marks $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all $(CMAKE_COMMAND) -E cmake_progress_start /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles 0.PHONY : all# 直接使用目标untitled4untitled4: cmake_check_build_system $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 untitled4.PHONY : untitled4## CMakeFiles/Makefile2# Convenience name for target.untitled4: CMakeFiles/untitled4.dir/rule.PHONY : untitled4# Build rule for subdir invocation for target.CMakeFiles/untitled4.dir/rule: cmake_check_build_system $(CMAKE_COMMAND) -E cmake_progress_start /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles 3 $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 CMakeFiles/untitled4.dir/all $(CMAKE_COMMAND) -E cmake_progress_start /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles 0.PHONY : CMakeFiles/untitled4.dir/rule# All Build rule for target.CMakeFiles/untitled4.dir/all: $(MAKE) $(MAKESILENT) -f CMakeFiles/untitled4.dir/build.make CMakeFiles/untitled4.dir/depend $(MAKE) $(MAKESILENT) -f CMakeFiles/untitled4.dir/build.make CMakeFiles/untitled4.dir/build @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles --progress-num=1,2,3 "Built target untitled4".PHONY : CMakeFiles/untitled4.dir/all##Shell# 生成depend$ make -f CMakeFiles/untitled4.dir/build.make CMakeFiles/untitled4.dir/dependScanning dependencies of target untitled4# 变更文件./CMakeFiles/untitled4.dir/depend.internal./CMakeFiles/untitled4.dir/depend.make./CMakeFiles/untitled4.dir/CXX.includecache# 构建$make -f CMakeFiles/untitled4.dir/build.make CMakeFiles/untitled4.dir/build[100%] Building CXX object CMakeFiles/untitled4.dir/Base.cpp.o[100%] Linking CXX executable untitled4</code></pre><pre><code class="shell"># ./CMakeFiles/untitled4.dir/depend.internalCMakeFiles/untitled4.dir/Base.cpp.o /tmp/tmp.3r11cPofBP/Base.cpp /tmp/tmp.3r11cPofBP/Base.hCMakeFiles/untitled4.dir/main.cpp.o /tmp/tmp.3r11cPofBP/Base.h /tmp/tmp.3r11cPofBP/main.cpp# ./CMakeFiles/untitled4.dir/depend.makeCMakeFiles/untitled4.dir/Base.cpp.o: ../Base.cppCMakeFiles/untitled4.dir/Base.cpp.o: ../Base.hCMakeFiles/untitled4.dir/main.cpp.o: ../Base.hCMakeFiles/untitled4.dir/main.cpp.o: ../main.cpp</code></pre><pre><code class="shell"># 生成依赖CMakeFiles/untitled4.dir/depend: cd /tmp/tmp.3r11cPofBP/cmake-build-debug && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /tmp/tmp.3r11cPofBP /tmp/tmp.3r11cPofBP /tmp/tmp.3r11cPofBP/cmake-build-debug /tmp/tmp.3r11cPofBP/cmake-build-debug /tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles/untitled4.dir/DependInfo.cmake --color=$(COLOR).PHONY : CMakeFiles/untitled4.dir/depend# 构建CMakeFiles/untitled4.dir/build: untitled4.PHONY : CMakeFiles/untitled4.dir/builduntitled4: CMakeFiles/untitled4.dir/main.cpp.ountitled4: CMakeFiles/untitled4.dir/Base.cpp.ountitled4: CMakeFiles/untitled4.dir/build.makeuntitled4: CMakeFiles/untitled4.dir/link.txt @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Linking CXX executable untitled4" $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/untitled4.dir/link.txt --verbose=$(VERBOSE)CMakeFiles/untitled4.dir/main.cpp.o: CMakeFiles/untitled4.dir/flags.makeCMakeFiles/untitled4.dir/main.cpp.o: ../main.cpp @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/tmp/tmp.3r11cPofBP/cmake-build-debug/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object CMakeFiles/untitled4.dir/main.cpp.o" /usr/bin/g++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o CMakeFiles/untitled4.dir/main.cpp.o -c /tmp/tmp.3r11cPofBP/main.cpp</code></pre><p>CMake识别变更和重新编译,是通过比对冒号左右两个文件的最后修改时间。<br>A:B A依赖B,如果B的修改时间晚于A说明A需要重新构建。</p><pre><code class="cmake">set(CurrentProtoGenP2P ${P2P_OUT_PATH_PATH}/${ProtoName}.proto.tars_Api.p2p.h)add_custom_command(OUTPUT ${CurrentProtoGenP2P} COMMAND sh -c "python2 ${P2P_SELECT_AND_BUILD} ${P2P_OUT_PATH_PATH} ${AllDirs} ${PROTOBUF_ROOT}/include/include ${Protoc} ${ProtoFile}" DEPENDS ${Protoc} ${ProtoFile} WORKING_DIRECTORY ${Dir})list(APPEND Srcs ${CurrentProtoGenP2P})</code></pre><h1 id="include-directory"><a href="#include-directory" class="headerlink" title="include_directory"></a>include_directory</h1><p>搜寻头文件的顺序</p><ol><li><> 搜索顺序: -I指定的路径, 搜索-isystem, 系统路径, -idirafter</li><li>“” 搜索顺序: 当前路径, 搜索-iquote, -I指定的路径, 搜索-isystem, 系统路径, -idirafter</li><li>CMake中include_directory对应-I</li></ol><pre><code class="CMake">function(add_unique list_var element) # 获取当前列表 set(current_list ${${list_var}}) # 检查元素是否已经在列表中 list(FIND current_list ${element} index) if(index EQUAL -1) # 如果元素不在列表中,添加它 list(APPEND current_list ${element}) # 更新原始列表变量 set(${list_var} ${current_list} PARENT_SCOPE) endif()endfunction()foreach(include_dir ${include_dirs}) message("add dir ${include_dir}") include_directories(${include_dir})endforeach()function(include_directories_recursively base_dir dst) # 获取当前目录的所有子目录 set(include_dirs) file(GLOB_RECURSE header_files "${base_dir}/*.h" "${base_dir}/*.hpp") foreach(header_file ${header_files}) get_filename_component(header_dir ${header_file} DIRECTORY) add_unique(include_dirs "-idirafter ${header_dir}") endforeach() string(REPLACE ";" " " include_dirs "${include_dirs}") set(${dst} "${${dst}} ${include_dirs}" PARENT_SCOPE)endfunction()set(DIR_AFTER)include_directories_recursively(${CMAKE_CURRENT_SOURCE_DIR} DIR_AFTER)include_directories_recursively(${PUBINCLUDE} DIR_AFTER)message("${DIR_AFTER}")</code></pre><ol><li>idirafter 能不能起作用</li><li>为什么Make没问题</li></ol><pre><code class="CMake">function(add_unique list_var element) # 获取当前列表 set(current_list ${${list_var}}) # 检查元素是否已经在列表中 list(FIND current_list ${element} index) if(index EQUAL -1) # 如果元素不在列表中,添加它 list(APPEND current_list ${element}) # 更新原始列表变量 set(${list_var} ${current_list} PARENT_SCOPE) endif()endfunction()function(include_directories_recursively base_dir dst) # 获取当前目录的所有子目录 set(include_dirs) file(GLOB_RECURSE header_files "${base_dir}/*.h" "${base_dir}/*.hpp") foreach(header_file ${header_files}) get_filename_component(header_dir ${header_file} DIRECTORY) add_unique(include_dirs "-idirafter ${header_dir}") endforeach() string(REPLACE ";" " " include_dirs "${include_dirs}") set(${dst} "${${dst}} ${include_dirs}" PARENT_SCOPE)endfunction()set(DIR_AFTER)include_directories_recursively(${CMAKE_CURRENT_SOURCE_DIR} DIR_AFTER)include_directories_recursively(${PUBINCLUDE} DIR_AFTER)message("${DIR_AFTER}")set(SVR_NAME "NewMainSvr")message("server name ${SVR_NAME}")file(GLOB_RECURSE PRJ_SRCS "*.cpp" "*.c")#添加目标add_library(${SVR_NAME} SHARED ${PRJ_SRCS})#链接库target_link_libraries(${SVR_NAME} ${COMMON_LIBS})target_link_libraries(${SVR_NAME} "i18n")set_target_properties(${SVR_NAME} PROPERTIES COMPILE_FLAGS "-D__HIGH_PERFORMANCE__ ${COMMON_CPP_FLAGS} ${DIR_AFTER}")</code></pre><h1 id="CMAKE优化依赖生成"><a href="#CMAKE优化依赖生成" class="headerlink" title="CMAKE优化依赖生成"></a>CMAKE优化依赖生成</h1><pre><code class="CMake"># 生成file(GLOB CFG_FILES "XML")foreach (CFG_FILE ${CFG_FILES}) get_filename_component(FILE_NAME ${CFG_FILE} NAME_WE) set(OUT_H ${RES_INCLUDE}/${FILE_NAME}.h) message(${OUT_H}) add_custom_command(OUTPUT ${OUT_H} COMMAND bash ConvertCfg.sh ${CFG_FILE} DEPENDS ${CFG_FILE} WORKING_DIRECTORY ${RES}/script/one ) list(APPEND XML_SRCS ${OUT_H})endforeach ()add_custom_target(XmlHeaders ALL DEPENDS ${XML_SRCS})# 添加依赖add_dependencies(${SVR_NAME} XmlHeaders)</code></pre><h1 id="CMake打进版本信息"><a href="#CMake打进版本信息" class="headerlink" title="CMake打进版本信息"></a>CMake打进版本信息</h1><pre><code class="CMake"># C.h.in#define BUILD_STAMP "@BUILDDATE@"# CMakeexecute_process( COMMAND date +%F_%R:%S OUTPUT_VARIABLE BUILDDATE OUTPUT_STRIP_TRAILING_WHITESPACE)# 会自动填充.h.in中的值configure_file(C.h.in C.h)# 项目中include .h使用宏即可</code></pre><p><strong>不能使用add_definitions -D宏来打包, 否则每次触发cmake时, 宏的改变会导致全体重新编译.</strong></p>]]></content>
<summary type="html">
<h1 id="aux-source-directory"><a href="#aux-source-directory" class="headerlink" title="aux_source_directory"></a>aux_source_directory</h1><
</summary>
<category term="CPP" scheme="http://blog.lsmg.xyz/categories/CPP/"/>
</entry>
<entry>
<title>如何更好的添砖加瓦2</title>
<link href="http://blog.lsmg.xyz/2024/06/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E7%B2%BE%E5%8D%8E%E6%95%B4%E7%90%86%E7%89%882/"/>
<id>http://blog.lsmg.xyz/2024/06/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E7%B2%BE%E5%8D%8E%E6%95%B4%E7%90%86%E7%89%882/</id>
<published>2024-06-24T09:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<p>本文是2024年上半年的经验总结</p><h2 id="个人成长"><a href="#个人成长" class="headerlink" title="个人成长"></a>个人成长</h2><p><strong>学习新东西和分享内容虽好, 但要避免乌龙</strong></p><p>实验的场景有问题, 最后得出的结论确实想要的, 分享出去之后才发现场景的问题, 导致结论也是错的.</p><p><strong>工具最好配备文档, 继续常见问题</strong></p><p>写了工具之后, 其他人使用过程中免不了有疑问, 如果逐一回答则会耗费精力, 所以最好把常见的写到文档中, 其他人有问题, 先让对面去看文档.</p><p><strong>分享出去的工具, 在精不在多</strong></p><p>多了之后, 不精的工具会耗费精力去答疑和修改, 所以分享就要分享好用的, 而不是随手写的就分享出去.</p><p><strong>多多钻研和发现值得思考的内容</strong></p><ol><li>编译出的elf文件为什么这么大? elf文件中有哪些内容? 这些内容中哪些可以减小? 如何减小? 减小了有何影响, 这里搞完之后收获还是有的.</li><li>elf文件变大之后, gdb分析变慢, 对gdb使用火焰图发现是在解析符号表, 所以才会卡慢.</li><li>服务器占用xx内存, 这些内存占用必定有他的原因和使用位置, 如何对这些进行分析整理?</li></ol><p><strong>总迭代时间超过半年的编译发布流水线的更新记录和反思</strong></p><p>编译发布流水线经过多次修改, 有的修改甚至到了重写地步, 总结发现下为什么没有一开始就能发现这些问题.</p><ol><li>初版直接把本地的编译命令放到了流水线的shell脚本中, 开了超多条流水线, 每条都写了很多代码</li><li>写了个超长的函数更新各个submodule, 流水线的更新并不好用</li><li>要求自动更新服务器, 修改编译上传脚本将版本号保存到本地, 修改发布脚本支持读取</li><li>要求能够发布IDC的版本, 将原本初版的流水线完全拷贝了一份用来编译release版本, 区分了space导致release和debug无法混发到腾讯云</li><li>要求支持指定submodule的分支发布, 发现改所有的流水线代码比较麻烦, <strong>统一了更新部分</strong></li><li>发布重试的要求, <strong>统一了构建部分</strong></li><li>要求增加海外部分, 如果再开一条流水线将会导致数量继续增加. 直接把构建部分全写到了脚本来控制.</li><li>上传有失败, 增加了上传重试</li><li>分支有/增加了转义</li><li>流水线加上了code_branch分组功能, 防止冲突.</li><li>用分支名命名感觉还是有点问题, <strong>目前看还能抗住</strong></li><li>异常错误处理, 编译会有编译加速失败, <strong>这个还没加重试</strong></li><li>目前一个发布环境也会有多套了, <strong>不能再只用idc, test当做版本号标识了</strong>.</li><li>一步到位, 把相关信息作为msg发到平台, 更新的时候不再读取本地版本号. 不使用花里胡哨的版本号标识, 直接使用构建信息.</li></ol><p><strong>多多学习和消化公司已有的学习资源</strong></p><ol><li>Q-Learning和KM内容太多了, 可以用来扩充知识面, 还能选取感兴趣的点进行深入学习.</li><li>还可以看看已经公开的晋升文档, 看看其他人搞了什么内容, 从中选取自己感兴趣的点.</li></ol><p><strong>平时遇到的问题尽量都进行记录</strong></p><p>及时总结反思,发现问题,尽量避免后续再次出现。对自己每周每月的提升有所了解。</p><p><strong>晨会</strong></p><p>晨会同步进度. 非业务负责的也能听懂做了什么, 还剩余哪些没做。有风险及时同步,为什么有风险, 原因是什么。</p><p><strong>联调</strong></p><p>及时了解相关关系人员, 自己功能提前完成后, 即使了解和催促下相关人员, 提前联调后释放出来,防止处理其他问题时还需要抽时间联调,打乱节奏。</p><p><strong>结合业务学习技术</strong></p><p>只是想着学技术容易找不到发力点,同时学习之后不便于验证。但是结合业务学习,就有发力点,同时可以尝试进行验证。</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p><strong>详细了解需求的背景和要求后再动手</strong></p><ol><li>防止一句话需求的理解存在偏差</li><li>防止实现错误, 最后还要进行修改, 如果带到了测试环境甚至线上, 修改的难度和不稳定会越来越大.</li></ol><p><strong>需求做到位即可, 防止做了没必要且无用的内容</strong></p><p>需求中一个功能本来可以用简单的方法实现, 却用了复杂的实现方式, 最后反而效果不好, 得不偿失.</p><p><strong>需求实现成本高的地方和产品沟通</strong></p><p>看看能不能换成本较低, 效果略微降低的方案.</p><p><strong>服务器命名问题</strong></p><p>新加了一大批服务器, 起初我想通过命名给这些服务器赋予功能, 但是会导致服务器的名字难以记忆, 最后分离了开来, 命名使用了简单的方式, 至于服务器的功能则额外加了一张功能表.</p><p><strong>加深自己参与的服务器各个模块的理解</strong></p><p>这样才能在商定各个服务器分工的时候, 给出合理的解释, 方便进行快速的分工.</p><p><strong>开工前的协议制定</strong></p><p>优先和其他开发定协议,协议一次性定好之后汇总发布出来,单条发送不便于查看。</p><p><strong>处理需求时进行记录</strong></p><p>配置和代码是分离的,需要记录需要改哪些配置,防止后续代码提交后存在配置未提交。</p>]]></content>
<summary type="html">
<p>本文是2024年上半年的经验总结</p>
<h2 id="个人成长"><a href="#个人成长" class="headerlink" title="个人成长"></a>个人成长</h2><p><strong>学习新东西和分享内容虽好, 但要避免乌龙</strong><
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="UP" scheme="http://blog.lsmg.xyz/tags/UP/"/>
</entry>
<entry>
<title>massif使用</title>
<link href="http://blog.lsmg.xyz/2024/04/Linux-massif/"/>
<id>http://blog.lsmg.xyz/2024/04/Linux-massif/</id>
<published>2024-04-13T16:31:22.000Z</published>
<updated>2025-11-29T09:26:42.705Z</updated>
<content type="html"><![CDATA[<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>现在内存量大管饱, 优化几兆甚至几十兆, 大部分时候收益不高. 但同一个实例创造大量副本, 且这些副本共存的情况下(1000个), 优化单个实例内存的收益就会上升(1MBx1000=1GB).</p><p>进行内存优化, 首先要知晓一个实例都涉及了哪些内存分配, 由于涉及位置多和熟悉程度等原因, 导致人肉观察效率很低, 所以需要借助工具.</p><p>工具需要能够详细记录一个实例每次申请的内存大小和申请位置, 将申请大小降序排序之后, 能够方便的找到优化起来收益较大的点.</p><h1 id="massif"><a href="#massif" class="headerlink" title="massif"></a>massif</h1><p>massif就是这样的工具</p><p>样例代码</p><pre><code class="cpp">$ cat m1.cpp #include <map>#include <thread>int foo(){ std::map<int, int> m; for (int i = 0; i < 1000; ++i) { m[i] = i; } return 0;}int main(){ std::thread t([](){foo();}); t.join(); return 0;}</code></pre><p>如下是massif导出的结果, 可以看到有栈被打印出来, 后面会对这些内容做简要说明</p><pre><code class="txt">$ cat 2.ms n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B)-------------------------------------------------------------------------------- 0 3,994,759 56,656 40,632 16,024 0#### new分配了40,632B内存 ####71.72% (40,632B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.#### 第一个组成部分 40,000B 用 -> 开头->70.60% (40,000B) 0x4037D5: __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<int const, int> > >::allocate(unsigned long, void const*) (new_allocator.h:104)| ->70.60% (40,000B) 0x403401: std::_Rb_tree<int, std::pair<int const, int>, std::_Select1st<std::pair<int const, int> >, std::less<int>, std::allocator<std::pair<int const, int> > >::_M_get_node() (stl_tree.h:370)| ->70.60% (40,000B) 0x402B80: std::_Rb_tree_node<std::pair<int const, int> >* std::_Rb_tree<int, std::pair<int const, int>, std::_Select1st<std::pair<int const, int> >, std::less<int>, std::allocator<std::pair<int const, int> > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<int const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<int const&>&&, std::tuple<>&&) (stl_tree.h:403)| ->70.60% (40,000B) 0x402913: std::_Rb_tree_iterator<std::pair<int const, int> > std::_Rb_tree<int, std::pair<int const, int>, std::_Select1st<std::pair<int const, int> >, std::less<int>, std::allocator<std::pair<int const, int> > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<int const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<int const, int> >, std::piecewise_construct_t const&, std::tuple<int const&>&&, std::tuple<>&&) (stl_tree.h:1669)| ->70.60% (40,000B) 0x40265D: std::map<int, int, std::less<int>, std::allocator<std::pair<int const, int> > >::operator[](int const&) (stl_map.h:465)| ->70.60% (40,000B) 0x40134E: foo() (m1.cpp:8)| ->70.60% (40,000B) 0x4013AE: main::{lambda()| ->70.60% (40,000B) 0x402243: void std::_Bind_simple<main::{lambda()| ->70.60% (40,000B) 0x40219A: std::_Bind_simple<main::{lambda()| ->70.60% (40,000B) 0x402133: std::thread::_Impl<std::_Bind_simple<main::{lambda()| ->70.60% (40,000B) 0x40ED4EF: execute_native_thread_routine (thread.cc:84)| ->70.60% (40,000B) 0x5545EA4: start_thread (pthread_create.c:307)| ->70.60% (40,000B) 0x5858B0C: clone (in /usr/lib64/libc-2.17.so)| #### 第二个组成部分 576B 用 -> 开头 ->01.02% (576B) 0x4012784: allocate_dtv (dl-tls.c:317)| ->01.02% (576B) 0x4012784: _dl_allocate_tls (dl-tls.c:533)| ->01.02% (576B) 0x554687B: allocate_stack (allocatestack.c:539)| ->01.02% (576B) 0x554687B: pthread_create@@GLIBC_2.2.5 (pthread_create.c:447)| ->01.02% (576B) 0x40ED73E: __gthread_create (gthr-default.h:662)| ->01.02% (576B) 0x40ED73E: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (thread.cc:142)| ->01.02% (576B) 0x4014CD: std::thread::thread<main::{lambda()| ->01.02% (576B) 0x4013CC: main (m1.cpp:15)| ->00.10% (56B) in 1+ places, all below ms_print's threshold (01.00%)</code></pre><p>使用grep提取实际分配内存的大小和位置并进行排序, 可以看到0x4037D5分配了40000B的内存, 回上文中查找, 找到是<code>0x40134E: foo() (m1.cpp:8)</code>触发的分配.</p><pre><code class="txt">$ grep "^->" 2.ms | grep -v "all below ms_print" | awk -F '[():]' '{gsub(",","");print $2,$3}' | sort -nr40000B 0x4037D5576B 0x4012784</code></pre><p>假设上文是一个副本分配的内存, 再进行一次副本创建则会得到下面的结果.</p><pre><code class="txt">$ grep "^->" 3.p | grep -v "all below ms_print" | awk -F '[():]' '{gsub(",","");print $2,$3}' | sort -nr80000B 0x4037D5576B 0x4012784</code></pre><p>用两次0x4037D5位置的内存相减, 差值就是一个副本所需要的大小</p><p>将所有差值排序后 就是一个副本涉及的新增的内存, 可以根据地址从大到小逐一排查是否可以进行优化(如副本见共享同一份数据, 避免多次创建)</p><pre><code class="txt">40000B 0x4037D5</code></pre><h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><pre><code class="sh"># threshold默认是1, 越小记录的精度越高, 如果过大会导致记录被合并无法展示细节, 这里指定的0,001# m1是二进制名称 valgrind --tool=massif --threshold=0.001 ./m1</code></pre><p>问题: massif默认会记录程序从启动开始所有的内存, 且是基于无法控制且数量有限的快照机制</p><ol><li>如果服务器需要较长时间初始化, 则会在初始化时消耗掉所有快照, 无法记录后续副本创建的分配.</li><li>快照是无法控制的, 无法方便的获得副本创建前后的内存消耗</li></ol><p>所以需要手动进行快照</p><pre><code class="sh"># A窗口$ valgrind --tool=massif --threshold=0.001 --vgdb=yes --vgdb-error=0 ./m1==28781== Massif, a heap profiler==28781== Copyright (C) 2003-2017, and GNU GPL'd, by Nicholas Nethercote==28781== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info==28781== Command: ./m1==28781== ==28781== (action at startup) vgdb me ... ==28781== ==28781== TO DEBUG THIS PROCESS USING GDB: start GDB like this==28781== /path/to/gdb ./m1==28781== and then give GDB the following command==28781== target remote | /usr/local/libexec/valgrind/../../bin/vgdb --pid=28781==28781== --pid is optional if only one valgrind process is running==28781== </code></pre><pre><code class="sh"># B窗口gdb ./m1# 输入上文的提示target remote | /usr/local/libexec/valgrind/../../bin/vgdb --pid=28781# 副本创建之前下断点b 6# 运行r# 此时来到副本创建之前的位置# 手动拍摄快照记录到1.mmonitor detailed_snapshot 1.m # 再次运行# 此时将要创建第二个副本, 也就意味着第一个副本创建完毕# 手动拍摄快照记录到2.mmonitor detailed_snapshot 2.m # 杀掉valgrindmonitor v.kill# 退出gdbquit</code></pre><p>对快照的原始数据进行处理, 就得到了开头的2.ms文件和格式化后的文件, 使用Excel对1.mp和2.mp相同地址的部分计算差值即可.</p><pre><code class="sh">ms_print 1.m > 1.msms_print 2.m > 2.msgrep "^->" 1.ms | grep -v "all below ms_print" | awk -F '[():]' '{gsub(",","");print $2,$3}' | sort -nr > 1.mpgrep "^->" 2.ms | grep -v "all below ms_print" | awk -F '[():]' '{gsub(",","");print $2,$3}' | sort -nr > 2.mp</code></pre>]]></content>
<summary type="html">
<h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>现在内存量大管饱, 优化几兆甚至几十兆, 大部分时候收益不高. 但同一个实例创造大量副本, 且这些副本共存的情况下(1000个), 优化单个
</summary>
<category term="Linux" scheme="http://blog.lsmg.xyz/categories/Linux/"/>
</entry>
<entry>
<title>常事UP</title>
<link href="http://blog.lsmg.xyz/2024/03/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-%E5%B8%B8%E4%BA%8BUP/"/>
<id>http://blog.lsmg.xyz/2024/03/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-%E5%B8%B8%E4%BA%8BUP/</id>
<published>2024-03-23T14:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<h1 id="旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号"><a href="#旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号" class="headerlink" title="旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号"></a>旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号</h1><p>原价890,改签费530,机票差价1100, 花了1630还不是和团一个飞机。最后团的飞机降价了还不到1600,最后2500买了1600的机票,血亏900.</p><p>后面出去还是早点组团订机票,不要心疼这一点了。</p><ol><li>电子设备: 手环, 手机, 手机充电器线, 耳机, 充电宝</li><li>洗面奶, 保湿, 几个牙线</li><li>证件类: 护照, 身份证, 港澳通行证(可无)</li><li>现金2W, 交通卡, visa卡, 手机卡, 卡针</li><li>路雷他定, 地奈德, 口罩</li><li>袜子内衣x3(4) 优先破旧的, 背心裤子x2, 外套?</li><li>遮阳伞</li><li>牙刷, 牙膏, 小洗衣液</li><li>眼镜布</li><li>指甲刀</li></ol><h1 id="1"><a href="#1" class="headerlink" title="1"></a>1</h1><ol><li>visa卡换绑定手机</li><li>胡子, 手脚指甲</li><li>手环充满电(续航开一下)</li></ol>]]></content>
<summary type="html">
<h1 id="旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号"><a href="#旅游想省1000多块钱买了2号回程机票,后面有人一起合租,改成4号" class="headerlink" title="旅游想省1000多块钱买了2号回程机票,后面有人一起
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="UP" scheme="http://blog.lsmg.xyz/tags/UP/"/>
</entry>
<entry>
<title>排行榜</title>
<link href="http://blog.lsmg.xyz/2024/01/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E6%8E%92%E8%A1%8C%E6%A6%9C/"/>
<id>http://blog.lsmg.xyz/2024/01/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E6%8E%92%E8%A1%8C%E6%A6%9C/</id>
<published>2024-01-25T14:38:08.000Z</published>
<updated>2025-11-29T09:26:42.708Z</updated>
<content type="html"><![CDATA[<ol><li>排行榜到处都是</li><li>什么样的场景下使用什么样的排行榜设计? 为什么? 选取这个有什么优点和缺点, 是否牺牲了什么.</li></ol><p>魔法棒: 即时(瞬间更新), 无上限, 支持多种子表单(年季月周日时分, 标签), 数据绝对安全, 不消耗硬件(网卡, 磁盘, CPU, 内存), 先到先得.</p><p>数据: 玩家ID, 数据值. 两个int64_t 16B, 亿人, 100 000 000 * 16 = 1600MB? 玩家ID做Key会不会有啥问题? 有没有其他数据?</p><p>CPU, 即时(瞬间更新):<br>无上限: 存储 + 更新限制<br>一键瞬间导出多种报表(年季月周日时分, 标签)<br>数据绝对安全<br>网卡: 分标签拉取? 子表单, 数据量怎么说都不会小, 分页拉取是必须的.<br>存储: 磁盘, 内存 只是玩家ID, 数据值的话 貌似不成问题.<br>先到先得: 稳定排序</p>]]></content>
<summary type="html">
<ol>
<li>排行榜到处都是</li>
<li>什么样的场景下使用什么样的排行榜设计? 为什么? 选取这个有什么优点和缺点, 是否牺牲了什么.</li>
</ol>
<p>魔法棒: 即时(瞬间更新), 无上限, 支持多种子表单(年季月周日时分, 标签), 数据绝对安全, 不消
</summary>
<category term="学习记录" scheme="http://blog.lsmg.xyz/categories/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"/>
</entry>
<entry>
<title>编译加速</title>
<link href="http://blog.lsmg.xyz/2023/12/%E4%BC%98%E5%8C%96-%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F/"/>
<id>http://blog.lsmg.xyz/2023/12/%E4%BC%98%E5%8C%96-%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F/</id>
<published>2023-12-13T14:48:22.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h1><h2 id="编译时间统计"><a href="#编译时间统计" class="headerlink" title="编译时间统计"></a>编译时间统计</h2><pre><code class="shell"># g++.shreal_compiler="/usr/bin/g++"filename=$(echo $* | grep -o "\ -c .*")output="/root/stat.log"/usr/bin/time -f "%U-%S $filename" -a -o $output $real_compiler $*# $export CXX=g++.shmkdir b2 && cd b2 && cmake .. -DCMAKE_BUILD_TYPE=debug && make -j24 BigWorldServer</code></pre><h2 id="nm"><a href="#nm" class="headerlink" title="nm"></a>nm</h2><pre><code class="shell">nm a.cpp.o | awk 'NF>=2 {print $(NF-1)}' | sort | uniq -c 15 b bss段 2506 n debug符号 2394 r rodate段 36 t text段 1 T global text? 71 u unique global symbol 448 U 未定义符号, 链接阶段会去寻找 426 V 弱符号 4 w 11854 W 弱未定义符号</code></pre><h1 id="全量编译"><a href="#全量编译" class="headerlink" title="全量编译"></a>全量编译</h1><ol><li>减少非必须要的编译项目<ol><li>单元测试类</li></ol></li><li>unitybuild减少编译单元(核心是减少头文件的重复编译)<ol><li>将多个cpp合并编译. 减少重复头文件的耗时</li></ol></li><li>使用预编译头减少编译单元的耗时(核心是减少头文件的重复编译)<ol><li>将耗时常用且不经常修改的加入到其中</li></ol></li><li>proto<ol><li>避免协议文件被头文件引用, 采用前置声明+定义和实现分离的方式, 将协议头文件放到cpp文件中.</li><li>避免超大proto文件, 根据使用范围和频率进行分割, 减少proto变更影响的范围.</li><li><strong>自动生成的proto是重灾区, 这些proto代码一个比一个量大.</strong></li></ol></li></ol><h1 id="增量编译"><a href="#增量编译" class="headerlink" title="增量编译"></a>增量编译</h1><p>CMake相关</p><ol><li>优化CMake等脚本, 不每次全量生成新文件, 而是配置依赖变更后才生成, 减少非必要的变更.</li></ol><p>枚举解耦<strong>未实操</strong></p><ol><li>减少枚举文件变更导致的重编, 提供STR到INT的映射<ol><li>开发期做这种替换</li><li>发布期换回正常方式</li></ol></li></ol><h1 id="链接加速"><a href="#链接加速" class="headerlink" title="链接加速"></a>链接加速</h1><p><a href="https://github.com/rui314/mold" target="_blank" rel="noopener">mold</a></p><p>更好的链接算法和使用多线程链接</p><p>使用之前</p><pre><code class="txt"> Performance counter stats for 'make BigWorldServer': 29,993.61 msec task-clock # 0.999 CPUs utilized 148 context-switches # 0.005 K/sec 26 cpu-migrations # 0.001 K/sec 777,595 page-faults # 0.026 M/sec <not supported> cycles <not supported> instructions <not supported> branches <not supported> branch-misses 30.008942931 seconds time elapsed 27.920985000 seconds user 2.075018000 seconds sys</code></pre>]]></content>
<summary type="html">
<h1 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h1><h2 id="编译时间统计"><a href="#编译时间统计" class="headerlink" title="编译时间统计"></a>编
</summary>
<category term="优化" scheme="http://blog.lsmg.xyz/categories/%E4%BC%98%E5%8C%96/"/>
<category term="编译加速" scheme="http://blog.lsmg.xyz/tags/%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F/"/>
</entry>
<entry>
<title>方向选择</title>
<link href="http://blog.lsmg.xyz/2023/12/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-%E6%96%B9%E5%90%91%E9%80%89%E6%8B%A9/"/>
<id>http://blog.lsmg.xyz/2023/12/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-%E6%96%B9%E5%90%91%E9%80%89%E6%8B%A9/</id>
<published>2023-12-11T19:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<ol><li>深入业务, 不只关注分配到的任务, 看看任务的上下游 关注下整体实现, 毕竟其他服务器的代码都有, 甚至还可以看公共服务的代码<ol><li>从客户端数据进来到回包回去的流程, 确实可以看看</li><li>对整体有把握才能知道可以优化的地方.</li></ol></li><li>性能压测, 编译优化</li><li>KM, 对于某个需求点(如排行榜), 可以看看一个排行榜有多少种实现方式, 每种方法的坑.</li><li>KM, 可以多看看极有可能发现预期之外的感兴趣的点, 一旦用到项目上直接UPUP.</li></ol><h1 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h1><p>不喜欢这个方面还是算了, 有开源的不用白不用. 项目里的无锁队列一年半了也没认真看过.</p><h1 id="工具侧"><a href="#工具侧" class="headerlink" title="工具侧"></a>工具侧</h1><p>针对开发中遇到的问题, 对于能够自动化的想法提供自动化工具, 优化已有工具的体验.</p><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><ol><li>工具与工具之间独立性强, 很容易就能写出一个新的工具, 投入少见效快</li><li>对于新的工具, 每次处理的时候也算是能了解一点内容</li></ol><h2 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h2><ol><li>后续需要进行工具的维护, 占用时间</li><li>工具就算写好了搭配上文档, 也会有人@你去处理, 占用时间, 重复操作比较麻烦.</li><li>难于出什么深度内容</li></ol><h2 id="做过的尝试"><a href="#做过的尝试" class="headerlink" title="做过的尝试"></a>做过的尝试</h2><ol><li>GM工具优化: 这个一直在用, 属于是评价比较好的, 改动也不是很大, 主要是提高了效率.</li><li>Wireshark抓包, 后台抓包工具: 前者运行在Windows机器无法支持解密, 也没法进行良好的操作和筛选. 后者运行在服务器, 支持解析. 最终都是没有太合适的应用场景, 没人用, 也就没维护了, 预期有更高级版本, 但是现在不想写工具.</li><li>服务器自动冒烟: 这个也是在用, 自动构建内网or外网的服务器, 发布服务器和配置. py脚本也不太熟练, 写的时候也比较折磨.</li><li>日志分析: 相对简单的脚本, 但是人要一直投入在上面. 就算有文档也会被@去处理. 后面尝试接入骏鹰来解决, 但感觉可能又要维护骏鹰.</li></ol><h2 id="体验"><a href="#体验" class="headerlink" title="体验"></a>体验</h2><p>最近半年也算是没有写新的工具, 主要是维护自动冒烟, 日志分析(可能要负责维护骏鹰).</p><p>目前比较排斥写工具类的, 感觉没有学到太多的深度的东西, 写了之后就要负责维护和答疑甚至绑在上面操作.</p><h1 id="服务器优化"><a href="#服务器优化" class="headerlink" title="服务器优化"></a>服务器优化</h1><h2 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h2><p>感觉比较高大上, 而且难点是找到问题在哪里</p><h2 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h2><p>做起来太难了, 完全没这方面经验, 没有什么大的成果.</p><h2 id="做过的尝试-1"><a href="#做过的尝试-1" class="headerlink" title="做过的尝试"></a>做过的尝试</h2><p>Prometheus+Grafana提供的火焰图插件: 感觉这个也没什么深度的样子, 只做了一个火焰图的插件, 感觉虽然能看到 但还是找不到热点的优化方法, 不是很便宜大碗. 受限于采集频率</p><p>编译加速: 这个感觉还是挺爽的, 文件变更数量较少情况下, 编译速度大幅优化了. <strong>但是没能做到进一步深入优化</strong>.</p><p>bpf: 这个最终甚至都没学完, 也是感觉无法落地.</p><p>服务器二进制包大小优化: 都是有损优化, 想无损的话还是没啥用途.</p><h2 id="体验-1"><a href="#体验-1" class="headerlink" title="体验"></a>体验</h2><p>高大上, 经验积累性质强, 成本高, 难于出成果.</p><h1 id="业务仔"><a href="#业务仔" class="headerlink" title="业务仔"></a>业务仔</h1><h2 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h2><p>培养代码的基本功</p><h2 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h2><p>适量的话倒是没什么缺点</p><h2 id="体验-2"><a href="#体验-2" class="headerlink" title="体验"></a>体验</h2><p>一直在积累相关的经验, 最近半年整理的也发布出去了, 后面也打算继续整理和发布, 也算是正向的吧.</p><p>反正需求一直会有的, 这方面还是不太额外花时间了.</p><h1 id="重构or架构优化"><a href="#重构or架构优化" class="headerlink" title="重构or架构优化"></a>重构or架构优化</h1><h2 id="优点-3"><a href="#优点-3" class="headerlink" title="优点"></a>优点</h2><h2 id="缺点-3"><a href="#缺点-3" class="headerlink" title="缺点"></a>缺点</h2><ol><li>这个更难了, 上面的优化章节还不需要对服务器有太多深入了解, 这里就需要找到问题, 并且有能力进行改动.</li></ol>]]></content>
<summary type="html">
<ol>
<li>深入业务, 不只关注分配到的任务, 看看任务的上下游 关注下整体实现, 毕竟其他服务器的代码都有, 甚至还可以看公共服务的代码<ol>
<li>从客户端数据进来到回包回去的流程, 确实可以看看</li>
<li>对整体有把握才能知道可以优化的地方.</li>
<
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="方向选择" scheme="http://blog.lsmg.xyz/tags/%E6%96%B9%E5%90%91%E9%80%89%E6%8B%A9/"/>
</entry>
<entry>
<title>如何更好的添砖加瓦</title>
<link href="http://blog.lsmg.xyz/2023/11/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E7%B2%BE%E5%8D%8E%E6%95%B4%E7%90%86%E7%89%88/"/>
<id>http://blog.lsmg.xyz/2023/11/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E7%B2%BE%E5%8D%8E%E6%95%B4%E7%90%86%E7%89%88/</id>
<published>2023-11-25T09:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>记录总结了从7月到11月积累的非技术经验, 涉及添砖加瓦方方面面.</p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本文是自己从7月到11月积累的部分非技术经验, 总结了工作记录中遇到的问题. 由于是个人经验积累, 适用性不强, 也不是大而全. 发布出来主要还是用于自己自勉, 整理了, 不发出来有感觉浪费.</p><h1 id="史山之从设计到制作"><a href="#史山之从设计到制作" class="headerlink" title="史山之从设计到制作"></a>史山之从设计到制作</h1><h2 id="需求设计"><a href="#需求设计" class="headerlink" title="需求设计"></a>需求设计</h2><p><strong>需求评审</strong><br>评审时, 如果不是很确定这部分功能, 自己负责的服务器能不能做, 就不要承接下来, 最后发现做不了, 再转手.</p><p><strong>需求文档到手</strong></p><ol><li>从需求文档中找到需求点, 协商好需求点由哪个服务器来处理, 防止开发重复内容或者无人处理. 或者由于种种原因是对方压根做不了, 只能自己来做.</li><li>尽量避免想当然的情况, 比如文档中说每天增加一次挑战次数, 但是没有提初始次数是多少次, 这时候最好沟通下.</li><li>虽然可能不清楚其他需求, 但还是尽量能够判断下和其他需求是否有重叠, 导致出现边界情况, 对好这个时候怎么处理.</li></ol><p><strong>需求文档看完, 开工之前, 先定协议</strong></p><p>定完协议之后, 各个参与人就可以独立开发. 如果先开发再定协议, 可能导致后定的协议不便于所有人使用.</p><p><strong>开工之前, 进行下预期设计, 同时评估时间</strong></p><ol><li>写代码之前先在vscode进行下预期设计, 写写伪代码. 虽然需求可能要的比较紧急, 但花一点时间提前设计下, 比直接撸起袖子开干还是感觉最终效率好很多.<ol><li>代码放到哪个模块</li><li>这个功能大概是什么流程</li></ol></li><li>预期设计完成后, 根据<strong>功能点和设计</strong>, 能够更加准确的进行排期.</li><li>根据需求工作量确定下复杂程度. 虽然完善的设计很重要, 但有时会因为过于完善, 导致简单的需求变得非常复杂, 同时后续也不一定用得上这次设计的完善机制.</li><li>预期下异常情况处理和兜底处理</li></ol><h2 id="制作"><a href="#制作" class="headerlink" title="制作"></a>制作</h2><p><strong>将重复的项目抽取出来</strong><br>第一次做的时候, 可能考虑到需求比较简单或者时间不足, 一个功能就放在了一起实现, 但是后续又双叒叕用到这个功能了.对于个人提升来说, 就可以考虑抽取出来了.</p><p><strong>做好兼容性处理</strong></p><p>负责的功能要将兼容性处理做完善, 能够做到其他人无感知是最好的. 不要想着其他人只需要小小的一点处理就能解决, 到时候不管提示再多次都会有人@问什么问题的, 所以最优解是其他人无感知.</p><p><strong>预估时间后无法完成后, 要及时周知相关人员</strong></p><p>应该及时通知,否则默认你这边完成了, 其他人进行了一些处理, 导致不必要的问题出现.</p><p><strong>开发的时候加足DEBUG日志</strong></p><ol><li>遇到问题的时, 查日志相对看代码是效率非常高的解决方式.</li><li>加日志的位置和内容还是吃经验. 个人感觉站在将来查问题的角度去加日志有帮助.</li><li>DEBUG日志一般会在线上关闭, 一些重要内容就要注意使用更高的等级, 否则一点日志都没, 外网问题全靠复现才能查.</li></ol><p><strong>个人统一的工具仓库</strong><br>工作中难免会写出来一些小工具, 提供给他人使用的时候一般都是放到公共代码库中, 这个时候工具就会在本地和公共代码库有两份, 注意只在一个地方修改, 否则容易出现不一致.</p><p><strong>需求中途变更后简化代码</strong></p><p>代码设计初期一般会留有一些考虑能够应对需求变化,但是需求变化后可能会导致实现可以简化,这个时候继续复用复杂的代码还是简化代码就需要考虑了.</p><p><strong>代码尽量一步成型</strong><br>个人感觉修改次数越多, 出问题的概率越高, 修改了这里忘记了同步修改其他地方是重灾区.</p><p><strong>避免设计无用的东西</strong><br>一些本来加个函数就能解决问题的, 是否要抽成一个模块就要仔细考虑下了.</p><p><strong>数值类配置化or计算化</strong></p><ol><li>数值类的最好不要写死<ol><li>如果是配置中的值,配置变了,写死的数值就会导致问题</li><li>还可能导致本来热更就能搞定的,需要重新编译服务器</li></ol></li><li>尽量不要写死参数, 配置或者计算得来, 否则后面还需要同步修改.</li></ol><p><strong>减少通用错误码的使用</strong></p><p>如果错误码一对一能够及时发现问题, 如果是多个地方使用的, 只能靠日志+看代码路径了.</p><p><strong>客户端参数校验</strong></p><p>老生常谈了, 基本上每本书里都会提这点, 这次就出现了所有服务器都没校验的一个操作, 甚至是客户端都没检验.</p><p><strong>状态校验放错了位置. 写的时候还是没考虑好运行路径</strong></p><p>加代码的时候, 仔细考虑好相关的运行路径. 可能某个分支下, 这次加的就是有问题的.</p><p><strong>加快调试速度</strong></p><p>需求写完之后, 想要测试下自己的代码, 尽量将GM工具完善好, 否则每次修改代码都要重复操作好几步, 导致花费的时间反而比加一下工具要多.</p><ul><li>副作用, GM工具要标注下使用场景, 否则错误场景使用后容易出乌龙问题.</li></ul><h1 id="史山之从修改到回归"><a href="#史山之从修改到回归" class="headerlink" title="史山之从修改到回归"></a>史山之从修改到回归</h1><h2 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h2><p><strong>修改前-不确定是bug还是feature时要进行确认</strong><br>否则可能将feature作为bug处理了, 后续还要改回来, 造成不必要的时间浪费或者麻烦.</p><p><strong>修改前-遇到BUG还是感觉留下现场,比重启解决问题更加重要</strong></p><p>问题可以后面解决,复现问题可能再也没有机会了</p><p><strong>修改前-简单方式解决问题</strong></p><p>对于一个小bug, 简单的打个补丁就可以解决的话, 还是偏向于打补丁. 至于刨根问题解决虽然好, 但是一旦牵扯过多, 然后自己对项目不了解, 大刀阔斧重构容易出问题.</p><p><strong>查问题中-用物体的事件经过查问题挺方便的</strong></p><p>事件驱动的场景下, 可以先查询顶层都有那些事件, 看看这些事件的内容和实际是否正确, 比一下钻到底层查问题要快很多.</p><p><strong>影响扩大-确定修改后果, 注意一个函数都有哪些作用</strong></p><ol><li>一个函数可能不是你想的那种作用, 或者带有一些条件, 再或者是副作用. 之前没有使用过的, 不熟悉的最好看一看实现.</li><li>修改一处代码的时候, 考虑全面是否会波及其他调用方</li></ol><p><strong>影响扩大-抽取函数注意不要影响到原区域功能</strong></p><p>发现一段代码可以复用的时候, 会从其他函数中将这个函数抽取出来. 但是抽出来的函数增加代码的时候, 注意不要影响到原位置. 如在抽取出来的函数中, 由于其他需求, 增加了一层判断, 导致原位置(抽取函数前的位置)功能被影响.</p><p><strong>影响扩大-enum中增加新类型</strong></p><p>enum中增加新类型后要注意看看这个enum的使用位置, 如果只是关注自己新写的代码忽略旧代码, 可能导致旧代码的判断过不去.</p><p><strong>修改后-BUG修复关联BUG单</strong></p><p>否则后续会忘记为啥修改这里, 当时是什么BUG做出的这样的处理.</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p><strong>尽可能进行全面的自测</strong></p><p>真的有某些代码是依靠BUG运行的, 修复BUG之后反而运行不了了, 所以基本的测试是必需的, 能想到的可能影响到的地方, 最好也是自测一下. 想到的地方不去测一下的话, 可能就是想到的这个地方出了问题.</p><p><strong>A通过B通过不等于A+B通过</strong></p><p>一个功能还是完整的测试吧,A功能虽然等于B+C功能,但B和C功能分别正确 不一定B+C就是正确的</p><p><strong>自动化测试</strong></p><p>一些功能测起来可能比较麻烦, 使用自动化工具会方便很多, 比如pyclient完全模拟一个客户端, 写代码有时候比点来点去构建测试环境方便很多.</p><h1 id="积累"><a href="#积累" class="headerlink" title="积累"></a>积累</h1><p><strong>问题的优先级安排</strong></p><p>并行处理任务的时候, 注意优先级的安排. 可能当前的工作进行到一半, 有其他高优先级的工作要做的话, 还是要切过去. 尤其是查BUG到一半的时候, 很容易上头, 导致又多查了半天.</p><p><strong>充分关注自己的工作</strong></p><p>其他人都开始要联调了, 自己的功能还没发布到服务器上. 虽然当时在处理其他问题, 但还是已经做过的善后优先一些.</p><p><strong>不懂就问</strong></p><p>文档较少, 全靠口口相传, 遇到不懂得项目, 一定要进行询问, 不能常识性的去做.</p><p><strong>重视不起眼的小问题,可能背后的原因是非常离谱的</strong></p><p>遇到过不止1-2次了, 看起来就是很小的一个问题, 深究起来问题可能一串一串的.</p><p><strong>特殊处理</strong></p><p>没有注释的代码, 可能使用了一些特殊规定, 如果错误删掉了就会影响到功能. 比如数值是0的时候, 按照1来处理, 看描述很简单, 但是实际代码可能没有那么直观.</p><p><strong>预期下当前所做是不是可以解决最终问题, 而不是当前某一步</strong></p><p>当前所做可能确实能解决当前问题, 但是不一定能解决最终问题. 比如传输日志之前需要压缩, 压缩成几个小包也是压缩, 压缩成超大包也是压缩, 但是压缩成超大包可能不利于传输和解压.</p><p><strong>去看看别人负责的模块, 不要只顾自己的模块</strong></p><p>对于多模块(多服务器)后台, 多去关注下自己负责模块之外的内容, 这样对项目了解的更深.</p><p><strong>问题的原因, 不一定是第一印象想到的点</strong></p><p>遇到问题即使下意识感觉问题就是那里, 也一定要确认下. 可能并不是那样.</p><h1 id="协作"><a href="#协作" class="headerlink" title="协作"></a>协作</h1><p><strong>他人回复的内容一定要仔细理解,不要含主观臆断</strong></p><ol><li>X场景下,……………………………(省略),这个功能就不需要了。(非X场景是需要的,不能直接删掉这个功能)</li><li>今天晚上就合入版本了(几点?能不能在全量发布服务器前完成,而不是晚上这种模糊时间)</li></ol><p><strong>后台开发代表全部后台, 前台开发代表全部前台</strong></p><p>后台有多个服务器,每个服务器不同的人负责,前台可能认为后台是一个整体,所以找你沟通的时候最好不要只考虑本服务器的事情, 有需要就拉上其他服务器的一起建群.</p><p><strong>帮忙</strong></p><p>帮忙处理东西的时候,要问清楚原因, 搞清楚要做啥, 不能只是直接照做.</p><p><strong>对应的事情尽量给对应的人去做</strong></p><p>运维可以通过内网传输日志, 比通过sz和rz快和稳定很多.</p><h1 id="36技"><a href="#36技" class="headerlink" title="36技"></a>36技</h1><p><strong>CR发起前可以自己整一个临时CR看看代码</strong></p><p>每次提交代码前, 全局的看一看自己的代码, 可能功能正确, 但是有不小心动到其他地方的代码.</p><p><strong>测试代码在最终CR的时候要及时去掉 使用TODO 名字方便检索</strong></p><p>使用TODO标记测试代码, 提交前批量查找下, 全部去掉.</p><p><strong>不建议手动操作流水线</strong></p><p>流水线中某一步错误后, 尽量重新触发流水线. 手动执行错误的一步, 很容易出错, 漏掉某些操作.</p><p><strong>自动化操作没有监管人</strong></p><p>流水线有的没有人监管, 即使将结果通知到了使用者, 使用者可能也会忽视结果, 导致有错误没有发现, 进而导致其他问题. 所以错误提示要尽可能的明显.</p><p><strong>压测和扩容</strong></p><ol><li>压测<ol><li>压测场景和实际场景不匹配<ol><li>出现问题之后,都能发现问题,关键是出现问题前发现限制点</li></ol></li></ol></li><li>扩容<ol><li>重要节点前, 预备一些机器, 否则出问题的时候, 机器没办法即时拿到.</li></ol></li></ol><p><strong>发现问题比解决问题更重要</strong></p><p>性能优化感觉难点是发现性能问题, 包括编译加速. (20%的问题造成了80%的负面影响, 如果去处理另外80%的问题收益就很低)</p><p><strong>指定时间点触发的循环定时器, 每轮都计算下时间相比固定时间的更加稳定</strong></p><p><strong>尽量统一函数对统一内容进行清除, 比如标志位, 这样方便发现错误清理的地方</strong></p><p><strong>测试东西或者搞新东西的时候, 注意不要影响到旧功能</strong></p><p>该开测试空间的开测试空间, 防止影响到其他空间</p>]]></content>
<summary type="html">
<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>记录总结了从7月到11月积累的非技术经验, 涉及添砖加瓦方方面面.</p>
<h1 id="前言"><a href="#前言" class=
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="UP" scheme="http://blog.lsmg.xyz/tags/UP/"/>
</entry>
<entry>
<title>如何实现火焰图半自由</title>
<link href="http://blog.lsmg.xyz/2023/11/%E9%A1%B9%E7%9B%AE%E5%88%B6%E4%BD%9C-PrometheusX%E7%81%AB%E7%84%B0%E5%9B%BE/"/>
<id>http://blog.lsmg.xyz/2023/11/%E9%A1%B9%E7%9B%AE%E5%88%B6%E4%BD%9C-PrometheusX%E7%81%AB%E7%84%B0%E5%9B%BE/</id>
<published>2023-11-18T12:50:02.000Z</published>
<updated>2025-11-29T09:26:42.712Z</updated>
<content type="html"><![CDATA[<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>Linux服务器端可以使用profile生成火焰图. profile从运行开始收集数据, 结束运行时将结果输出, 过了这村就没这店.</p><p>有没有可能持续使用profile进行采集, 将数据收集起来, 然后查询任意时间段的火焰图?</p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>答案是√, 使用Prometheus存储profile采集的数据, 使用Grafana的火焰图插件(V9.5.2)将结果展示出来. <strong>本文结束, 感谢观看!</strong></p><h1 id="要解决的问题"><a href="#要解决的问题" class="headerlink" title="要解决的问题"></a>要解决的问题</h1><ol><li><p>profile的原始数据是栈调用(隐含时间信息), 火焰图是树状结构(丢失时间信息), 如果想要查询任意时间段就要存储原始栈调用数据和其对应的时间.</p></li><li><p>Grafana提供了火焰图插件, 但是只接受火焰图数据, Prometheus中存储的原始栈数据无法直接使用.</p></li><li><p>Grafana和Prometheus对字符串的处理能力极差</p></li></ol><p>所以需要实现一个Proxy, 接受Grafana的查询请求, 转去请求Prometheus, 将结果进行处理后返回Grafana进行展示.</p><h1 id="PrometheusProxy实现"><a href="#PrometheusProxy实现" class="headerlink" title="PrometheusProxy实现"></a>PrometheusProxy实现</h1><h2 id="profile输出"><a href="#profile输出" class="headerlink" title="profile输出"></a>profile输出</h2><p><code>threadname;stack0;stack1;stack2 num</code></p><p><code>threadname;stack1;stack4;stack2 num</code></p><p>线程名, 栈帧, 采集到的数量. 这些数据将会上报到Prometheus中进行存储.</p><p>上报的格式需要依照Prometheus要求进行下格式化, 这里就不详细介绍了, 只需要知道Prometheus中存储了这些原始的数据(时间, 栈, 对应采集的次数)即可.</p><h2 id="Grafana查询协议"><a href="#Grafana查询协议" class="headerlink" title="Grafana查询协议"></a>Grafana查询协议</h2><pre><code class="txt"># URL/api/v1/query_range# BODYend=1694848740&query=cpu_func_profile{processname="HelloServer"}&start=1694827140&step=15</code></pre><p>start 起始时间<br>end 结束时间<br>query 向Prometheus请求的表达式<br>step 步长</p><h2 id="Prometheus协议"><a href="#Prometheus协议" class="headerlink" title="Prometheus协议"></a>Prometheus协议</h2><pre><code class="go">// Prometheus请求协议即是上面的Grafana协议// Prometheus回应协议是如下结构对应的json数据type PrometheusReportData struct { Status string `json:"status"` Data struct { ResultType string `json:"resultType"` Result []struct { Metric struct { Name string `json:"__name__"` Instance string `json:"instance"` Job string `json:"job"` Stack string `json:"stack"` } `json:"metric"` Values [][]any `json:"values"` } `json:"result"` } `json:"data"`}</code></pre><p>将完整的Grafana请求转发给Prometheus, Prometheus会进行筛选和合并后返回, 所以无需对结果进行额外处理, 只需要从其中拿到汇总后的栈数据即可.</p><p>stack: 这个是我们上报的栈数据<code>stack0;stack1;stack2</code></p><p>Values: 是Value的数组, Value有且仅有两个元素, 第一个元素是数据对应的时间戳, 第二个元素是栈数据的<code>num</code></p><p>所以遍历所有数据统计出一个<code>map<stack, num></code>, Prometheus的任务就完成了.</p><h2 id="Grafana火焰图插件回包json格式"><a href="#Grafana火焰图插件回包json格式" class="headerlink" title="Grafana火焰图插件回包json格式"></a>Grafana火焰图插件回包json格式</h2><pre><code class="go">type Metric struct { Name string `json:"__name__"` Instance string `json:"instance"` Job string `json:"job"` Label string `json:"label"` Level string `json:"level"` Self string `json:"self"` Value string `json:"value"`}type Result struct { Metric Metric `json:"metric"` Values string `json:"values"`}type PrometheusFlameData struct { Status string `json:"status"` Data struct { ResultType string `json:"resultType"` Result []Result `json:"result"` } `json:"data"`}</code></pre><p>PrometheusFlameData的其他数据从Prometheus的回包中拷贝过来即可, 重要的是PrometheusFlameData.Result</p><p>Result.Values: 是Value的数组, Value有且仅有两个元素, 第一个元素是数据对应的时间戳, 第二个元素是栈数据的<code>num</code>, <strong>这个已经不重要了, 火焰图并不需要每个节点的时间, 可以任意填充.</strong>, 如<code>[[1684829000,"1"]]</code></p><p>Result.Metric: 将栈数据转换成多叉树后, 前序遍历节点后得到的结果.</p><ul><li>Label: 函数名称(节点名称)</li><li>level: 层级</li><li>self: 自己作为栈最后一帧的数据数量</li><li>value: 自己和子节点所有self的合</li></ul><h2 id="PrometheusProxy处理"><a href="#PrometheusProxy处理" class="headerlink" title="PrometheusProxy处理"></a>PrometheusProxy处理</h2><p>将Grafana的请求原封不动, 转发给Prometheus进行处理, Prometheus数据返回后Proxy进行处理转换为火焰图插件数据格式, 进而返回给Grafana.</p><h1 id="需要注意的点"><a href="#需要注意的点" class="headerlink" title="需要注意的点"></a>需要注意的点</h1><h2 id="Prometheus存储数据的原理"><a href="#Prometheus存储数据的原理" class="headerlink" title="Prometheus存储数据的原理"></a>Prometheus存储数据的原理</h2><p>从上文<code>Prometheus协议</code>的格式来看, stack部分和values部分是分开的, 如果一个stack在多个时间被采集到, 只会存储新数据的时间和次数到values中.</p><p>这里提到的stack实际是一个字段, 此外还有线程名, 进程名等字段. 如果一整条上报数据是一样的, Prometheus只会存储时间和次数部分, 如果整条上报数据有一丝一毫差别 就会导致整条数据被记录一次. 导致空间占用暴增(因为stack很长).</p><p>说人话就是不要在字段中增加随机值, 否则会导致上报数据无法复用, 每次上报都要存储完成数据, 导致存储量爆炸.</p><h2 id="C-编译选项"><a href="#C-编译选项" class="headerlink" title="C++编译选项"></a>C++编译选项</h2><p>profile打印出完成的栈结构 需要在程序编译的时候 指定<code>-fno-omit-frame-pointer</code></p><h2 id="针对指定的URL和内容过滤"><a href="#针对指定的URL和内容过滤" class="headerlink" title="针对指定的URL和内容过滤"></a>针对指定的URL和内容过滤</h2><p>Grafana查询的URL是<code>/api/v1/query_range</code>, 对应的标签是cpu_func_profile, 可以只针对这种情况处理.</p><p>其他情况使用doProxy直接转发, 否则无法使用自动补全等一些提示</p><h2 id="profile采集频率"><a href="#profile采集频率" class="headerlink" title="profile采集频率"></a>profile采集频率</h2><p>由于profile需要消耗机器CPU, 可以采用每隔X分钟后运行Y分钟的profile, 将数据格式化后供Prometheus提取.</p><h2 id="半自由"><a href="#半自由" class="headerlink" title="半自由"></a>半自由</h2><p>火焰图准确与否与采集频率有很大关系, profile持续运行会消耗很多的CPU, 所以目前是采取了运行X分钟停止Y分钟的做法, 如果能持续运行profile就能解决这个问题.</p><h1 id="扩展-倒转火焰图"><a href="#扩展-倒转火焰图" class="headerlink" title="扩展-倒转火焰图"></a>扩展-倒转火焰图</h1><pre><code class="txt">func1#func2#func3func1#func3func2#func3func4#func3</code></pre><p>如上四个调用栈, 从火焰图上无法轻易看出来func3占用了过多的CPU, 换一种思路将栈倒过来, 就能发现func3占用了过多CPU, 这一步操作只需要在PrometheusProxy中调用一个函数倒转切割后的栈,就能实现.</p>]]></content>
<summary type="html">
<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>Linux服务器端可以使用profile生成火焰图. profile从运行开始收集数据, 结束运行时将结果输出, 过了这村就没这店.</p>
</summary>
<category term="项目制作" scheme="http://blog.lsmg.xyz/categories/%E9%A1%B9%E7%9B%AE%E5%88%B6%E4%BD%9C/"/>
</entry>
<entry>
<title>写博客技巧</title>
<link href="http://blog.lsmg.xyz/2023/11/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E5%86%99%E5%8D%9A%E5%AE%A2/"/>
<id>http://blog.lsmg.xyz/2023/11/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E5%86%99%E5%8D%9A%E5%AE%A2/</id>
<published>2023-11-15T10:38:08.000Z</published>
<updated>2025-11-29T09:26:42.707Z</updated>
<content type="html"><![CDATA[<h1 id="写博客技巧"><a href="#写博客技巧" class="headerlink" title="写博客技巧"></a>写博客技巧</h1><h2 id="文章分类和特点"><a href="#文章分类和特点" class="headerlink" title="文章分类和特点"></a>文章分类和特点</h2><p><strong>分类</strong></p><ul><li>大众科普</li><li>方案创新</li><li>项目总结</li><li>深度技术</li></ul><p><strong>特点</strong></p><ul><li>受众多样</li><li>通俗大众</li><li>传播属性</li><li>格式灵活</li></ul><h2 id="写文章的价值和作用"><a href="#写文章的价值和作用" class="headerlink" title="写文章的价值和作用"></a>写文章的价值和作用</h2><ol><li>温故而知新</li><li>便于传播分享, 及时发现问题</li><li>提高技术影响力</li></ol><h3 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h3><ol><li>事前<ol><li>同理心, 确定目标读者</li><li>攒素材, 积累文章素材</li><li>定好题, 确定文章主题</li><li>搭骨架, 确定文章结构</li></ol></li><li>事中<ol><li>画好图, 绘制文章配图</li><li>有干货, 提升文章内容</li><li>勤润色, 反复润色文章</li></ol></li><li>事后<ol><li>练内功, 扩大阅读范围</li></ol></li></ol><h4 id="同理心-认知偏差"><a href="#同理心-认知偏差" class="headerlink" title="同理心-认知偏差"></a>同理心-认知偏差</h4><p>了解之后就难于理解不了解人的想法, 下意识认为对方已经了解某些知识点</p><ul><li>一些基础概念, 读者不一定了解</li><li>因果关系需要更进一步解释</li><li>站在读者角度考虑, 最好的方式是找没有背景同学检验</li><li>面相受众 What, Why, How</li><li>外行人看不懂, 内行人没必要看</li></ul><p>检测知识的最终途径, 将其传播给另一个没有背景的人</p><h4 id="攒素材"><a href="#攒素材" class="headerlink" title="攒素材"></a>攒素材</h4><p>写文章的时候捉襟见肘, 不要在决定写文章的时候 才去找素材. 素材积累之后可以整理出来.</p><p>有些粗粗看过的, 可能含有非常多的点</p><h4 id="定好主题"><a href="#定好主题" class="headerlink" title="定好主题"></a>定好主题</h4><p>确定主要范围, 不要歪楼, 不相关的简单介绍.</p><p>确定知识边界, 普适性的文章, 不需要从识字教起.</p><ul><li>工作总结</li><li>学习材料</li><li>热点话题</li><li>观察生活</li><li>深挖本质</li><li>巧用对比</li></ul><p>主题上升, 和更大的主题联系起来, 变归纳为聚焦</p><p>主题写明动作和动机</p><p>结构化思维: 穿线(讲通, 将多个知识点联系起来, 如从电路讲到程序运行), 归纳(小主题归纳到一个大范围,看看范围都有啥), 深挖(逐字逐句), 聚焦(只讨论关注的点, 将关注的点讲明白)</p><p>获取主题灵感: 发散思维, 逻辑思维, 同理心. 在平时多去关注一些</p><p>确认分享欲, 搜索相关主题(穿线很少), 思考对其他人是否有价值, 属于那种灵感来源, 主题上升, 标题是否是文章主要内容的一句话概括</p><p>标题小技巧, 图片中</p><h4 id="搭骨架"><a href="#搭骨架" class="headerlink" title="搭骨架"></a>搭骨架</h4><p>主题有了 素材有了</p><p>有规律 贴近旧有认知 利于大脑记忆</p><p>金字塔原理: 结构化思考, 沟通, 写作. 一本大厚书 芭芭拉明托</p><p><strong>结构化思维</strong></p><ul><li><p>梅切原则: 相互独立(没有交集), 完全穷尽(没有空隙)</p></li><li><p>简单性原理: 文章不需要面面俱到, 主线不相关内容, 可以考虑删减. 结构简单, 文字简省.</p></li><li><p>不要用复杂的东西解释另一个复杂的东西, 文章结构不要太复杂(精简层数 每层中case精简)</p></li></ul><h4 id="画好图"><a href="#画好图" class="headerlink" title="画好图"></a>画好图</h4><p>字不如数, 数不如表, 表不如图. 人脑处理图片相比文字更快</p><p>配图不一定需要时专业的, 主要目的是帮助理解 不一定需要多么严谨, 放在文章中能说明问题就好.</p><h4 id="有干货"><a href="#有干货" class="headerlink" title="有干货"></a>有干货</h4><p>取决于文章主题, 是否是水货</p><h4 id="勤润色"><a href="#勤润色" class="headerlink" title="勤润色"></a>勤润色</h4><p>语言晦涩, 排版不好, 多迭代, 反复打磨文章</p><p>语言精简(不能无脑删减), 内容分段, 准确(善用专业词汇 鼠标点两下(双击), 准确数字(90%)), 生动, 例子(不求多 命中要害, 极端例子 说明问题)</p><p>发表之前至少自己读一次. 善用AI润色, 重新解读不要机翻 机械搬</p><h4 id="练内功"><a href="#练内功" class="headerlink" title="练内功"></a>练内功</h4>]]></content>
<summary type="html">
<h1 id="写博客技巧"><a href="#写博客技巧" class="headerlink" title="写博客技巧"></a>写博客技巧</h1><h2 id="文章分类和特点"><a href="#文章分类和特点" class="headerlink" title=
</summary>
<category term="学习记录" scheme="http://blog.lsmg.xyz/categories/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"/>
</entry>
<entry>
<title>UP月记录</title>
<link href="http://blog.lsmg.xyz/2023/10/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E6%9C%88%E8%AE%B0%E5%BD%95/"/>
<id>http://blog.lsmg.xyz/2023/10/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP%E6%9C%88%E8%AE%B0%E5%BD%95/</id>
<published>2023-10-27T09:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<h1 id="月记录备份"><a href="#月记录备份" class="headerlink" title="月记录备份"></a>月记录备份</h1><h2 id="2023-07"><a href="#2023-07" class="headerlink" title="2023-07"></a>2023-07</h2><p>以后写工具应该注意,一个工具放两个代码库维护是容易出问题的,能统一尽量统一,统一不了应该只在一个里面修改</p><ol><li>对方回复的内容一定要仔细理解,不要含主观臆断<ol><li>X场景下,……………………………(省略),这个功能就不需要了。(非X场景是需要的,不能直接删掉这个功能)</li><li>今天晚上就合入版本了(几点?能不能在全量发布服务器前完成,而不是晚上这种模糊时间)</li></ol></li><li>需求中途变更<ol><li>代码设计初期一般会留有一些考虑能够应对需求变化,但是需求变化后可能会导致实现可以简化,这个时候继续服用复杂的代码还是简化代码就需要考虑了</li></ol></li><li>多沟通协调<ol><li>后台有多个服务器,每个服务器不同的人负责,前台可能认为后台是一个整体,所以找你沟通的时候最好不要只考虑本服务器的事情。<ol><li>我这里完成了 前台认为所有服务器完成了</li></ol></li><li>对方代码还没合入的时候,你说对面完成了,结果实际没完成</li></ol></li><li>测试<ol><li>一个功能还是完整的测试吧,A功能虽然等于B+C功能,但B和C功能分别正确 不一定B+C就是正确的<ol><li>pyclient加上这个还是很方便的</li></ol></li></ol></li><li>GM工具<ol><li>GM工具好多都是有使用场景的,非使用场景使用会出现问题,所以还是要标注清楚</li></ol></li><li>流水线异常<ol><li>流水线异常的时候手动操作了报错部分,然而报错导致后续操作也被中断了,但是忘记了操作后续部分,只操作了报错部分<ol><li>流水线都现成的了 直接用吧 别手动操作了、</li></ol></li><li>新版服务器未更新到目标服务器 出了N个乌龙BUG单</li></ol></li></ol><h2 id="2023-08"><a href="#2023-08" class="headerlink" title="2023-08"></a>2023-08</h2><ol><li>兜底方案<ol><li>当时在众多人说自己无法进入游戏的时候,都没有去考虑补上兜底方案<ol><li>因为当时考虑到这些都是异常情况,正常情况玩家地图上是不会有自己的主城,只要是正常环境就没有问题</li><li>然而遇到了Logic崩溃了,Logic没有记录选州成功,此时大地图已经选州了,玩家游戏直接卡死,之后才把兜底补上。这次就不是异常情况了,是正常情况下可能会出现的问题了。</li></ol></li></ol></li><li>一个不起眼的小问题,可能背后的原因是非常离谱的。<ol><li>dev环境选州Logic崩溃,竟然是由于Logic代码写错的原因(为啥外网没有遇到呢?)</li><li>测试反馈添加的主城全部报错30000,结果是因为roleId循环的,已经添加主城的roleId再次添加主城,由于兜底机制+指定roleId主城存在就会将roleId顺延,顺延之后的就是空roleid,后面就拿不到离线时间的数据</li></ol></li><li>注重系统整体架构的设计<ol><li>有输入相比自己死磕能够成长的更快<ol><li>看看其他人的方案拆解<ol><li>不能同一帧将所有点位检查这种情况,才想到要延时进行刷新。</li><li>不能是单单的将需求点列出来,需要对应到具体的改动是啥,这样评估时间才准确。</li><li>注意下时间评估这里,目前我评估的时间还是非常的不准确的</li></ol></li><li>看看其他人的cr,不然自己没有负责过的模块 是一点都不清楚<ol><li>同时看了之后还有和其他人PK的机会</li><li>看看其他人的设计,学一学自己将来才可能遇到,不然都是在自己思维下兜圈子</li><li>看看别人的CR和方案,这里为什么这么设计,自己想的话如何设计,一下对比就出来了。进行后续沟通还能了解到更多。</li></ol></li></ol></li><li>之前看到帝国觉醒只是想到了战斗服需要在压力场景下减少发包<ol><li>但是大地图是不是需要呢?完全没有考虑过,大地图是否需要<ol><li>大地图实际是不需要的,至少目前的同步机制是够用的</li><li>战斗服可能是需要的,不过后面就没跟进了解了</li></ol></li><li>24年2月又做了不下发其他人的PET, 我为什么没想到呢?</li></ol></li><li>千人测试的时候,重启城郊会导致战斗服也需要重启的问题</li><li>千人测试的时候,Logic崩溃导致事件完成了任务没有完成</li></ol></li><li>输入<ol><li>代码</li><li>CR</li><li>方案拆解和评估</li><li>KM文章</li><li>开源项目</li></ol></li><li>注意点(不要将问题带到线上,这样处理起来非常的麻烦,而且会增加不靠谱)<ol><li>异常情况处理</li><li>兜底</li><li>自测</li></ol></li><li>需求拆解和方案评估<ol><li>不能是单单的将需求点列出来,需要对应到具体的改动是啥,这样评估时间才准确。</li><li>注意某些重要的异常情况处理和兜底处理</li></ol></li><li>之前删掉了一段代码,导致城郊出现问题,忘记为啥删除的了,后面修BUG还是带上BUG单的链接把<ol><li>没有强制检查, 保持的不太好</li></ol></li><li>防御性编程,校验外部传进来的参数</li><li>野怪最大等级改了获取位置,写代码的将等级为0认为是错误,直接返回了, 对于能跑的屎山小心动.<ol><li>然而赏金这里有特殊处理,等级是0则按1级</li></ol></li></ol><h2 id="2023-09"><a href="#2023-09" class="headerlink" title="2023-09"></a>2023-09</h2><ol><li>遇到BUG还是感觉留下现场,比重启解决问题更加重要<ol><li>问题可以后面解决,复现问题可能再也没有机会了</li></ol></li><li>编码规范<ol><li>空指针这种错误都犯了<ol><li>改一个位置的时候,尤其要注意使用的所有参数,这些参数可能在外面并没有校验</li><li>改代码比新写代码更容易出现</li></ol></li></ol></li><li>帮忙<ol><li>帮忙处理东西的时候,一定要搞清楚,问清楚。自己也要看清楚,不能只是不带脑子的执行了。</li></ol></li><li>注意可能发生死循环代码的兜底<ol><li>服务器更新脚本中就遇到了,重复的更新服务器。</li></ol></li><li>数值类的最好不要写死<ol><li>如果是配置中的值,配置变了,写死的数值就会导致问题</li><li>还可能导致本来热更就能搞定的,需要重新编译服务器</li></ol></li><li>可能还是要减少通用错误码的使用, 兜底的通用错误码竟然用到了.</li><li>客户端参数校验<ol><li>没想到出现了非联盟成员拆联盟建筑的问题。</li></ol></li><li>压测<ol><li>压测场景和实际场景不匹配<ol><li>出现问题之后,都能发现问题,关键是出现问题前发现限制点</li></ol></li><li>扩容<ol><li>寻路服爆炸了,然而没有办法扩容,没有机器。</li></ol></li></ol></li><li>重新编译的问题,没想到还学到了不少。<ol><li>tars文件重新生成的问题<ol><li>先用md5比对写了一版</li><li>结果还是直接用CMake写最好,几行的事情</li></ol></li><li>CMake和Make的基本原理</li><li>依赖分析,编译加速?</li></ol></li></ol><h2 id="2023-10"><a href="#2023-10" class="headerlink" title="2023-10"></a>2023-10</h2><ol><li>迁移日志<ol><li>最后日志传输工作给了运维来搞,自己搞还是太麻烦了<ol><li>该找运维的找运维了</li></ol></li><li>不要压缩超大包,或者进行分卷了<ol><li>解压起来对磁盘要求太高了(预期下当前所做是不是可以解决问题, 而不是只顾当前压缩)</li></ol></li></ol></li><li>性能优化感觉难点是发现性能问题, 包括编译加速. (20%的问题造成了80%的负面影响, 如果去处理另外80%的问题收益就很低)</li><li>下午看了看相关的博客,还是感觉要坚持输入一些内容,整理整理哪些可以看吧<ol><li>阮一峰, 内部论坛</li></ol></li><li>接触了下战斗服和逻辑服的代码, 把相关环境也搭建好了, 后续也确实看过几次对应的代码.</li><li>终于把之前想过的时间触发器抽了个Module出来, 后面就可以复用了. 写Module之前vscode中写下预期设计挺好的, 继续坚持了.</li><li>做事之前尽量确认好, 不然竹篮打水一场空, 时间浪费不少.<ol><li>需求理解一定要正确, 仔仔细细逐句逐句的看需求点.</li><li>看看需求的特殊要求, 比如退盟之后是否重置, 这个影响到了数据记录到哪里.</li><li>还得考虑下客户端是否支持</li><li>考虑下实现的复杂程度, 可能预期很简单, 但是因为牵扯过多或者不支持导致变得复杂.</li></ol></li><li>指定时间点触发的循环定时器, 每轮都计算下时间相比固定时间的更加稳定.</li><li>主城周围搜索物体, 采用涡旋状搜索, 类似蚊香.</li><li>有的功能大地图并不知道有没有, 还是说下不知道之类的吧, 最后接下来了发现是别人的工作.</li><li>代码BUG<ol><li>显性BUG, 状态校验放错了位置. 写的时候还是没考虑好运行路径.</li><li>隐形BUG, 抽函数后加东西, 导致相对于原有增加了一些功能, 这种是很危险的抽函数.</li></ol></li><li>看到小问题的时候 深究深究就会发现可能并不是那么简单<ol><li>转表工具报错, 空白列占位</li></ol></li><li>尽量统一函数对统一内容进行清除, 比如标志位, 这样方便发现错误清理的地方</li><li>指针判空, break<ol><li>这次是在JavaScript中没有判空, 有的block没有数据</li></ol></li><li>遇到问题即使下意识感觉问题就是那里, 也一定要确认下. 可能并不是那样.</li><li>如果解决一个小bug, 完美的方式改动很多的话, 感觉不如简单的处理下.<ol><li>长久来说, 还是要考虑下解决这个问题, 算是一个优化点? </li></ol></li><li>尽量不要写死参数, 配置或者计算得来, 否则后面还需要同步修改.</li><li>测试东西或者搞新东西的时候, 注意不要影响到旧的东西, 该开测试空间的开测试空间<ol><li>这次自动提单提了2K+, 如果不是提到了临时空间 估计直接爆炸了.</li></ol></li></ol><h2 id="2023-11"><a href="#2023-11" class="headerlink" title="2023-11"></a>2023-11</h2><ol><li>配置化<ol><li>IDC的流水线配置化了, 一些写死的内容移到了外面, 同时把流水线进行了统一, 不然一下改几条流水线直接升天</li></ol></li><li>查BUG<ol><li>仔细检查触发条件, 为什么这个场景触发了, 其他类似场景没触发, 这两个场景看起来很相似, 哪里有区别</li></ol></li><li>隐形问题, <strong>只有监控没有报警or明显提醒, 几乎相当于没有监控</strong>.<ol><li>磁盘炸了, 服务器对外无法提供服务.</li></ol></li><li>降本增效<ol><li>对于非重要数据, 用了Prometheus的双写服务, 把数据向自己搭建的服务器上同步一份, 这样就不用在腾讯云存储太长时间</li></ol></li><li>写BUG<ol><li>城郊里的功能 没有加开关, 影响到了大地图</li></ol></li><li>坚持记录每天所做所想, 不然忘了自己做了啥还是难受.</li><li>公共位置要使用公共账号和密码, 不把自己账号密码存进去, 容易出问题</li><li>当时看到经分文档里两个说明很别扭, ABBA格式, 不是ABAB, 想着容易出错, 结果真的把AB用反了.</li><li>7月就记录过了, GM工具标注使用场景, 结果到了11月还是没几个工具标注, 复习下.</li><li>JPS寻路算法学习, 这个得出的就不是最短路径</li><li><strong>移动接口的调用地方太多了, 结果搞出来了一个死循环的移动, A向B移动被修正到C点, A向B移动. 能够统一调用位置就好了.</strong></li><li>有的需求来来回回改的太多了, 加上后面加了新需求, 旧的代码没有删掉, 新旧代码同时作用导致表现有问题.<ol><li>说的就是回城控制</li></ol></li></ol><h2 id="2023-12"><a href="#2023-12" class="headerlink" title="2023-12"></a>2023-12</h2><ol><li>编译大小分析<ol><li>-fno-loop-optimize这个拖了很久,还搞了个乌龙出来</li><li><em>编译的时候没有-r, 用release和debug版本比较的, 最重要的是通知到群里的是乌龙信息</em></li></ol></li><li>日志分析, 看了看骏鹰的日志清洗相关的内容, 写了一个ES导出脚本, 这下可以一次查询所有的日志了, 还不会占用IO. 顺便把WARN也导出了, 只需要改一个字段.<ol><li><em>这个轮子是真TM的难用. 用开源的你就大大方方的用, 结果旧版本+乱搭配</em></li><li><em>后面还是尽量充当制定规则的, 整好文档, 减少被@的次数</em></li></ol></li><li>深入浅出编译链接, 看了看留了点印象, 但是也不太深</li><li>尝试优化elf文件大小<ol><li><em>大小可以减少, 得看看副作用具体是啥, 文档描述的不太清楚</em></li><li><em>变迁一下, elf是object文件合并的, 直接去统计下object文件获取更好, 已经帮忙分割开了.</em></li><li>两个方面<ol><li>整体优化, 通过添加编译选项, 去除非必要的debug信息</li><li>单独优化, 查看较大的object文件分析原因</li></ol></li></ol></li><li>gdb卡慢的原因分析<ol><li><em>目前来看就是卡在了符号表这里, 首次运行慢, 后续运行快</em></li><li><em>好像是首次跑火焰图</em></li></ol></li><li>C++避免重定义, 定义和声明分开<ol><li>增加inline</li><li>定义放到不会被include的文件中, 如cpp, h文件可能目前没有被include但是将来不一定</li></ol></li><li>小需求<ol><li>死亡军队过期兜底删除<ol><li><em>现在写别人分配的一句话需求, 小心不少了, 基本都会问清楚.</em></li></ol></li><li>城郊拾取不再强行要求有军队</li><li>联盟领地buff</li></ol></li><li>怪物组复活和自动拾取逻辑修改<ol><li>怪物组复活这里最开始通过事件处理已经理不清了, 换成了定时器检测.</li><li>自动拾取这里有一个参数一直都用错了, 最后的目标不一定是击败者</li><li><em>感觉开始处理还是取巧了一些, 结果后面要重写了</em></li></ol></li><li>查看是啥问题导致global更新不上去的, 打包global版本服务器<ol><li><em>很迷的问题, 换成request好了一点</em></li></ol></li><li>竞技场支持怪物组和BOSS<ol><li><em>小需求, 但是这个需求整体还是很大的, 周边系统还是要去看一看</em></li><li>JJC需求</li><li>定时器修改</li></ol></li><li>堡垒刷新bug的修复, <em>当时刷新理解的有偏差, 导致写了bug出来, 进了新的阶段刷新不出新堡垒</em>, 验收和测试也没发现, 最后修的时候真的是火急火燎的.</li></ol><h2 id="2024-1"><a href="#2024-1" class="headerlink" title="2024-1"></a>2024-1</h2><ol><li>整了个编译发布流水线的历史变更记录, 希望能总结出一点东西, 毕竟这个东西变更了太多版本了, 为什么没有一开始一步到位, 是有哪些问题之类的.</li><li>bug部分和up部分都拆出去了, 这里就搞下汇总之类的, bug那边也能写点记录, 方便后续查看</li><li>上周六终于是找了个腾讯学堂的视频看完了, 写了点总结, 这貌似是我接近两年来, 第一次看完历史视频并写笔记? 之前只整理过直播如何写博客的</li><li>占矿<ol><li>起初是想用击败者, <strong>结果参数用错了</strong>, 参数并不是击败者. 不过就算是<strong>用对了, 也会有参数为空的情况出现</strong>.</li><li>改成从战败者的follower中选取, 但是被击败这个事件中, 野怪的follower信息被清理了, 所以提前进行判断, 结果发起的<strong>MoveTo会被后续的覆盖</strong>, 改成立即移动之后依然有问题.</li><li>此外还有一个问题, 依赖战死和取消战斗状态, 提前之后就会被影响.</li><li>直接改成了从玩所有军队中获取, 改回了之前的阶段, 结果军队列表中存在死亡的军队. 过滤了死亡军队目前正常了.</li></ol></li><li>我大意了.jpg<ol><li>事件监听错, 本该是A事件, 结果监听了B事件.</li><li>误删正常代码</li></ol></li><li>考虑不全面<ol><li>参数未判断, 0范围的视野也进行了添加.</li><li>开服日期之前启动服务器, 此时计时器需要为负天数. (这里还出过一个时区的问题)</li><li>恢复为旧的代码时, 旧代码已经不能执行了.</li></ol></li><li>做了没必要的<ol><li>直接过滤掉所有可能阻挡位置的遗忘堡垒点位, 不要为了展示高超的技术写复杂了.(之前做了太多额外功能了 百分比阻挡这些, 直接粗暴点就好)</li></ol></li></ol><h2 id="2024-2"><a href="#2024-2" class="headerlink" title="2024-2"></a>2024-2</h2><ol><li>增加成员变量之后需要考虑,构造函数, 拷贝构造函数, 移动构造函数, 赋值运算符<ol><li>漏掉之后会导致新字段丢失的问题, 比较难查.</li></ol></li><li>添加1和添加2分两步的RPC操作, 添加1到添加2之间会有时间间隔, 添加1之后执行的操作没有添加2的信息.<ol><li>有新增添加或者修改操作的时候, 可能添加1也需要修改, 支持直接添加这些字段, 分两步会有时间间隔.</li></ol></li><li>一些内容可以和策划对, 更简单的实现是否可行<ol><li>从击败军队还是所有军队中选一只去占矿</li></ol></li><li>看了podagent的实现, 本身podagent不需要任何特殊配置, 依托游戏服务器启动时向其中注册(socket)信息.</li></ol><h2 id="2024-3"><a href="#2024-3" class="headerlink" title="2024-3"></a>2024-3</h2><h2 id="2024-4"><a href="#2024-4" class="headerlink" title="2024-4"></a>2024-4</h2>]]></content>
<summary type="html">
<h1 id="月记录备份"><a href="#月记录备份" class="headerlink" title="月记录备份"></a>月记录备份</h1><h2 id="2023-07"><a href="#2023-07" class="headerlink" title=
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="UP" scheme="http://blog.lsmg.xyz/tags/UP/"/>
</entry>
<entry>
<title>游戏设计</title>
<link href="http://blog.lsmg.xyz/2023/10/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E6%B8%B8%E6%88%8F%E8%AE%BE%E8%AE%A1/"/>
<id>http://blog.lsmg.xyz/2023/10/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E6%B8%B8%E6%88%8F%E8%AE%BE%E8%AE%A1/</id>
<published>2023-10-24T10:38:08.000Z</published>
<updated>2025-11-29T09:26:42.708Z</updated>
<content type="html"><![CDATA[<h1 id="游戏设计"><a href="#游戏设计" class="headerlink" title="游戏设计"></a>游戏设计</h1><h2 id="传输"><a href="#传输" class="headerlink" title="传输"></a>传输</h2><h3 id="可靠UDP的实现"><a href="#可靠UDP的实现" class="headerlink" title="可靠UDP的实现"></a>可靠UDP的实现</h3><p>资料学习<br>TCP为什么可靠<br>超时重传、流量控制和拥塞控制<br>TCP使用基于ACK的方式来处理分组丢失。如果每次只发送一个分组,等到ACK到达后再发送后续分组,这种简单的模式下依然存在着下面的问题。</p><ol><li>要等待ACK多长时间?</li><li>分组或ACK丢失了怎么办?<br>由于网络传输是双向的,发送一个分组后等待ACK,会导致发送方向或接收方向的网络处于相对空闲的状态。为了提高效率,需要向网络中发送多个分组。虽然提升了效率,但也带来了更多的问题</li><li>一次发送多少个分组?</li><li>分组较多时,以什么样的时间间隔发送分组?</li><li>接收方如何知道哪些分组被接收到了,哪些还没有?<br>发送分组数和发送间隔,受接收方处理效率及网络通道处理效率的限制<br>超时重传<br>虽然可以选取过去一段时间分组的平均RTT来预测接下来一段时间分组的平均RTT,但由于平均值的局限性,会存在大量低于和高于平均值的RTT,高于平均值的RTT的分组将会被认为丢失,然而这些分组并没有丢失。所以超时时间应该高于平均RTT。<br>RTO:超时时间<br>RTT:发送后到接收到ACK的时间<br>没有发生超时时,RTO是根据RTT计算得来。发生超时后,为了防止重传二义性的问题,RTO=RTOx2,直到不发生超时,恢复之前的算法。<br>滑动窗口<br>滑动窗口协议解决了问题5。发送窗口中记录着哪些分组已经被确认接收、哪些分组发送了还未被确认接收以及哪些分组已经就绪但还未发送。接收窗口中记录着哪些分组已经被接收和确认,哪些分组将会被接收进而等待确认以及哪些分组无法被接收进而丢弃。<br>不过滑动窗口也带来了下面的问题</li><li>发送和接收窗口应该设置为多大<br>流量控制<br>使用窗口进行流量控制,窗口分为滑动窗口和拥塞窗口。<br>● 滑动窗口的大小(rwnd)表示接收方的缓存大小<br>● 拥塞窗口的大小(cwnd)表示发送后但未被确认的数据包大小<br>发送方发送窗口大小(橙色部分)为接受方接收窗口大小<br>发送方发送后未被确认的数据量(黑框中部分)<br>最终发送的数据量由发送窗口和拥塞窗口中较小的一方限制。</li></ol><p>基于窗口的流量控制<br>为了处理接收方处理效率相对发送方低的问题,使用基于窗口的流量控制。接收方需要告知发送方其接收窗口的大小。这样发送方就可以根据接收方的窗口大小来调整发送窗口的大小。</p><p>基于拥塞控制的流量控制<br>为了处理网络通道之间所有中转设备和线路限制导致的问题,使用拥塞控制来解决。</p><p>TCP高延迟原因<br>TCP协议中规定了,发生超时时,RTO=RTOx2。超时时间的增长,导致数据包在真正丢失时,无法被及时的重传,超时时间越长,无法被及时重传的情况越严重。</p><p>TCP使用ARQ模型中此编号K前所有包已收到。当K+1发生了丢失时,虽然K+2可能已经收到,但发送端无法得知K+2和之后数据包的情况,只能全部重传,进而出现不必要的重发。TCP中的SACK?</p><p>SACK<br><a href="https://www.rfc-editor.org/rfc/rfc2018" target="_blank" rel="noopener">https://www.rfc-editor.org/rfc/rfc2018</a><br>MTU为1500<br>MSS最大为1460<br>TCP额外包头最大为60字节。SACK需要消耗8*n+2字节的额外包头长度,所以理论最多描述4段block,不过由于时间戳消耗10字节的额外包头长度,所以实际为3段block。</p><p>UDP为什么不可靠<br>UDP如何可靠<br>涉及到功能的选择和切换<br>KCP协议<br><a href="https://github.com/skywind3000/kcp" target="_blank" rel="noopener">https://github.com/skywind3000/kcp</a></p><p>相关工具<br>丢包模拟</p><h3 id="与平台无关的可靠UDP"><a href="#与平台无关的可靠UDP" class="headerlink" title="与平台无关的可靠UDP"></a>与平台无关的可靠UDP</h3><h2 id="帧同步"><a href="#帧同步" class="headerlink" title="帧同步"></a>帧同步</h2><h3 id="帧同步的后台设计"><a href="#帧同步的后台设计" class="headerlink" title="帧同步的后台设计"></a>帧同步的后台设计</h3><h3 id="帧同步的前台设置(UE4)"><a href="#帧同步的前台设置(UE4)" class="headerlink" title="帧同步的前台设置(UE4)"></a>帧同步的前台设置(UE4)</h3>]]></content>
<summary type="html">
<h1 id="游戏设计"><a href="#游戏设计" class="headerlink" title="游戏设计"></a>游戏设计</h1><h2 id="传输"><a href="#传输" class="headerlink" title="传输"></a>传输</h
</summary>
<category term="学习记录" scheme="http://blog.lsmg.xyz/categories/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"/>
</entry>
<entry>
<title>设计模式</title>
<link href="http://blog.lsmg.xyz/2023/10/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<id>http://blog.lsmg.xyz/2023/10/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/</id>
<published>2023-10-20T18:38:08.000Z</published>
<updated>2025-11-29T09:26:42.708Z</updated>
<content type="html"><![CDATA[<h1 id="创造型模式"><a href="#创造型模式" class="headerlink" title="创造型模式"></a>创造型模式</h1><p>单元生抽工厂</p><h2 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h2><h2 id="原型模式"><a href="#原型模式" class="headerlink" title="原型模式"></a>原型模式</h2><p>提供克隆接口, 能够对指定的物体进行克隆生成新物体.</p><h2 id="生成器模式"><a href="#生成器模式" class="headerlink" title="生成器模式"></a>生成器模式</h2><p>相对于工厂模式一次性获取产品, 生成器模式则是可以一步一步对产品进行组装, 组装完成后获取最终的产品.</p><h2 id="抽象工厂模式"><a href="#抽象工厂模式" class="headerlink" title="抽象工厂模式"></a>抽象工厂模式</h2><p>系工厂, 有多个产品, 每个产品提供一个接口.</p><p>一个工厂可以获得本工厂生产的各种产品. 如日系工厂可以提供各种日式家具, 中系工厂可以提供各种中式家具.</p><h2 id="工厂模式"><a href="#工厂模式" class="headerlink" title="工厂模式"></a>工厂模式</h2><p>苹果工厂, 提供统一的苹果生产接口.</p><p>通过向工厂获取接口传入不同的苹果种类, 获取对应的工厂.</p><p>要求红富士返回红富士工厂, 要求糖心苹果返回糖心苹果工厂, 由于都是工厂 所以外部不用关注具体是哪个工厂.</p><h1 id="结构型模式"><a href="#结构型模式" class="headerlink" title="结构型模式"></a>结构型模式</h1><p>元代适外(室外)装桥组</p><h2 id="享元模式"><a href="#享元模式" class="headerlink" title="享元模式"></a>享元模式</h2><p>将class中重复很多的字段抽取出来, 在多个类中共享这些重复的字段, 减少内存占用</p><h2 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h2><p>经理设置秘书代理自己, 外部人员需要通过秘书向经理递交材料, 秘书还可能会对材料进行归纳总结.</p><h2 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h2><p>给不兼容的物体设置包装类, 包装类能够和原本不兼容的对象协作.</p><h2 id="外观模式"><a href="#外观模式" class="headerlink" title="外观模式"></a>外观模式</h2><p>复杂的系统提供简单的接口.</p><h2 id="装饰模式"><a href="#装饰模式" class="headerlink" title="装饰模式"></a>装饰模式</h2><p>将一个对象放入封装对象中, 为对象添加新的功能.</p><h2 id="桥接模式"><a href="#桥接模式" class="headerlink" title="桥接模式"></a>桥接模式</h2><p>通过固定的接口, 将一个整体分离成前台和后台, 前后互不关注具体实现, 只关注接口.</p><p>UI界面和操作系统通过一些固定的接口进行交互, UI界面不在乎是什么系统 只要能相应这些接口就行. 操作系统不在乎是什么UI, 只要通过固定的接口向自己发送请求即可.</p><h2 id="组合模式"><a href="#组合模式" class="headerlink" title="组合模式"></a>组合模式</h2><p>将对象组合成树状结构, 对外一个接口就可能访问到所有叶子.</p><h1 id="行为模式"><a href="#行为模式" class="headerlink" title="行为模式"></a>行为模式</h1><p>模访状任命中备迭观策(模仿状任命中被爹观测)</p><h2 id="模板方法模式"><a href="#模板方法模式" class="headerlink" title="模板方法模式"></a>模板方法模式</h2><p>父类中定义算法框架, 子类可以根据需求重写算法特定的步骤.</p><h2 id="访问者模式"><a href="#访问者模式" class="headerlink" title="访问者模式 ?"></a>访问者模式 ?</h2><p>将算法和所需对象分离开来</p><h2 id="迭代器模式"><a href="#迭代器模式" class="headerlink" title="迭代器模式"></a>迭代器模式</h2><p>不暴露底层数据结构的情况下, 遍历所有元素</p><h2 id="状态模式"><a href="#状态模式" class="headerlink" title="状态模式"></a>状态模式</h2><p>将对象需要状态的操作都抽象出来放到状态父类中, 状态子类中根据自身的状态实现这些操作. 对象不需要关心当前的状态, 只需要根据操作调用对应函数即可, 状态的更新由状态子类负责.</p><h2 id="责任链模式"><a href="#责任链模式" class="headerlink" title="责任链模式"></a>责任链模式</h2><p>将请求沿着处理链进行发送, 每个人都可以处理请求, 或者传递给下个人员.</p><h2 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h2><p>将请求和相关参数包装成独立对象. 对立对象可以将方法参数化 延迟 或者压入队列, 还能实现撤销</p><h2 id="中介者模式"><a href="#中介者模式" class="headerlink" title="中介者模式"></a>中介者模式</h2><p>禁止众多的对象随意互相耦合, 将请求发送给中介者由中介者进行转发. 例如一个塔台和多个飞机交流, 飞机只需要和一个塔台交流就能得知其他飞机的信息.</p><h2 id="备忘录模式"><a href="#备忘录模式" class="headerlink" title="备忘录模式"></a>备忘录模式</h2><p>对象需要实现保存状态和恢复状态的函数. 保存状态的函数将当前状态需要保存的数据生成快照. 管理器调用函数生成快照并保存起来, 当恢复状态的时候读取快照, 应用到对象上.</p><h2 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h2><p>从众多的事件中订阅某个事件, 忽略其他事件.</p><h2 id="策略模式"><a href="#策略模式" class="headerlink" title="策略模式"></a>策略模式</h2><p>将众多算法放到不同的类中, 是的可以通过简单的操作更换算法.</p>]]></content>
<summary type="html">
<h1 id="创造型模式"><a href="#创造型模式" class="headerlink" title="创造型模式"></a>创造型模式</h1><p>单元生抽工厂</p>
<h2 id="单例模式"><a href="#单例模式" class="headerlink
</summary>
<category term="学习记录" scheme="http://blog.lsmg.xyz/categories/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/"/>
</entry>
<entry>
<title>个人UP</title>
<link href="http://blog.lsmg.xyz/2023/10/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP/"/>
<id>http://blog.lsmg.xyz/2023/10/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95-UP/</id>
<published>2023-10-16T09:48:22.000Z</published>
<updated>2025-11-29T09:26:42.706Z</updated>
<content type="html"><![CDATA[<h1 id="规范"><a href="#规范" class="headerlink" title="规范"></a>规范</h1><h2 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h2><p>代码整洁之道中内容很多, 不过这里单单只有命名一节. 被提醒过几次命名不合适, 这部分就着重整理出来, 每次命名的时候都思考思考. 能立刻改善自然最好, 不能立刻也要逐步前进.</p><p><strong>名副其实、避免误导、使用读的出来的名称、避免可爱(要实用)</strong><br>看到名字就知道这个变量是个什么东西,theList就不知道是个什么东西,studentList就好一些。<br>不能变量描述的是A,实际是B。比如给Map类型起名List</p><p><strong>做有意义的区分</strong><br>a1,a2,a3这些就没啥意义,x,y,z就有了<br>book和theBook也没区别</p><p><strong>使用可以搜索的名称</strong><br>找7就非常难找,但是找DAY_PEER_WEEK就好找了</p><p><strong>避免思维映射</strong><br>不要起只有自己能理解的取巧的名称,其他人可能是理解不了的</p><p><strong>方法名使用动词或者动词短语</strong><br>get、set、is</p><p><strong>每个概念对应一个词</strong><br>tower是地图中编码里塔的名称,瞭望塔最好起名watchTower不要和其他冲突了。</p><p><strong>添加有意义的语境</strong><br>name是名字的意思,petName?playerName?无法区分<br>firstName,lastName,street,city,state,这些放一起能看出来是地址,但是单单一个state就看不出来了,不如addrState,或者使用Address类</p><p><strong>命名统一</strong><br>命名要统一,多个人合作的时候要提前商量好,复制粘贴代码后也要注意代码的命名。</p><p><strong>可以直接用机制来命名</strong><br>比如模数余数这些名词</p><p><strong>避免无意义修饰</strong><br>阶段是阶段 等级是等级,不要用阶段等级这种东西</p><p><strong>GPT</strong><br>多用用GPT,这个起名字真的好用, 很有参考价值</p><p><strong>break和nullptr</strong><br>switch-case丢失break, 指针没有判空, 写全新模块的时候基本遇不到, 重灾区是在原有位置加代码.</p><p><strong>注释</strong><br>命名的章节混进来一点点注释, 我感觉问题不大.</p><ol><li>复杂的功能描述下, 不然只有天才能<strong>立刻</strong>知道这一坨代码是干啥的</li><li>其他人或者连自己看起来都很怪的实现, 要说明下原因.</li></ol><h2 id="函数-写故事-反复打磨"><a href="#函数-写故事-反复打磨" class="headerlink" title="函数-写故事-反复打磨"></a>函数-写故事-反复打磨</h2><h3 id="book"><a href="#book" class="headerlink" title="book"></a>book</h3><p><strong>短小</strong><br>避免嵌套太多层</p><p><strong>只做一件事情,做好一件事情</strong><br>编写函数是为了将大一些的概念(函数名称),拆分成另一个抽象层上的一系列步骤。</p><ul><li>函数名下都是同一个抽象层的步骤,实际还是做了一件事。</li></ul><p>检查函数是否只做了一件事,可以看看函数是否可以再拆出一个函数</p><ul><li>函数不仅仅是单纯的重新全是代码,必须<strong>要改变抽象层级</strong></li></ul><p><strong>使用描述性的名称</strong><br>长的具有描述性的名称,比短的令人费解的名称要好。<br>IDE中改名很容易,可以多次修改找到一个最具描述性的名称</p><p><strong>函数参数</strong><br>限制传入bool参数,bool参数表明了,可能存在两个分支,为true一个为false一个,可以考虑拆成函数。<br>给函数取个名字,用来解释函数的意图、<strong>参数和顺序和意图</strong><br>assertEqual改成assertExpectedEqualsActual(Expected,actual),能够<strong>减少记忆参数顺序的负担</strong>。</p><p><strong>无副作用</strong><br>函数承诺只做一件事情(函数名描述),但是<strong>还会做其他被藏起来的事情(函数名没有描述的)</strong>,这个就是函数会有的副作用。有人轻信了函数的名称,就会有这些藏起来的事情导致的风险。</p><p><strong>减少重复</strong></p><p><strong>结构化编程</strong><br>尽量保证函数只有一个出口,break,continue等语句限制使用。这些在短小函数中影响不是很大,但是在<strong>长函数</strong>中影响很大。</p><p><strong>反复打磨</strong><br>一步到位写出完美的代码是很难的,开始应该注重功能的实现,后面可以进行打磨(拆解函数、消除重复、修改名称)</p><h3 id="经验"><a href="#经验" class="headerlink" title="经验"></a>经验</h3><ol><li>向其他函数增加代码时,如果时独立代码,抽出函数</li></ol><h2 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h2><h3 id="book-1"><a href="#book-1" class="headerlink" title="book"></a>book</h3><p>最好是使用代码本身当作注释,当一行或者一块代码需要注释的时候,可以考虑简化这些代码。<br>注释不能美化代码<br>通过增加变量来进行注释</p><p>错误的注释</p><ul><li>注释可能会误导使用者,错误的注释比起没有注释更麻烦。</li><li>随着代码更新,只改代码不改注释,注释可能无法再与代码对应上</li><li>真正起作用的是代码,不是注释。所以可能出现注释和功能对应不上的问题</li></ul><p>需要注释的地方</p><ul><li>反直觉的代码,这些代码由于某些特殊原因,以反直觉的方式出现</li><li>TODO</li><li>警示作用,某些代码可能具有破坏力</li><li>针对复杂的返回值进行说明(更好的方式是简化返回值)</li></ul><h3 id="经验-1"><a href="#经验-1" class="headerlink" title="经验"></a>经验</h3><ol><li>部分特殊的机制要加说明,不然就只有天才能<strong>立刻知道</strong>这里是什么意思了</li></ol><h2 id="杂项"><a href="#杂项" class="headerlink" title="杂项"></a>杂项</h2><ol><li>const能加的就加上吧</li><li>代码还是统一一些<ol><li>判断是不是0 用==0或者if统一一点</li></ol></li><li><strong>空指针这种错误都犯了</strong><ol><li>改一个位置的时候,尤其要注意使用的所有参数,这些参数可能在外面并没有校验</li><li>改代码比新写代码更容易出现</li></ol></li><li><strong>switch语句中丢失break</strong></li><li>注意可能发生死循环代码的兜底<ol><li>服务器更新脚本中就遇到了,重复的更新服务器。</li></ol></li></ol><h1 id="记录总结"><a href="#记录总结" class="headerlink" title="记录总结"></a>记录总结</h1><h2 id="屎山"><a href="#屎山" class="headerlink" title="屎山"></a>屎山</h2><h3 id="指针判空"><a href="#指针判空" class="headerlink" title="指针判空"></a>指针判空</h3><p>使用指针时,一定要注意进行判空</p><h3 id="break"><a href="#break" class="headerlink" title="break"></a>break</h3><p>switch中增加case的时候, 注意补齐break</p><h2 id="全新屎山-设计"><a href="#全新屎山-设计" class="headerlink" title="全新屎山-设计"></a>全新屎山-设计</h2><h3 id="需求设计"><a href="#需求设计" class="headerlink" title="需求设计"></a>需求设计</h3><ol><li>看需求文档的时候 根据文档先定协议</li><li>需求理解一定要正确, 仔仔细细逐句逐句的看需求点. 需求点找出来之后,判断好是哪个服务器来做。防止开发重复的事项。</li><li>一些需要多个服务器共同实现的功能,要协商好处理方式。</li><li>查看策划的文档的时候,还是有想当然的内容<ol><li>次数每天1天,上限5次。第1天想当然为5次,结果跟策划确认了是1次,而不是5次。</li></ol></li><li>写代码之前先在vscode进行下预期设计, 比直接撸起袖子写代码好很多.</li><li>看看需求的特殊要求, 比如退盟之后是否重置, 这个影响到了数据记录到哪里.</li><li>考虑下客户端或者其他模块是否能如自己预期提供支持<ol><li>可能预期的内容, 客户端根本没法做, 还是得服务器来做.</li></ol></li><li>考虑下实现的复杂程度, 可能预期很简单, 但是因为牵扯过多或者不支持导致变得复杂.</li><li>A场景下不用处理, 不代表B场景下不用处理</li><li>方案设计应该把框架性的描述到位<ol><li>这次的跨天代码,文档中描述了,但是没有完整描述放到哪里。结果放到了Ext中,后面又改成了Role中。</li></ol></li><li>反复想了很多次 感觉没有问题,然后就没有测试<ol><li>结果还是出了问题,跟预期的一样,把leader和member的标记清掉了</li></ol></li></ol><p><strong>加了新的子类型后没有适配</strong></p><ul><li>看到报错后 也没有去看原因</li><li>加了子类型也没看原有城郊BOSS的特殊处理</li><li>加了子类型也没通知客户端,<strong>应该是更改协议之后都要通知</strong></li></ul><p><strong>方案可能可以用,但是对于目前还能用的代码,这种改动就需要考量下了。</strong><br>现有的敌我判断,只需要在IsEnemy中加几行代码就可以了,大刀阔斧的去改成方案中的设计,可能会遇到很多的问题。<br>改动的时候应该考虑下改动小的方式?而不是去整一套新的方案。<br>复杂的方案为了通用性,后面维护起来也是很麻烦的。</p><h3 id="时间评估"><a href="#时间评估" class="headerlink" title="时间评估"></a>时间评估</h3><p>涉及增加Module + 协议 代码量200~300行 略微修改其他地方 1.5D</p><ol><li>需求拆解和方案评估<ol><li>不能是单单的将需求点列出来,需要对应到具体的改动是啥,这样评估时间才准确。</li><li>注意某些重要的异常情况处理和兜底处理</li></ol></li></ol><h2 id="全新屎山-制作"><a href="#全新屎山-制作" class="headerlink" title="全新屎山-制作"></a>全新屎山-制作</h2><h3 id="通用的机制应该尽可能能够复用"><a href="#通用的机制应该尽可能能够复用" class="headerlink" title="通用的机制应该尽可能能够复用"></a>通用的机制应该尽可能能够复用</h3><ol><li>在ext中新加了一个跨天的功能,但是这个功能其他ext可能也要用,应该放到上一级方便所有ext使用</li><li>终于把之前想过的时间触发器抽了个Module出来, 后面就可以复用了</li></ol><h3 id="一些临时的内容应该加足注释"><a href="#一些临时的内容应该加足注释" class="headerlink" title="一些临时的内容应该加足注释"></a>一些临时的内容应该加足注释</h3><p>比如明明可能有很多配置,按理来说应该是用参数索引,但是写死了,应该说明原因。</p><pre><code class="cpp">// 目前只有1条配置,先写死id为1const auto *config = GetResFactory().FindResStrongpointRewardCfg(1);</code></pre><h3 id="一定要处理好不兼容变更的兜底,如果影响到太多人,容易出问题还是要加上兜底"><a href="#一定要处理好不兼容变更的兜底,如果影响到太多人,容易出问题还是要加上兜底" class="headerlink" title="一定要处理好不兼容变更的兜底,如果影响到太多人,容易出问题还是要加上兜底"></a>一定要处理好不兼容变更的兜底,如果影响到太多人,容易出问题还是要加上兜底</h3><h3 id="预估时间后无法完成-没有及时通知"><a href="#预估时间后无法完成-没有及时通知" class="headerlink" title="预估时间后无法完成 没有及时通知"></a>预估时间后无法完成 没有及时通知</h3><p>应该及时通知,否则认为完成了,后面会出现各种问题。这次就是配置全换掉了 导致半成品的怪物组被启用了</p><h3 id="日志加的不够"><a href="#日志加的不够" class="headerlink" title="日志加的不够"></a>日志加的不够</h3><ol><li>查问题的时候比预期难太多了,后面补了两个主要位置的日志<ol><li>选举队长的位置 增加了旧队长和新队长的状态</li><li>创建怪物组的时候会打印 玩家ID 怪物组ID 怪物组的配置 这样发现问题的时候能够快速定位到创建位置,之后就能看到问题野怪的ID</li></ol></li></ol><h3 id="统一的工具仓库"><a href="#统一的工具仓库" class="headerlink" title="统一的工具仓库"></a>统一的工具仓库</h3><p>以后写工具应该注意,一个工具放两个代码库维护是容易出问题的,能统一尽量统一,统一不了应该只在一个里面修改.</p><h3 id="需求中途变更"><a href="#需求中途变更" class="headerlink" title="需求中途变更"></a>需求中途变更</h3><p>代码设计初期一般会留有一些考虑能够应对需求变化,但是需求变化后可能会导致实现可以简化,这个时候继续服用复杂的代码还是简化代码就需要考虑了</p><h3 id="尽量一步成型代码,后续修改容易顾此失彼,修改了这里忘了其他敌方"><a href="#尽量一步成型代码,后续修改容易顾此失彼,修改了这里忘了其他敌方" class="headerlink" title="尽量一步成型代码,后续修改容易顾此失彼,修改了这里忘了其他敌方"></a>尽量一步成型代码,后续修改容易顾此失彼,修改了这里忘了其他敌方</h3><h3 id="避免设计无用的东西"><a href="#避免设计无用的东西" class="headerlink" title="避免设计无用的东西"></a>避免设计无用的东西</h3><p>给堡垒加了adaptor,然而是一个函数能搞定的判断,多余了</p><h3 id="数值类配置化or计算化"><a href="#数值类配置化or计算化" class="headerlink" title="数值类配置化or计算化"></a>数值类配置化or计算化</h3><ol><li>数值类的最好不要写死<ol><li>如果是配置中的值,配置变了,写死的数值就会导致问题</li><li>还可能导致本来热更就能搞定的,需要重新编译服务器</li></ol></li><li>尽量不要写死参数, 配置或者计算得来, 否则后面还需要同步修改.</li></ol><h3 id="减少通用错误码的使用"><a href="#减少通用错误码的使用" class="headerlink" title="减少通用错误码的使用"></a>减少通用错误码的使用</h3><p>如果错误码一对一能够及时发现问题, 如果是多个地方使用的, 只能靠日志+看代码路径了.</p><h3 id="客户端参数校验"><a href="#客户端参数校验" class="headerlink" title="客户端参数校验"></a>客户端参数校验</h3><p>没想到出现了非联盟成员拆联盟建筑的问题。</p><h3 id="状态校验放错了位置-写的时候还是没考虑好运行路径"><a href="#状态校验放错了位置-写的时候还是没考虑好运行路径" class="headerlink" title="状态校验放错了位置. 写的时候还是没考虑好运行路径"></a>状态校验放错了位置. 写的时候还是没考虑好运行路径</h3><h2 id="全新屎山-装饰"><a href="#全新屎山-装饰" class="headerlink" title="全新屎山-装饰"></a>全新屎山-装饰</h2><h3 id="GM工具"><a href="#GM工具" class="headerlink" title="GM工具"></a>GM工具</h3><ol><li>开发的时候可以考虑好后续可能用到的GM工具,虽然会略微增加开发时间,但是后续用到的时候是真的方便</li><li>GM工具好多都是有使用场景的,非使用场景使用会出现问题,所以还是要标注清楚</li></ol><h2 id="旧屎山-修改"><a href="#旧屎山-修改" class="headerlink" title="旧屎山-修改"></a>旧屎山-修改</h2><h3 id="确定修改后果-注意一个函数都有哪些作用"><a href="#确定修改后果-注意一个函数都有哪些作用" class="headerlink" title="确定修改后果, 注意一个函数都有哪些作用"></a>确定修改后果, 注意一个函数都有哪些作用</h3><p>最大等级的BUG去掉InitModuleDependency中的广播后,确实去掉了开启时候的广播,但导致ClientConfig中的内容也没有被填写上。<br>且由于满阶段nextCheckInterval为0时添加的定时器 又掩盖了这个问题的及时发现 导致后面发现后改的挺急的</p><p>没有调用OnIdle出现的问题,结果加上OnIdle调用后影响到了回城</p><h3 id="把表现正确的当BUG修了"><a href="#把表现正确的当BUG修了" class="headerlink" title="把表现正确的当BUG修了"></a>把表现正确的当BUG修了</h3><p>这种还是要确认好,不然浪费双倍的时间</p><h3 id="删除代码时应该注意-删除原位置无用的内容"><a href="#删除代码时应该注意-删除原位置无用的内容" class="headerlink" title="删除代码时应该注意 删除原位置无用的内容"></a>删除代码时应该注意 删除原位置无用的内容</h3><p>新位置因为需要保证运行,所以一般不会出现问题,但是原位置的多余内容,一般不影响运行,不便于发现。</p><h3 id="查问题的时候-用物体的事件经过查问题挺方便的"><a href="#查问题的时候-用物体的事件经过查问题挺方便的" class="headerlink" title="查问题的时候 用物体的事件经过查问题挺方便的"></a>查问题的时候 用物体的事件经过查问题挺方便的</h3><h3 id="遇到BUG还是感觉留下现场,比重启解决问题更加重要"><a href="#遇到BUG还是感觉留下现场,比重启解决问题更加重要" class="headerlink" title="遇到BUG还是感觉留下现场,比重启解决问题更加重要"></a>遇到BUG还是感觉留下现场,比重启解决问题更加重要</h3><p>问题可以后面解决,复现问题可能再也没有机会了</p><h3 id="BUG修复关联BUG单"><a href="#BUG修复关联BUG单" class="headerlink" title="BUG修复关联BUG单"></a>BUG修复关联BUG单</h3><p>之前删掉了一段代码,导致城郊出现问题,忘记为啥删除的了,后面修BUG还是带上BUG单的链接把</p><h3 id="抽取函数注意不要影响到原区域功能"><a href="#抽取函数注意不要影响到原区域功能" class="headerlink" title="抽取函数注意不要影响到原区域功能"></a>抽取函数注意不要影响到原区域功能</h3><p>抽函数后加东西, 导致相对于原有增加了一些功能, 这种是很危险的抽函数.</p><h3 id="简单方式解决问题"><a href="#简单方式解决问题" class="headerlink" title="简单方式解决问题"></a>简单方式解决问题</h3><p>如果解决一个小bug, 完美的方式改动很多的话, 感觉不如简单的处理下</p><p>长久来说, 还是要考虑下解决这个问题, 算是一个优化点?</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><h3 id="测试好了再转单,可能确实把当前问题修了,但导致其他问题出现"><a href="#测试好了再转单,可能确实把当前问题修了,但导致其他问题出现" class="headerlink" title="测试好了再转单,可能确实把当前问题修了,但导致其他问题出现"></a>测试好了再转单,可能确实把当前问题修了,但导致其他问题出现</h3><p>改了BUG不好好验证, 而是急着去查下一个BUG.</p><h3 id="全面测试-不留空缺"><a href="#全面测试-不留空缺" class="headerlink" title="全面测试, 不留空缺"></a>全面测试, 不留空缺</h3><p>战斗无法打起来</p><p>因为战斗无法打起来,好多地方开发的时候都没有测试到,结果反而这里才是问题所在</p><h3 id="A通过B通过不等于A-B通过"><a href="#A通过B通过不等于A-B通过" class="headerlink" title="A通过B通过不等于A+B通过"></a>A通过B通过不等于A+B通过</h3><p>一个功能还是完整的测试吧,A功能虽然等于B+C功能,但B和C功能分别正确 不一定B+C就是正确的</p><h3 id="自动化测试"><a href="#自动化测试" class="headerlink" title="自动化测试"></a>自动化测试</h3><p>pyclient这个还是很方便的</p><h2 id="心态"><a href="#心态" class="headerlink" title="心态"></a>心态</h2><h3 id="问题的优先级安排"><a href="#问题的优先级安排" class="headerlink" title="问题的优先级安排"></a>问题的优先级安排</h3><p>下午差城郊防守建筑的BUG查上头了,虽然查出来了但导致了开战距离BUG的延迟了,进而导致了赏金联调的滞后。下午的时候防守建筑的BUG实际可以延后的</p><h3 id="充分关注自己的工作"><a href="#充分关注自己的工作" class="headerlink" title="充分关注自己的工作"></a>充分关注自己的工作</h3><p>赏金都开始联调了,我的功能还没发上去</p><h3 id="不懂就问"><a href="#不懂就问" class="headerlink" title="不懂就问"></a>不懂就问</h3><ol><li>之前一直不知道转测时候填的内容有什么用,导致填的比较随意,导致后面还有同学来提醒,最开始填的时候也确实没有问过。</li><li>还是多问问那个sql语句是啥,原来是描述用的。。。</li></ol><h3 id="重视不起眼的小问题,可能背后的原因是非常离谱的"><a href="#重视不起眼的小问题,可能背后的原因是非常离谱的" class="headerlink" title="重视不起眼的小问题,可能背后的原因是非常离谱的"></a>重视不起眼的小问题,可能背后的原因是非常离谱的</h3><ol><li>dev环境选州Logic崩溃,竟然是由于Logic代码写错的原因(为啥外网没有遇到呢?)</li><li>测试反馈添加的主城全部报错30000,结果是因为roleId循环的,已经添加主城的roleId再次添加主城,由于兜底机制+指定roleId主城存在就会将roleId顺延,顺延之后的就是空roleid,后面就拿不到离线时间的数据</li><li>转表提交报错, 原因是空白行没有被标记占用, 导致新加字段写到了注释行上.</li></ol><h3 id="特殊处理"><a href="#特殊处理" class="headerlink" title="特殊处理"></a>特殊处理</h3><ol><li>野怪最大等级改了获取位置,写代码的将等级为0认为是错误,直接返回了<ol><li>然而赏金这里有特殊处理,等级是0则按1级</li></ol></li></ol><h3 id="预期下当前所做是不是可以解决最终问题-而不是当前某一步"><a href="#预期下当前所做是不是可以解决最终问题-而不是当前某一步" class="headerlink" title="预期下当前所做是不是可以解决最终问题, 而不是当前某一步"></a>预期下当前所做是不是可以解决最终问题, 而不是当前某一步</h3><ol><li>压缩了个超大包, 确实解决了压缩的问题, 但是传输麻烦死了</li></ol><h3 id="去看看别人负责的模块-不要只顾自己的模块"><a href="#去看看别人负责的模块-不要只顾自己的模块" class="headerlink" title="去看看别人负责的模块, 不要只顾自己的模块"></a>去看看别人负责的模块, 不要只顾自己的模块</h3><ol><li>接触了下战斗服和逻辑服的代码, 把相关环境也搭建好了, 后续也确实看过几次对应的代码.</li></ol><h3 id="遇到问题即使下意识感觉问题就是那里-也一定要确认下-可能并不是那样"><a href="#遇到问题即使下意识感觉问题就是那里-也一定要确认下-可能并不是那样" class="headerlink" title="遇到问题即使下意识感觉问题就是那里, 也一定要确认下. 可能并不是那样"></a>遇到问题即使下意识感觉问题就是那里, 也一定要确认下. 可能并不是那样</h3><h2 id="沟通和协作"><a href="#沟通和协作" class="headerlink" title="沟通和协作"></a>沟通和协作</h2><h3 id="他人回复的内容一定要仔细理解,不要含主观臆断"><a href="#他人回复的内容一定要仔细理解,不要含主观臆断" class="headerlink" title="他人回复的内容一定要仔细理解,不要含主观臆断"></a>他人回复的内容一定要仔细理解,不要含主观臆断</h3><ol><li>X场景下,……………………………(省略),这个功能就不需要了。(非X场景是需要的,不能直接删掉这个功能)</li><li>今天晚上就合入版本了(几点?能不能在全量发布服务器前完成,而不是晚上这种模糊时间)</li></ol><h3 id="后台开发代表全部后台-前台开发代表全部前台"><a href="#后台开发代表全部后台-前台开发代表全部前台" class="headerlink" title="后台开发代表全部后台, 前台开发代表全部前台"></a>后台开发代表全部后台, 前台开发代表全部前台</h3><ol><li>后台有多个服务器,每个服务器不同的人负责,前台可能认为后台是一个整体,所以找你沟通的时候最好不要只考虑本服务器的事情。<ol><li>我这里完成了 前台认为所有服务器完成了</li></ol></li><li>对方代码还没合入的时候,你说对面完成了,结果实际没完成</li></ol><h3 id="不兼容变更时做好兜底"><a href="#不兼容变更时做好兜底" class="headerlink" title="不兼容变更时做好兜底"></a>不兼容变更时做好兜底</h3><ol><li>当时在众多人说自己无法进入游戏的时候,都没有去考虑补上兜底方案<ol><li>因为当时考虑到这些都是异常情况,正常情况玩家地图上是不会有自己的主城,只要是正常环境就没有问题</li><li>然而遇到了Logic崩溃了,Logic没有记录选州成功,此时大地图已经选州了,玩家游戏直接卡死,之后才把兜底补上。这次就不是异常情况了,是正常情况下可能会出现的问题了。</li></ol></li><li>重新编译的问题,没想到还学到了不少。<ol><li>tars文件重新生成的问题<ol><li>先用md5比对写了一版</li><li>结果还是直接用CMake写最好,几行的事情</li></ol></li><li>CMake和Make的基本原理</li><li>依赖分析,编译加速?</li></ol></li></ol><h3 id="帮忙"><a href="#帮忙" class="headerlink" title="帮忙"></a>帮忙</h3><p>帮忙处理东西的时候,一定要搞清楚,问清楚。自己也要看清楚,不能只是不带脑子的执行</p><h3 id="不要将问题带到线上,这样处理起来非常的麻烦,而且会增加不靠谱"><a href="#不要将问题带到线上,这样处理起来非常的麻烦,而且会增加不靠谱" class="headerlink" title="不要将问题带到线上,这样处理起来非常的麻烦,而且会增加不靠谱"></a>不要将问题带到线上,这样处理起来非常的麻烦,而且会增加不靠谱</h3><ol><li>异常情况处理</li><li>兜底</li></ol><h3 id="该找运维的找运维-对应的时间给对应的人去做"><a href="#该找运维的找运维-对应的时间给对应的人去做" class="headerlink" title="该找运维的找运维(对应的时间给对应的人去做)"></a>该找运维的找运维(对应的时间给对应的人去做)</h3><ol><li>最后日志传输工作给了运维来搞</li></ol><h3 id="有的功能大地图并不知道有没有-还是说下不知道之类的吧-最后接下来了发现是别人的工作"><a href="#有的功能大地图并不知道有没有-还是说下不知道之类的吧-最后接下来了发现是别人的工作" class="headerlink" title="有的功能大地图并不知道有没有, 还是说下不知道之类的吧, 最后接下来了发现是别人的工作"></a>有的功能大地图并不知道有没有, 还是说下不知道之类的吧, 最后接下来了发现是别人的工作</h3><h2 id="技巧"><a href="#技巧" class="headerlink" title="技巧"></a>技巧</h2><h3 id="CR发起前可以自己整一个临时CR看看代码"><a href="#CR发起前可以自己整一个临时CR看看代码" class="headerlink" title="CR发起前可以自己整一个临时CR看看代码"></a>CR发起前可以自己整一个临时CR看看代码</h3><h3 id="测试代码在最终CR的时候要及时去掉-使用TODO-名字方便检索"><a href="#测试代码在最终CR的时候要及时去掉-使用TODO-名字方便检索" class="headerlink" title="测试代码在最终CR的时候要及时去掉 使用TODO 名字方便检索"></a>测试代码在最终CR的时候要及时去掉 使用TODO 名字方便检索</h3><h3 id="不建议手动操作自动化代码"><a href="#不建议手动操作自动化代码" class="headerlink" title="不建议手动操作自动化代码"></a>不建议手动操作自动化代码</h3><ol><li>流水线异常的时候手动操作了报错部分,然而报错导致后续操作也被中断了,但是忘记了操作后续部分,只操作了报错部分<ol><li>流水线都现成的了 直接用吧 别手动操作了</li></ol></li></ol><h3 id="自动化操作没有监管人"><a href="#自动化操作没有监管人" class="headerlink" title="自动化操作没有监管人"></a>自动化操作没有监管人</h3><p>新版服务器未更新到目标服务器 出了N个乌龙BUG单</p><h3 id="压测和扩容"><a href="#压测和扩容" class="headerlink" title="压测和扩容"></a>压测和扩容</h3><ol><li>压测<ol><li>压测场景和实际场景不匹配<ol><li>出现问题之后,都能发现问题,关键是出现问题前发现限制点</li></ol></li></ol></li><li>扩容<ol><li>寻路服爆炸了,然而没有办法扩容,没有机器。</li></ol></li></ol><h3 id="发现问题比解决问题更重要"><a href="#发现问题比解决问题更重要" class="headerlink" title="发现问题比解决问题更重要"></a>发现问题比解决问题更重要</h3><ol><li>性能优化感觉难点是发现性能问题, 包括编译加速. (20%的问题造成了80%的负面影响, 如果去处理另外80%的问题收益就很低)</li></ol><h3 id="指定时间点触发的循环定时器-每轮都计算下时间相比固定时间的更加稳定"><a href="#指定时间点触发的循环定时器-每轮都计算下时间相比固定时间的更加稳定" class="headerlink" title="指定时间点触发的循环定时器, 每轮都计算下时间相比固定时间的更加稳定"></a>指定时间点触发的循环定时器, 每轮都计算下时间相比固定时间的更加稳定</h3><h3 id="主城周围搜索物体-采用涡旋状搜索-类似蚊香"><a href="#主城周围搜索物体-采用涡旋状搜索-类似蚊香" class="headerlink" title="主城周围搜索物体, 采用涡旋状搜索, 类似蚊香"></a>主城周围搜索物体, 采用涡旋状搜索, 类似蚊香</h3><h3 id="尽量统一函数对统一内容进行清除-比如标志位-这样方便发现错误清理的地方"><a href="#尽量统一函数对统一内容进行清除-比如标志位-这样方便发现错误清理的地方" class="headerlink" title="尽量统一函数对统一内容进行清除, 比如标志位, 这样方便发现错误清理的地方"></a>尽量统一函数对统一内容进行清除, 比如标志位, 这样方便发现错误清理的地方</h3><h3 id="测试东西或者搞新东西的时候-注意不要影响到旧的东西-该开测试空间的开测试空间"><a href="#测试东西或者搞新东西的时候-注意不要影响到旧的东西-该开测试空间的开测试空间" class="headerlink" title="测试东西或者搞新东西的时候, 注意不要影响到旧的东西, 该开测试空间的开测试空间"></a>测试东西或者搞新东西的时候, 注意不要影响到旧的东西, 该开测试空间的开测试空间</h3><p>这次自动提单提了2K+, 如果不是提到了临时空间 估计直接爆炸了.</p><h2 id="充电"><a href="#充电" class="headerlink" title="充电"></a>充电</h2><ol><li>输入<ol><li>代码</li><li>CR</li><li>方案拆解和评估</li><li>KM文章</li><li>开源项目</li></ol></li></ol><p><strong>123</strong></p><ol><li>有输入相比自己死磕能够成长的更快<ol><li>看看其他人的方案拆解<ol><li>不能同一帧将所有点位检查这种情况,才想到要延时进行刷新。</li><li>不能是单单的将需求点列出来,需要对应到具体的改动是啥,这样评估时间才准确。</li><li>注意下时间评估这里,目前我评估的时间还是非常的不准确的</li></ol></li><li>看看其他人的cr,不然自己没有负责过的模块 是一点都不清楚<ol><li>同时看了之后还有和其他人PK的机会</li><li>看看其他人的设计,学一学自己将来才可能遇到,不然都是在自己思维下兜圈子</li><li>看看别人的CR和方案,这里为什么这么设计,自己想的话如何设计,一下对比就出来了。进行后续沟通还能了解到更多。</li></ol></li></ol></li><li>之前看到帝国觉醒只是想到了战斗服需要在压力场景下减少发包<ol><li>但是大地图是不是需要呢?完全没有考虑过,大地图是否需要<ol><li>大地图实际是不需要的,至少目前的同步机制是够用的</li><li>战斗服可能是需要的,不过后面就没跟进了解了</li></ol></li></ol></li><li>千人测试的时候,重启城郊会导致战斗服也需要重启的问题</li><li>千人测试的时候,Logic崩溃导致事件完成了任务没有完成</li></ol>]]></content>
<summary type="html">
<h1 id="规范"><a href="#规范" class="headerlink" title="规范"></a>规范</h1><h2 id="命名"><a href="#命名" class="headerlink" title="命名"></a>命名</h2><p>代码整
</summary>
<category term="个人记录" scheme="http://blog.lsmg.xyz/categories/%E4%B8%AA%E4%BA%BA%E8%AE%B0%E5%BD%95/"/>
<category term="UP" scheme="http://blog.lsmg.xyz/tags/UP/"/>
</entry>
</feed>