走一趟云效+k8s的部署流程

介绍

花了2周左右的时间把k8s全部过了一篇,从溺死到知识的海洋里到产生上岸的错觉,这篇小日志也算对这2周的一个总结。

文章想表达什么

  • 了解k8s的基础知识
  • 部署一套初级可运行的k8s集群
  • 了解基于阿里云效持续集成的部署整体流程
  • 一些踩过的坑

需要具备

  • Linux相关基础知识,能使用常用命令
  • docker基础知识 ,dockerfile 编写能力
  • Git基础知识,发布流程上需要用到
  • 20块钱,jd云打表计费体验一把全套流程

相关的还有阿里的一套发布部署平台分别是

code库
云效
镜像仓库

基础

k8s是什么

k8s全程kubernetes 因 k->s之间正好 8个字母而的名,它是一个为服务容器而生的一个特有工具,也是微服务等热门技术的落地方案可选项之一。它为下面几种场景提供了解决方案

  • 负载均衡
  • 服务发现与调度
  • 服务自愈
  • 服务动态扩容

服务

ps:笔者也是才接触2周的小菜,下面的介绍就操作为主,介绍为辅。至于对于下面基础服务的描述有不到位的地方也欢迎指出。

pod

k8s的灵魂所在,pod是容器的最小承载。我们一般不会去操作pod,但是我们应用到的是pod提供的服务。管理pod会交给更上层的服务去完成。

Deployment

管理 pod容器的一个调度工具,定义一定数量的 pod,Deployment会维持Pod数量与期望数量一致它解决了RC(Replication Controller)一些不能解决的问题(滚动发布等)也是现在最常用的pod控制器 ps:所以其它3种我就忽略掉了

ConfigMap

配置文件信息或者环境变量,是存储在etc内的持久化信息,可能更具需要写出文件或者设置一个环境变量

Service

提供容器间的相互通信 ip+port 的形式相互访问

Ingress-nginx-controller

需要安装的扩展,功能是蛮多的详细可以去看下文档。主要就是负载均衡和路由分发

Ingress

ingress-nginx-controller 的映射关系

工具

minikube

Minikube 是一种可以让你在本地轻松运行 Kubernetes 的工具。 Minikube 在笔记本电脑上的虚拟机(VM)中运行单节点 Kubernetes 集群, 供那些希望尝试 Kubernetes 或进行日常开发的用户使用。

Minikube 支持以下 Kubernetes 功能:

  • DNS
  • NodePorts
  • ConfigMaps 和 Secrets
  • Dashboards
  • 容器运行时: Docker、CRI-O 以及 containerd
  • 启用 CNI (容器网络接口)
  • Ingress

测试下来Ingress在国内安装会出现诸多问题,即使使用阿里云提供的镜像版minikube也是一样

阿里云 minikube

kubectl

管理k8s集群的命令行管理工具

配置文件

Deployment.yaml

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
apiVersion: apps/v1  # 指定api版本,此值必须在kubectl api-versions中
kind: Deployment # 指定创建资源的角色/类型
metadata: # 资源的元数据/属性
name: lnmp # 资源的名字,在同一个namespace中必须唯一
namespace: default # 部署在哪个namespace中
labels: # 设定资源的标签
app: lnmp-spec
version: stable
spec: # 资源规范字段
replicas: 3 # 声明副本数目
revisionHistoryLimit: 3 # 保留历史版本
selector: # 选择器
matchLabels: # 匹配标签
app: lnmp-spec
version: stable
template: # 模版
metadata: # 资源的元数据/属性
annotations: # 自定义注解列表
sidecar.istio.io/inject: "false" # 自定义注解名字
labels: # 设定资源的标签
app: lnmp-spec
version: stable
spec: # 资源规范字段
containers:
- name: php7 # 容器的名字
image: registry.cn-shanghai.aliyuncs.com/zjj_test/blog:20200916133624 # 容器使用的镜像地址
volumeMounts:
- mountPath: /www/blog/.env
subPath: .env
name: env
ports:
- name: http # 名称
containerPort: 9200 # 容器开发对外的端口
protocol: TCP # 协议
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "cd /www/blog && /usr/local/bin/composer install"]

- name: nginx # 容器的名字
image: registry.cn-shanghai.aliyuncs.com/acs-sample/nginx:latest # 容器使用的镜像地址
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx
ports:
- name: http # 名称
containerPort: 80 # 容器开发对外的端口
protocol: TCP # 协议
imagePullSecrets: # 镜像仓库拉取密钥
- name: regcred
volumes:
- name: www
emptyDir: {}
- name: nginx
configMap:
name: blog-nginx-config
- name: env
configMap:
name: blog-env

Service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: lnmp-net
spec:
type: NodePort #这里代表是NodePort类型的
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: lnmp-spec

Ingress-nginx-controller.yaml

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
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx

---
# Source: ingress-nginx/templates/controller-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
data:
---
# Source: ingress-nginx/templates/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ''
resources:
- nodes
verbs:
- get
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- update
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
---
# Source: ingress-nginx/templates/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- namespaces
verbs:
- get
- apiGroups:
- ''
resources:
- configmaps
- pods
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- update
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- configmaps
resourceNames:
- ingress-controller-leader-nginx
verbs:
- get
- update
- apiGroups:
- ''
resources:
- configmaps
verbs:
- create
- apiGroups:
- ''
resources:
- endpoints
verbs:
- create
- get
- update
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
---
# Source: ingress-nginx/templates/controller-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-service-webhook.yaml
apiVersion: v1
kind: Service
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller-admission
namespace: ingress-nginx
spec:
type: ClusterIP
ports:
- name: https-webhook
port: 443
targetPort: webhook
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true'
service.beta.kubernetes.io/aws-load-balancer-type: nlb
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: LoadBalancer
externalTrafficPolicy: Local
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: registry.cn-beijing.aliyuncs.com/fcu3dx/nginx-ingress-controller:v0.35.0
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
---
# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
# before changing this value, check the required kubernetes version
# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
name: ingress-nginx-admission
webhooks:
- name: validate.nginx.ingress.kubernetes.io
rules:
- apiGroups:
- extensions
- networking.k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
failurePolicy: Fail
sideEffects: None
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
namespace: ingress-nginx
name: ingress-nginx-controller-admission
path: /extensions/v1beta1/ingresses
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
verbs:
- get
- update
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- secrets
verbs:
- get
- create
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-create
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-create
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: create
image: docker.io/jettech/kube-webhook-certgen:v1.2.2
imagePullPolicy: IfNotPresent
args:
- create
- --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
- --namespace=$(POD_NAMESPACE)
- --secret-name=ingress-nginx-admission
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-patch
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-patch
labels:
helm.sh/chart: ingress-nginx-2.13.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.35.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: patch
image: docker.io/jettech/kube-webhook-certgen:v1.2.2
imagePullPolicy: IfNotPresent
args:
- patch
- --webhook-name=ingress-nginx-admission
- --namespace=$(POD_NAMESPACE)
- --patch-mutating=false
- --secret-name=ingress-nginx-admission
- --patch-failure-policy=Fail
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000

Ingress.yaml

1
2
3
4
5
6
7
8
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
backend:
serviceName: lnmp-net
servicePort: 80

###configmap.yaml

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
apiVersion: v1  # 指定api版本,此值必须在kubectl api-versions中
kind: ConfigMap # 指定创建资源的角色/类型
metadata: # 资源的元数据/属性
name: blog-nginx-config # 资源的名字,在同一个namespace中必须唯一
namespace: default # 部署在哪个namespace中
data:
nginx.conf: "

server {
listen 80;

server_name www.phpzjj.com;

index index.html index.htm index.php;
root /www/blog/public/;

location / {
try_files $uri $uri/
/index.php$is_args$query_string;
}

location /nginx_status
{
stub_status on;
access_log off;
}

location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}

location ~ .*\\.(js|css)?$
{
expires 12h;
}


location ~ \\.php(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^((?U).+\\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
include fastcgi_params;

}
}

"
---
apiVersion: v1 # 指定api版本,此值必须在kubectl api-versions中
kind: ConfigMap # 指定创建资源的角色/类型
metadata: # 资源的元数据/属性
name: blog-env
namespace: default # 部署在哪个namespace中
data:
.env: "
APP_NAME=Laravel

APP_ENV=local

APP_KEY=***

APP_DEBUG=true

APP_URL=https://www.phpzjj.com

LOG_CHANNEL=stack


DB_CONNECTION=mysql

DB_HOST=***

DB_PORT=3306

DB_DATABASE=blog

DB_USERNAME=***

DB_PASSWORD=***




BROADCAST_DRIVER=log

CACHE_DRIVER=file



SESSION_DRIVER=file

SESSION_LIFETIME=120

QUEUE_DRIVER=sync



REDIS_HOST=127.0.0.1

REDIS_PASSWORD=null

REDIS_PORT=6378




MAIL_DRIVER=smtp

MAIL_HOST=smtpdm.aliyun.com

MAIL_PORT=25

MAIL_USERNAME=admin@mail.phpzjj.com

MAIL_PASSWORD=***

MAIL_ENCRYPTION=null



MAIL_FROM_ADDRESS=admin@mail.phpzjj.com

MAIL_FROM_NAME='张俊杰的博客'




PUSHER_APP_ID=

PUSHER_APP_KEY=

PUSHER_APP_SECRET=

PUSHER_APP_CLUSTER=mt1


MIX_PUSHER_APP_KEY=\"${PUSHER_APP_KEY}\"

MIX_PUSHER_APP_CLUSTER=\"${PUSHER_APP_CLUSTER}\"


JWT_SECRET=***

"


Service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: lnmp-net
spec:
type: NodePort #这里代表是NodePort类型的
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: lnmp-spec

基于阿里云效的部署流程

PHP因为不需要编译部署流程可以分成以下几类

  1. FTP一把梭,常见于小型野团队
  2. 先传Git,然后再FTP一把梭
  3. 先传Git,然后WebHook触发脚本部署
  4. 如下图,先上传到类似云效这样的部署平台,然后触发一定的流程最后触发部署脚本

在上图中其实是我博客现在正在用的一个部署流程,在部署环境什么都没有去做,构建的时候服务器上安装的工具触发了一个shell脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# !/bin/bash
codedir="/www/blog"
rdc_build_meta="origin/master"
# 拉取GIT主分支
cd $codedir
git fetch
git reset --hard $rdc_build_meta
git pull
composer install

#清理opc
WEBDIR="/www/blog/blog/public/"
RANDOM_NAME=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)
echo "<?php opcache_reset(); ?>" > ${WEBDIR}${RANDOM_NAME}.php
curl https://www.phpzjj.com/${RANDOM_NAME}.php
rm ${WEBDIR}${RANDOM_NAME}.php

下面我将尝试下面这种构建方式,把项目代码作为docker镜像的方式去发布和部署。

DockFile

因为我的博客在本地开发的时候是基于Docker环境进行的,所以计划的是直接把以前构建好的docker compose中的php7作为基础镜像包来发布

自用php开发环境

当然也可以直接构建然后发布到私有仓库

php7 DockerFile

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
FROM php:7.1-fpm-jessie



RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && \
echo 'deb http://mirrors.163.com/debian/ jessie main non-free contrib' > /etc/apt/sources.list && \
echo 'deb http://mirrors.163.com/debian/ jessie-updates main non-free contrib' >> /etc/apt/sources.list && \
echo 'deb http://mirrors.163.com/debian-security/ jessie/updates main non-free contrib' >> /etc/apt/sources.list

RUN apt-get update
RUN apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng-dev \
libxml2-dev \
libmagickwand-dev \
libmagickcore-dev \
libgmp-dev \
&& ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/local/include/ \
&& docker-php-ext-configure gmp \
&& docker-php-ext-install -j$(nproc) gmp \
&& docker-php-ext-install -j$(nproc) iconv mcrypt \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd

# pecl貌似被墙了,http://pecl.php.net/ 要包自己下
RUN curl 'http://pecl.php.net/get/redis-4.0.2.tgz' -o redis.tgz \
&& pecl install redis.tgz \
&& curl 'http://pecl.php.net/get/xdebug-2.6.0.tgz' -o xdebug.tgz \
&& pecl install xdebug.tgz \
&& curl 'http://pecl.php.net/get/swoole-4.0.2.tgz' -o swoole.tgz \
&& pecl install swoole.tgz \
&& curl 'http://pecl.php.net/get/imagick-3.4.3.tgz' -o imagick.tgz\
&& pecl install imagick.tgz \
&& pecl install grpc-1.30.0 \
&& pecl install protobuf-3.12.2 \
&& docker-php-ext-enable imagick redis xdebug swoole gmp grpc protobuf

RUN docker-php-ext-install mysqli pdo_mysql opcache\
&& curl -sS https://getcomposer.org/installer | php \
&& mv /var/www/html/composer.phar /usr/local/bin/composer \
&& composer config -g repo.packagist composer https://packagist.phpcomposer.com

#安装分词
RUN curl 'http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2' -o scws.tar.bz2 \
&& tar xvjf scws.tar.bz2 \
&& cd scws-1.2.3 \
&& ./configure --prefix=/usr/local/scws \
&& make \
&& make install \
&& cd phpext \
&& phpize \
&& ./configure --with-scws=/usr/local/scws \
&& make \
&& make install \
&& cd ../../ \
&& rm -rf scws scws-1.2.3.tar.bz2 \
&& docker-php-ext-enable scws \
&& echo "scws.default.charset = utf8" >> /usr/local/etc/php/conf.d/docker-php-ext-scws.ini \
&& echo "scws.default.fpath = /usr/local/scws/etc" >> /usr/local/etc/php/conf.d/docker-php-ext-scws.ini

#pcntl 安装

RUN cd /usr/src/ \
&& mkdir /usr/src/php \
&& tar -xvf php.tar.xz -C ./php\
&& cd /usr/src/php \
&& mv * php7 \
&& cd /usr/src/php/php7/ext/pcntl \
&& phpize \
&& ./configure --with-php-config=/usr/local/bin/php-config \
&& make && make install \
&& docker-php-ext-enable pcntl



RUN cd /var/www/html/ \
&& rm -rf redis.tgz scws-1.2.3 scws.tar.bz2 xdebug.tgz \
&& mkdir /etc/php-fpm.d/

#xdebug配置
RUN echo "xdebug.remote_host= 10.0.1.1 \n xdebug.remote_port = 9123 \n xdebug.idekey = PHPSTORM \n xdebug.remote_log='/www/xdebug_php7.log' \n xdebug.remote_enable = 1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

阿里云私有仓库

地址

先建立一个自己的命名空间设置为私有

然后建立一个放基础环境的仓库

把刚才本地编译好的docker image 打包发布到仓库中

1
2
3
$ sudo docker login --username=XXX registry.cn-shanghai.aliyuncs.com
$ sudo docker tag [ImageId] registry.cn-shanghai.aliyuncs.com/zjj_test/php7:[镜像版本号]
$ sudo docker push registry.cn-shanghai.aliyuncs.com/zjj_test/php7:[镜像版本号]

最后记得授权下,后面会用到

#京东云部署k8s

购买

地址

注意这边尽量购买 1核 2G X 2 以上 的配置

整个创建过程在 10分钟左右 再创建完成过后(ps一定要在结束后)

配置本机客服端

复制出客户配置文件写入

1
vim ~/.kube/config

我们先 kubectl get pod 看下是否配置成功

1
2
3
NAME                                           READY   STATUS              RESTARTS   AGE
init-jcr-token-refresher-dbtcl 0/1 Completed 0 2m18s
jdcloud-jcr-credential-cron-1600497600-pkqrq 0/1 ContainerCreating 0 6s

配置secret

配置这个的目的是为了有权限在私有仓库中拉取images

1
2
3
4
5
kubectl create secret docker-registry regcred \
--docker-server=registry.cn-shanghai.aliyuncs.com \
--docker-username=*** \
--docker-password=*** \
--docker-email=***

配置configmap/deployment/service

用上面写好的yaml 直接创建就好,结尾我会给出创建包

1
kubectl create -f configmap.yaml

访问

在访问前要仔细确认,是否pod已经启动

等它们全部为running状态我们可以尝试下面2种方法去访问它们

我们再确认下service已近创建成功

Kubernetes proxy 模式

1
kubectl proxy --port=8080

然后访问

1
2
3
http://localhost:8080/api/v1/proxy/namespaces/<NAMESPACE>/services/<SERVICE-NAME>:<PORT-NAME>/

http://localhost:8080/api/v1/namespaces/default/services/http:lnmp-net:/proxy/

Ingress 访问

安装 Nginx

1
kubectl create -f ingress-nginx.yaml

等待安装成功

1
kubectl get pod -n ingress-nginx

ingress-nginx-controller-597f4f6fb5-qzgkj 为 Running 后就可以配置详细的访问规则了

1
kubectl create -f ingress.yaml

等待分配IP,这可能要花1分钟左右

1
kubectl get ingress

现在可以直接通过IP访问了

阿里云效配置k8s发布

去填写刚才的客户端管理信息

注意状态信息

回到上一步骤填写对应信息(主要是几个yaml文件里面的标签

接下来我们进入流水线添加构建,注意构建配置里面的文件名

我们在代码目录放入构建配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 请参考 https://help.aliyun.com/document_detail/59293.html 了解更多关于release文件的编写方式 

# 构建源码语言类型
code.language=php7.0

# docker构建所用的Dockerfile的路径
docker.file=Dockerfile


# docker构建完成之后,要push到的docker repo
docker.repo=registry.cn-shanghai.aliyuncs.com/zjj_test/blog


# 使用时间戳做docker tag,这样打出来的docker镜像就形如:registry.cn-hangzhou.aliyuncs.com/mynamespace/container-app:20170622232633
docker.tag=${TIMESTAMP}

放入 Dockerfile 文件

1
2
3
4
5
6
7
8
9
10
FROM registry.cn-shanghai.aliyuncs.com/zjj_test/php7:1.0
USER root
COPY blog.tgz /www/tgz/blog.tgz
RUN tar zxvf /www/tgz/blog.tgz -C /www
RUN /usr/local/bin/composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
RUN apt-get update
RUN apt-get install -y git unzip
WORKDIR /www/blog
RUN chmod 777 -R /www/blog/storage
RUN /usr/local/bin/composer install

这样它就会去拉取我们刚才的基础镜像包,然后完成composer install流程后再创建一个images发布到 registry.cn-shanghai.aliyuncs.com/zjj_test/blog 仓库等待 k8s去拉取

最后就是部署流程选择刚才配置好的k8s测试环境了

测试发布

我们在测试分支把banner里面的文字改掉试试

去看下流水线

在进行composer install了

最后包也成功上传到了我们的私有镜像仓库

代码发布单中可以看到第一批发布完成正在等待确认

然后登陆集群可以看见,k8s采用滚动发布(金丝雀发布)的方式新创建了一个deployment然后启动了一个pod替换了一个老的pod

我们继续发布

老的pod彻底替换掉

代码生效

参考文档

https://kubernetes.io/zh/docs/tasks/configure-pod-container/pull-image-private-registry/

https://www.mantian.site/blog/2019/07/12/Kubernetes%E5%AD%A6%E4%B9%A0-%E2%80%94%E2%80%94-%E5%A6%82%E4%BD%95%E5%B0%86%E8%87%AA%E5%B7%B1%E7%9A%84%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%E4%B8%BAk8s-service/

http://www.dockerone.com/article/4884

https://www.jianshu.com/p/18441c7434a6

树型结构的基础概念和操作

这篇小笔记主要记录了树型结构的各个节点学术名称,树的种类,树的前中后三序遍历以及树的插入删除查找。

个人感觉比较难的是树节点的删除操作,文字描述比较少。代码比较直观的描述

基本定义

二叉树(Binary tree):二叉树特点是每个结点最多只能有两棵子树,且
有左右之分,一个连通的无环图。

名称 定义
根节点 一颗树最上面的节点
父节点 如果一个节点下面有多个节点,那他就是下面子节点的父节点
子节点 如果节点上面还有节点,那它就是上层父节点的子节点
叶子节点 没有子节点的节点
兄弟节点 多个节点有同一父节
节点高度 节点到叶子节点的最长路径
树的深度 根节点到这个节点所经历的边个数
根节点到尾节点的数量

ps: 高度和深度可以依靠记忆去联想,高度是自下而上的,深度是自上而下的。

树的分类

类型 定义
满二叉树 叶子节点都在同一层,除叶子节点都有左右节点
完全二叉树 除叶子节点以外的左节点都必须有值
平衡二叉树 AVL树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树
二叉搜索树 左节点数据小于 父节点 右节点大于父节点
红黑树 自平衡二叉查找树

基础算法

树的前/中/后 遍历 && 计算深度

遍历二叉树主要是依靠递归公式,代码本身并没有什么特别之处理解就好

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

package main

import (
"fmt"
"math"
)

type treeNode struct {
name string
left *treeNode
right *treeNode
}

func main() {
node := treeNode{
name: "A",
left: nil,
right: nil,
}

node.addLeftNode("B")
node.left.addLeftNode("D")
node.left.addRightNode("E")

node.addRightNode("C")
node.right.addRightNode("G")
node.right.addLeftNode("F")

fmt.Println("-------前序遍历--------")
node.preOrder()
fmt.Println("-------中序遍历--------")
node.inOrder()
fmt.Println("-------后序遍历--------")
node.postOrder()
fmt.Println("-------计算深度--------")
fmt.Println(maxDepth(&node))
}

//前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
// Print r -> preOrder(left) -> preOrder(right)

func (node *treeNode) preOrder() {
if node == nil {
return
}

fmt.Println(node.name)

node.left.preOrder()
node.right.preOrder()

}

// max(left) max(right)
func maxDepth(root *treeNode) int {

if root == nil {
return 0
}

return int(math.Max(float64(maxDepth(root.right)), float64(maxDepth(root.left))) + 1)

}

//中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
// print inOrder(r.left) -> print r.name -> print inOrder(r.right)
func (node *treeNode) inOrder() {

if node == nil {
return
}

node.left.inOrder()
fmt.Println(node.name)
node.right.inOrder()

}


//后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。
// print postOrder(left) -> postOrder(right) -> print r.name
func (node *treeNode) postOrder() {
if node == nil {
return
}

node.left.postOrder()
node.right.postOrder()
fmt.Println(node.name)
}


func (node *treeNode) addLeftNode (value string){
leftNode := treeNode{
name: fmt.Sprintf("左边->%s", value),
left: nil,
right: nil,
}
node.left = &leftNode
}


func (node *treeNode) addRightNode (value string) {
reightNode := treeNode{
name: fmt.Sprintf("右边->%s", value),
left: nil,
right: nil,
}
node.right = &reightNode
}

##二叉搜索树

二叉搜索树是一种特殊的树结构,定义比较简单 比父节点小的值放右节点,比父节点大的放右边。用中序遍历的时候就是一个从小到大的有序数列了

ps: 二叉搜索树在遇见 连续有规律的排序时会退化成单向链表

二叉搜索树

  • 写入 : 注意比父节点大放右边小放左边
  • 查找 : 和二分查和写入的找思想相同
  • 删除 : 删除分如下3种方案
情况 方案
删除值下面没有子节点 直接删除
删除值下面有一个子节点 父节点指向这个值的下个节点
删除值下面有子节点 把左子节点最大的值放当前位置或者把右子节点最小值放最大位置
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
package main

import "fmt"

type treeNode struct {
data int
leftNode,rightNode *treeNode
}

func main() {

numbers := []int{8, 3, 10, 1, 6, 14, 4, 7, 13, 200, 100, 99, 201}
node := treeNode{}


for i:=0 ; i < len(numbers); i++ {
insert(&node, numbers[i])
}


node.rightNode.inOrder()

fmt.Println(search(&node, 201))

node.delete(4)


node.rightNode.inOrder()

}

func insert(node *treeNode,data int) {
if (node.data > data) {

if(node.leftNode == nil) {

node.leftNode = &treeNode{
data: data,
leftNode: nil,
rightNode: nil,
}

} else {
insert(node.leftNode, data)
}

} else {
if (node.rightNode == nil) {
node.rightNode = &treeNode{
data: data,
leftNode: nil,
rightNode: nil,
}
} else {
insert(node.rightNode, data)
}
}
}

func search(node *treeNode, data int) bool{

p := node

for p != nil {

if p.data > data {
p = p.leftNode
} else if (p.data < data) {
p = p.rightNode
} else {
return true
}
}

return false
}

func (node *treeNode) delete (data int) {
p,pp := node,node// PP:父节点 p 当前节点

for p != nil && p.data != data {
pp = p
if p.data > data {
p = p.leftNode
} else {
p = p.rightNode
}
}

if pp == nil {
return
}

//删除值下面没有子节点|直接删除
if p.leftNode == nil && p.rightNode == nil{
pp.leftNode =nil
pp.rightNode =nil
} else if p.leftNode != nil && p.rightNode != nil {
//TODO 回来再琢磨下
} else if p.leftNode != nil || p.rightNode !=nil {

if p.leftNode != nil {
p = p.leftNode
} else {
p = p.rightNode
}

if pp.rightNode != nil && pp.rightNode.data == data {
pp.rightNode = p
} else {
pp.leftNode = p
}


}
node = pp


}

func (node *treeNode) addLeftNode (value int){
left := treeNode{
data: value,
leftNode: nil,
rightNode: nil,
}
node.leftNode = &left
}

func (node *treeNode) addRightNode (value int){
right := treeNode{
data: value,
leftNode: nil,
rightNode: nil,
}
node.rightNode = &right
}

func (node *treeNode) inOrder() {

if node == nil {
return
}


node.leftNode.inOrder()
fmt.Println(node.data)
node.rightNode.inOrder()

}

插图工具 : https://app.diagrams.net/
插图下载 : https://github.com/zhangjunjie6b/diagramsResource

参考 :

https://time.geekbang.org/column/article/67856?utm_source=pinpaizhuanqu&utm_medium=geektime&utm_campaign=guanwang&utm_term=guanwang&utm_content=0511

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91/1602879?fr=aladdin

https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8F%89%E6%A0%91#%E5%9C%96%E8%AB%96%E4%B8%AD%E7%9A%84%E5%AE%9A%E7%BE%A9

东方航空周末随心飞监控软件v1.0(服务器版)

CLI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

东航随心飞抢票工具

使用: dh [-s 出发地] [-t 到达地点] [-dt 出发时间] [-c 航班编号]

选项:
-c string
航班编号
-dt string
出发时间
-h 帮助
-s string
出发地点
-t string
到达地点

前言

东方航空推出3322周末随心飞后,机票异常难买。这个项目就是因为我也买了这个套餐,需要一点取巧的方法来捡漏刷票而存在的,这样可以说是一劳半年内都可以用。

声明

我也知道代码有诸多不完善的地方诸如

  • 一个main文件没有解耦开来
  • 邮件通知不可配置必须自己编译,通知未设置频率
  • 各种兼容问题,没有封装成docker包

    就上述问题我持着知道但是不会去改的态度,因为这是一个具有时效性的非团队软件。

#原理

其实我们的目的就一个,就是监控东航官网出票系统,看有没有余票

官网查询地址

1
http://www.ceair.com/booking/sha-xnn-200801_CNY.html

我们可以发现,航班数据其实是进行了一个异步请求的,并且这个异步请求里面埋了一下暗装。它的CURL格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
curl $'http://www.ceair.com/otabooking/flight-search\u0021doFlightSearch.shtml' \
-H 'Connection: keep-alive' \
-H 'Accept: application/json, text/javascript, */*; q=0.01' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
-H 'Origin: http://www.ceair.com' \
-H 'Referer: http://www.ceair.com/booking/sha-xnn-200801_CNY.html' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Cookie: gr_user_id=316cac51-2692-45cd-a39e-b70248150dd8; grwng_uid=399b9fae-32ca-4fcd-a02b-2204cbe3ab2b; s_fid=73AB8873AD8160C8-25BD91BA291AE489; smidV2=20200618160625fe2625aafe9e7f1be5d6388b4253ecf000c2501c131082fb0; es_login_user=ZHANG%252FJUNJIE; _fbp=fb.1.1592467719875.1625792994; pt_71d4c6a5=uid=2XPdIyg/7hcAc6xJVDxZNg&nid=0&vid=LijfdB1Jcu/OiSL1ChfTDQ&vn=2&pvn=7&sact=1592467754060&to_flag=0&pl=T/Sc0xCX-mv523FP0TDGZw*pt*1592467746520; language=zh_CN; Webtrends=d644d8a7.5a8574dd256c2; _ga=GA1.2.5901444.1592467805; 84bb15efa4e13721_gr_last_sent_cs1=34A0F183FA98F1222CE52BBDA098BBF4; ssxmod_itna=Qq+xnQG=i=ExBiDzOImPY5GQE4BKND70tmYY00QxBMb4iNDnD8x7YDvmIh4pn4cjYnqNLet0mxqhFhoeuPezj3rfa8KicGXzD84i7DKqibDCqD1D3qDkWm7FZ8DADi3DEDDm2Dmqi8DITtDAfLlDDfcDAUV0qGw9MdDGdI/4wr574t9nLj0D0PoKGorpA5=zYnLYie=0Bh5KRvHq2GTZYfmBGFNYOxTlEWTQmGij74=eD===; ssxmod_itna2=Qq+xnQG=i=ExBiDzOImPY5GQE4BKND70tmYY00D8T1olxGNq=qGaKlKfHzbT4hxAP4c4adIrwzebnYxGqdzeFR87efr84IbHKaoFE2/t=7Fvd4qIlFRzkFkkaWWpfxu9uctxTv27dYca9DKhcjWGVxuo1xqK44wK06exOETQoCuhdK7GHrGtxbor9eqwO83rSlorOUm=xZ0fqDWa6P7jkKeUNjwi0SrO0K7kSjLa4OC0zOQfoHTiOzaiDHFFxZB70IZXiVAHHVBn0RB7KVWOKu6OiyMnhLMctuzixN9cxN=rc5NnguPVnvV9uZGKSYDbGImfhKzwxo+KfPnn2bQbiA71A9IjYK67FeYMb7Yu7FeOoxBo6YreorU+3cc/8O1DudPY==YR+bWfYmnO3Pq3vb=quPhE4du/FPr6hqcBMGFG3ATofAA8tkFV+9zGR/RW4WxDKMxzYac0D1YtqYdvDYAkDa0jG3tzGDdba064L8dATqqBrX=dvcqdD49ph/ZKu7NNOrK2DzYd1BNg00NtRnR3McYPGWe7O77DKIu2cz6c2hY7BG+8iqEhrKDcyY552qD7=DYFk=DKFGDD; ecrmWebtrends=124.79.118.230.1594005387905; _gid=GA1.2.1121419070.1594005373; user_cookie=true; ceairWebType=new; JSESSIONID=ukmOH+wWEr13SvauKe5kztE8.laputaServer6; 84bb15efa4e13721_gr_session_id=f2c59e6b-fd4c-4778-9cde-8698dca37a4a; 84bb15efa4e13721_gr_last_sent_sid_with_cs1=f2c59e6b-fd4c-4778-9cde-8698dca37a4a; 84bb15efa4e13721_gr_cs1=34A0F183FA98F1222CE52BBDA098BBF4; 84bb15efa4e13721_gr_session_id_f2c59e6b-fd4c-4778-9cde-8698dca37a4a=true; _gat=1; _gat_UA-80008755-11=1' \
--data-raw '_=47c796f0c0f311ea931a0db120f1f5cc&searchCond={"adtCount":1,"chdCount":0,"infCount":0,"currency":"CNY","tripType":"OW","recommend":false,"reselect":"","page":"0","sortType":"a","sortExec":"a","seriesid":"47c796f0c0f311ea931a0db120f1f5cc","segmentList":[{"deptCd":"SHA","arrCd":"XNN","deptDt":"2020-08-01","deptAirport":"","arrAirport":"","deptCdTxt":"上海","arrCdTxt":"西宁","deptCityCode":"SHA","arrCityCode":"XNN"}],"version":"A.1.0"}' \
--compressed \
--insecure

好的告辞,不想在找暗装上花费太多的时间,因此我走了第二条路 selenium + chromedriver

安装

cli 版本主要是用于服务器上,因此安装分2部

安装 linux chrome

  1. 下载 chrome 最下方找到 其他平台 下载Linux版
  2. rpm -ivh [下载文件名]

或者直接yum安装

1
yum install google-chrome-stable.x86_64

找到对应版本的chromedriver

镜像包下载
版本对应查看

运行

1
nohup ./main.x -c MU9501 -dt 2020-08-08 -s 上海 -t 潮汕 >> log.txt &

技术调用

东方航空周末随心飞监控软件v1.0(go+GUI)

GUI

前言

东方航空推出3322周末随心飞后,机票异常难买。这个项目就是因为我也买了这个套餐,需要一点取巧的方法来捡漏刷票而存在的,这样可以说是一劳半年内都可以用。

#原理

其实我们的目的就一个,就是监控东航官网出票系统,看有没有余票

官网查询地址

1
http://www.ceair.com/booking/sha-xnn-200801_CNY.html

我们可以发现,航班数据其实是进行了一个异步请求的,并且这个异步请求里面埋了一下暗装。它的CURL格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
curl $'http://www.ceair.com/otabooking/flight-search\u0021doFlightSearch.shtml' \
-H 'Connection: keep-alive' \
-H 'Accept: application/json, text/javascript, */*; q=0.01' \
-H 'X-Requested-With: XMLHttpRequest' \
-H 'User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
-H 'Origin: http://www.ceair.com' \
-H 'Referer: http://www.ceair.com/booking/sha-xnn-200801_CNY.html' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Cookie: gr_user_id=316cac51-2692-45cd-a39e-b70248150dd8; grwng_uid=399b9fae-32ca-4fcd-a02b-2204cbe3ab2b; s_fid=73AB8873AD8160C8-25BD91BA291AE489; smidV2=20200618160625fe2625aafe9e7f1be5d6388b4253ecf000c2501c131082fb0; es_login_user=ZHANG%252FJUNJIE; _fbp=fb.1.1592467719875.1625792994; pt_71d4c6a5=uid=2XPdIyg/7hcAc6xJVDxZNg&nid=0&vid=LijfdB1Jcu/OiSL1ChfTDQ&vn=2&pvn=7&sact=1592467754060&to_flag=0&pl=T/Sc0xCX-mv523FP0TDGZw*pt*1592467746520; language=zh_CN; Webtrends=d644d8a7.5a8574dd256c2; _ga=GA1.2.5901444.1592467805; 84bb15efa4e13721_gr_last_sent_cs1=34A0F183FA98F1222CE52BBDA098BBF4; ssxmod_itna=Qq+xnQG=i=ExBiDzOImPY5GQE4BKND70tmYY00QxBMb4iNDnD8x7YDvmIh4pn4cjYnqNLet0mxqhFhoeuPezj3rfa8KicGXzD84i7DKqibDCqD1D3qDkWm7FZ8DADi3DEDDm2Dmqi8DITtDAfLlDDfcDAUV0qGw9MdDGdI/4wr574t9nLj0D0PoKGorpA5=zYnLYie=0Bh5KRvHq2GTZYfmBGFNYOxTlEWTQmGij74=eD===; ssxmod_itna2=Qq+xnQG=i=ExBiDzOImPY5GQE4BKND70tmYY00D8T1olxGNq=qGaKlKfHzbT4hxAP4c4adIrwzebnYxGqdzeFR87efr84IbHKaoFE2/t=7Fvd4qIlFRzkFkkaWWpfxu9uctxTv27dYca9DKhcjWGVxuo1xqK44wK06exOETQoCuhdK7GHrGtxbor9eqwO83rSlorOUm=xZ0fqDWa6P7jkKeUNjwi0SrO0K7kSjLa4OC0zOQfoHTiOzaiDHFFxZB70IZXiVAHHVBn0RB7KVWOKu6OiyMnhLMctuzixN9cxN=rc5NnguPVnvV9uZGKSYDbGImfhKzwxo+KfPnn2bQbiA71A9IjYK67FeYMb7Yu7FeOoxBo6YreorU+3cc/8O1DudPY==YR+bWfYmnO3Pq3vb=quPhE4du/FPr6hqcBMGFG3ATofAA8tkFV+9zGR/RW4WxDKMxzYac0D1YtqYdvDYAkDa0jG3tzGDdba064L8dATqqBrX=dvcqdD49ph/ZKu7NNOrK2DzYd1BNg00NtRnR3McYPGWe7O77DKIu2cz6c2hY7BG+8iqEhrKDcyY552qD7=DYFk=DKFGDD; ecrmWebtrends=124.79.118.230.1594005387905; _gid=GA1.2.1121419070.1594005373; user_cookie=true; ceairWebType=new; JSESSIONID=ukmOH+wWEr13SvauKe5kztE8.laputaServer6; 84bb15efa4e13721_gr_session_id=f2c59e6b-fd4c-4778-9cde-8698dca37a4a; 84bb15efa4e13721_gr_last_sent_sid_with_cs1=f2c59e6b-fd4c-4778-9cde-8698dca37a4a; 84bb15efa4e13721_gr_cs1=34A0F183FA98F1222CE52BBDA098BBF4; 84bb15efa4e13721_gr_session_id_f2c59e6b-fd4c-4778-9cde-8698dca37a4a=true; _gat=1; _gat_UA-80008755-11=1' \
--data-raw '_=47c796f0c0f311ea931a0db120f1f5cc&searchCond={"adtCount":1,"chdCount":0,"infCount":0,"currency":"CNY","tripType":"OW","recommend":false,"reselect":"","page":"0","sortType":"a","sortExec":"a","seriesid":"47c796f0c0f311ea931a0db120f1f5cc","segmentList":[{"deptCd":"SHA","arrCd":"XNN","deptDt":"2020-08-01","deptAirport":"","arrAirport":"","deptCdTxt":"上海","arrCdTxt":"西宁","deptCityCode":"SHA","arrCityCode":"XNN"}],"version":"A.1.0"}' \
--compressed \
--insecure

好的告辞,不想在找暗装上花费太多的时间,因此我走了第二条路 selenium + chromedriver

安装

go mod 全部定义好了有环境直接编译或者运行就可以正常跑起来了,
唯一需要注意的是 chromedriver 版本是 (83.0.4103.39) 对应 chrome 的 83.x系列
需要自行匹配 chromedriver 和 chrome的版本
mac 可以用 main.go_darwin_amd64.app (注:一定是命令行启动,否则自动退出)

镜像包下载

版本对应查看

技术调用

可扩展方向

如果这是一个可以盈利的点子,我会去掉GUI把它服务端化。然后套上IP代理池,做个前端UI来实现收费监控。
但是鉴于 3322 的时效性和宣传的乏力我还是放弃了这一块时间的投入,毕竟现在就这样是够用的,比隔几分钟自己拿出手机看看来得好

项目地址

周末随心飞东航余票监控软性

记录一个因为使用不规范引发的debug开启后报错问题

我想不止我一个人在使用thinkPHP的时候出现过很多诡异的情况,也不止我一个人在打开和关闭框架中的DEBUG时得到2种运行结果。这次就较一下真,看下到底是框架垃圾还是我们使用过程中的确存在不规范(人垃圾)

背景

因为事业环境因素需要统一过滤项目组中各个业务线的搜索词是否合法。采用的解决方案如下

  1. 用Go的生态 load 一颗 trie树到内存
  2. PHP 走 RPC 调用启动的这个Go服务
  3. PHP 提供一个短连接的内网RPC服务供各个业务线使用

疑问:

  • 为什么要用trie树

    trie树 + AC自动机是目前比较高效的违禁词过滤方法

  • 为什么不直接调用Go服务而要过一层PHP

    想在这层PHP上做熔断报警机制,业务层并发量大PHP的curl库超时最少1秒,在并发量比较高的场景1秒的等待开销太大。

技术选型

RPC协议
小马哥的Hprose

项目框架
thinkPHP3.1 (历史原因)

常驻监控
supervisor

问题

在实际操作期间,其实蛮顺利的

  • GO常驻服务&&接口暴露
  • 服务层PHP调用Go接口
  • 应用层PHP调用PHP提供的服务接口

只是再最后一步出现了一个问题我发现应用层的PHP调用服务层的掉不通,而且会抛出错误。经过一些调试发现最诡异的是把thinkPHP的APP_DEBUG关掉接能正常调用

解决

首先想到的是各种语法层面的问题

发现Hprose的方法发布核心函数addFunction 是这样写的

1
2
3
4
5
6
7
8
9
10
11
12
13
public function addFunction($func, $alias = '', array $options = array()) {
if (!is_callable($func)) {
throw new Exception('Argument func must be callable.');
}
if (is_array($alias) && empty($options)) {
$options = $alias;
$alias = '';
}
if (empty($alias)) {
if (is_string($func)) {
$alias = $func;
}
...

is_callable可以得出作者想让我们传入的是一个回调函数,这里为了排除是魔改过后的框架有类调用找不到的情况,传入一个匿名函数个人感觉比较稳妥而且没什么问题(排除法慢慢来)代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function xxx()
{
$server = new \Hprose\Http\Server();

$server->addFunction(function ($keyword) {
try {
return $this->client->Validate($keyword);
}catch (Exception $e) {
return $this->error($keyword);
}

}, "validate");
$server->start();
}

尝试了这种方法后继续掉用,发现问题还是没有得到解决但是报错信息变成了

1
2
3
4
Exception: Wrong Response: 
Rm2{s5"state"s5"false"s7"keyword" in /Users/zhangjunjie/code/testdocker/phpdocker/www/blog/vendor/hprose/hprose/src/Hprose/Client.php:351
Stack trace:

我们定位到 Client.php:351 行

从这一行基本可以肯定的是代码在处理$response值的时候得到的不是预期值,那么这个和我们的 thinkPHP的APP_DEBUG有什么必然的联系吗?

我们继续尝试,直接输出这个$response值

打开了APP_DEBUG

1
string(36) "Rm2{s5"state"s5"false"s7"keyword""

关闭了APP_DEBUG

1
2
string(36) "Rm2{s5"state"s5"false"s7"keyword"e}z"

这里就比较清楚了乖乖,在打开了debug的情况下框架吐出来的值吃了我们3个字符 e}z

从文档中可以看出序列化协议规范

名称 含义
0x52(‘R’) RPC 结果
m 可递归引用类型
2 map长度
0x7A(‘z’) 结束

这里可一很明确的肯定了,在打开thinkPHP的APP_DEBUG的情况下会丢失结尾标识符

下面我们就继续扒代码看下是什么原因产生的,我们顺着服务端的 $server->start();断点法不断找进去发现

在这个方法里面作者指定了一个header头,这是http协议用来告诉应用程序它的报文长度的。

那么由此我们可以想到是这个header头给了错误的限制导致这样的情况,涉及到http协议我们这边使用抓包工具分析报文是最稳妥的方法(直接看页面输出的值很多凭借肉眼都很难识别出来)

打开了APP_DEBUG

关闭了APP_DEBUG

看见那个熟悉的...了吗,看见那个ef bb bf了吧,对它就是我们最TM恨的BOM头!

那么最后一个问题,这个BOM是哪个文件里面的呢?

想一下为什么打开APP_DEBUG和关闭它得出的结果不一样,对!!!

打开APP_DEBUG的时候配置文件会实时的读入程序通过拼接的方式往下执行,那么带着这个猜想我们去看下配置文件

结论

  • thinkPHP在APP_DEBUG打开的情况下会把配置文件读入程序进行拼接。
  • BOM头会产生各种各样奇怪的问题
  • 不能怪框架垃圾,这确实是我们使用中不规范造成的问题

树莓派组件家用nas的一点感受

这次冠状病毒期间蛮长一段的一直对nas有了解,也一直想拥有一台,奈何这不是一个强需求而且价格较贵就没行动。家里18年买的树莓派小车还在,索性拆掉然后组装了一个nas出来

能干什么

在做这个小东西以前我对nas的概念只停留在网络硬盘这一块,在完成过后了解到这小玩意能干的事情其实蛮多的,虽然没群辉强大但是够用。

  • 多端的备份云存储
  • 2T的云存储设备
  • 离线下载
  • 小型家用站点

来几张图

docker可视化管理工具,比较好用

网盘+自动手机相册同步

离线下载

OpenMediaVault 管理界面

从这张图其实可以得出pi3完全够用,1G内存跑4-5个docker小容器也是妥妥没什么问题的

费用

树莓派3b 200 左右
希捷2T 500 元
合计 700 到手

时间成本 2-3天

使用下来的感受

优点

  • 全套开源DIV空间大
  • 和群辉比便宜很多
  • 好玩

缺点

  • 依赖网络(比如天翼的垃圾路由就没办法做内网host和范映射)
  • 树莓派3b是百兆网口极限也就11-12MB了

需要用到的包

https://www.balena.io/etcher/

http://www.pc6.com/mac/222175.html

https://www.raspberrypi.org/downloads/raspbian/

apt-get update
apt-get install openssl libssl-dev vim
wget -O - https://github.com/OpenMediaVault-Plugin-Developers/installScript/raw/master/install|sudo bash

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN

二分查找和它的变种

二分查找是快速定位一个有序数组的算法,它的优点就是减少对比次数。是一个日常生活中也可以用到的算法思想

优点

  • 减少对比次数
  • o(logn)

缺点

  • 要求查找的数组是有序数组
  • 查找数组过小二分查找反而慢

原型

思路

  • 取出中间数 (right-left)/2 只能得到第一次的中间数,正确做法是 left+(right-left)/2
  • 中间数和给到的数比大小,如果中间数大就把数组的左边拿去对比反之也一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func bsearch(list[]int, num int,left int, right int )  bool{
for right >= left {

center := left+(right-left)/2

if list[center] == num {
return true
} else if list[center] < num {
left = center + 1
} else {
right = center -1
}
}
return false
}

##变种

查找第一个值等于给定元素

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

func bsearch(list[]int, num int,left int, right int) int {

for right >= left {

center := left + (right-left) >> 1

if (list[center] > num) {
right = center
} else if list[center] < num{
left = center
} else if list[center] == num {

if (center == 0 || list[center-1] != num){
return center
} else {
right = center-1
}

}

}

return -1

}

查找最后一个值等于给定的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func bsearch2(list[]int, num int,left int, right int) int {
for right >= left {
center := left + (right-left) >> 1

if (list[center] > num) {
right = center
} else if list[center] < num {
left = center
} else if list[center] == num {
if (center == 0 || list[center+1] != num) {
return center
}
left = left + 1
}
}

return -1
}

查找第一个大于等于给定的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func bsearch3(list[]int, num int,left int, right int) int {

for right >= left {

center := left + (right-left) >> 1

if list[center] >= num {
if center == 0 || list[center - 1] < num {
return center
}
right = right-1
} else if list[center] < num {
left = center
}
}

return -1
}

查找最后一个小于等于给定的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func bsearch4(list[]int, num int,left int, right int) int {

for right >= left {

center := left + (right-left) >> 1

if list[center] > num {
right = center
} else if list[center] <= num {

if center == 0 || list[center + 1] > num {
return center
}

left = center + 1
}
}

return -1
}

桶排序与计数排序

桶排序,计数排序 都是非基于比较的排序算法,在一定数据量下(O(k)>O(n*log(n)))快于任何基于比较的排序算法 这3个排序算法的通用性不是很高,我认为排序算法虽然实用性在实际生产中各有不同但是他们的思路是比较重要的。桶排序 计数排序 基数排序 都对要排序数组有点要求。

桶排序

桶排序号称是最快的排序方法,但是也是最耗费空间的排序方法。

  • 场景: 有5个学生,满分5分 他们考试分数分别为 (2,2,1,5,4)需要按照分数升序排序

  • 思路: 满分5分,我们需要6个桶 int[5],桶的下边即为所有的分数 0~5,然后遍历每个学生的分数把数据塞入桶内,出现一次桶内的值加1。输出时遍历桶,桶内值是几就输出几次

  • 优点: 只遍历一次数组,速度快简单

  • 缺点: 1.对数据范围有依赖 2.会浪费空间 3.只能记录值不能记录键

桶排序还适用于外部排序,我们经常遇见一些面试题诸如
有一亿条数据,电脑内存只有128MB怎么给他们排序,这样的题就是说计算器内存不够用了不能全部load进内存该怎么处理

正确的方法是

  1. 确认数据每一条的大小
  2. 根据数据的大小合理分出若干桶范围比如 100w 放成一个桶记录文件数据范围在1-100W,这样的桶搞100个。
  3. 遍历1亿条数据,写入这100个桶中
  4. 每个桶中的数据在放出来 load 进内存,用快排排好序吐出到文件中去
  5. 把排好序的桶合并成一个文件

计数排序

计数排序的思路和桶排序的差不多,优点是不用比较可以弥补桶排序的不能记录键的问题。缺点 1.对数据范围有依赖 2.会浪费空间

  • 思路:
  1. 准备用于计数的桶A
  2. 把需要排序的字段放入桶A,记录出现次数
  3. 准备桶B,根据桶A的情况统计数字出现的位置。
  4. 桶B中的每一个桶里面的值放入小于等于桶键的个数
  5. 用需要排序的数据 去遍历桶B得到的桶值就是这个数据放置的位置
  • 步骤
  1. 排序 2,5,3,0,2,3,0,3

桶A 键是排序数组的范围值, 值是出现的次数

  1. 桶B 键是排序数组的范围值,值是小于等于键值的数值(桶A左值加当前位置)

  1. 倒叙去遍历需要排序的数组,在桶B上面获取相应的所在位置。桶B的键是需要排序的值,值是排序值所在的位置

归并排序与快速排序

归并排序

归并排序的核心实现就是分治,主要利用一个递归无限的把一个数组分为左右2部分,然后利用一个合并函数把排好序的数组退回去。理解起来比较容易

思路

  1. 分解2个函数 meage_sort 负责递归分解出数组,meage负责合并排序数组
  2. 找到数组的中间数,即长度/2,递归的中断条件为传递进去的长度为0
  3. 通过中间数把meage_sort拆分迭下去,它的参数就是 中间数的左边元素和右边元素
  4. meage函数中需要有2个指针,分别指向 leftright数组的第一个元素
  5. leftright 长度为结构体条件,循环比较他们的大小,同时移动指针指向下一位直到一边全部放入临时数组
  6. 把剩下的另一边数据塞回meage的返回值
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
func meage_sort(nums []int) []int{
length := len(nums)
if length <= 1 {
return nums
}
p := length/2

left := meage_sort(nums[:p])
right := meage_sort(nums[p:])
return meage(left, right)
}

func meage (left []int, right []int) (m []int){
l,r := 0,0

for l < len(left) && r < len(right){
if left[l] > right[r] {
m = append(m, right[r])
r++
} else {
m = append(m, left[l])
l++
}
}
//剩下的放入末尾
m = append(m,right[r:]...)
m = append(m,left[l:]...)
return
}

时间和空间复杂度都为 o(n log n) 属于稳定排序,非原地排序

快排

快排是能用到实战中的基础算法知识,掌握细节还是很值得的。

思路

  • 找一个基准值这用数组第一个(如果追求极致可以单独把取基准值拎出来做成函数,取3-5个值取中间值)

  • 从数组最左边和最右边取值和基准值做对比,左边小右边大。如果条件不符合,位置做交换

  • 左边第一个值(基准值会被覆盖,所以代码顺序必须是先从右边开始),左边第一个值放入临时对比基准变量 p

  • 值交换完成后执行移位操作,因为这个值是目标位置,避免下面循环再次比对

  • 临时值复位,递归左右两边

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
func quick_sort(nums []int ,left int ,rigth int){

if (left>=rigth){
return
}
l,r,p := left,rigth,nums[left]

for r>l{
for r>l && nums[r] > p{
r--
}
if r>l {
nums[l] = nums[r]
l++
}
for r>l && nums[l] < p{
l++
}

if r>l {
nums[r] = nums[l]
r--
}

}
nums[l] = p
quick_sort(nums,left, l-1)
quick_sort(nums,l+1, rigth)

}

冒泡排序 与 插入排序

排序算法在面试和工作中其实使用的地方还是蛮多的,一个人在上海空闲时间巨大。这边整理一批常用的算法解法,做做笔记记录下来~

冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢”浮”到数列的顶端。


思路

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

经过优化的冒泡算法

  1. 右边已经为有序,所以每次子循环会跳过 count-i
  2. 如果一次循环下来没有需要交换的元素,证明已经完成排序,所以直接break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func sortArray(nums []int) []int {
count := len(nums)
for i := 0; i < count; i++ {
flag := false
for j := 0 ; j < (count-i-1); j++ {
if nums[j] > nums[j+1] {
temp := nums[j]
nums[j] = nums[j+1]
nums[j+1] = temp
flag = true
}
}
if (!flag) {
break
}
}
return nums
}
是否稳定算法 平均时间复杂度 空间复杂度
o(n^2) o(1)

遍历法

这个方法是我自己取的,因为我没在排序算法中找打它的描述。但是它在我看来的确是最简单的排序方法。大概思路就2个循环,第一个循环控制数组元素的移动,第二个做比对,如果条件表达式不满足就交换个位置。其实一开始我认为这种排序方法就是冒泡的,但是冒泡的定义是 比较相邻的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
func sortArray(nums []int) []int {
count := len(nums)
for i := 0; i < count; i++ {
for j := i ; j < count; j++ {
if nums[j] > nums[i] {
temp := nums[j]
nums[j] = nums[i]
nums[i] = temp
}
}
}
return nums
}
是否稳定算法 平均时间复杂度 空间复杂度
o(n^2) o(1)

插入排序

插入排序就是把一个数组分配成左右2部分,左边和右边。左边是排序好的右边是未排序的。
类似 4,5,6,3,2,1 字符串。我们分别为左右2边,4,5 | 6,3,2,1 排序时用右边的第一个数 6 和左边的数依次做对比,如果满足条件表达式就把 左边的数依次后移然后把6插入进去

技巧:其实算法本身的思路很好理解,关键点是如何用程序去实现。在这个算法中我们只要理解下面几个表达式基本上就理解了插入排序的精髓了

  • for i:=1; i<count ; i++ 数组为0开头,这边 赋值为1 主要目的是忽略第一位直接从第2位元素开始比较

  • heard := nums[i] 这边的heard变量是一个临时变量,因为在找到插入位后。左边的数组会依次后移,这样原先的比较位(右1 会被覆盖掉

  • j := i-1 是为了取到 4,5 | 6,3,2,1 左边最后一位的小标。即 i=2,j=2-1, j = 1,下标对应的值是5,然后 j– , 5 -> 4 ->end 依次去寻找插入位。

  • 为什么heard > nums[j] 一旦不满足就马上跳出循环,因为插入排序左边的数都是有序的,有一个不满足都表示缘分未到(不是当前排序位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func sortArray(nums []int) []int {
count := len(nums)
for i:=1; i<count ; i++ {
heard := nums[i]
j := i-1
for ; j >= 0; j -- {
if (heard > nums[j]) {
nums[j+1] = nums[j]
} else {
break
}
}
nums[j+1] = heard
}
return nums
}
是否稳定算法 平均时间复杂度 空间复杂度
o(n^2) o(1)

图片来源:https://www.runoob.com/w3cnote/ten-sorting-algorithm.html

请我喝杯咖啡吧~