-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
694 lines (694 loc) · 268 KB
/
index.json
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
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
[
{
"uri": "https://liuzeng01.github.io/booknote/algorithm/",
"title": "algorithm",
"tags": [],
"description": "algorithm",
"content": " 动态规划 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/august/",
"title": "August",
"tags": [],
"description": "2020 08",
"content": " HTTPS \r\rMySQL 30个基本问题 \r\r一次XHR错误的排查过程 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/javascript/august/",
"title": "August",
"tags": [],
"description": "2020 08",
"content": " Node JS \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/dockerfile/command/",
"title": "dockerfile基础命令",
"tags": [],
"description": "dockerfile",
"content": " dockerfile 基础命令 FROM 指定基础镜像 FROM nginx\nRUN 执行命令 exec 格式: RUN [\u0026quot;可执行文件\u0026quot;, \u0026quot;参数1\u0026quot;, \u0026quot;参数2\u0026quot;]\rRUN apt-get update\rRUN apt-get install -y gcc libc6-dev make\rCOPY 复制文件 COPY [\u0026quot;\u0026lt;源路径1\u0026gt;\u0026quot;,... \u0026quot;\u0026lt;目标路径\u0026gt;\u0026quot;]\rCOPY package.json /usr/src/app/\rADD 更高级的复制文件 ADD [\u0026quot;\u0026lt;源路径1\u0026gt;\u0026quot;,... \u0026quot;\u0026lt;目标路径\u0026gt;\u0026quot;]\r源路径可以使是url,如果是压缩包,自动解压缩,不推荐使用\nFROM scratch\rADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz\rCMD 容器启动命令 shell 格式: CMD \u0026lt;命令\u0026gt;\rexec 格式: CMD [\u0026quot;可执行文件\u0026quot;, \u0026quot;参数1\u0026quot;, \u0026quot;参数2\u0026quot;...]\r在指定了 ENTRYPOINT 指令后,用 CMD 指 定具体的参数\nCMD [ \u0026quot;sh\u0026quot;, \u0026quot;-c\u0026quot;, \u0026quot;echo $HOME\u0026quot; ]\r#CMD echo $HOME\rENTRYPOINT 入口点 shell 格式: ENTRYPOINT \u0026lt;命令\u0026gt;\rexec 格式: ENTRYPOINT [\u0026quot;可执行文件\u0026quot;, \u0026quot;参数1\u0026quot;, \u0026quot;参数2\u0026quot;...]\rdocker run 的参数 \u0026ndash;entrypoint可以替换 在指定了 ENTRYPOINT 指令后,用 CMD 指 定具体的参数 ENTRYPOINT [ \u0026quot;curl\u0026quot;, \u0026quot;-s\u0026quot;, \u0026quot;http://ip.cn\u0026quot; ]\nENV 设置环境变量 ENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt;\rENV \u0026lt;key1\u0026gt;=\u0026lt;value1\u0026gt; \u0026lt;key2\u0026gt;=\u0026lt;value2\u0026gt;...\r构建时与容器运行时同时存在\nENV NODE_VERSION 7.2.0\rRUN curl -SLO \u0026quot;https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.ta r.xz\u0026quot;\rARG 构建参数 ARG \u0026lt;参数名\u0026gt;[=\u0026lt;默认值\u0026gt;]\r 构建时存在,运行时不存在 docker build 中用 \u0026ndash;build-arg \u0026lt;参数名\u0026gt;=\u0026lt;值\u0026gt; 可覆盖\nVOLUME 定义匿名卷 VOLUME [\u0026quot;\u0026lt;路径1\u0026gt;\u0026quot;, \u0026quot;\u0026lt;路径2\u0026gt;\u0026quot;...]\rVOLUME \u0026lt;路径\u0026gt;\r指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据\nVOLUME /data\rEXPOSE 声明端口\rEXPOSE \u0026lt;端口1\u0026gt; [\u0026lt;端口2\u0026gt;...]\rdocker run -P 端口时,会自动随机映射 EXPOSE 的端口\nWORKDIR 指定工作目录 WORKDIR \u0026lt;工作目录路径\u0026gt;\rWORKDIR 指定工作目录(或称为当前目录);目录不存在,WORKDIR自动建立;改变以后各层的工作目录的位置\rWORKDIR /app\rUSER 指定当前用户 USER \u0026lt;用户名\u0026gt;\rRUN groupadd -r redis \u0026amp;\u0026amp; useradd -r -g redis redis USER redis\rRUN [ \u0026quot;redis-server\u0026quot; ]\rHEALTHCHECK 健康检查 HEALTHCHECK [选项] CMD \u0026lt;命令\u0026gt; :设置检查容器健康状况的命令\rHEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令\rFROM nginx\rRUN apt-get update \u0026amp;\u0026amp; apt-get install -y curl \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s \\\rCMD curl -fs http://localhost/ || exit 1\rMAINTAINER 作者信息 MAINTAINER puanghu\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/cmd/",
"title": "docker命令",
"tags": [],
"description": "docker",
"content": " Harbor 部署 \r\rAliyun Registry \r\rvue \u0026#43; docker \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/",
"title": "Go",
"tags": [],
"description": "Golang",
"content": " Go语言第三方库 \r\rNote \r\rCook Book \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/",
"title": "Go语言第三方库",
"tags": [],
"description": "GitHub上的Go开源库",
"content": " viper \r\rroutine \r\rcobra \r\rhugo \r\rnutsdb \r\rtview \r\r\r "
},
{
"uri": "https://liuzeng01.github.io/essay/august/https/",
"title": "HTTPS",
"tags": [],
"description": "2020 08",
"content": " 1. 加密的大致种类: 1、不可逆加密。比如 MD5、SHA、HMAC 典型用途:\n密码总不能明文存到数据库吧,所以一般加密存起来,只要用户的输入经过同样的加密算法 对比一下就知道密码是否正确了,所以没必要可逆。\r 2、可逆加密。\n对称加密。比如:AES、DES、3DES、IDEA、RC4、RC5、RC6 典型用途:\n用同一个密码加密和解密,太常见了,我用密码加密文件发给你,你只能用我的密码才能解开。\r 非对称加密(就是公私钥)。比如:RSA、DSA、ECC\n典型用途: 加密(保证数据安全性)使用公钥加密,需使用私钥解密。 认证(用于身份判断)使用私钥签名,需使用公钥验证签名。\r 2. 如何加密 用不可逆加密可行吗? 首先不可逆加密的是不是可以直接排除了,不知道为啥的,可以想一想自己的目的是什么哈。\n用对称加密可行吗? 如果通信双方都各自持有同一个密钥,且没有别人知道,这两方的通信安全当然是可以被保证的,然而最大的问题就是这个密钥怎么让传输的双方知晓,同时不被别人知道,想一想:是不是不管怎么传,中间都有可能被截获,密钥都被截获了,其他的安全是不是也就无从谈起了。看来纯粹的对称加密不能解决http的安全问题。\n用非对称加密(rsa)可行吗? 试想一下:如果服务器生成公私钥,然后把公钥明文给客户端(有问题,下面说),那客户端以后传数据用公钥加密,服务端用私钥解密就行了,这貌似能保证浏览器到服务端的通道是安全的,那服务端到浏览器的通道如何保证安全呢?\n那既然一对公私钥能保证,那如果浏览器本身也生成一对公私钥匙,然后把公钥明文发给服务端,抛开明文传递公钥的问题,那以后是不是可以安全通信了,的确可以!但https本身却不是这样做的,最主要的原因是非对称加密非常耗时,特别是加密解密一些较大数据的时候有些力不从心,当然还有其他原因。既然非对称加密非常耗时,那只能再考虑对称加密了。\n用非对称加密 + 对称加密可行吗? 上面提到浏览器拥有服务器的公钥,那浏览器生成一个密钥,用服务器的公钥加密传给服务端,然后服务端和浏览器以后拿这个密钥以对称加密的方式通信不就好了!完美! 所以接下来说一下上面遗留的一个问题:服务端的公钥是明文传过去的,有可能导致什么问题呢? 如果服务端在把明文公钥传递给浏览器的时候,被黑客截获下来,然后把数据包中的公钥替换成自己伪造的公钥(当然他自己有自己的私钥),浏览器本身是不知道公钥真假的,所以浏览器还是傻傻的按照之前的步骤,生成对称密钥,然后用假的公钥加密传递给服务端,这个时候,黑客截获到报文,然后用自己的私钥解密,拿到其中的对称密钥,然后再传给服务端,就这样神不知鬼不觉的,对称密钥被黑客截取,那以后的通信其实就是也就全都暴露给黑客了。\n上面的流程到底哪里存在问题,以致使黑客可以任意冒充服务端的公钥! 其实根本原因就是浏览器无法确认自己收到的公钥是不是网站自己的。\n如何保证浏览器收到的公钥一定是该网站的公钥 现实生活中,如果想证明某身份证号一定是小明的,怎么办?看身份证。这里国家机构起到了“公信”的作用,身份证是由它颁发的,它本身的权威可以对一个人的身份信息作出证明。互联网中能不能搞这么个公信机构呢?给网站颁发一个“身份证”?当然可以,这就是平时经常说的数字证书。\n什么是数字证书?证书都包含什么? 身份证之所以可信,是因为背后是国家,那数字证书如何才可信呢?这个时候找CA(Certificate Authority)机构。办身份证需要填写自己的各种信息,去CA机构申请证书需要什么呢?至少应该有以下几项吧:\n1、网站域名 2、证书持有者 3、证书有效期 4、证书颁发机构 5、服务器公钥(最主要) 6、接下来要说的签名时用的hash算法\r那证书如何安全的送达给浏览器,如何防止被篡改呢?给证书盖个章(防伪标记)不就好了?这就又引出另外一个概念:数字签名。 什么是数字签名?签名的过程是什么 签名的过程其实也很简单:\n1、CA机构拥有非对称加密的私钥和公钥。\r2、CA对证书明文信息进行hash。\r3、对hash后的值用私钥加密,得到数字签名。\r所以呢,总结一下:CA机构颁发的证书包含(证书内容的明文+签名)。 浏览器收到服务下发的证书之后,拿到证书明文和签名,怎么验证是否篡改了呢? 大家知道,私钥签名,公钥验签。证书里面的签名是CA机构用私钥签名的,所以我只要用CA机构的公钥验证一下签名不就好了,怎么验证呢? 还记得证书里面的明文包含什么吧,不记得的话看看上面的内容。\n1、拿到证书里面明文的hash算法并对明文内容进行hash运算,得到A 。\r2、用CA的公钥解密签名得到B 。\r3、比较A 和 B,如果相等,说明没有被篡改,否则浏览器提示证书不可信。\r有没有发现一个问题?CA的公钥从哪里获取呢?\n 这个简单,CA权威机构本来也没多少个,所以,浏览器内部都内置了各大CA机构的公钥信息。 简单总结一下: 如果证书被篡改,浏览器就提示不可信,终止通信,如果验证通过,说明公钥没问题,一定没被篡改。 公钥没被篡改,那浏览器生成的对称加密用的密钥用公钥加密发送给服务端,也只有服务端的私钥能解开,所以保证了 对称密钥不可能被截获,对称密钥没被截获,那双方的通信就一定是安全的。\n黑客依然可以截获数据包,但是全都是经过对称加密的密钥加密的,他也可以篡改,只是没有任何意义了,黑客也不会做吃力不讨好的事了\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/hugo/",
"title": "hugo语法",
"tags": [],
"description": "Essay",
"content": " 1. hugo MarkDown : Insert Picture And Design the Dimensions\n\u0026lt;img src=\u0026#34;../../../image/zsmart/pcrf/nat1.jpg\u0026#34; width = \u0026#34;640\u0026#34; alt=\u0026#34;NAT1\u0026#34; align=center /\u0026gt; \u0026lt;img src=\u0026#34;../../../image/zsmart/pcrf/nat2.jpg\u0026#34; width = \u0026#34;640\u0026#34; alt=\u0026#34;NAT2\u0026#34; align=center /\u0026gt; 2. Ace Document button The button shortcode allows you to add a button to the page. This button is a HTML anchor element and can thus be used to link to another page or website.\n\rButton\r\rButton\r\rButton\r\rButton\r\rButton\r\rButton\r\rButton\r\rUsage Place the following shortcode on the page Copy\r\r\r{{\u0026lt; button style=\u0026#34;STYLE\u0026#34; link=\u0026#34;https://yourwebsite.com\u0026#34; \u0026gt;}} [content] {{\u0026lt; /button \u0026gt;}} \r\r\rParameters style The style parameter is directly applied to the alert as a class in the format \u0026ldquo;btn-{STYLE}\u0026quot;. Bootstrap comes with a variety of styles that can be used with this:\n primary secondary danger warning success info Each style can also be presented as an \u0026lsquo;outline\u0026rsquo; variant by prefixing the style with \u0026lsquo;outline-\u0026rsquo;.\nExample: style=\u0026quot;outline-primary\u0026quot;.\nlink The link parameter may consist of an URL leading to a page or other website. Simply use it by defining a URL for the button to link to.\nExample: link=\u0026quot;https://google.com\u0026quot;.\n Alert The alerts shortcode allows you to let information stand out by means of an alert styled box. This can be used to indicate danger, warning, success or info.\n\rI\u0026rsquo;m a danger alert\r\rI\u0026rsquo;m a warning alert\r\rI\u0026rsquo;m a success alert\r\rI\u0026rsquo;m an info alert\r\rUsage Place the following shortcode on the page Copy\r\r\r{{\u0026lt; alert style=\u0026#34;STYLE\u0026#34; \u0026gt;}} [content] {{\u0026lt; /alert \u0026gt;}} \r\r\rParameters Style danger warning success info Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/json/",
"title": "Json ",
"tags": [],
"description": "Daily Note",
"content": " Json func Marshal(v interface{}) ([]byte, error) // json编码\n func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) // json编码带缩进\n func Unmarshal(data []byte, v interface{}) error // json解码\n package main import ( \u0026#34;fmt\u0026#34; // \u0026#34;reflect\u0026#34; \u0026#34;encoding/json\u0026#34; ) type User struct{ // Name string `json:\u0026#34;-,\u0026#34;` // 这个导出才是 {\u0026#34;-\u0026#34;:\u0026#34;张三\u0026#34;,\u0026#34;age\u0026#34;:20} // Name string `json:\u0026#34;-\u0026#34;` // always omitted // Name string `json:\u0026#34;,omitempty\u0026#34;` // 如果为零值导出为空. 然而不想重命名. Name string `json:\u0026#34;name,omitempty\u0026#34;` // 如果为零值导出为空. // Age int `json:\u0026#34;age,string\u0026#34;` // 这个会把int变成json string... // 上面那个很有用. 在 A map =\u0026gt; string =\u0026gt; B map 的时候可能会被弄成科学计数法.. // 用字符串表示时间戳这种可以避免这个... Age int `json:\u0026#34;age\u0026#34;` sex int `json:\u0026#34;sex\u0026#34;` } func main() { u := User{\u0026#34;张三\u0026#34;, 20, 1} uu, _ := json.Marshal(u) // 只会导出公有的, 即首字母大写的 fmt.Println(string(uu)) } Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/",
"title": "July",
"tags": [],
"description": "2020 07",
"content": " hugo语法 \r\r网络相关 \r\rGin \r\rGolang面试题 \r\rPowershell \r\rSSH免密登陆 \r\rtoml \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/",
"title": "July Note",
"tags": [],
"description": "Daily Note",
"content": " Json \r\rSort \r\rSSH \r\r文件操作 \r\rContext \r\rgRPC \r\rTag \r\rtran \r\r循环读取输入 \r\rGo数据结构 \r\rGo语言查询不定字段 \r\rIP \r\rmod \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/javascript/august/nodejs/",
"title": "Node JS",
"tags": [],
"description": "2020 08",
"content": " 1. 升级nodejs的步骤 sudo npm cache clean -f # 清除Node缓存 sudo npm install n -g # 安装node版本管理工具 n sudo n stable (安装node最新版本) # 安装最新版本 sudo n 8.9.4 (安装node指定版本8.9.4) # 安装指定版本 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/",
"title": "Note",
"tags": [],
"description": "Daily Note",
"content": " July Note \r\rAugust \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/",
"title": "Shell",
"tags": [],
"description": "Shell",
"content": " Shell 范例 \r\rShell Case \r\rScript命令 \r\rSupervisor \r\rVim快捷键 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/shellexample/",
"title": "Shell 范例",
"tags": [],
"description": "Shell",
"content": " Shell 范例 1 \r\rgrep/sed/awk \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/shellexample/basic/",
"title": "Shell 范例 1 ",
"tags": [],
"description": "Shell",
"content": " 1. 数值操作 # 赋值,递增 [root@master week1]# i=1 [root@master week1]# let i++ [root@master week1]# echo $i 2 [root@master week1]# [root@master week1]# ((i++)) [root@master week1]# echo $i 3 ## 运算 [root@master week1]# expr $i * 5 15 ## 求幂 [root@master week1]# let $i=2**2 4 ## 进制转换 obase:目标进制 ,ibase:当前进制;转换数值 [root@master ~]# echo \u0026#34;obase=10;ibase=8;11\u0026#34;| bc -l 9 ## 产生一系列数值 [root@master ~]# seq 1 10 1 2 3 4 5 6 7 8 9 10 [root@master ~]# seq 1 2 10 1 3 5 7 9 2. 逻辑语句 while #! /bin/bash i=1 while [ $i -lt 10000 ] # lt 小于 do ((i++)) done echo $i Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/sort/",
"title": "Sort",
"tags": [],
"description": "Daily Note",
"content": " 实现Go提供的sort接口,来进行自定义排序 只需要实现下列三个接口,就可以进行排序\n package sort type Interface interface { Len() int // 获取元素数量 Less(i, j int) bool // i,j是序列元素的指数。 Swap(i, j int) // 交换元素 } 自己模拟了一个最简单的示例\npackage main\rimport (\r\u0026quot;fmt\u0026quot;\r\u0026quot;sort\u0026quot;\r)\rfunc main() {\rvar mysort MySort\rvar a SortType\ra.id = 5\ra.Name = \u0026quot;a\u0026quot;\rvar b SortType\rb.id = 51\rb.Name = \u0026quot;b\u0026quot;\rvar c SortType\rc.id = 6\rc.Name = \u0026quot;c\u0026quot;\rvar d SortType\rd.id = 1\rd.Name = \u0026quot;d\u0026quot;\rmysort = []SortType{a, b, c, d}\rsort.Sort(mysort)\rfmt.Println(mysort)\r}\rtype MySort []SortType\rtype SortType struct {\rName string\rid int\r}\rfunc (a MySort) Len() int { return len(a) }\rfunc (a MySort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\rfunc (a MySort) Less(i, j int) bool { return a[i].id \u0026lt; a[j].id }\r//输出结果为\r[{d 1} {a 5} {c 6} {b 51}]\r 接口实现不受限于结构体,任何类型都可以实现接口。但是系统自定义的类型是无法实现的,因为GO语言自己实现了这些[]int ,[]string 的接口。如果要自己实现自定义的排序的话,可以添加别名类型。\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/booknote/sre/",
"title": "SRE",
"tags": [],
"description": "SRE",
"content": " SRE介绍 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/booknote/sre/sre%E4%BB%8B%E7%BB%8D/",
"title": "SRE介绍",
"tags": [],
"description": "Note",
"content": " \rSRE\r\rSRE是指Site Reliability Engineer (网站可靠性工程师)。他是软件工程师和系统管理员的结合,一个SRE工程师基本上需要掌握很多知识:算法,数据结构,编程能力,网络编程,分布式系统,可扩展架构,故障排除。\r\r\r\rSRE都干些什么? SRE不是做底层硬件维护,而是负责各种服务的性能和稳定性。\r远离底层硬件,更多靠近软件基础架构层面,帮助企业客户打造强大的软件基础构架。\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/ssh/",
"title": "SSH",
"tags": [],
"description": "Daily Note",
"content": " 利用Go语言提供的ssh包实现一个ssh工具 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; \u0026#34;golang.org/x/crypto/ssh\u0026#34; ) func main() { session, err := connect(\u0026#34;root\u0026#34;, \u0026#34;abc@123A\u0026#34;, \u0026#34;10.45.11.115\u0026#34;, 22) if err != nil { log.Fatal(err) } defer session.Close() fmt.Printf(\u0026#34;----\u0026gt;\u0026#34;) session.Stdout = os.Stdout session.Stderr = os.Stderr session.Stdin = os.Stdin session.Run(\u0026#34;sh\u0026#34;) } func connect(user, password, host string, port int) (*ssh.Session, error) { var ( addr string clientConfig *ssh.ClientConfig client *ssh.Client session *ssh.Session err error ) clientConfig = \u0026amp;ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ ssh.Password(password), }, Timeout: 30 * time.Second, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } // connet to ssh \taddr = fmt.Sprintf(\u0026#34;%s:%d\u0026#34;, host, port) if client, err = ssh.Dial(\u0026#34;tcp\u0026#34;, addr, clientConfig); err != nil { return nil, err } // create session \tif session, err = client.NewSession(); err != nil { return nil, err } return session, nil } 需要补充的:包装下os.Stdin接口,加入点显示什么的 ssh用来执行远程批量任务比较好。这个先写下来,回头实现下。 解决下windows下os.Stdin结束的行尾存在\u0026rsquo;\\r\\n\u0026rsquo;问题\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/spf13/viper/",
"title": "viper",
"tags": [],
"description": "github.com/spf13/viper",
"content": " 1. 介绍 Viper是Go应用程序的完整配置解决方案,包括12-Factor应用程序。它旨在在应用程序中工作,并可以处理所有类型的配置需求和格式。它支持:\n 设置默认值\r从JSON,TOML,YAML,HCL和Java属性配置文件中读取\r实时观看和重新读取配置文件(可选)\r从环境变量中读取\r从远程配置系统(etcd或Consul)读取,并观察变化\r从命令行标志读取\r从缓冲区读取\r设置显式值\r Viper可以被认为是所有应用程序配置需求的注册表。\n在构建现代应用程序时,您不必担心配置文件格式; 你可以专注于构建出色的软件。 Viper 可以做如下工作:\n 加载并解析JSON、TOML、YAML、HCL 或 Java properties 格式的配置文件\r可以为各种配置项设置默认值\r可以在命令行中指定配置项来覆盖配置值\r提供了别名系统,可以不破坏现有代码来实现参数重命名\r可以很容易地分辨出用户提供的命令行参数或配置文件与默认相同的区别\r Viper读取配置信息的优先级顺序,从高到低,如下:\n 显式调用Set函数\r命令行参数\r环境变量\r配置文件\rkey/value 存储系统\r默认值\r Viper 的配置项的key不区分大小写。\n2. 使用示例 1. 配置文件示例 internal.toml,配置数据库的连接信息 [DataBase] DBType = \u0026#34;mysql\u0026#34; Host = \u0026#34;127.0.0.1:3306\u0026#34; Username = \u0026#34;golang\u0026#34; Password = \u0026#34;golang\u0026#34; Charset = \u0026#34;utf8\u0026#34; 2. 声明数据库连接对象 type DBModel struct { Engine *sql.DB DBInfo *DBInfo } type DBInfo struct { DBType string Host string Username string Password string Charset string } func NewDBModel(info *DBInfo) *DBModel { return \u0026amp;DBModel{ Engine: nil, DBInfo: info, } } func (m *DBModel) Connect() error { var err error s := \u0026#34;%s:%s@tcp(%s)/gostudy?\u0026#34; + \u0026#34;charset=%s\u0026amp;parseTime=True\u0026amp;loc=Local\u0026#34; dsn := fmt.Sprintf(s, m.DBInfo.Username, m.DBInfo.Password, m.DBInfo.Host, m.DBInfo.Charset, ) m.Engine,err= sql.Open(m.DBInfo.DBType, dsn) if err != nil { return err } return nil } 3. 声明viper对象,编写读取配置方法 type Setting struct { vp *viper.Viper } func NewSetting(configs ...string) (*Setting, error) { vp := viper.New() vp.SetConfigName(\u0026#34;internal\u0026#34;) for _, config := range configs { if config != \u0026#34;\u0026#34; { vp.AddConfigPath(config) } } vp.SetConfigType(\u0026#34;toml\u0026#34;) err := vp.ReadInConfig() if err != nil { return nil, err } s := \u0026amp;Setting{vp} return s, nil } var sections = make(map[string]interface{}) func (s *Setting) ReadSection(k string, v interface{}) error { err := s.vp.UnmarshalKey(k, v) if err != nil { return err } if _, ok := sections[k]; !ok { sections[k] = v } return nil } 4. 声明配置路径 var configs string 5. 调用读取配置文件方法 func settupsetting(){ configs = \u0026#34;./configs\u0026#34; s,err := pkg.NewSetting(strings.Split(configs, \u0026#34;,\u0026#34;)...) if err != nil { log.Fatal(err) } err = s.ReadSection(\u0026#34;DataBase\u0026#34;,\u0026amp;DBSettings) fmt.Println(DBSettings) } 6. 连接数据库 var dbmodel *global.DBModel func init(){ dbmodel = global.NewDBModel(\u0026amp;global.DBSettings) err := dbmodel.Connect() if err != nil { log.Println(err) } } 7. 函数入口 func main(){ defer dbmodel.Engine.Close() sqlstr := \u0026#34;select count(*) from gostudy\u0026#34; var name string rowObj := dbmodel.Engine.QueryRow(sqlstr) rowObj.Scan(\u0026amp;name) fmt.Println(name) } 3. 极简示例 配置文件示例 [singal] name = \u0026#34;hello\u0026#34; age = 25 address = \u0026#34;Nanjing\u0026#34; job = \u0026#34;programmer\u0026#34; 代码示例 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; \u0026#34;log\u0026#34; ) type singal struct { Name string Age int Address string Job string } func main() { vp := viper.New() vp.AddConfigPath(\u0026#34;./\u0026#34;) vp.SetConfigName(\u0026#34;singal\u0026#34;) vp.SetConfigType(\u0026#34;toml\u0026#34;) err := vp.ReadInConfig() if err != nil { log.Fatal(err) } var s *singal err = vp.UnmarshalKey(\u0026#34;singal\u0026#34;,\u0026amp;s) if err != nil{ log.Fatal(err) } fmt.Println(s) } // 注意这样一个问题:这个unmarshalkey的过程是用了json的反序列化。所以如果结构体的组成字段为小写开头的话, // 是不能正常解析的。配置文件里面倒是大小写无所谓。 "
},
{
"uri": "https://liuzeng01.github.io/booknote/algorithm/dynamic/",
"title": "动态规划",
"tags": [],
"description": "algorithm",
"content": " 1.实例说明什么是动态规划 计算1到10的和 计算过程:1+2+3+4+5+6+7+8+9+10,得55\n上述如果口算,最快也要前后凑整10的相加,就是需要计算所有的值,才能得出结果。这时候,如果再加一个11,你肯定会脱口而出,得66,为什么直接能说出来呢,因为你记住了前面所求的得和,这一次得计算不需要关心前面这个55怎么来得,只关注这一步即可。\n引出定义:(有点难理解)在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线.这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策问题。在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法。\n优化理解:动态规划是把一个大问题拆解成一堆小问题,这个本身没什么问题,但是核心不是能拆分,而是取决于该问题拆分出的小问题是否能被特定的规律重复利用。 2. 实际问题解决例子:路径最小值问题 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点\n例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。\n过程解析:自上而下计算时,只要每一步都记录路径上最小的值即可,因为路径上只能访问相邻的两个点,所以比较两个相邻点和本点数据的和,保存较小的一个值即可。不断向下类推,最后一组数据中最小的值就是i最小路径。\nfunc minimumTotal(triangle [][]int) int { for i, v := range triangle { if i == 0 {//第一层没有上一级,不用计算 continue } for idx, val := range v { if idx == len(v)-1 {//最后一个值,他的上一级比他小一位数,所以相邻点只有一个直接赋值 triangle[i][idx] = val + triangle[i-1][idx-1] continue } if idx == 0 {//第一位,相邻点也只有一个,直接赋值 triangle[i][idx] = val + triangle[i-1][idx] continue } if val+triangle[i-1][idx] \u0026gt; val+triangle[i-1][idx-1] {// 比较相邻点较小值,并记录 triangle[i][idx] = val + triangle[i-1][idx-1]// 不需要原来的值,直接赋值保存即可,因为只关心路径结果即可 } else { triangle[i][idx] = val + triangle[i-1][idx] } } } min := triangle[len(triangle)-1][0] for _, v := range triangle[len(triangle)-1] {//最后一组中最小值就是路径最小 if v \u0026lt; min { min = v } } return min } Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/",
"title": "文件操作",
"tags": [],
"description": "Note",
"content": " 1.打开关闭文件 file open/close\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { // 只读方式打开当前目录下的main.go文件 \tfile, err := os.Open(\u0026#34;./main.go\u0026#34;) if err != nil { fmt.Println(\u0026#34;open file failed!, err:\u0026#34;, err) return } // 关闭文件 \tfile.Close() } 2.文件读取 file read 它接收一个字节切片,返回读取的字节数和可能的具体错误,读到文件末尾时会返回0和io.EOF\nfunc (f *File) Read(b []byte) (n int, err error) func main() { // 只读方式打开当前目录下的main.go文件 \tfile, err := os.Open(\u0026#34;./main.go\u0026#34;) if err != nil { fmt.Println(\u0026#34;open file failed!, err:\u0026#34;, err) return } defer file.Close() // 使用Read方法读取数据 \tvar tmp = make([]byte, 128) n, err := file.Read(tmp) if err == io.EOF { fmt.Println(\u0026#34;文件读完了\u0026#34;) return } if err != nil { fmt.Println(\u0026#34;read file failed, err:\u0026#34;, err) return } fmt.Printf(\u0026#34;读取了%d字节数据\\n\u0026#34;, n) fmt.Println(string(tmp[:n])) } 3.使用for循环读取文件中的所有数据。 func main() { // 只读方式打开当前目录下的main.go文件 \tfile, err := os.Open(\u0026#34;./main.go\u0026#34;) if err != nil { fmt.Println(\u0026#34;open file failed!, err:\u0026#34;, err) return } defer file.Close() // 循环读取文件 \tvar content []byte var tmp = make([]byte, 128) for { n, err := file.Read(tmp) if err == io.EOF { fmt.Println(\u0026#34;文件读完了\u0026#34;) break } if err != nil { fmt.Println(\u0026#34;read file failed, err:\u0026#34;, err) return } content = append(content, tmp[:n]...) } fmt.Println(string(content)) } 4.bufio 可以用来配合channel使用读取大文件\n package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) // bufio按行读取示例 func main() { file, err := os.Open(\u0026#34;./xx.txt\u0026#34;) if err != nil { fmt.Println(\u0026#34;open file failed, err:\u0026#34;, err) return } defer file.Close() reader := bufio.NewReader(file) for { line, err := reader.ReadString(\u0026#39;\\n\u0026#39;) //注意是字符 \tif err == io.EOF { if len(line) != 0 { fmt.Println(line) } fmt.Println(\u0026#34;文件读完了\u0026#34;) break } if err != nil { fmt.Println(\u0026#34;read file failed, err:\u0026#34;, err) return } fmt.Print(line) } } 5.ioutil 读取整个文件 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; ) // ioutil.ReadFile读取整个文件 func main() { content, err := ioutil.ReadFile(\u0026#34;./main.go\u0026#34;) if err != nil { fmt.Println(\u0026#34;read file failed, err:\u0026#34;, err) return } fmt.Println(string(content)) } 6.文件写入操作 os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。 func OpenFile(name string, flag int, perm FileMode) (*File, error) {\r...\r}\n name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:\n 模式 含义 os.O_WRONLY 只写 os.O_CREATE 创建文件 os.O_RDONLY 只读 os.O_RDWR 读写 os.O_TRUNC 清空 os.O_APPEND 追加 perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。\n 7.Write和WriteString func main() { file, err := os.OpenFile(\u0026#34;xx.txt\u0026#34;, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) if err != nil { fmt.Println(\u0026#34;open file failed, err:\u0026#34;, err) return } defer file.Close() str := \u0026#34;hello 沙河\u0026#34; file.Write([]byte(str)) //写入字节切片数据 \tfile.WriteString(\u0026#34;hello 小王子\u0026#34;) //直接写入字符串数据 } bufio.NewWriter\n func main() { file, err := os.OpenFile(\u0026#34;xx.txt\u0026#34;, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) if err != nil { fmt.Println(\u0026#34;open file failed, err:\u0026#34;, err) return } defer file.Close() writer := bufio.NewWriter(file) for i := 0; i \u0026lt; 10; i++ { writer.WriteString(\u0026#34;hello沙河\\n\u0026#34;) //将数据先写入缓存 \t} writer.Flush() //将缓存中的内容写入文件 } ioutil.WriteFile\n func main() { str := \u0026#34;hello 沙河\u0026#34; err := ioutil.WriteFile(\u0026#34;./xx.txt\u0026#34;, []byte(str), 0666) if err != nil { fmt.Println(\u0026#34;write file failed, err:\u0026#34;, err) return } } 8.copyFile 借助io.Copy()实现一个拷贝文件函数。\n// CopyFile 拷贝文件函数 func CopyFile(dstName, srcName string) (written int64, err error) { // 以读方式打开源文件 \tsrc, err := os.Open(srcName) if err != nil { fmt.Printf(\u0026#34;open %s failed, err:%v.\\n\u0026#34;, srcName, err) return } defer src.Close() // 以写|创建的方式打开目标文件 \tdst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fmt.Printf(\u0026#34;open %s failed, err:%v.\\n\u0026#34;, dstName, err) return } defer dst.Close() return io.Copy(dst, src) //调用io.Copy()拷贝内容 } func main() { _, err := CopyFile(\u0026#34;dst.txt\u0026#34;, \u0026#34;src.txt\u0026#34;) if err != nil { fmt.Println(\u0026#34;copy file failed, err:\u0026#34;, err) return } fmt.Println(\u0026#34;copy done!\u0026#34;) } 实现一个cat命令 使用文件操作相关知识,模拟实现linux平台cat命令的功能。\n package main import ( \u0026#34;bufio\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) // cat命令实现 func cat(r *bufio.Reader) { for { buf, err := r.ReadBytes(\u0026#39;\\n\u0026#39;) //注意是字符 \tif err == io.EOF { break } fmt.Fprintf(os.Stdout, \u0026#34;%s\u0026#34;, buf) } } func main() { flag.Parse() // 解析命令行参数 \tif flag.NArg() == 0 { // 如果没有参数默认从标准输入读取内容 \tcat(bufio.NewReader(os.Stdin)) } // 依次读取每个指定文件的内容并打印到终端 \tfor i := 0; i \u0026lt; flag.NArg(); i++ { f, err := os.Open(flag.Arg(i)) if err != nil { fmt.Fprintf(os.Stdout, \u0026#34;reading from %s failed, err:%v\\n\u0026#34;, flag.Arg(i), err) continue } cat(bufio.NewReader(f)) } } Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/august/",
"title": "August",
"tags": [],
"description": "Golang",
"content": " Got下载工具 \r\r二维码生成 \r\rGo语言设计模式 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/booknote/designpattern/",
"title": "Design Pattern",
"tags": [],
"description": "Design Pattern",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/golang/",
"title": "Golang of linux ",
"tags": [],
"description": "在Linux上配置Go开发环境",
"content": " 1. 安装包 # 安装bai mercurial包 yum install mercurial -y # 安装git包 yum install git -y # 安装gcc 主要是Cgo yum install gcc -y # wget 下载golang的压缩包 # 可以去代理网站下载 https://golang.google.cn/dl/ wget https://golang.google.cn/dl/go1.14.6.linux-amd64.tar.gz tar -zxvf *tar.gz # 打开系统环境变量声明文件添加环境变量 vi /etc/profile export GOROOT= export PATH= export GOPATH= # 使我们添加的环境变量即使生效 source /etc/profile # 验证一下是否安装成功 go version \r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/august/got/",
"title": "Got下载工具",
"tags": [],
"description": "Golang",
"content": " 1.安装 go get github.com/melbahja/got/cmd/got 或者直接下载release包也可以\n2. 命令行使用方法 got https://example.com/file.mp4 got --out /path/to/save https://example.com/file.mp4 3. 代码中引用 package main import \u0026#34;log\u0026#34; import \u0026#34;github.com/melbahja/got\u0026#34; func main() { dl, err := got.New(\u0026#34;https://golang.google.cn/dl/go1.14.7.windows-amd64.zip\u0026#34;, \u0026#34;go.zip\u0026#34;) if err != nil { log.Fatal(err) } // Start the download \terr = dl.Start() } //可以包装到携程里面 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/cmd/harbor/",
"title": "Harbor 部署",
"tags": [],
"description": "Docker",
"content": " 1. 介绍 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必须的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户 使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中,确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等\n2. 组成 1 组成Harbor的容器 Harbor大概需要以下几个容器组成:\rui:Harbor的核心服务\rlog:运行着rsyslog的容器,进行日志收集\rmysql:由官方mysql镜像构成的数据库容器\rnginx:使用nginx做反向代理\rregistry:官方的Docker registry\radminserver:Harbor的配置数据管理器\rjobservice:Harbor的任务管理服务\rredis:用于存储session\r 2. Harbor依赖的外部组件 Nginx(即proxy代理层):Nginx前端代理,主要用于分发前端页面ui访问和镜像上传和下载流量。Harbor的registry,UI,token等服务,通过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将强求转发给后端不同的服务。\n Registry v2:镜像仓库,负责存储镜像文件。Docker官方镜像仓库,负责存储Docker镜像,并处理docker push/pull命令。由于我们对用户进行访问控制,即不同的用户对Docker image有不同的读写权限,Registry会指向同一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token,Registry会通过公钥对token进行解密验证。\n Database(Mysql或者Postgresql):为core services提供数据库服务,负责存储用户权限、审计日志、Docker image分组信息等数据。\n 3. Harbor自有组件 Core services(Admin Server):这是Harbor的核心功能,主要提供以下服务:\n UI:提供图形化界面,帮助用户管理registry上的镜像(image),并对用户进行授权。 webhook:为了及时获取registry上image状态变化的情况,在Registry上配置webhook,把状态传递给UI模块。 Auth服务:负责根据用户权限给每个docker push/pull命令签发token。Docker客户端向Registry服务发起的请求,如果不包含token,会被重定向到这里,获得token后再重新向Registry进行请求。 API:提供Harbor RESTful API。 3. 部署 1. 安装docker 2. 安装docker-compose curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose docker-compose --version # 查看是否安装成功 3. 安装harbor # 这里采用在线安装的方式,也可以采用离线安装的方式 wget https://github.com/goharbor/harbor/releases/download/v1.10.4/harbor-online-installer-v1.10.4.tgz tar xvf harbor-online-installer-v1.10.4.tgz # 接下来修改下配置文件harbor.yaml # hostname: 192.168.24.131 (自己本机地址) # port: 5000 (自己定义端口号,记得不要使用80端口) # harbor_admin_password: Harbor12345 (admin进入密码) # 然后将https那几行注释掉。虚拟机内部的docker仓库,可以先使用http # 启动准备文件 ./prepare.sh # 启动安装文件 ./install.sh # 在浏览器里面登陆控制台 192.168.24.131:5000 # 修改默认密码 # 新建项目 # 因为使用的是http建立的仓库,所以还需要配置下insecure参数 4. 推送镜像 vim /usr/lib/systemd/system/docker.service # 给启动配置添加参数 ExecStart=/usr/bin/dockerd --insecure-registry 192.168.24.131:5000 # 重启docker systemctl daemon-reload systemctl restart docker # 拉取一个测试镜像 docker pull hello-world docker images # 修改下镜像的tag,进行推送 docker tag hello-world 192.168.24.131:5000/public/hello-world # pubilc 是之前harbor新增的项目 docker login 192.168.24.131:5000 docker push 192.168.24.131:5000/public/hello-world # 登陆控制台查看结果 # 现在查看下后台镜像仓库存储位置的信息 cd /usr/harbor/data/registry/docker/registry/v2/repositories/public/hello-world [root@master hello-world]# ls -lrta total 0 drwxr-xr-x 3 10000 10000 25 Aug 2 20:32 .. drwxr-xr-x 3 10000 10000 20 Aug 2 20:32 _layers drwxr-xr-x 2 10000 10000 6 Aug 2 20:32 _uploads drwxr-xr-x 5 10000 10000 55 Aug 2 20:32 . drwxr-xr-x 4 10000 10000 35 Aug 2 20:32 _manifests # 可以看到,已经成功的推送镜像到镜像仓库了。 # 同理,可以通过别的主机来拉取镜像 ,只需要添加--insecure-registry参数就行 [root@k8s-node1 ~]# docker pull 192.168.24.131:5000/public/hello-world:v1 v1: Pulling from public/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 Status: Downloaded newer image for 192.168.24.131:5000/public/hello-world:v1 5.Harbor停止与启动命令 # 停止 docker-compose -f docker-compose.yaml down -v # 启动 docker-compose -f docker-compose.yaml up -d TODO: 需要补充https的部分,添加自签证书什么的 \r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/template/podservice/",
"title": "Kubernetes集群中Pod获取自身信息",
"tags": [],
"description": "Kubernetes",
"content": " env: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: MY_POD_SERVICE_ACCOUNT valueFrom: fieldRef: fieldPath: spec.serviceAccountName 用在获取pod相关信息的情景下,比如部署NSQD服务的时候,给NSQD注册一个广播地址,需要用到这个\npiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqd name: nsqd spec: replicas: 1 selector: matchLabels: app: nsqd template: metadata: labels: app: nsqd spec: containers: - image: nsqio/nsq name: nsqd env: - name: HOSTIP valueFrom: fieldRef: fieldPath: status.podIP ports: - containerPort: 4151 - containerPort: 4150 command: [\u0026#34;/nsqd\u0026#34;] args: [\u0026#34;--lookupd-tcp-address=$(NSQLOOKUP_SERVICE_SERVICE_HOST):4160\u0026#34;,\u0026#34;--broadcast-address=$(HOSTIP)\u0026#34;] Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/",
"title": "Linux",
"tags": [],
"description": "Linux Daily",
"content": " Shell \r\rRedis \r\rDocker \r\rKernel \r\rOpenStack \r\rKubernetes \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/service/nsq/",
"title": "NSQ部署实践",
"tags": [],
"description": "Kubernetes",
"content": " 1. nsq基本概念 2. 传统方式部署 # 第一步先下载二进制包 ## https://nsq.io/deployment/installing.html cd /usr/local/nsq-1.1.0.linux-amd64.go1.10.3/bin/ nohup ./nsqlookupd \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 \u0026amp; nohup ./nsqd --lookupd-tcp-address=127.0.0.1:4160 \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 \u0026amp; nohup ./nsqadmin --lookupd-http-address=127.0.0.1:4161 \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 \u0026amp; 3. docker部署 docker部署 docker pull nsqio/nsq docker run -d --name lookupd -p 4160:4160 -p 4161:4161 nsqio/nsq /nsqlookupd docker inspect -f \u0026#39;{{ .NetworkSettings.IPAddress }}\u0026#39; lookupd # 获取nsqlookup的地址172.17.0.1 docker run -d --name nsqd -p 4150:4150 -p 4151:4151 nsqio/nsq /nsqd --broadcast-address=172.17.0.1 --lookupd-tcp-address=172.17.0.2:4160 docker run -d --name nsqadmin -p 4171:4171 nsqio/nsq /nsqadmin --lookupd-http-address=172.17.0.2:4161 ## 使用起来不是特别方便 docker-compose部署 version: \u0026#39;2\u0026#39; services: nsqlookupd: image: nsqio/nsq command: /nsqlookupd networks: - nsq-network hostname: nsqlookupd ports: - \u0026#34;4161:4161\u0026#34; - \u0026#34;4160:4160\u0026#34; nsqd: image: nsqio/nsq command: /nsqd --lookupd-tcp-address=nsqlookupd:4160 # command: /nsqd --lookupd-tcp-address=nsqlookupd:4160 -broadcast-address=虚拟机地址 depends_on: - nsqlookupd hostname: nsqd networks: - nsq-network ports: - \u0026#34;4151:4151\u0026#34; - \u0026#34;4150:4150\u0026#34; nsqadmin: image: nsqio/nsq command: /nsqadmin --lookupd-http-address=nsqlookupd:4161 depends_on: - nsqlookupd hostname: nsqadmin ports: - \u0026#34;4171:4171\u0026#34; networks: - nsq-network networks: nsq-network: 4. kubernetes中部署 为了方便利用Kubernetes部署的优点,我拆分了下docker-compose的服务,将nsq容器分开,服务分别绑定,做了下nsq的kubernetes的迁移。\nnsqlookup\napiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqlookup name: nsqlookup spec: replicas: 1 selector: matchLabels: app: nsqlookup template: metadata: labels: app: nsqlookup spec: containers: - image: nsqio/nsq name: nsqlookup ports: - containerPort: 4160 - containerPort: 4161 command: [\u0026#34;/nsqlookupd\u0026#34;] --- apiVersion: v1 kind: Service metadata: name: nsqlookup-service labels: app: nsqlookup spec: ports: - port: 4161 name: port1 targetPort: 4161 - port: 4160 name: port0 targetPort: 4160 selector: app: nsqlookup nsqd\npiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqd name: nsqd spec: replicas: 1 selector: matchLabels: app: nsqd template: metadata: labels: app: nsqd spec: containers: - image: nsqio/nsq name: nsqd env: - name: HOSTIP valueFrom: fieldRef: fieldPath: status.podIP ports: - containerPort: 4151 - containerPort: 4150 command: [\u0026#34;/nsqd\u0026#34;] args: [\u0026#34;--lookupd-tcp-address=$(NSQLOOKUP_SERVICE_SERVICE_HOST):4160\u0026#34;,\u0026#34;--broadcast-address=$(HOSTIP)\u0026#34;] --- apiVersion: v1 kind: Service metadata: name: nsqd-service labels: app: nsqd spec: ports: - port: 4151 targetPort: 4151 name: port1 - port: 4150 name: port0 targetPort: 4150 selector: app: nsqd nsqadmin\napiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqadmin name: nsqadmin spec: replicas: 1 selector: matchLabels: app: nsqadmin template: metadata: labels: app: nsqadmin spec: containers: - image: nsqio/nsq name: nsqadmin ports: - containerPort: 4171 command: [\u0026#34;/nsqadmin\u0026#34;] args: [\u0026#34;--lookupd-http-address=$(NSQLOOKUP_SERVICE_SERVICE_HOST):4161\u0026#34;] --- apiVersion: v1 kind: Service metadata: name: nsqadmin-service labels: app: nsqadmin spec: type: NodePort ports: - port: 4171 nodePort: 30050 selector: app: nsqadmin 注意先创建nsqlookup的service成功后才能创建其他两个deployment 然后就可以对nsqd,nsadmin做扩容了\n写两个服务测试下消息队列 1. producer package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/nsqio/go-nsq\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;sync\u0026#34; \u0026#34;time\u0026#34; \u0026#34;os\u0026#34; ) var err error // 推送消息 func main() { nsqdaddr := os.Getenv(\u0026#34;NSQD_SERVICE_SERVICE_HOST\u0026#34;) url := nsqdaddr + \u0026#34;:4150\u0026#34; topicName := \u0026#34;test\u0026#34; config := nsq.NewConfig() // new producer, err := nsq.NewProducer(url, config) if err != nil { fmt.Println(\u0026#34;nsq.NewProducer\u0026#34;, err) return } fmt.Println(\u0026#34;nsq.NewProducer\u0026#34;, \u0026#34;√\u0026#34;) defer producer.Stop() producer.SetLogger(log.New(ioutil.Discard, \u0026#34;\u0026#34;, log.LstdFlags), nsq.LogLevelInfo) // ping err = producer.Ping() if err != nil { fmt.Println(\u0026#34;producer.Ping\u0026#34;, err) return } fmt.Println(\u0026#34;producer.Ping\u0026#34;, \u0026#34;√\u0026#34;) msgCt:=1000 wg := \u0026amp;sync.WaitGroup{} wg.Add(msgCt) // 测试10 次 for i := 0; i \u0026lt; msgCt; i++ { // 消息内容 msg := time.Now().Format(\u0026#34;0102150405\u0026#34;) sendMessage(producer, topicName, msg) wg.Done() time.Sleep(10*time.Millisecond) // time.Sleep(1 * time.Second) } wg.Wait() fmt.Println(\u0026#34;producer.Push.Status\u0026#34;, \u0026#34;ok\u0026#34;) } // 发送消息 func sendMessage(producer *nsq.Producer, topicName string, msg string) { err = producer.Publish(topicName, []byte(msg)) if err != nil { fmt.Println(\u0026#34;producer.Publish\u0026#34;, err) return } fmt.Println(\u0026#34;producer.Publish\u0026#34;,msg, \u0026#34;√\u0026#34;) } 2. consumer package main import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/nsqio/go-nsq\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;sync\u0026#34; ) func main() { testNSQ() } type NSQHandler struct { } func (this *NSQHandler) HandleMessage(msg *nsq.Message) error { fmt.Println(\u0026#34;receive\u0026#34;, msg.NSQDAddress, \u0026#34;message:\u0026#34;, string(msg.Body)) return nil } const ( TOPIC = \u0026#34;test\u0026#34; CHANNEL_1 = \u0026#34;consumer_channel_1\u0026#34; CHANNEL_2 = \u0026#34;consumer_channel_2\u0026#34; ) func testNSQ() { URL := os.Getenv(\u0026#34;NSQD_SERVICE_SERVICE_HOST\u0026#34;) +\u0026#34;:4150\u0026#34; waiter := sync.WaitGroup{} waiter.Add(1) go func() { defer waiter.Done() config := nsq.NewConfig() config.MaxInFlight = 10 for i := 0; i \u0026lt; 10; i++ { consumer, err := nsq.NewConsumer(TOPIC, CHANNEL_1, config) if nil != err { fmt.Println(\u0026#34;err\u0026#34;, err) return } consumer.SetLogger(log.New(ioutil.Discard, \u0026#34;\u0026#34;, log.LstdFlags), nsq.LogLevelInfo) consumer.AddHandler(\u0026amp;NSQHandler{}) err = consumer.ConnectToNSQD(URL) if nil != err { fmt.Println(\u0026#34;err\u0026#34;, err) return } fmt.Println(CHANNEL_1,i) } select {} }() 3. Dockerfile FROMgolang:alpine AS build-envADD . $GOPATH/src/nsqENV GOPROXY=\u0026#34;https://goproxy.cn,direct\u0026#34;RUN cd $GOPATH/src/nsq \u0026amp;\u0026amp; go mod downloadRUN cd $GOPATH/src/nsq/consumer \u0026amp;\u0026amp; go build -o consumer \u0026amp;\u0026amp; cp consumer /RUN cd $GOPATH/src/nsq/producer \u0026amp;\u0026amp; go build -o producer \u0026amp;\u0026amp; cp producer /# final stageFROMalpine as consumerWORKDIR/appCOPY --from=build-env producer /appCOPY --from=build-env consumer /appENTRYPOINT ./consumer \u0026amp;\u0026amp; ./producer4. deployment yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqtest2 name: nsqtest2 spec: replicas: 1 selector: matchLabels: app: nsqtest2 template: metadata: labels: app: nsqtest2 spec: containers: - image: 192.168.24.131:5000/public/nsqtest # 个人镜像仓库地址 name: nsqtest2 command: [\u0026#34;/app/producer\u0026#34;] apiVersion: apps/v1 kind: Deployment metadata: labels: app: nsqtest name: nsqtest spec: replicas: 1 selector: matchLabels: app: nsqtest template: metadata: labels: app: nsqtest spec: containers: - image: 192.168.24.131:5000/public/nsqtest name: nsqtest command: [\u0026#34;/app/consumer\u0026#34;] producer容器会不停的重启,生成消息并发送到nsq队列,然后consumer容器会从消息队列里面取出来进行消费\n试了下 ,利用kubernetes管理 消息队列应用的好处\n 消费者生产者都可以自由的伸缩,并且可以调度到不同的主机上,不影响任何业务 消息队列可以自由伸缩,当消息过多的时候 ,可以增加实例,消息少的时候,可以收缩 namespace隔离 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/redis/",
"title": "Redis",
"tags": [],
"description": "Redis",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/x-mod/routine/",
"title": "routine",
"tags": [],
"description": "github.com/x-mod/routine",
"content": "github.com/x-mod/routine 为什么要写这么一个基础库呢?\n 每段程序都是从 main 函数开始的,但是,却不会将整个程序的功能实现都放在 main 函数里。而是,通过层层功能的抽象与封装,最终,在 main 函数仅提供功能函数的入口。所以,当我们看很多大型程序时,其实,main 函数是非常简单的。\n 但是,即使 main 函数越变越简单,有些必要的功能则是逃不掉的。例如程序启动参数、程序的信号处理,这些通常还是会放在 main 函数进行处理。\n 除了以上 main 函数本身的处理以外,我发现将程序中的执行绪,也就是固定的 Go 协程的入口放在 main 函数中进行定义,可以帮助维护者更加快速的理解应用的逻辑实现。\n link 使用方法 import \u0026#34;github.com/x-mod/routine\u0026#34; //timeout timeout := routine.Timeout(time.Minute, exec) //retry retry := routine.Retry(3, exec) //repeat repeat := routine.Repeat(10, time.Second, exec) //concurrent concurrent := routine.Concurrent(4, exec) //schedule executor crontab := routine.Crontab(\u0026#34;* * * * *\u0026#34;, exec) //command command := routine.Command(\u0026#34;echo\u0026#34;, routine.ARG(\u0026#34;hello routine!\u0026#34;)) //parallel parallel := routine.Parallel(exec1, exec2, exec3, ...) //sequence sequece := routine.Append(exec1, exec2, exec3, ...) github上提供的示例程序\n package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;os\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; _ \u0026#34;net/http/pprof\u0026#34; \u0026#34;runtime/trace\u0026#34; \u0026#34;github.com/x-mod/routine\u0026#34; ) func prepare(ctx context.Context) error { log.Println(\u0026#34;prepare begin\u0026#34;) defer log.Println(\u0026#34;prepare end\u0026#34;) trace.Logf(ctx, \u0026#34;prepare\u0026#34;, \u0026#34;prepare ... ok\u0026#34;) return nil } func cleanup(ctx context.Context) error { log.Println(\u0026#34;cleanup begin\u0026#34;) defer log.Println(\u0026#34;cleanup end\u0026#34;) time.Sleep(time.Millisecond * 50) trace.Logf(ctx, \u0026#34;cleanup\u0026#34;, \u0026#34;cleanup ... ok\u0026#34;) return nil } func foo(ctx context.Context) error { log.Println(\u0026#34;foo begin\u0026#34;) defer log.Println(\u0026#34;foo end\u0026#34;) time.Sleep(time.Second * 2) trace.Logf(ctx, \u0026#34;foo\u0026#34;, \u0026#34;sleeping 2s done\u0026#34;) return nil } func bar(ctx context.Context) error { log.Println(\u0026#34;bar begin\u0026#34;) defer log.Println(\u0026#34;bar end\u0026#34;) for i := 0; i \u0026lt; 10; i++ { log.Println(i) trace.Logf(ctx, \u0026#34;bar\u0026#34;, \u0026#34;counting ... %d\u0026#34;, i) } return nil } func main() { f, err := os.Create(\u0026#34;trace.out\u0026#34;) if err != nil { log.Fatalf(\u0026#34;failed to create trace output file: %v\u0026#34;, err) } defer func() { if err := f.Close(); err != nil { log.Fatalf(\u0026#34;failed to close trace file: %v\u0026#34;, err) } }() if err := routine.Main( context.TODO(), routine.ExecutorFunc(bar), routine.Signal(syscall.SIGINT, routine.SigHandler(func() { os.Exit(1) })), routine.Prepare(routine.ExecutorFunc(prepare)), routine.Cleanup(routine.ExecutorFunc(cleanup)), routine.Trace(f), routine.Go(routine.ExecutorFunc(foo)), routine.Go(routine.ExecutorFunc(foo)), routine.Go(routine.ExecutorFunc(foo)), ); err != nil { log.Println(err) } } 接下来是自己的一些模仿实现\n package main import ( \u0026#34;context\u0026#34; \u0026#34;time\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/x-mod/routine\u0026#34; ) func main() { err := routine.Main( context.TODO(), routine.ExecutorFunc(bar), //main协程入口 \troutine.Prepare(routine.ExecutorFunc(prepare)), //启动开始前的准备函数 \troutine.Cleanup(routine.ExecutorFunc(exitclean)), //启动关闭后的清理函数 \troutine.Go(routine.ExecutorFunc(dosomething)), //启动并发的协程 \troutine.Go(routine.Concurrent(5, routine.ExecutorFunc(conexec))), //启动并发执行同一程序 \t//routine.Go(routine.Crontab(\u0026#34;*/1 * * * *\u0026#34;, routine.ExecutorFunc(crontabexec))), //定时执行任务,配置crontab表达式 \troutine.Go(routine.Repeat(3, time.Second, routine.ExecutorFunc(repeatexec))), //定时重复执行一项程序 \t//routine.Go(routine.Deadline(time.Now().Add(5*time.Second), routine.ExecutorFunc(deadlinexec))),这个没搞明白 \t) if err != nil { fmt.Println(err) } } func bar(ctx context.Context) error { fmt.Println(\u0026#34;bar begin\u0026#34;) fmt.Println(time.Now()) defer fmt.Println(\u0026#34;bar end\u0026#34;) return nil } func prepare(ctx context.Context) error { fmt.Println(\u0026#34;Start Prepare !!\u0026#34;) defer fmt.Println(\u0026#34;Prepare finish !!\u0026#34;) return nil } func exitclean(ctx context.Context) error { fmt.Println(\u0026#34;Exiting !!\u0026#34;) defer fmt.Println(\u0026#34;Exited\u0026#34;) return nil } func dosomething(ctx context.Context) error { routine.New(routine.ExecutorFunc(bar)).Execute(ctx) //启动新的协程 \ttime.Sleep(50 * time.Millisecond) fmt.Println(time.Now()) return nil } func conexec(ctx context.Context) error { fmt.Println(\u0026#34;并发执行中!!!\u0026#34;) return nil } func crontabexec(ctx context.Context) error { fmt.Println(\u0026#34;定时执行中 ~~~\u0026#34;) return nil } func repeatexec(ctx context.Context) error { fmt.Println(\u0026#34;重复执行中,每隔一秒执行一次\u0026#34;) return nil } func deadlinexec(ctx context.Context) error { for i := 0; i \u0026lt; 10; i++ { fmt.Printf(\u0026#34;等待deadline。。。,%d 秒\\n\u0026#34;, i) time.Sleep(time.Second) } return nil } image title 总结 这个库包装了所有常见的需要goroutine操作的情景,可以支持并发,串行,重复执行,重试,定时执行,启动新协程等一系列常用的Go并发场景,以后多包装使用这个库,能剩下不少的事情 \u0026ndash;\n"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/shellcase/",
"title": "Shell Case",
"tags": [],
"description": "shell case",
"content": " 注意事项 开头加解释器:#!/bin/bash 语法缩进,使用四个空格;多加注释说明。 命名建议规则:变量名大写、局部变量小写,函数名小写,名字体现出实际作用。 默认变量是全局的,在函数中变量local指定为局部变量,避免污染其他作用域。 有两个命令能帮助我调试脚本:set -e 遇到执行非0时退出脚本,set-x 打印执行过程\n1.获取随机字符串或数字 获取随机8位字符串:\n方法1: # echo $RANDOM |md5sum |cut -c 1-8 471b94f2 方法2: # openssl rand -base64 4 vg3BEg== 方法3: # cat /proc/sys/kernel/random/uuid |cut -c 1-8 ed9e032c 获取随机8位数字:\n方法1: # echo $RANDOM |cksum |cut -c 1-8 23648321 方法2: # openssl rand -base64 4 |cksum |cut -c 1-8 38571131 方法3: # date +%N |cut -c 1-8 69024815 cksum:打印CRC效验和统计字节 2.定义一个颜色输出字符串函数 方法1: function echo_color() { if [ $1 == \u0026#34;green\u0026#34; ]; then echo -e \u0026#34;\\033[32;40m$2\\033[0m\u0026#34; elif [ $1 == \u0026#34;red\u0026#34; ]; then echo -e \u0026#34;\\033[31;40m$2\\033[0m\u0026#34; fi } 方法2: function echo_color() { case $1 in green) echo -e \u0026#34;[32;40m$2[0m\u0026#34; ;; red) echo -e \u0026#34;[31;40m$2[0m\u0026#34; ;; *) echo \u0026#34;Example: echo_color red string\u0026#34; esac } 使用方法:echo_color green \u0026#34;test\u0026#34; function关键字定义一个函数,可加或不加。 3.批量创建用户 #!/bin/bash DATE=$(date +%F_%T) USER_FILE=user.txt echo_color(){ if [ $1 == \u0026#39;green\u0026#39; ]; then echo -e \u0026#34;[32;40m$2[0m\u0026#34; elif [ $1 == \u0026#39;red\u0026#39; ]; then echo -e \u0026#34;[31;40m$2[0m\u0026#34; fi } # 如果用户文件存在并且大小大于0就备份 if [ -s $USER_FILE ]; then mv $USER_FILE $USER_FILE-$DATE.bak echo_color green \u0026#34;$USER_FILEexist, rename ${USER_FILE}-${DATE}.bak\u0026#34; fi echo -e \u0026#34;User Password\u0026#34; \u0026gt;\u0026gt; $USER_FILE echo \u0026#34;-------------------\u0026#34;\u0026gt;\u0026gt; $USER_FILE for USER in user{1..10}; do if ! id $USER \u0026amp;\u0026gt;/dev/null; then PASS=$(echo $RANDOM |md5sum |cut -c 1-8) useradd $USER echo $PASS |passwd --stdin $USER \u0026amp;\u0026gt; /dev/dell echo -e \u0026#34;$USER$PASS\u0026#34; \u0026gt;\u0026gt; $USER_FILE echo \u0026#34;$USERUser create successful.\u0026#34; else echo_color red \u0026#34;$USERUser already exists!\u0026#34; fi done 4.检查软件包是否安装 #!/bin/bash if rpm -q sysstat \u0026amp;\u0026gt;/dev/null; then echo \u0026#34;sysstat is already installed.\u0026#34; else echo \u0026#34;sysstat is not installed!\u0026#34; fi 5.检查服务状态 #!/bin/bash PORT_C=$(ss -ant |grep -c 6443) PS_C=$(ps -ef |grep kube-apiserver |grep -vc grep) if [ $PORT_C -eq 0 -o $PS_C -eq 0 ]; then echo \u0026#34;kube-apiserver service dowmped\u0026#34; else echo \u0026#34;kube-apiserver service running!\u0026#34; fi 6.检查主机存活状态 方法1:将错误IP放到数组里面判断是否ping失败三次 #!/bin/bash for IP in $IP_LIST; do NUM=1 while [ $NUM -le 3 ]; do if ping -c 1 $IP \u0026amp;\u0026gt; /dev/null; then echo \u0026#34;$IPPing is successful.\u0026#34; break else FAIL_COUNT[$NUM]=$IP let NUM++ fi done if [ ${#FAIL_COUNT[*]} -eq 3 ]; then echo \u0026#34;${FAIL_COUNT[1]}Ping is failure!\u0026#34; unset FAIL_COUNT[*] fi done 方法2:将错误次数放到FAIL_COUNT变量里面判断是否ping失败三次 #!/bin/bash for IP in $IP_LIST; do FAIL_COUNT=0 for (( i=1;i\u0026lt;=3;i++)); do if ping -c 1 $IP \u0026amp;\u0026gt;/dev/null; then echo \u0026#34;$IPPing is successful.\u0026#34; break else let FAIL_COUNT++ fi done if [ $FAIL_COUNT -eq 3 ]; then echo \u0026#34;$IPPing is failure!\u0026#34; fi done 方法3:利用for循环将ping通就跳出循环继续,如果不跳出就会走到打印ping失败 #!/bin/bash ping_success_status() { if ping -c 1 $IP \u0026amp;\u0026gt;/dev/null; then echo \u0026#34;$IPPing is successful.\u0026#34; continue fi } for IP in $IP_LIST; do ping_success_status ping_success_status ping_success_status echo \u0026#34;$IPPing is failure!\u0026#34; done 7.监控CPU、内存和硬盘利用率 #!/bin/bash DATE=$(date +%F\u0026#34; \u0026#34;%H:%M) IP=$(ifconfig eth0 |awk \u0026#39;/netmask/ {print $2}\u0026#39;) if ! which vmstat \u0026amp;\u0026gt; /dev/null; then echo \u0026#34;vmstat command no found, Please install procps package.\u0026#34; exit 1 fi ## CPU US=$(vmstat |awk \u0026#39;NR==3 {print $13}\u0026#39;) SY=$(vmstat |awk \u0026#39;NR==3 {print $14}\u0026#39;) IDLE=$(vmstat |awk \u0026#39;NR==3 {print $15}\u0026#39;) WAIT=$(vmstat |awk \u0026#39;NR==3 {print $15}\u0026#39;) USE=$(($US+$SY)) if [ $USE -ge 50 ];then echo \u0026#34; Date: $DATEHost: $IPProblem: CPU utilization $USE\u0026#34; fi ## Mem TOTAL=$(free -m |awk \u0026#39;/Mem/ {print $2}\u0026#39;) USE=$(free -m |awk \u0026#39;/Mem/ {print $3}\u0026#39;) FREE=$(free -m |awk \u0026#39;/Mem/ {print $4+$6}\u0026#39;) if [ $FREE -lt 1024 ]; then echo \u0026#34; Date: $DATEHost: $IPProblem: Total=$TOTAL,Use=$USE,Free=$FREE\u0026#34; fi #disk PART_USE=$(df -h |awk -F\u0026#39;[% ]+\u0026#39; \u0026#39;BEGIN{OFS=\u0026#34;=\u0026#34;} /^\\/dev/ {print $1,$2,$5,$6}\u0026#39;) for i in $PART_USE; do PART=$(echo $i |cut -d\u0026#34;=\u0026#34; -f1) TOTAL=$(echo $i |cut -d \u0026#34;=\u0026#34; -f2) USE=$(echo $i |cut -d\u0026#34;=\u0026#34; -f3) MOUNT=$(echo $i |cut -d\u0026#34;=\u0026#34; -f4) if [ $USE -gt 80 ]; then echo \u0026#34; Date: $DATEHost: $IPTotal: $TOTALProblem: $PART=$USE($MOUNT)\u0026#34; fi done 8.批量主机磁盘利用率监控 前提监控端和被监控端SSH免交互登录或者密钥登录。 写一个配置文件保存被监控主机SSH连接信息,文件内容格式:IP User Port\n#!/bin/bash HOST_INFO=host.info for IP in $(awk \u0026#39;/^[^#]/ {print $1}\u0026#39; $HOST_INFO); do USER=$(awk -v ip=$IP \u0026#39;ip==$1 {print $2}\u0026#39; $HOST_INFO) PORT=$(awk -v ip=$IP \u0026#39;ip==$1 {print $3}\u0026#39; $HOST_INFO) TMP_FILE=/tmp/disk.tmp ssh -p $PORT $USER@$IP df -h \u0026gt; $TMP_FILE USE_RATE_LIST=$(awk \u0026#39;BEGIN{OFS=\u0026#34;=\u0026#34;} /^\\/dev/ {print $NF,int($5)}\u0026#39; $TMP_FILE) for USE_RATE in $USE_RATE_LIST; do PART_NAME=${USE_RATE%=*} ##从右到左,非贪婪匹配,匹配到的删除 USE_RATE=${USE_RATE#*=} ##从左到右,非贪婪匹配,匹配到的删除 if [ $USE_RATE -ge 10 ];then echo \u0026#34;Warning: $IP$PART_NAMEPartition usage $USE_RATE%!\u0026#34; fi done done 9.检查网站可用性 #!/bin/bash #-------------------- #1)检查URL可用性 #方法1: check_url() { HTTP_CODE=$(curl -o /dev/null --connect-timeout 3 -s -w \u0026#34;%{http_code}\u0026#34; $1) if [ $HTTP_CODE -ne 200 ]; then echo \u0026#34;Warning: $1Access failure!\u0026#34; fi } #方法2: check_url_2() { if ! wget -T 10 --tries=1 --spider $1 \u0026amp;\u0026gt;/dev/null; then #-T超时时间,--tries尝试1次,--spider爬虫模式 echo \u0026#34;Warning: $1Access failure!\u0026#34; fi } #check_url www.baidu.com #check_url_2 www.aaaa.com #2)判断三次URL可用性 #思路与上面检查主机存活状态一样。 #--------------------------------- URL_LIST=\u0026#34;www.baidu.com www.agasgf.com\u0026#34; #------ #方法1:利用循环技巧,如果成功就跳出当前循环,否则执行到最后一行 check_url_3() { HTTP_CODE=$(curl -o /dev/null --connect-timeout 3 -s -w \u0026#34;%{http_code}\u0026#34; $1) if [ $HTTP_CODE -eq 200 ]; then continue fi } for URL in $URL_LIST; do check_url_3 $URL check_url_3 $URL check_url_3 $URL echo \u0026#34;Warning: $URLAccess failure!\u0026#34; done #------ #方法2:错误次数保存到变量 for URL in $URL_LIST; do FAIL_COUNT=0 for ((i=1;i\u0026lt;=3;i++)); do HTTP_CODE=$(curl -o /dev/null --connect-timeout 3 -s -w \u0026#34;%{http_code}\u0026#34; $URL) if [ $HTTP_CODE -ne 200 ]; then let FAIL_COUNT++ else break fi done if [ $FAIL_COUNT -eq 3 ]; then echo \u0026#34;Warning: $URLAccess failure!\u0026#34; fi done #------ #方法3:错误次数保存到数组 for URL in $URL_LIST;do NUM=1 unset FAIL_COUNT while [ $NUM -le 3 ]; do HTTP_CODE=$(curl -o /dev/null --connect-timeout 3 -s -w \u0026#34;%{http_code}\u0026#34; $URL) if [ $HTTP_CODE -ne 200 ]; then FAIL_COUNT[$NUM]=$URL let NUM++ else break fi done if [ ${#FAIL_COUNT[@]} -eq 3 ];then echo \u0026#34;Warning: $URLAccess failure!\u0026#34; fi done 10.检查MySQL主从同步状态 #!/bin/bash USER=bak PASSWD=123456 IO_SQL_STATUS=$(mysql -u$USER -p$PASSWD -e show slave statusG |awk -F: /Slave_.*_Running/{gsub(\u0026#34;: \u0026#34;,\u0026#34;:\u0026#34;);print $0} ) #gsub去除冒号后面的空格 for i in $IO_SQL_STATUS; do THREAD_STATUS_NAME=${i%:*} THREAD_STATUS=${i#*:} if [ \u0026#34;$THREAD_STATUS\u0026#34; != \u0026#34;Yes\u0026#34; ]; then echo \u0026#34;Error: MySQL Master-Slave $THREAD_STATUS_NAMEstatus is $THREAD_STATUS!\u0026#34; fi done 11.批量创建目录 dir=( /home/med/data/local_cdr/CC/post /home/med/data/local_cdr/CC/pre /home/med/data/local_cdr/CC/blacklist /home/med/data/local_cdr/SIC/eSIM /home/med/data/local_cdr/SIC/prepack /home/med/data/local_cdr/SIC/blank_USIM /home/med/data/local_cdr/SIC/LoyaltyVoucher /home/med/data/local_cdr/DSCM/dealerInfo /home/med/data/local_cdr/DSCM/distributorInfo /home/med/data/local_cdr/DSCM/salesHier /home/med/data/local_cdr/DSCM/salesPIC /home/med/data/local_cdr/DSCM/staffInformation /home/med/data/local_cdr/DSCM/subchannel /home/med/data/local_cdr/DSCM/salesPositionType /home/med/data/local_cdr/DSCM/dealerAddAddress /home/med/data/local_cdr/DSCM/distyUser /home/med/data/local_cdr/OC/error /home/med/data/local_cdr/OC/umobile /home/med/data/local_cdr/OC/SubsciberActivation ) for imageName in ${dir[@]} ; do mkdir -p $imageName done 12. 创建文件链接 软链接\n1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式\r2.软链接可以 跨文件系统 ,硬链接不可以\r3.软链接可以对一个不存在的文件名进行链接\r4.软链接可以对目录进行链接\r 硬链接:\n1.硬链接,以文件副本的形式存在。但不占用实际空间。\r2.不允许给目录创建硬链接\r3.硬链接只有在同一个文件系统中才能创建\r 命令参数 -b 删除,覆盖以前建立的链接 -d 允许超级用户制作目录的硬链接 -f 强制执行 -i 交互模式,文件存在则提示用户是否覆盖 -n 把符号链接视为一般目录 -s 软链接(符号链接) -v 显示详细的处理过程\nln -s log2013.log link2013 # 给文件创建软链接,为log2013.log文件创建软链接link2013,如果log2013.log丢失,link2013将失效 ln -sn harbor-v1.10.4/ harbor/ # 川建目录到目录的链接 13. scp [root@master ~]# scp usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] [-l limit] [-o ssh_option] [-P port] [-S program] [[user@]host1:]file1 ... [[user@]host2:]file2 scp就是secure copy,一个在linux下用来进行远程拷贝文件的命令。\n有时我们需要获得远程服务器上的某个文件,该服务器既没有配置ftp服务器,也没有做共享,无法通过常规途径获得文件时,只需要通过简单的scp命令便可达到目的。\n用法:\n# 拷贝文件到远程主机 scp /home/dist/app root@192.168.24.132:/root/data/ # 从远程主机拷贝文件 scp root@192.168.24.132:/root/data/ /home/dist/app # 拷贝文件夹到远程主机 scp -r /home/dist/app/ root@192.168.24.132:/root/data/ # 从远程主机拷贝文件夹 scp -r root@192.168.24.132:/root/data/ /home/dist/app/ # 从远程主机拷贝文件夹里的所有文件,但不拷贝文件夹 scp root@192.168.24.132:/root/data/* /home/dist/app/ 14. systemctl设置开机自动启动 要自定义一个服务,需要在 /usr/lib/systemd/system/ 下添加一个配置文件:.service\n配置文件的内容说明 [Unit]: 服务的启动顺序与依赖关系 Description: 当前服务的简单描述 After: 当前服务(\u0026lt;software-name\u0026gt;.service)需要在这些服务启动后,才启动 Before: 和 After 相反,当前服务需要在这些服务启动前,先启动 Wants:表示当前服务\u0026#34;弱依赖\u0026#34;于这些服务。即当前服务依赖于它们,但是没有它们,当前服务也能正常运行。 Requires: 表示\u0026#34;强依赖\u0026#34;关系,即如果该服务启动失败或异常退出,那么当前服务也必须退出。 [Service] 服务运行参数的设置 Type=forking 后台运行的形式 PIDFile=/software-name/pid pid文件路径 EnvironmentFile=/xxx/prod.env 通过文件设定环境变量 WorkingDirectory=/xxx/xxx 工作目录 ExecStartPre 启动前要做什么(为启动做准备) ExecStart 服务的具体运行命令(对非 workingdirectory 的文件,必须用绝对路径!因为 systemd 不使用 shell,也就不查找 PATH) ExecReload 重载命令 ExecStop 停止命令 ExecStartPre:启动服务之前执行的命令 ExecStartPost:启动服务之后执行的命令 ExecStopPost:停止服务之后执行的命令 RuntimeDirectory=xxxx RuntimeDirectoryMode=0775 PrivateTmp=True 表示给服务分配独立的临时空间 RestartSec 自动重启当前服务间隔的秒数 Restart 定义何种情况 Systemd 会自动重启当前服务,可能的值包括always(总是重启)、on-success、on-failure 等 # 程序的 user 和 group User=ryan Group=ryan 注意:启动、重载、停止命令全部要求使用绝对路径 [Install] 定义如何安装这个配置文件,即怎样做到开机启动。 # Target的含义是服务组,表示一组服务。 WantedBy=multi-user.target Type 说明\n Type=simple(默认值):systemd认为该服务将立即启动。服务进程不会fork。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket激活型。\n Type=forking:systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便systemd能够跟踪服务的主进程。\n Type=oneshot:这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes使得systemd在服务进程退出之后仍然认为服务处于激活状态\n Type=notify:与 Type=simple相同,但约定服务会在就绪后向systemd发送一个信号。这一通知的实现由 libsystemd-daemon.so提供。\n Type=dbus:若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。\n 配置举例 [Unit]\rDescription=Harbor\rAfter=docker.service systemd-networkd.service systemd-resolved.service\rRequires=docker.service\rDocumentation=http://github.com/vmware/harbor\r[Service]\rType=simple\rRestart=on-failure\rRestartSec=5\rExecStart=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml up -d\rExecStop=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml down -v\r[Install]\rWantedBy=multi-user.target\rsystemctl的一些常用命令 systemctl enable ss-server.service # 启用服务,即开机自动启动 systemctl disable ss-server.service # 取消服务,取消开机启动 systemctl start ss-server.service # 启动服务 systemctl stop ss-server.service # 停止服务 systemctl restart ss-server.service # 重启服务(stop + start) systemctl reload ss-server.service # 服务不 stop,直接加载配置更新等(对应 ExecReload) # 检查状态 systemctl status ss-server.service -l systemctl list-units --type=service # 查看所有服务 15.筛选报错日志 filename=( $(grep $1 -rl *) ) for file in ${filename[@]};do echo $file cat -n $file|grep $1 done 16. 获取本机IP地址 ifconfig eth0|grep \u0026#34;inet addr\u0026#34;|awk \u0026#39;{print $2}\u0026#39;|awk -F: \u0026#39;{print $2}\u0026#39; 这个网卡的名称可以具体情况具体定\n17. 彩色输出 echo -e \u0026#34;\\e[1;32m Green color \\e[0m\u0026#34; # 输出颜色 echo -e \u0026#34;\\e[1;42m Green PlayGround \\e[0m\u0026#34; # 背景色 30-37 40-47 黑红绿黄蓝红青白 18. 获取进程ID pgrep xxx ## 获取进程所涉及的环境变量 cat /proc/xxx/environ |tr \u0026#39;\\0\u0026#39; \u0026#39;\\n\u0026#39; 19. shell一些builtin特性 # 获取某变量长度 var=$(ls -lrt) echo ${#var} # 获取当前shell echo $0 echo $SHELL # 判断是否为root $UID -ne 0 # 获取退出状态码 echo $? # 利用tee来保存输出 ls -lrt |tee test.log ls -lrt |tee -a test.log ## 追加内容 20 数组和列表 #! /bin/bash declare -A map1 map1[test]=23 map1[dev]=34 for num in ${!map1[*]} do echo $num done # 输出键名 for num in ${map1[*]} do echo $num done # 输出值名 for num in ${!map1[*]} do echo $num,${map1[$num]} done ## 键值对 21. 获取终端信息 tput lines tput cols # 获取终端行数列数 tput longtime # 终端名称 tput cup 100 100 # 移动光标 tput setb n # 设置颜色 color ls # 展示颜色 tput self m # 设置前景色 22.获取输入密码 #! /bin/bash echo \u0026#39;Please input the password\u0026#39; stty -echo read password stty echo echo $password # stty -echo 不显示输出 # stty echo 显示输出 23.日期相关 date date +%s # 显示unix时间戳 ## ntpdate -s time-b.nist.gov 24. 获取所有参数 #! /bin/bash for i in `seq 1 $#` do echo $i $1 shift done 25. 重复执行直到完成 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/sql/",
"title": "SQL",
"tags": [],
"description": "SQL",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/august/qrcode/",
"title": "二维码生成",
"tags": [],
"description": "Golang",
"content": " \r使用Go语言编程时,生成任意内容的二维码是非常方便的,因为我们有go-qrcode这个库。该库的源代码托管在github上,大家可以下载使用 https://github.com/skip2/go-qrcode。 使用如下函数生成一个二维码图片\nfunc WriteFile(content string, level RecoveryLevel, size int, filename string) error WriteFile函数的原型定义如上,它有几个参数,大概意思如下:\ncontent表示要生成二维码的内容,可以是任意字符串。\nlevel表示二维码的容错级别,取值有Low、Medium、High、Highest。\nsize表示生成图片的width和height,像素单位。\nfilename表示生成的文件名路径。\nRecoveryLevel类型其实是个int,它的定义和常量如下。\ntype RecoveryLevel int const ( // Level L: 7% error recovery. Low RecoveryLevel = iota // Level M: 15% error recovery. Good default choice. Medium // Level Q: 25% error recovery. High // Level H: 30% error recovery. Highest ) RecoveryLevel越高,二维码的容错能力越好。\npackage main import \u0026#34;github.com/skip2/go-qrcode\u0026#34; func main() { qrcode.WriteFile(\u0026#34;http://liuzeng01.github.io\u0026#34;,qrcode.Medium,256,\u0026#34;blog_qrcode.png\u0026#34;) } 这样就生成了一个指向指定网址的二维码\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/net/",
"title": "网络相关",
"tags": [],
"description": "daily note",
"content": " 1. 网络结构 因特网服务提供商 ISP(Internet Service Provider) 不同的ISP根据其规模大小被拆分为不同层次,覆盖面积最广的就是主干ISP,通常为国家级范围,其次是地区,借助一个或多个主干并联,最后是本地ISP,可以直接连主干,也可以连地区,也就是我们最常用的(比如公司内网私有云、学校、公有云厂商)\n任何一个ISP(除了顶层)都可以和多个ISP互联,成为他们的下级,也就是多宿(multi-home), 这时候即使提供商之一出现问题,他仍然不会断网\n用户那么多,主干ISP就那么几个,肯定要涵盖超大的网络数据流量,就算他有最大的带宽,可以完成流量交换,但是距离越远传输速度越慢是亘古不变的真理,如果先把相邻地区流量转发到千里之外再转发回来,是一种对网络资源的浪费\n为了避免这种浪费,一种更有效快速的转发方式出现了,那就是因特网交换点 IXP (Internet Exchange Point)\n这个时候BAT在干嘛呢?BAT 严格意义上来说不属于ISP,他们是提供内容的,所以被叫做因特网内容提供商 ICP(Internet Content Provider),内容供应商有内容,他们的数据中心需要和运营商网络互联互通,才能被他们的用户访问。\n一般是拉通了专线边界网关协议 BGP(Border Gateway Protocol),简单来说你向BAT发消息,他们就近原则回消息,根据你访问的源地址,判断消息从哪里过来的,如果是电信,就走电信的线路回去。如果是联通,就走联通的线路回去。\n以前互联网数据中心 IDC(Internet Data Center)都是运营商的,为了让自己的服务跑的更快,现在是 BAT都自建数据中心,也就是云,多余的计算资源也向外提供,把计算放到云上,这就是云计算.\n再结合一个叫软件定义网络 SDN(Software Defined Network)的东西,特别是可编程路由,使得路由可以根据业务需求,流量需求,怎么省钱怎么来,用他来充当IXP的角色,成本比路由器还要便宜,而且可以按需调度,可以支持复杂流量工程,以实现分布式拒绝服务(DDOS)防止被攻击。\n2. 网络协议 网络协议类似于人类协议,只不过把对话的人换成了某些硬件和软件设备(例如计算机、手机、路由器),他们之间使用的都是同一种语言(byte 流,最终解析成电信号),严格受协议的制约。\n 硬件直接通过网线互联,通过协议控制网卡之间的比特流\n 端系统通过阻塞控制协议,控制收发包过程中分组发送速率\n 路由器协议,控制分组从源到目的地之间的路径\n 七层协议 从第七层到第一层,分别对应了不同的一个数据包(报文)在不同解决的不同处理办法(协议),每个报文在传输出去的时候会经历打包的过程,套七层外套,在接收和传递的时候会经历拆包的过程,脱下七层衣服。\n每一层都会携带一些关键信息给对应的设备识别,为了保证输出包顺利的投递,在传输过程中会经历很多设备,也会经历很多次拆包打包。\n交换机和路由器的区别就是,路由器在网络层,可以处理TCP/IP协议,可以把一个IP分配给多个主机而交换机不能,交换机在链路层是根据MAC寻址,可以提供防火墙功能而路由器不能。路由器主要用来连接多个网络,交换机主要用来使局域网连接更多计算机。\n常见家用交换机是指二层交换机,现在有三层及三层以上的交换机可基于网络层甚至传输层工作。\n没有提到会话层和表示层,我个人的理解这两层从来没有独立实现过,都是和应用层在一起实现。一般的程序员都是在应用层编程,应用层主要加入了会话保持、断点下载能力,表示层主要是做数据加密解密压缩以及转码(虚拟终端协议 VTP,定义了统一的字符集、终端命令、格式控制符等等)的工作,比如大部分主机使用ASCII码,IBM 主机使用 EBCDIC 编码\n有一个安全加密层,很多人都使用过,只是一直没有人想去划分层次结构,它的名字叫SSL/TLS,有了安全层提供的服务,位于应用层的HTTP/SMTP/FTP,都可以在其名字后加一个S(Security),比如HTTPS,其实这个世界压根不存在HTTPS协议,只有HTTP协议,加上 S 的后缀只是告诉大家HTTP使用的是六层结构,有了SSL/TLS的安全保护。\n网络延迟丢包 为什么高峰期就会卡,不应该是我独享的宽带吗?事实上是共享的,在介绍为什么共享之前,先介绍下传输过程中产生的速度损耗。\n还是让我们回到最常用的分组交换,分组交换我们说过,他把报文切片,变成一个一个的数据包进行传输,在传输给分组交换机的时候,交换机本身会进行一些包检查,这里浪费的时间叫节点处理时延(nodal processing delay)\n多数分组交换机在链路的输入端使用存储转发传输(store-and-forward transmission)机制,在交换机输出该分组的第一个比特之前,必须接收到整个分组,这个等待的时间叫存储转发时延\n在分组交换机接收完毕整个分组数据以后,肯定要继续传输到目标主机上,每个分组交换机上都连接了很多链路(主机),每条链路都有一个输出队列(output queue,也称输出缓存)\n假如某个分组需要传输到某个链路上的主机,而这条链路被另一个分组传输占用了,那么这个分组就必须在输出队列里排队等待,这个等待的时间叫排队时延(queuing delay),类比收费站排队等待过关时浪费的时间。\n但是输出队列的缓存空间是有限的,如果他被撑爆了,将会出现数据丢失,这又被称为分组丢失(packet loss),俗语叫丢包\n收费站收钱,然后给卡的过程浪费的时间,叫传输时延(transmission delay),也就是分组交换机发出去那一瞬间浪费的时间,一般极短。\n传输出去以后,数据在路上跑,跑到下一个交换机之前,在路上浪费的时间叫传播时延(propagation delay)\n在整个过程中,端系统经过一个一个的路由器/交换机,最终到达目的端系统浪费的总时间叫端到端时延,也可以用第五节提供的跟踪工具可以看到三次握手分别浪费的时间,是不是很酷。\n一根100M带宽,如果全部跑满,他的峰值吞吐量(throughput)就是12Mbps,吞吐量就是单位时间内成功地传送数据的数量(以比特、字节、分组等测量)\n我们的家庭网络一般都是使用局域网(LAN)接入,其实也是带宽共享的一种。一般来说,整个小区是一个局域网,与电信骨干网相连接的带宽总出口如果有100M,也就是说,如果小区内有一百人同时在线,那么就是有一百人在共享这100M的带宽,因此,共享带宽下的宽带接入,在上网用户稍多时可能速度会有所降低。\n在宽带较不普及的小区,共享带宽会有很高的性价比,因为共享带宽比较便宜,又正好没人和你抢带宽;但如果在宽带较普及的小区,可以用ADSL(全称为 Asymmetric Digital Subscriber Line,非对称数字用户线路),每个用户的网络终端都有单独的一条线路与ADSL局域端相连,可以保证所有带宽是由每一用户独享的,即使用户激增,其速度也不会减慢。\n但是现在都到21世界20年代了,ADSL是用电话线上网,最大理论上行速率可达到 1Mbps,下行速率可达 8Mbps,早被淘汰了,老老实实用LAN,办大一点的宽带才是王道\n补充链接 对 IXP 的一些思考 \nICP 百度百科 \n阿里云说的多线 BGP 接入到底指的是什么东西? \nBGP 维基百科 \nIDC 百度百科 \nSDN 与 IXP \n网络协议 百度百科 \n对等网络-百度百科 \n路由选择协议-百度百科 \n学习笔记-计算机网络 \n计算机网络(自顶向下方法)学习笔记 \n宽带中的 ADSL 与 LAN 有什么区别? \n什么是共享带宽? \n交换机和路由器有什么区别 \n为什么说交换机工作在数据链路层? \n表示层( presentation layer)和会话层(session layer)为什么会被弃用? \n应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 \nOSI 七层模型的学习-会话层、表示层和应用层 \n网络安全攻击分类分析 \n网络灵魂九问\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/cmd/aliyunrepo/",
"title": "Aliyun Registry",
"tags": [],
"description": "docker",
"content": " 1. 登陆阿里云的registry sudo docker login --username=liuzeng_icloud registry.cn-hangzhou.aliyuncs.com 2. 从Registry中拉取镜像 sudo docker pull registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:[镜像版本号] 3. 将镜像推送到Registry sudo docker login --username=liuzeng_icloud registry.cn-hangzhou.aliyuncs.com sudo docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:[镜像版本号] sudo docker push registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:[镜像版本号] Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/spf13/cobra/",
"title": "cobra",
"tags": [],
"description": "github.com/spf13/cobra",
"content": "\rcobra\r\rCobra 是一个用来创建命令行的 golang 库,同时也是一个用于生成应用和命令行文件的程序, 包括docker,k8s 都用的类似方式去实现,用于实现CLI非常好用\r\r\r\r可提供的功能点 简易的子命令行模式,如 app server, app fetch等等 完全兼容posix命令行模式 嵌套子命令subcommand 支持全局,局部,串联flags 使用Cobra很容易的生成应用程序和命令,使用cobra create appname和cobra add cmdname 如果命令输入错误,将提供智能建议,如 app srver,将提示srver没有,是否是app server 自动生成commands和flags的帮助信息 自动生成详细的help信息,如app help 自动识别-h,\u0026ndash;help帮助flag 自动生成应用程序在bash下命令自动完成功能 自动生成应用程序的man手册 命令行别名 自定义help和usage信息 可选的紧密集成的viper apps 安装方法 go get -v github.com/spf13/cobra/cobra\n使用方法 1. 新建一个cobra项目 cobra init --pkg-name cobrademo\n2. 目录结构 ▾ demo ▾ cmd/ root.go main.go main.go package main import ( \u0026#34;GoStudy/week16/cobrademo/cmd\u0026#34; ) func main() { cmd.Execute() } root.go package cmd import ( \u0026#34;GoStudy/week16/cobrademo/internal\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; \u0026#34;os\u0026#34; homedir \u0026#34;github.com/mitchellh/go-homedir\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) var cfgFile string var name string // rootCmd represents the base command when called without any subcommands var rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;cobrademo\u0026#34;, Short: \u0026#34;A brief description of your application\u0026#34;, Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default is $HOME/.cobrademo.yaml)\u0026#34;) } // initConfig reads in config file and ENV variables if set. func initConfig() { } 各个函数的作用\n rootCmd 注册业务函数,实现业务功能 init 提供命令行对外接口 initconfig 读取配置文件 execute 提供main调用的包装入口 3. 简易实现 新建一个功能包 internal ,在这里面来写业务逻辑\npackage internal import \u0026#34;fmt\u0026#34; func Name(name string){ fmt.Printf(\u0026#34;The input name is %s\u0026#34;,name) } 在cmd/root.go下面增加关联\npackage cmd import ( \u0026#34;GoStudy/week16/cobrademo/internal\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; \u0026#34;os\u0026#34; homedir \u0026#34;github.com/mitchellh/go-homedir\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) var cfgFile string var name string // rootCmd represents the base command when called without any subcommands var rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;cobrademo\u0026#34;, Short: \u0026#34;A brief description of your application\u0026#34;, Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { if len(name) == 0 { cmd.Help() return } internal.Name(name) }, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default is $HOME/.cobrademo.yaml)\u0026#34;) rootCmd.Flags().StringVarP(\u0026amp;name,\u0026#34;name\u0026#34;,\u0026#34;n\u0026#34;,\u0026#34;\u0026#34;,\u0026#34;input your name :\u0026#34;) } // initConfig reads in config file and ENV variables if set. func initConfig() { } 主要需要修改的就是init函数与rootCmd下面的Run函数 4. 添加子命令 cobra add test 然后就会在cmd包下新建了一个test.go的文件,用来实现子命令的入口\npackage cmd import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; ) // testCmd represents the test command var testCmd = \u0026amp;cobra.Command{ Use: \u0026#34;test\u0026#34;, Short: \u0026#34;A brief description of your command\u0026#34;, Long: `A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { fmt.Println(\u0026#34;test called\u0026#34;) }, } func init() { rootCmd.AddCommand(testCmd) // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command \t// and all subcommands, e.g.: \t// testCmd.PersistentFlags().String(\u0026#34;foo\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;A help for foo\u0026#34;) // Cobra supports local flags which will only run when this command \t// is called directly, e.g.: \t// testCmd.Flags().BoolP(\u0026#34;toggle\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;Help message for toggle\u0026#34;) } "
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/context/",
"title": "Context",
"tags": [],
"description": "Golang",
"content": " context源码阅读 1. 什么是 context Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。\ncontext 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。\n随着 context 包的引入,标准库中很多接口因此加上了 context 参数,例如 database/sql 包。context 几乎成为了并发控制和超时控制的标准做法。\n2. 为什么有 context Go 常用来写后台服务,通常只需要几行代码,就可以搭建一个 http server。\n在 Go 的 server 里,通常每来一个请求都会启动若干个 goroutine 同时工作:有些去数据库拿数据,有些调用下游接口获取相关数据…… 这些 goroutine 需要共享这个请求的基本数据,例如登陆的 token,处理请求的最大超时时间(如果超过此值再返回数据,请求方因为超时接收不到)等等。当请求被取消或是处理时间太长,这有可能是使用者关闭了浏览器或是已经超过了请求方规定的超时时间,请求方直接放弃了这次请求结果。这时,所有正在为这个请求工作的 goroutine 需要快速退出,因为它们的“工作成果”不再被需要了。在相关联的 goroutine 都退出后,系统就可以回收相关的资源。\n再多说一点,Go 语言中的 server 实际上是一个“协程模型”,也就是说一个协程处理一个请求。例如在业务的高峰期,某个下游服务的响应变慢,而当前系统的请求又没有超时控制,或者超时时间设置地过大,那么等待下游服务返回数据的协程就会越来越多。而我们知道,协程是要消耗系统资源的,后果就是协程数激增,内存占用飙涨,甚至导致服务不可用。更严重的会导致雪崩效应,整个服务对外表现为不可用,这肯定是 P0 级别的事故。这时,肯定有人要背锅了。\n其实前面描述的 P0 级别事故,通过设置“允许下游最长处理时间”就可以避免。例如,给下游设置的 timeout 是 50 ms,如果超过这个值还没有接收到返回数据,就直接向客户端返回一个默认值或者错误。例如,返回商品的一个默认库存数量。注意,这里设置的超时时间和创建一个 http client 设置的读写超时时间不一样,这里不详细展开。\ncontext 包就是为了解决上面所说的这些问题而开发的:在 一组 goroutine 之间传递共享的值、取消信号、deadline…\n用简练一些的话来说,在Go 里,我们不能直接杀死协程,协程的关闭一般会用 channel+select 方式来控制。但是在某些场景下,例如处理一个请求衍生了很多协程,这些协程之间是相互关联的:需要共享一些全局变量、有共同的 deadline 等,而且可以同时被关闭。再用 channel+select 就会比较麻烦,这时就可以通过 context 来实现。\n一句话:context 用来解决 goroutine 之间退出通知、元数据传递的功能。\n3. context 底层实现原理 3.1 整体概览 context包结构 各个函数作用 3.2 context接口 type Context interface { // 当 context 被取消或者到了 deadline,返回一个被关闭的 channel Done() \u0026lt;-chan struct{} // 在 channel Done 关闭后,返回 context 取消原因 Err() error // 返回 context 是否会被取消以及自动取消时间(即 deadline) Deadline() (deadline time.Time, ok bool) // 获取 key 对应的 value Value(key interface{}) interface{} } Context 是一个接口,定义了 4 个方法,它们都是幂等的。也就是说连续多次调用同一个方法,得到的结果都是相同的。\nDone() 返回一个 channel,可以表示 context 被取消的信号:当这个 channel 被关闭时,说明 context 被取消了。注意,这是一个只读的channel。 我们又知道,读一个关闭的 channel 会读出相应类型的零值。并且源码里没有地方会向这个 channel 里面塞入值。换句话说,这是一个 receive-only 的 channel。因此在子协程里读这个 channel,除非被关闭,否则读不出来任何东西。也正是利用了这一点,子协程从 channel 里读出了值(零值)后,就可以做一些收尾工作,尽快退出。\nErr() 返回一个错误,表示 channel 被关闭的原因。例如是被取消,还是超时。\nDeadline() 返回 context 的截止时间,通过此时间,函数就可以决定是否进行接下来的操作,如果时间太短,就可以不往下做了,否则浪费系统资源。当然,也可以用这个 deadline 来设置一个 I/O 操作的超时时间。\nValue() 获取之前设置的 key 对应的 value。\n3.3 cancel接口 type canceler interface { cancel(removeFromParent bool, err error) Done() \u0026lt;-chan struct{} } 实现了上面定义的两个方法的 Context,就表明该 Context 是可取消的。源码中有两个类型实现了 canceler 接口:*cancelCtx 和 *timerCtx。注意是加了 * 号的,是这两个结构体的指针实现了 canceler 接口。\nContext 接口设计成这个样子的原因:\n “取消”操作应该是建议性,而非强制性 caller 不应该去关心、干涉 callee 的情况,决定如何以及何时 return 是 callee 的责任。caller 只需发送“取消”信息,callee 根据收到的信息来做进一步的决策,因此接口并没有定义 cancel 方法。\n “取消”操作应该可传递 “取消”某个函数时,和它相关联的其他函数也应该“取消”。因此,Done() 方法返回一个只读的 channel,所有相关函数监听此 channel。一旦 channel 关闭,通过 channel 的“广播机制”,所有监听者都能收到。\n 3.4 emptyCtx实现 type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() \u0026lt;-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil } //这实际上是一个空的 context,永远不会被 cancel,没有存储值,也没有 deadline。 var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo } 3.5 cancelCtx实现 type cancelCtx struct { Context // 保护之后的字段 mu sync.Mutex done chan struct{} children map[canceler]struct{} err error } func (c *cancelCtx) Done() \u0026lt;-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d } func (c *cancelCtx) Err() error { c.mu.Lock() err := c.err c.mu.Unlock() return err } type stringer interface { String() string } func contextName(c Context) string { if s, ok := c.(stringer); ok { return s.String() } return reflectlite.TypeOf(c).String() } func (c *cancelCtx) String() string { return contextName(c.Context) + \u0026#34;.WithCancel\u0026#34; } var closedchan = make(chan struct{}) func init() { close(closedchan) } func (c *cancelCtx) cancel(removeFromParent bool, err error) { // 必须要传 err if err == nil { panic(\u0026#34;context: internal error: missing cancel error\u0026#34;) } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // 已经被其他协程取消 } // 给 err 字段赋值 c.err = err // 关闭 channel,通知其他协程 if c.done == nil { c.done = closedchan } else { close(c.done) } // 遍历它的所有子节点 for child := range c.children { // 递归地取消所有子节点 child.cancel(false, err) } // 将子节点置空 c.children = nil c.mu.Unlock() if removeFromParent { // 从父节点中移除自己 removeChild(c.Context, c) } } 总体来看,cancel() 方法的功能就是关闭 channel:c.done;递归地取消它的所有子节点;从父节点从删除自己。达到的效果是通过关闭 channel,将取消信号传递给了它的所有子节点。goroutine 接收到取消信号的方式就是 select 语句中的读 c.done 被选中。\n3.6 cancelCtx具体实现 var Canceled = errors.New(\u0026#34;context canceled\u0026#34;) func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} } func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, \u0026amp;c) return \u0026amp;c, func() { c.cancel(true, Canceled) } } func propagateCancel(parent Context, child canceler) { if parent.Done() == nil { return // parent is never canceled \t} if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { // parent has already been canceled \tchild.cancel(false, p.err) } else { if p.children == nil { p.children = make(map[canceler]struct{}) } p.children[child] = struct{}{} } p.mu.Unlock() } else { go func() { select { case \u0026lt;-parent.Done(): child.cancel(false, parent.Err()) case \u0026lt;-child.Done(): } }() } } // parentCancelCtx follows a chain of parent references until it finds a // *cancelCtx. This function understands how each of the concrete types in this // package represents its parent. func parentCancelCtx(parent Context) (*cancelCtx, bool) { for { switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return \u0026amp;c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } } } // removeChild removes a context from its parent. func removeChild(parent Context, child canceler) { p, ok := parentCancelCtx(parent) if !ok { return } p.mu.Lock() if p.children != nil { delete(p.children, child) } p.mu.Unlock() } 这是一个暴露给用户的方法,传入一个父 Context(这通常是一个 Background,作为根节点),返回新建的 context,新 context 的 done channel 是新建的(前文讲过)。\n当 WithCancel 函数返回的 CancelFunc 被调用或者是父节点的 done channel 被关闭(父节点的 CancelFunc 被调用),此 context(子节点) 的 done channel 也会被关闭。\n注意传给 WithCancel 方法的参数,前者是 true,也就是说取消的时候,需要将自己从父节点里删除。第二个参数则是一个固定的取消错误类型:\n3.7 timerCtx实现 timerCtx 基于 cancelCtx,只是多了一个 time.Timer 和一个 deadline。Timer 会在 deadline 到来时,自动取消 context。\ntype timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time } func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true } func (c *timerCtx) String() string { return contextName(c.cancelCtx.Context) + \u0026#34;.WithDeadline(\u0026#34; + c.deadline.String() + \u0026#34; [\u0026#34; + time.Until(c.deadline).String() + \u0026#34;])\u0026#34; } func (c *timerCtx) cancel(removeFromParent bool, err error) { c.cancelCtx.cancel(false, err) if removeFromParent { // Remove this timerCtx from its parent cancelCtx\u0026#39;s children. \tremoveChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock() } Deadline或者TimeOut实现 var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return \u0026#34;context deadline exceeded\u0026#34; } func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok \u0026amp;\u0026amp; cur.Before(d) { // The current deadline is already sooner than the new one. \treturn WithCancel(parent) } c := \u0026amp;timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) dur := time.Until(d) if dur \u0026lt;= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed \treturn c, func() { c.cancel(false, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { c.timer = time.AfterFunc(dur, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } } func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) } 3.8 valueCtx实现 // A valueCtx carries a key-value pair. It implements Value for that key and // delegates all other calls to the embedded Context. type valueCtx struct { Context key, val interface{} } // stringify tries a bit to stringify v, without using fmt, since we don\u0026#39;t // want context depending on the unicode tables. This is only used by // *valueCtx.String(). func stringify(v interface{}) string { switch s := v.(type) { case stringer: return s.String() case string: return s } return \u0026#34;\u0026lt;not Stringer\u0026gt;\u0026#34; } func (c *valueCtx) String() string { return contextName(c.Context) + \u0026#34;.WithValue(type \u0026#34; + reflectlite.TypeOf(c.key).String() + \u0026#34;, val \u0026#34; + stringify(c.val) + \u0026#34;)\u0026#34; } func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) } func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic(\u0026#34;nil key\u0026#34;) } if !reflectlite.TypeOf(key).Comparable() { panic(\u0026#34;key is not comparable\u0026#34;) } return \u0026amp;valueCtx{parent, key, val} } 4. 具体使用 context 使用起来非常方便。源码里对外提供了一个创建根节点 context 的函数:\nfunc Background() Context background 是一个空的 context, 它不能被取消,没有值,也没有超时时间。\n有了根节点 context,又提供了四个函数创建子节点 context:\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context context 会在函数传递间传递。只需要在适当的时间调用 cancel 函数向 goroutines 发出取消信号或者调用 Value 函数取出 context 中的值。\n4.1 WithCancel使用 package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { ctx := context.Background() cancelCtx ,cancelFunc := context.WithCancel(ctx) go cancelTest(cancelCtx) //注意要放到协程里运行 \ttime.Sleep(3*time.Second) cancelFunc() fmt.Println(\u0026#34;end\u0026#34;) } func cancelTest(ctx context.Context) { for { fmt.Println(\u0026#34;Pritln\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026#34;) time.Sleep(time.Second) } } 4.2 WithTimeOut package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main() { ctx := context.Background() timeoutCtx,_ := context.WithTimeout(ctx,3*time.Second) go cancelTest(timeoutCtx) for i:= 0; i\u0026lt;10;i++{ time.Sleep(time.Second) fmt.Println(\u0026#34;Waiting ....\u0026#34;) } } func cancelTest(ctx context.Context) { for { fmt.Println(\u0026#34;Pritln\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026#34;) time.Sleep(time.Second) select { case \u0026lt;-ctx.Done(): return default: } } } 4.3 WithDeadline package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/jinzhu/now\u0026#34; ) func main() { ctx := context.Background() deadline := now.EndOfMinute() deadlineCtx,_ := context.WithDeadline(ctx,deadline) go cancelTest(deadlineCtx) for i:= 0; i\u0026lt;60;i++{ time.Sleep(time.Second) fmt.Println(\u0026#34;Waiting ....\u0026#34;) } } func cancelTest(ctx context.Context) { for { fmt.Println(\u0026#34;Pritln\u0026gt;\u0026gt;\u0026gt;\u0026gt;\u0026#34;) time.Sleep(time.Second) select { case \u0026lt;-ctx.Done(): return default: } } } 4.4 WithValue package main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;time\u0026#34; ) func main(){ ctx := context.Background() timer := time.Now() valueCtx := context.WithValue(ctx,\u0026#34;StartTime\u0026#34;,timer) valueFunc(valueCtx) time.Sleep(time.Second) timer = time.Now() valueCtx2 := context.WithValue(valueCtx,\u0026#34;StartTime\u0026#34;,timer) valueFunc(valueCtx) valueFunc(valueCtx2) } func valueFunc(ctx context.Context){ fmt.Println(ctx.Value(\u0026#34;StartTime\u0026#34;)) } //注意子context的value和父context的value同时设置时,只会读取子context的key和value 运行结果\n2020-07-23 15:33:32.2033806 +0800 CST m=+0.003972601 2020-07-23 15:33:32.2033806 +0800 CST m=+0.003972601 2020-07-23 15:33:33.2211325 +0800 CST m=+1.021724501 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/",
"title": "Docker",
"tags": [],
"description": "Docker",
"content": " docker命令 \r\rdockerfile构建镜像 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/",
"title": "Essay",
"tags": [],
"description": "Article seek",
"content": " August \r\rJuly \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/gin/",
"title": "Gin",
"tags": [],
"description": "Golang",
"content": " 1. gin入门使用 package main import ( \u0026#34;github.com/gin-gonic/gin\u0026#34; \u0026#34;time\u0026#34; ) func main() { r := gin.Default() r.GET(\u0026#34;/\u0026#34;, func(ctx *gin.Context) { ctx.JSON(200,gin.H{ \u0026#34;Content\u0026#34;:\u0026#34;This is the gin demo\u0026#34;, \u0026#34;Time\u0026#34;:time.Now(), }) }) r.Run(\u0026#34;:8080\u0026#34;) } 输出日志\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\r[GIN-debug] [WARNING] Running in \u0026quot;debug\u0026quot; mode. Switch to \u0026quot;release\u0026quot; mode in production.\r- using env:\texport GIN_MODE=release\r- using code:\tgin.SetMode(gin.ReleaseMode)\r[GIN-debug] GET / --\u0026gt; main.main.func1 (3 handlers)\r[GIN-debug] Listening and serving HTTP on :8080\r[GIN] 2020/07/23 - 18:10:06 | 200 | 17.9786ms | 127.0.0.1 | GET \u0026quot;/\u0026quot;\r[GIN] 2020/07/23 - 18:10:28 | 200 | 0s | 127.0.0.1 | GET \u0026quot;/\u0026quot;\r[GIN] 2020/07/23 - 18:10:28 | 404 | 0s | 127.0.0.1 | GET \u0026quot;/favicon.ico\u0026quot;\r1.1 Http方法 最开始的HTTP 0.9版本只有一个GET方法,也就是我们在浏览器中直接输入网址回车请求的方法,这是一个幂等方法,用于获取服务器上的资源。\n在HTTP 1.0的时候又增加了HEAD和POST方法,其中常用的就是POST方法,一般用于我们给服务端提交一个资源,导致服务器的资源发生变化。\n在HTTP1.1版本,也就是HTTP 1系列的最后一个版本中,也是我们当下比较常用的HTTP版本,增加了更多的HTTP 方法。比如OPTIONS, PUT, DELETE, TRACE和CONNECT,这样在HTTP 1.1 版本中,HTTP的方法达到了8个。\n下面我们看下这些方法被赋予的意义。\nGET\nGET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据.\nHEAD\nHEAD方法请求一个与GET请求的响应相同的响应,但没有响应体.\nPOST\nPOST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用.\nPUT\nPUT方法用请求有效载荷替换目标资源的所有当前表示。\nDELETE\nDELETE方法删除指定的资源。\nCONNECT\nCONNECT方法建立一个到由目标资源标识的服务器的隧道。\nOPTIONS\nOPTIONS方法用于描述目标资源的通信选项。\nTRACE\nTRACE方法沿着到目标资源的路径执行一个消息环回测试。\nPATCH\nPATCH方法用于对资源应用部分修改。\n1.2 RestFul API 我们开发的一个个Web应用服务或者程序,其实就是对服务器的资源的CRUD(创建、检索、更新和删除),所以 RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。\n在 RESTful API 中,使用的主要是以下五种HTTP方法:\nGET,表示读取服务器上的资源\rPOST,表示在服务器上创建资源\rPUT,表示更新或者替换服务器上的资源\rDELETE,表示删除服务器上的资源\rPATCH,表示更新/修改资源的一部分\r 1.3 Gin RestFul r.GET(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.POST(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.PUT(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.HEAD(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.PATCH(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.DELETE(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) r.Any(\u0026#34;/\u0026#34;, func(ctx *gin.Context) {}) 1.4 Gin 路由匹配 Gin的路由采用的是httprouter,所以它的路由参数的定义和httprouter也是一样的。\n/users/:id 就是一种路由匹配模式,也是一个通配符,其中:id就是一个路由参数,我们可以通过c.Param(\u0026ldquo;id\u0026rdquo;)获取定义的路由参数的值,然后用来做事情,比如打印出来。\n/users/:id这种匹配模式是精确匹配的,只能匹配一个,我们举几个例子说明:\nPattern: /users/:id\n /users/123 匹配\r/users/哈哈 匹配\r/users/123/go 不匹配\r/users/ 不匹配\r 这里我故意写了/users/哈哈,并且是匹配的,意思就是对于Gin路径中的匹配都是字符串,它是不区分数字、字母和汉字的,都匹配。\n这里还需要说明的是,Gin的路由是单一的,不能有重复。\n星号路由参数 上面我们介绍的是:号的路由参数,这种路由参数最常用。还有一种不常用的就是*号类型的参数,表示匹配所有。\n以/users/*id为例:\nPattern: /users/*id\n/users/123 匹配 /users/哈哈 匹配 /users/123/go 匹配 /users/ 匹配 我们把上面的例子改下:\nfunc main() { r := gin.Default() r.GET(\u0026#34;/users/*id\u0026#34;, func(c *gin.Context) { id := c.Param(\u0026#34;id\u0026#34;) c.String(200, \u0026#34;The user id is %s\u0026#34;, id) }) r.Run(\u0026#34;:8080\u0026#34;) } 现在我们运行,浏览器里访问http://localhost:8080/users/123,会看到如下信息:\n The user id is /123\r 是否发现区别了,我们获取到的id不是123了,而是/123,多了一个/\n同样的你试试http://localhost:8080/users/123/go会发现显示的信息是:\n The user id is /123/go\r 是一个/开头的路径字符串。\n这里要特别说明一点的是,如果你用浏览器访问http://localhost:8080/users,会被重定向到http://localhost:8080/users/,然后显示的信息如下:\n The user id is /\r 重定向的根本原因在于/users没有匹配的路由,但是有匹配/users/的路由,所以就会被重定向到/users/。现在我们注册一个/users来验证下这个猜测:\nr.GET(\u0026quot;/users\u0026rdquo;, func(c gin.Context) { c.String(200, \u0026ldquo;这是真正的/users\u0026rdquo;) }) 现在再访问http://localhost:8080/users*,会看到显示的信息变成了:\n 这是真正的/users\r 这也间接证明了/users/*id和/users这两个路由是不冲突的,可以被Gin注册。\n以上自动重定向的原理,得益于gin.RedirectTrailingSlash 等于true的配置。如果我们把它改为false就不会自动重定向了。\n1.5 Query参数 在Gin中,为我们提供了简便的方法来获取查询参数的值,我们只需要知道查询参数的key(参数名)就可以了。\nfunc main() { r := gin.Default() r.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { c.String(200, c.Query(\u0026#34;wechat\u0026#34;)) }) r.Run(\u0026#34;:8080\u0026#34;) } 我们运行这段代码,打开浏览器访问http://localhost:8080/?wechat=flysnow_org,就可以看到flysnow_org文字。这表示我们通过**c.Query(\u0026ldquo;wechat\u0026rdquo;)**获取到了查询参数wechat的值是flysnow_org。\nQuery方法为我们提供了获取对应key的值的能力,如果该key不存在,则返回\u0026quot;\u0026ldquo;字符串。如果对于一些数字参数,比如id如果返回为空的话,我们进行字符串转数字的时候会报错,这时候,我们就可以通过DefaultQuery方法指定一个默认值:\nc.DefaultQuery(\u0026#34;wechat\u0026#34;, \u0026#34;flysnow_org\u0026#34;) c.DefaultQuery(\u0026#34;id\u0026#34;, \u0026#34;0\u0026#34;) Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/golang/",
"title": "Golang面试题",
"tags": [],
"description": "golang",
"content": " 1. 面试题 golang面试题 2. 解答 1. 选择题 1. 属于关键字的是 func struct Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort和Heapsort是两个不同的名字。\nGo语言中类似if和switch的关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。\nbreak default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var 此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。\n内建常量: true false iota nil 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error 内建函数: make len cap new append copy close delete complex real imag panic recover 这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。\n2.定义全局字符串变量 A D package main import ( \u0026#34;fmt\u0026#34; ) var name = \u0026#34;name\u0026#34; var addr string func main() { addr = \u0026#34;nanjing\u0026#34; fmt.Println(name) fmt.Println(addr) } 3. 通过指针变量p 访问其成员变量name p.name,(*p).name\r package main import \u0026#34;fmt\u0026#34; type person struct { name string name1 string name2 string } func main() { var p = new(person) p.name = \u0026#34;name\u0026#34; fmt.Println(p.name) (*p).name1 = \u0026#34;name1\u0026#34; fmt.Println((*p).name1) //(\u0026amp;p).name2 = \u0026#34;name2\u0026#34; //fmt.Println((\u0026amp;p).name2) // \\main.go:16:6: (\u0026amp;p).name2 undefined (type **person has no field or method name2) //.\\main.go:17:18: (\u0026amp;p).name2 undefined (type **person has no field or method name2) } 4. 关于接口和类的说法 ABD 接口实现的条件\n5. 关于字符串连接,使用正确的是 BD 6. 关于协程,下面说法正确是 AD 7. 关于init函数,下面说法正确的是:AB package main import \u0026#34;fmt\u0026#34; func init(){ fmt.Println(\u0026#34;init1\u0026#34;) } func init(){ fmt.Println(\u0026#34;init2\u0026#34;) } func main() { str1 := fmt.Sprintf(\u0026#34;abc%d\u0026#34;,123) fmt.Println(str1) } 8.关于循环语句 C for循环 Go语言只有for循环这一种循环语句\nfor initialization; condition; post { // zero or more statements } for循环三个部分不需括号包围。大括号强制要求,左大括号必须和post语句在同一行。 initialization语句是可选的,在循环开始前执行。initalization如果存在,必须是一条简单语句(simple statement),即,短变量声明、自增语句、赋值语句或函数调用。\n9. 对add函数调用正确的是 ABD package main import \u0026#34;fmt\u0026#34; func add(args ...int)int { sum := 0 for _,v := range args{ sum += v } return sum } func main() { fmt.Println(add(1,2,3)) fmt.Println(add(1,2)) //fmt.Println(add([]int{1,2,3})) // cannot use []int literal (type []int) as type int in argument to add \tfmt.Println(add([]int{1,2,3}...)) } 10.关于类型转化,下面语法正确的是 C package main import \u0026#34;fmt\u0026#34; func main() { type myint int var i int = 1 var j = myint(i) fmt.Println(j) } 11. 关于局部变量的初始化,使用正确的是 ABC 12. 关于const常量定义,下面正确的使用方式是 ABD 常量\n13. 关于布尔变量b的赋值,下面错误的用法是 BC 14. 下面的程序可能的运行结果是 C package main import ( \u0026#34;fmt\u0026#34; ) func main() { if true { defer fmt.Printf(\u0026#34;1\u0026#34;) } else { defer fmt.Printf(\u0026#34;2\u0026#34;) } fmt.Printf(\u0026#34;3\u0026#34;) } //31 15. 关于switch语句,下面说法正确的有 BD switch\n16. golang中没有隐藏的this指针,这句话的含义是 ACD A 方法施加的对象显式传递,没有被隐藏起来\rB golang沿袭了传统面向对象编程中的诸多概念,比如继承、虚函数和构造函数\rC golang的面向对象表达更直观,对于面向过程只是换了一种语法形式来表达\rD 方法施加的对象不需要非得是指针,也不用非得叫this\r 17. golang中的引用类型包括 ABCD 值类型,引用类型都包括哪些\n 基本的数据类型 int系列,float系列,bool,string, 数组和结构体struct 引用类型包括指针,slice切片,map ,chan ,interface 值类型和引用类型的使用特点 值类型 直接存放值,内存通常在栈中分配\n应用类型变量存储的地址(也就是通过指针访问类型里面的数据),通常真正的值在堆上分配。当没有变量引用这个地址的时候,该值会被gc回收。\n引用类型与值类型\n18. golang中的指针运算包括 BC 19. 关于main函数(可执行程序的执行起点),下面说法正确的是:ABCD 20. 下面赋值正确的是 BD var y interface{} = nil var w error = nil 21. 关于整型切片的初始化正确的是 BCD A中没有指定划拨的内存的大小\n22. 从切片中删除一个元素,下面的算法实现正确的是 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/grpc/",
"title": "gRPC",
"tags": [],
"description": "Golang",
"content": " 1. gRPC gRPC是一个高性能、开源和通用的RPC框架,面向移动和HTTP/2设计。\r目前提供C、Java和Go语言版本,分别是grpc、grpc-java、grpc-go。\rgRPC基于HTTP/2标准设计,带来诸如双向流、流控、头部压缩、单TCP连接上的多复用请求等特性。\r这些特性使得其在移动设备上表现更好,更省电和节省空间占用。\rgRPC由google开发,是一款语言中立、平台中立、开源的远程过程调用系统。\r grpc具体是什么 在gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同机器上服务端应用的方法,\r使得你能够更容易地创建分布式应用和服务。与许多RPC系统类似,gRPC也是基于以下理念:\r定义一个服务,指定其能够被远程调用的方法(包括参数和返回类型)。\r在服务端实现这个接口,并运行一个gRPC服务器来处理客户端调用。\r在客户端拥有一个存根能够像服务端一样的方法。\rgRPC客户端和服务端可以在多种环境中运行和交互,从google内部的服务器到你的笔记本,并且可以使用任何gRPC支持的语言编写。\r所以你可以很容易滴用Java创建一个gRPC服务端,用go、python、ruby来创建客户端。\r此外,google最新api将有gRPC版本的接口,使你很容易地将Google的功能集成到你的应用中。\r 2. protoc 与 protocol buffer gRPC默认使用protocol buffers,这是google开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如JSON)\r可以用proto files创建gRPC服务,用protocol buffers消息类型来定义方法参数和返回类型。\r 3. 使用实例 3.1 Unary RPC proto syntax = \u0026#34;proto3\u0026#34;;package helloworld;service Greeter{ rpc sayhello(HelloRequest) returns (HelloReply){}}message HelloRequest{ string name = 1;}message HelloReply{ string message =1;}具体使用: 1. 第一行声明语法 2. 接下来定义RPC服务,入参与出参 3. 定义入参结构体,注意语法结构 4. 定义出参结构体\n 支持的数据类型 基本类型: bool , string ,bytes([]byte),int32,int64,uint32,uint64,double(float64),float(float32) 嵌套类型: 相当于Go语言的结构体嵌套 oneof: 使用时只能定义其中的一个 enum: 使用时定义的类型需要为其中枚举的类型 map: 相当于go语言的map,定义方式为map\u0026lt;string,string\u0026gt; 声明消息体的时候,可以加上repeat字段,表示可以重复。相当于动态数组 接下来需要使用工具生成调用的文件\nprotoc --go_out=plugins=grpc:. ./proto/*.proto 这个命令的作用是,根据我们编写的Proto文件,生成Go语言类型的proto buffer文件,方便后面进行调用 client package main import ( pb \u0026#34;GoStudy/week16/grpc/proto\u0026#34; \u0026#34;context\u0026#34; \u0026#34;google.golang.org/grpc\u0026#34; \u0026#34;log\u0026#34; ) func SayHello(client pb.GreeterClient)error{ resp,err := client.Sayhello(context.Background(),\u0026amp;pb.HelloRequest{ Name: \u0026#34;Liuzeng\u0026#34;, }) if err!= nil { return err } log.Println(\u0026#34;clinet sayhello to server :\u0026#34;,resp.Message) return nil } func main() { conn,_ := grpc.Dial(\u0026#34;:9000\u0026#34;,grpc.WithInsecure()) defer conn.Close() client :=pb.NewGreeterClient(conn) err := SayHello(client) if err != nil { log.Println(err) } } 使用方式:(在此处详细描述,后面不再展开)\n grpc.Dial,建立RPC连接 创建client对象(直接调用生成的pb.go里面定义的工厂方法就行了) 调用RPC函数 入参为client对象,出参为error(出参入参都可以自己定义,这个无所谓) 调用pb.go里面给client对象生成的RPC方法,根据之前在Proto文件里面定义的调用方法,取出返回值来 处理出错内容,返回 处理返回值 server package main import ( pb \u0026#34;GoStudy/week16/grpc/proto\u0026#34; \u0026#34;context\u0026#34; \u0026#34;google.golang.org/grpc\u0026#34; \u0026#34;net\u0026#34; ) type GreeterServer struct { } func (s *GreeterServer)Sayhello(ctx context.Context,r *pb.HelloRequest) (*pb.HelloReply,error){ return \u0026amp;pb.HelloReply{ Message: \u0026#34;Hello\u0026#34; + r.Name, },nil } func main(){ server :=grpc.NewServer() pb.RegisterGreeterServer(server,\u0026amp;GreeterServer{}) lis,_:= net.Listen(\u0026#34;tcp\u0026#34;,\u0026#34;:9000\u0026#34;) server.Serve(lis) } 使用方法:\n 声明一个RPC服务端 声明一个服务端结构体,注册到pb.go里面的服务端注册服务上 开始监听调用服务的端口 提供RPC服务 给服务端结构体定义远程调用方法 进行业务逻辑处理 写入Reply返回体 综上所述,关键点就在于proto文件的定义,这个相当于两方都共同遵守的一个规范。client和server都是要按照proto文件里面定义的方法,才能编写对应的服务 3.2 服务端流式RPC 服务端多次返回,客户端一次接受\nproto client server 3.3 客户端流式RPC 客户端多次请求,服务端一次接受\nproto client server 3.4 双向流式RPC 交互式,客户端多次请求,服务端多次发送\nproto client server Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/gohugoio/hugo/",
"title": "hugo",
"tags": [],
"description": "github.com/gohugoio/hugo",
"content": " \rDescription\r\rHugo是由Go语言实现的静态网站生成器。简单、易用、高效、易扩展、快速部署。 Hugo采用 Go 编程语言开发,Hugo 的设计目标是让创建网站重新变得有趣。\r\r\r\rShow More \rHugo 是一个通用的网站框架。从技术上讲,Hugo 是一个静态站点生成器。与动态构建页面的系统不同,Hugo 在创建或更新内容时构建页面。由于网站的浏览频率远高于编辑频率,因此 Hugo 旨在为您的网站最终用户提供最佳的浏览体验,并为网站作者提供理想的写作体验。\r使用 Hugo 构建的网站非常快速和安全。Hugo 构建的网站可以托管在任何地方,包括 Netlify、Heroku、GoDaddy、DreamHost、GitHub Pages、GitLab Pages、Surge、Aerobatic、Firebase、Google Cloud Storage、Amazon S3、Rackspace、Azure, 和 CloudFront,并且与 CDN 更配。Hugo 网站在运行时不需要数据库或依赖于诸如 Ruby、Python 或 PHP 等昂贵的运行时环境。\r我们认为 Hugo 是一个理想的网站创建工具,具有几乎即时的构建时间,能够在网站修改时即刻重建。\r\r\n1. Hugo 部署 二进制安装(推荐:简单、快速) 到 Hugo Releases 下载对应的操作系统版本的Hugo二进制文件(hugo或者hugo.exe)\n 编译部署 熟悉Go语言的朋友可以试下这个方法,不过great wall的存在让依赖不太好下载。不过下载下之后可以自己进行魔改。geek们可以试下。\ngo get -u -v github.com/spf13/hugo\n 开始建立hugo project hugo new site mydocs 建立好之后的目录结构为\n▸ archetypes/ ▸ content/ ▸ layouts/ ▸ static/ config.toml 刚创建好的hugo project还需设定主题模板之类的 到主题网站上面下载一个中意的模板,创建目录 themes,在 themes 目录里把皮肤 git clone 下来 Copy\r\r\r# 创建 themes 目录 $ cd themes $ git clone https://github.com/spf13/hyde.git \r\r\r 开始运行\nhugo server\r然后打开浏览器,输入http://localhost:1313就可以实时预览了\n 2. Ace document 配置方法 hugo new site docs cd docs cd themes git clone https://github.com/vantagedesign/ace-documentation 然后将themes\\ace-documentation\\exampleSite下面的三个文件夹拷贝到docs目录下面,覆盖掉原来的文件。 如果想自己开始创建文章的话,可以将content下面的文章清空,然后重新开始编写文章\n#接下来就可以生成静态站点了,在docs目录下输入 hugo #然后将生成的public文件夹发布到静态文章服务器就行了。 #文章后面会提供一个静态文章服务器的Go语言脚本,可以自己在虚拟机或者云服务器上先试下 3. 使用Github pages进行发布 在自己的GitHub仓库中新建一个仓库,Github提供了免费的静态页面托管服务,创建仓库的时候可以用github用户名.github.io创建,然后直接访问这个地址就可以访问自己的主页了。我们接下来就需要在public目录下面git clone这个仓库,然后把我们更新的内容推送上去,直接打开页面预览就可以了。\n$ cd public $ git init $ git remote add origin https://github.com/github用户名/GitHub用户名.github.io $ git add -A $ git commit -m \u0026#34;first commit\u0026#34; $ git push -u origin master 4.静态网站服务器 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;path/filepath\u0026#34; ) func main() { p, _ := filepath.Abs(filepath.Dir(\u0026#34;.\u0026#34;)) http.Handle(\u0026#34;/\u0026#34;, http.FileServer(http.Dir(p))) err := http.ListenAndServe(\u0026#34;:8088\u0026#34;, nil) if err != nil { fmt.Println(err) } } 这是个非常简易的脚本,不过用来显示我们之前做的静态网页是足够了。将public下面的所有文件拷入编译后的可执行文件目录下,执行这个可执行文件后,就可以去浏览效果了 下面会抽时间再补充一个自动同步文件的程序\n "
},
{
"uri": "https://liuzeng01.github.io/programing/java/",
"title": "Java",
"tags": [],
"description": "Java",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kernel/",
"title": "Kernel",
"tags": [],
"description": "Kernel",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/tips/kubectl/",
"title": "kubectl命令用法",
"tags": [],
"description": "Kubernetes",
"content": " 1. 基础命令: create,delete,get,run,expose,set,explain,edit\ncreate 命令:根据文件或者输入来创建资源 kubectl create -f demo-deployment.yaml kubectl create -f demo-service.yaml delete 命令:删除资源 # 根据yaml文件删除对应的资源,但是yaml文件并不会被删除,这样更加高效 kubectl delete -f demo-deployment.yaml kubectl delete -f demo-service.yaml # 也可以通过具体的资源名称来进行删除,使用这个删除资源,同时删除deployment和service资源 kubectl delete 具体的资源名称 get 命令 :获得资源信息 # 查看所有的资源信息 kubectl get all kubectl get --all-namespaces # 查看pod列表 kubectl get pod # 显示pod节点的标签信息 kubectl get pod --show-labels # 根据指定标签匹配到具体的pod kubectl get pods -l app=example # 查看node节点列表 kubectl get node # 显示node节点的标签信息 kubectl get node --show-labels # 查看pod详细信息,也就是可以查看pod具体运行在哪个节点上(ip地址信息) kubectl get pod -o wide # 查看服务的详细信息,显示了服务名称,类型,集群ip,端口,时间等信息 kubectl get svc kubectl get svc -n kube-system # 查看命名空间 kubectl get ns kubectl get namespaces # 查看所有pod所属的命名空间 kubectl get pod --all-namespaces # 查看所有pod所属的命名空间并且查看都在哪些节点上运行 kubectl get pod --all-namespaces -o wide # 查看目前所有的replica set,显示了所有的pod的副本数,以及他们的可用数量以及状态等信息 kubectl get rs # 查看已经部署了的所有应用,可以看到容器,以及容器所用的镜像,标签等信息 kubectl get deploy -o wide kubectl get deployments -o wide run 命令:在集群中创建并运行一个或多个容器镜像 语法:run NAME \u0026ndash;image=image [\u0026ndash;env=\u0026quot;key=value\u0026rdquo;] [\u0026ndash;port=port] [\u0026ndash;replicas=replicas] [\u0026ndash;dry-run=bool] [\u0026ndash;overrides=inline-json] [\u0026ndash;command] \u0026ndash; [COMMAND] [args\u0026hellip;] # 示例,运行一个名称为nginx,副本数为3,标签为app=example,镜像为nginx:1.10,端口为80的容器实例 kubectl run nginx --replicas=3 --labels=\u0026#34;app=example\u0026#34; --image=nginx:1.10 --port=80 # 示例,运行一个名称为nginx,副本数为3,标签为app=example,镜像为nginx:1.10,端口为80的容器实例,并绑定到k8s kubectl run nginx --image=nginx:1.10 --replicas=3 --labels=\u0026#34;app=example\u0026#34; --port=80 --overrides=\u0026#39;{\u0026#34;apiVersion\u0026#34;:\u0026#34;apps/v1\u0026#34;,\u0026#34;spec\u0026#34;:{\u0026#34;template\u0026#34;:{\u0026#34;spec\u0026#34;:{\u0026#34;nodeSelector\u0026#34;:{\u0026#34;kubernetes.io/hostname\u0026#34;:\u0026#34;k8s-node1\u0026#34;}}}}}\u0026#39; expose 命令:创建一个service服务,并且暴露端口让外部可以访问 # 创建一个nginx服务并且暴露端口让外界可以访问 kubectl expose deployment nginx --port=88 --type=NodePort --target-port=80 --name=nginx-service set 命令:配置应用的一些特定资源,也可以修改应用已有的资源 使用 kubectl set \u0026ndash;help查看,它的子命令,env,image,resources,selector,serviceaccount,subject。 语法:resources (-f FILENAME | TYPE NAME) ([\u0026ndash;limits=LIMITS \u0026amp; \u0026ndash;requests=REQUESTS] kubectl set resources 命令 这个命令用于设置资源的一些范围限制。\n 资源对象中的Pod可以指定计算资源需求(CPU-单位m、内存-单位Mi),即使用的最小资源请求(Requests),限制(Limits)的最大资源需求,Pod将保证使用在设置的资源数量范围。\n 对于每个Pod资源,如果指定了Limits(限制)值,并省略了Requests(请求),则Requests默认为Limits的值。\n 可用资源对象包括(支持大小写):replicationcontroller、deployment、daemonset、job、replicaset。\n # 将deployment的nginx容器cpu限制为“200m”,将内存设置为“512Mi” kubectl set resources deployment nginx -c=nginx --limits=cpu=200m,memory=512Mi # 设置所有nginx容器中 Requests和Limits kubectl set resources deployment nginx --limits=cpu=200m,memory=512Mi --requests=cpu=100m,memory=256Mi # 删除nginx中容器的计算资源值 kubectl set resources deployment nginx --limits=cpu=0,memory=0 --requests=cpu=0,memory=0 kubectl set selector 命令 设置资源的 selector(选择器)。如果在调用\u0026quot;set selector\u0026quot;命令之前已经存在选择器,则新创建的选择器将覆盖原来的选择器。\n selector必须以字母或数字开头,最多包含63个字符,可使用:字母、数字、连字符\u0026rdquo; - \u0026quot; 、点\u0026rdquo;.\u0026ldquo;和下划线\u0026rdquo; _ \u0026ldquo;。如果指定了\u0026ndash;resource-version,则更新将使用此资源版本,否则将使用现有的资源版本。\n 注意:目前selector命令只能用于Service对象。\n 语法:selector (-f FILENAME | TYPE NAME) EXPRESSIONS [--resource-version=version] kubectl set image 命令 用于更新现有资源的容器镜像。\n 可用资源对象包括:pod (po)、replicationcontroller (rc)、deployment (deploy)、daemonset (ds)、job、replicaset (rs)。\n # 将deployment中的nginx容器镜像设置为“nginx:1.9.1” kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1 # 所有deployment和rc的nginx容器镜像更新为“nginx:1.9.1” kubectl set image deployments,rc nginx=nginx:1.9.1 --all # 将daemonset abc的所有容器镜像更新为“nginx:1.9.1” kubectl set image daemonset abc *=nginx:1.9.1 # 从本地文件中更新nginx容器镜像 kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml explain 命令:用于显示资源文档信息 kubectl explain rs edit 命令: 用于编辑资源信息 # 编辑Deployment nginx的一些信息 kubectl edit deployment nginx # 编辑service类型的nginx的一些信息 kubectl edit service/nginx 2. 设置命令 label,annotate,completion\nlabel命令: 用于更新(增加、修改或删除)资源上的 label(标签) label 必须以字母或数字开头,可以使用字母、数字、连字符、点和下划线,最长63个字符。 如果 \u0026ndash;overwrite 为 true,则可以覆盖已有的label,否则尝试覆盖label将会报错。 如果指定了\u0026ndash;resource-version,则更新将使用此资源版本,否则将使用现有的资源版本。 # 给名为foo的Pod添加label unhealthy=true kubectl label pods foo unhealthy=true # 给名为foo的Pod修改label 为 \u0026#39;status\u0026#39; / value \u0026#39;unhealthy\u0026#39;,且覆盖现有的value kubectl label --overwrite pods foo status=unhealthy # 给 namespace 中的所有 pod 添加 label kubectl label pods --all status=unhealthy # 仅当resource-version=1时才更新 名为foo的Pod上的label kubectl label pods foo status=unhealthy --resource-version=1 # 删除名为“bar”的label 。(使用“ - ”减号相连) kubectl label pods foo bar- annotate命令:更新一个或多个资源的Annotations信息。也就是注解信息,可以方便的查看做了哪些操作。 Annotations由key/value组成。 Annotations的目的是存储辅助数据,特别是通过工具和系统扩展操作的数据,更多介绍在这里。 如果\u0026ndash;overwrite为true,现有的annotations可以被覆盖,否则试图覆盖annotations将会报错。 如果设置了\u0026ndash;resource-version,则更新将使用此resource version,否则将使用原有的resource version。 # 更新Pod“foo”,设置annotation “description”的value “my frontend”,如果同一个annotation多次设置,则只使用最后设置的value值 kubectl annotate pods foo description=\u0026#39;my frontend\u0026#39; # 根据“pod.json”中的type和name更新pod的annotation kubectl annotate -f pod.json description=\u0026#39;my frontend\u0026#39; # 更新Pod\u0026#34;foo\u0026#34;,设置annotation“description”的value“my frontend running nginx”,覆盖现有的值 kubectl annotate --overwrite pods foo description=\u0026#39;my frontend running nginx\u0026#39; # 更新 namespace中的所有pod kubectl annotate pods --all description=\u0026#39;my frontend running nginx\u0026#39; # 只有当resource-version为1时,才更新pod \u0026#39;foo\u0026#39; kubectl annotate pods foo description=\u0026#39;my frontend running nginx\u0026#39; --resource-version=1 # 通过删除名为“description”的annotations来更新pod \u0026#39;foo\u0026#39;。 # 不需要 -overwrite flag。 kubectl annotate pods foo description- completion命令:用于设置 kubectl 命令自动补全 # 在 bash 中设置当前 shell 的自动补全,要先安装 bash-completion 包 source \u0026lt;(kubectl completion bash) # 在您的 bash shell 中永久的添加自动补全 echo \u0026#34;source \u0026lt;(kubectl completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 配置completion时出现的问题: [root@master bin]# kubectl c-bash: _get_comp_words_by_ref: command not found loud-controller-man^C # 原因:未安装bash-completion,或者安装没生效 解决方法: [root@master bin]# yum install bash-completion -y [root@master bin]# source /usr/share/bash-completion/bash_completion [root@master bin]# source \u0026lt;(kubectl completion bash) 3. 部署命令 rollout,rolling-update,scale,autoscale\nrollout 命令: 用于对资源进行管理 可用资源包括:deployments,daemonsets。 子命令: history(查看历史版本) pause(暂停资源) resume(恢复暂停资源) status(查看资源状态) undo(回滚版本)\n# 语法 $ kubectl rollout SUBCOMMAND # 回滚到之前的deployment $ kubectl rollout undo deployment/abc # 查看daemonet的状态 $ kubectl rollout status daemonset/foo rolling-update命令: 执行指定ReplicationController的滚动更新 该命令创建了一个新的RC, 然后一次更新一个pod方式逐步使用新的PodTemplate,最终实现Pod滚动更新,new-controller.json需要与之前RC在相同的namespace下\n# 使用frontend-v2.json中的新RC数据更新frontend-v1的pod $ kubectl rolling-update frontend-v1 -f frontend-v2.json # 使用JSON数据更新frontend-v1的pod $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - # 其他的一些滚动更新 $ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2 $ kubectl rolling-update frontend --image=image:v2 $ kubectl rolling-update frontend-v1 frontend-v2 --rollback scale命令:扩容或缩容 Deployment、ReplicaSet、Replication Controller或 Job 中Pod数量 # 将名为foo中的pod副本数设置为3。 $ kubectl scale --replicas=3 rs/foo kubectl scale deploy/nginx --replicas=30 # 将由“foo.yaml”配置文件中指定的资源对象和名称标识的Pod资源副本设为3 $ kubectl scale --replicas=3 -f foo.yaml # 如果当前副本数为2,则将其扩展至3。 $ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql # 设置多个RC中Pod副本数量 $ kubectl scale --replicas=5 rc/foo rc/bar rc/baz autoscale命令:这个比scale更加强大,也是弹性伸缩策略 ,它是根据流量的多少来自动进行扩展或者缩容。 指定Deployment、ReplicaSet或ReplicationController,并创建已经定义好资源的自动伸缩器。使用自动伸缩器可以根据需要自动增加或减少系统中部署的pod数量。 # 使用 Deployment “foo”设定,使用默认的自动伸缩策略,指定目标CPU使用率,使其Pod数量在2到10之间 $ kubectl autoscale deployment foo --min=2 --max=10 # 使用RC“foo”设定,使其Pod的数量介于1和5之间,CPU使用率维持在80% $ kubectl autoscale rc foo --max=5 --cpu-percent=80 3. 集群管理命令 certificate,cluster-info,top,cordon,uncordon,drain,taint\ncertificate命令:用于证书资源管理,授权等 # 例如,当有node节点要向master请求,那么是需要master节点授权的 $ kubectl certificate approve node-csr-81F5uBehyEyLWco5qavBsxc1GzFcZk3aFM3XW5rT3mw node-csr-Ed0kbFhc_q7qx14H3QpqLIUs0uKo036O2SnFpIheM18 cluster-info 命令:显示集群信息 $ kubectl cluster-info top 命令:用于查看资源的cpu,内存磁盘等资源的使用率 # 以前需要heapster,后替换为metrics-server $ kubectl top pod --all-namespaces cordon命令:用于标记某个节点不可调度 uncordon命令:用于标签节点可以调度 drain命令:用于在维护期间排除节点。 taint命令:用于给某个Node节点设置污点 4. 集群故障排查和调试命令 describe,logs,exec,attach,port-foward,proxy,cp,auth\ndescribe命令:显示特定资源的详细信息 # 查看my-nginx pod的详细状态 $ kubectl describe po my-nginx logs命令:用于在一个pod中打印一个容器的日志,如果pod中只有一个容器,可以省略容器名 语法:kubectl logs [-f] [-p] POD [-c CONTAINER] # 返回仅包含一个容器的pod nginx的日志快照 $ kubectl logs nginx # 返回pod ruby中已经停止的容器web-1的日志快照 $ kubectl logs -p -c ruby web-1 # 持续输出pod ruby中的容器web-1的日志 $ kubectl logs -f -c ruby web-1 # 仅输出pod nginx中最近的20条日志 $ kubectl logs --tail=20 nginx # 输出pod nginx中最近一小时内产生的所有日志 $ kubectl logs --since=1h nginx 参数选项:\n-c, --container=\u0026quot;\u0026quot;: 容器名。\r-f, --follow[=false]: 指定是否持续输出日志(实时日志)。\r--interactive[=true]: 如果为true,当需要时提示用户进行输入。默认为true。\r--limit-bytes=0: 输出日志的最大字节数。默认无限制。\r-p, --previous[=false]: 如果为true,输出pod中曾经运行过,但目前已终止的容器的日志。\r--since=0: 仅返回相对时间范围,如5s、2m或3h,之内的日志。默认返回所有日志。只能同时使用since和since-time中的一种。\r--since-time=\u0026quot;\u0026quot;: 仅返回指定时间(RFC3339格式)之后的日志。默认返回所有日志。只能同时使用since和since-time中的一种。\r--tail=-1: 要显示的最新的日志条数。默认为-1,显示所有的日志。\r--timestamps[=false]: 在日志中包含时间戳。\r exec命令:进入容器进行交互,在容器中执行命令 语法:kubectl exec POD [-c CONTAINER] \u0026ndash; COMMAND [args\u0026hellip;] 命令选项:\n-c, --container=\u0026quot;\u0026quot;: 容器名。如果未指定,使用pod中的一个容器。\r-p, --pod=\u0026quot;\u0026quot;: Pod名。\r-i, --stdin[=false]: 将控制台输入发送到容器。\r-t, --tty[=false]: 将标准输入控制台作为容器的控制台输入。\r # 进入nginx容器,执行一些命令操作 $ kubectl exec -it nginx-deployment-58d6d6ccb8-lc5fp bash attach命令:连接到一个正在运行的容器。 语法:kubectl attach POD -c CONTAINER 参数选项:\n-c, --container=\u0026quot;\u0026quot;: 容器名。如果省略,则默认选择第一个 pod。\r-i, --stdin[=false]: 将控制台输入发送到容器。\r-t, --tty[=false]: 将标准输入控制台作为容器的控制台输入。\r # 获取正在运行中的pod 123456-7890的输出,默认连接到第一个容器 $ kubectl attach 123456-7890 # 获取pod 123456-7890中ruby-container的输出 $ kubectl attach 123456-7890 -c ruby-container # 切换到终端模式,将控制台输入发送到pod 123456-7890的ruby-container的“bash”命令,并将其输出到控制台/ # 错误控制台的信息发送回客户端。 $ kubectl attach 123456-7890 -c ruby-container -i -t cp命令:拷贝文件或者目录到pod容器中 用于pod和外部的文件交换,类似于docker 的cp,就是将容器中的内容和外部的内容进行交换。 5. 其他命令 api-servions,config,help,plugin,version\napi-servions命令:打印受支持的api版本信息 # 打印当前集群支持的api版本 $ kubectl api-versions help命令:用于查看命令帮助 # 显示全部的命令帮助提示 $ kubectl --help # 具体的子命令帮助,例如 $ kubectl create --help config 命令: 用于修改kubeconfig配置文件(用于访问api,例如配置认证信息) 设置 kubectl 与哪个 Kubernetes 集群进行通信并修改配置信息。查看 使用 kubeconfig 跨集群授权访问 文档获取详情配置文件信息。 # 显示合并的 kubeconfig 配置 $ kubectl config view # 同时使用多个 kubeconfig 文件并查看合并的配置 $ KUBECONFIG=~/.kube/config:~/.kube/kubconfig2 kubectl config view # 获取 e2e 用户的密码 $ kubectl config view -o jsonpath=\u0026#39;{.users[?(@.name == \u0026#34;e2e\u0026#34;)].user.password}\u0026#39; # 展示当前所处的上下文 $ kubectl config current-context # 设置默认的上下文为 my-cluster-name $ kubectl config use-context my-cluster-name # 添加新的集群配置到 kubeconf 中,使用 basic auth 进行鉴权 $ kubectl config set-credentials kubeuser/foo.kubernetes.com --username=kubeuser --password=kubepassword # 使用特定的用户名和命名空间设置上下文。 $ kubectl config set-context gce --user=cluster-admin --namespace=foo \\ \u0026amp;\u0026amp; kubectl config use-context gce version 命令:打印客户端和服务端版本信息 # 打印客户端和服务端版本信息 $ kubectl version plugin 命令:运行一个命令行插件 Kubectl命令使用\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/k8sbuild/",
"title": "Kubernetes搭建",
"tags": [],
"description": "基于虚拟机搭建",
"content": " K8s\r\rKubernetes作为容器编排工具,简化容器管理,提升工作效率而颇受青睐。很多新手部署Kubernetes由于“科学上网”问题举步维艰,本文以实战经验详解kubeadm不用“科学上网”部署Kubernetes的最简方法。\r\r\r\rKubernetes(简称K8S)是开源的容器集群管理系统,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。它既是一款容器编排工具,也是全新的基于容器技术的分布式架构领先方案。在Docker技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等功能,提高了大规模容器集群管理的便捷性。\nK8S集群中有管理节点与工作节点两种类型。管理节点主要负责K8S集群管理,集群中各节点间的信息交互、任务调度,还负责容器、Pod、NameSpaces、PV等生命周期的管理。工作节点主要为容器和Pod提供计算资源,Pod及容器全部运行在工作节点上,工作节点通过kubelet服务与管理节点通信以管理容器的生命周期,并与集群其他节点进行通信。\n1. 虚拟机部署 从centos的镜像开始生成,网络模式选择NAT NAT模式子网地址192.168.24.1,子网掩码 255.255.255.0 ,网关地址 192.168.24.2 ,子网区段 192.168.24.3-192.168.24.254\r 配置静态IP 192.168.24.131\r192.168.24.132\r192.168.24.133\r 设置主机名hostname,管理节点设置主机名为 master 。 hostnamectl set-hostname master # 需要设置其他主机名称时,可将 master 替换为正确的主机名node1、node2即可。 编辑 /etc/hosts 文件,添加域名解析 cat \u0026lt;\u0026lt;EOF \u0026gt;\u0026gt;/etc/hosts 10.10.10.10 master 10.10.10.11 node1 10.10.10.12 node2 EOF 关闭防火墙、selinux和swap。 systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i \u0026#34;s/^SELINUX=enforcing/SELINUX=disabled/g\u0026#34; /etc/selinux/config swapoff -a sed -i \u0026#39;s/.*swap.*/#\u0026amp;/\u0026#39; /etc/fstab 配置内核参数,将桥接的IPv4流量传递到iptables的链 cat \u0026gt; /etc/sysctl.d/k8s.conf \u0026lt;\u0026lt;EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF sysctl --system 配置国内yum源 yum install -y wget mkdir /etc/yum.repos.d/bak \u0026amp;\u0026amp; mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo yum clean all \u0026amp;\u0026amp; yum makecache 配置国内Kubernetes源 cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF 配置 docker 源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo 2. 安装 安装docker yum install -y docker-ce-18.06.1.ce-3.el7 systemctl enable docker \u0026amp;\u0026amp; systemctl start docker docker -v 安装kubeadm、kubelet、kubectl yum install -y kubelet-1.14.2 kubeadm-1.14.2 kubectl-1.14.2 kubernetes-cni-0.7.5-0.x86_64 systemctl enable kubelet Kubelet负责与其他节点集群通信,并进行本节点Pod和容器生命周期的管理。Kubeadm是Kubernetes的自动化部署工具,降低了部署难度,提高效率。Kubectl是Kubernetes集群管理工具。\n3. 配置Master节点 在master进行Kubernetes集群初始化 kubeadm init --kubernetes-version=1.14.2 \\ --apiserver-advertise-address=192.168.24.131 \\ --image-repository registry.aliyuncs.com/google_containers \\ --service-cidr=10.1.0.0/16 \\ --pod-network-cidr=10.244.0.0/16 记录生成的最后部分内容 kubeadm join 192.168.24.131:6443 --token vs6pdy.2t9kvhq4babs2gu8 \\ --discovery-token-ca-cert-hash sha256:60e5986910f6d76166b747fbe2ed061c9b638aca33392807590abfd6bf818f02 配置kubectl工具 mkdir -p /root/.kube cp /etc/kubernetes/admin.conf /root/.kube/config kubectl get nodes kubectl get cs 部署flannel网络 kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/a70459be0084506e4ec919aa1c114638878db11b/Documentation/kube-flannel.yml 查看Kubelet的状态,如果发现有这种报错,则需要修改下配置 Jul 27 00:30:36 k8s-node1 kubelet[2843]: E0727 00:30:36.322259 2843 kubelet.go:2170] Container runtime network not ready: NetworkReady=false reason:NetworkPl...initialized\rJul 27 00:30:41 k8s-node1 kubelet[2843]: I0727 00:30:41.080124 2843 transport.go:132] certificate rotation detected, shutting down client connections to star...credentials\rJul 27 00:30:41 k8s-node1 kubelet[2843]: W0727 00:30:41.133004 2843 cni.go:213] Unable to update cni config: No networks found in /etc/cni/net.d\rJul 27 00:30:41 k8s-node1 kubelet[2843]: E0727 00:30:41.323559 2843 kubelet.go:2170] Container runtime network not ready: NetworkReady=false reason:NetworkPl...initialized\r....\rvi /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf ### 在文件中添加 Environment=\u0026#34;KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/ --cni-bin-dir=/opt/cni/bin\u0026#34; ### 然后重启kubelet 使master节点也成为node节点 kubectl taint nodes --all node-role.kubernetes.io/master\r4. 部署node节点 在所有node节点上进行如下操作 kubeadm join 192.168.24.131:6443 --token vs6pdy.2t9kvhq4babs2gu8 \\ --discovery-token-ca-cert-hash sha256:60e5986910f6d76166b747fbe2ed061c9b638aca33392807590abfd6bf818f02 此命令为集群初始化时(kubeadm init)返回结果中的内容。\n在master节点输入命令检查集群状态 kubectl get nodes 重点查看STATUS内容为Ready时,则说明集群状态正常\n5. 部署dashboard 创建Dashboard的yaml文件 wget https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml sed -i \u0026#39;s/k8s.gcr.io/loveone/g\u0026#39; kubernetes-dashboard.yaml sed -i \u0026#34;160a \\ \\ \\ \\ \\ \\ nodePort: 30001\u0026#34; kubernetes-dashboard.yaml sed -i \u0026#34;161a \\ \\ type:\\ NodePort\u0026#34; kubernetes-dashboard.yaml 部署Dashboard kubectl create -f kubernetes-dashboard.yaml 创建完成后,检查相关服务运行状态 kubectl get deployment kubernetes-dashboard -n kube-system kubectl get pods -n kube-system -o wide kubectl get services -n kube-system netstat -ntlp|grep 30001 查看访问Dashboard的认证令牌 kubectl describe secrets -n kube-system $(kubectl -n kube-system get secret | awk \u0026#39;/dashboard-admin/{print $1}\u0026#39;) 使用输出的token登录Dashboard 需要创建下管理员角色之类的\nvim k8s-admin.yaml apiVersion: v1 kind: ServiceAccount metadata: name: dashboard-admin namespace: kube-system --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: dashboard-admin subjects: - kind: ServiceAccount name: dashboard-admin namespace: kube-system roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io create管理员角色 kubectl create -f k8s-admin.yaml 获取dashboard管理员角色token 获取dashboard secret kubectl get secret -n kube-system 找到刚才创建的角色获取token kubectl describe secret dashboard-admin-token-jgpsv -n kube-system\r使用令牌登录 eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkYXNoYm9hcmQtYWRtaW4tdG9rZW4tdDlmcTUiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGFzaGJvYXJkLWFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiYjA4NTY4ZjQtY2ZkZi0xMWVhLTk5NzUtMDAwYzI5MGJkODc3Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmRhc2hib2FyZC1hZG1pbiJ9.GKTkeGsJgRbx0rdor70HVAeJ02bjP3Z3wL9JumaCBy1f1yGXNJxh4RqZLdTvjV11JsSDgDXmvs9I3h5aSDK8FlBwTNI7K2KYVG6683pjlU1nvTnAQa7j6O32SWlHHY3UfnEFE1oa8HFLF-VZCNeHsD9oD-GtBWyh2THQJm78ZfjCNxJuf8Bu_gff1Rc0UcXgWNb70s002XfomRoFkoB5dJkRwxe1pIZy3diTvn7FxmXFA0SAInVUhsNdSqyk9s4Ov1Sa2RFTFNx6m0USujl8po6rEp2Jwsqt6gBIPF04KcNgm5VFMtc1BNw5gxnanBTd1AO3y7tXaDrtMtvh42Azcw\r解决dashboard在chrome里面无法打开的问题 mkdir key \u0026amp;\u0026amp; cd key #生成证书 openssl genrsa -out dashboard.key 2048 openssl req -new -out dashboard.csr -key dashboard.key -subj \u0026#39;/CN=192.168.246.200\u0026#39; openssl x509 -req -in dashboard.csr -signkey dashboard.key -out dashboard.crt #删除原有的证书secret kubectl delete secret kubernetes-dashboard-certs -n kube-system #创建新的证书secret kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kube-system #查看pod kubectl get pod -n kube-system #重启pod kubectl delete pod \u0026lt;pod name\u0026gt; -n kube-system Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/c/makefile/",
"title": "Makefile",
"tags": [],
"description": "C/C++",
"content": " Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/august/mysql/",
"title": "MySQL 30个基本问题",
"tags": [],
"description": "Article seek",
"content": " 1. 关系型数据库 关系型数据库,是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库。用户通过查询来检索数据库中的数据,而查询是一个用于限定数据库中某些区域的执行代码。\n简单来说,关系模式就是二维表格模型。\n2. 关系型数据库优势 关系型数据库的优势:\n 易于理解 关系型二维表的结构非常贴近现实世界,二维表格,容易理解。 支持复杂查询 可以用 SQL 语句方便的在一个表以及多个表之间做非常复杂的数据查询。 支持事务 可靠的处理事务并且保持事务的完整性,使得对于安全性能很高的数据访问要求得以实现。 3. SQL 与 MySQL 结构化查询语言 (Structured Query Language) 简称SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。 MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,广泛地应用在 Internet 上的中小型网站中。\n4. MySQL 与 MariaDB MySQL 最初由瑞典 MySQL AB 公司开发,MySQL 的创始人是乌尔夫·米卡埃尔·维德纽斯,常用昵称蒙提(Monty)。\n在被甲骨文公司收购后,现在属于甲骨文公司(Oracle) 旗下产品。Oracle 大幅调涨MySQL商业版的售价,因此导致自由软件社区们对于Oracle是否还会持续支持MySQL社区版有所隐忧。\nMySQL 的创始人就是之前那个叫 Monty 的大佬以 MySQL为基础成立分支计划 MariaDB\n两者基本完全相同\n5. MySQL 数据类型 MySQL 数据类型非常丰富,常用类型简单介绍如下:\n 整数类型:BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT\r浮点数类型:FLOAT、DOUBLE、DECIMAL\r字符串类型:CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB\r日期类型:Date、DateTime、TimeStamp、Time、Year\r其他数据类型:BINARY、VARBINARY、ENUM、SET...\r 6. int(10) 和 bigint(10)能存储的数据大小一样吗? 不一样,具体原因如下:\nint 能存储四字节有符号整数。 bigint 能存储八字节有符号整数。 所以能存储的数据大小不一样,其中的数字 10 代表的只是数据的显示宽度。[^13]\n显示宽度指明Mysql最大可能显示的数字个数,数值的位数小于指定的宽度时数字左边会用空格填充,空格不容易看出。 如果插入了大于显示宽度的值,只要该值不超过该类型的取值范围,数值依然可以插入且能够显示出来。 建表的时候指定 zerofill 选项,则不足显示宽度的部分用 0 填充,如果是 1 会显示成 0000000001。 如果没指定显示宽度, bigint 默认宽度是 20 ,int默认宽度 11\n7. 存储引擎 查看数据库表当前支持的引擎,可以用下面查询语句查看:\nshow table status from \u0026#39;your_db_name\u0026#39; where name=\u0026#39;your_table_name\u0026#39;; # 查询结果表中的 Engine 字段指示存储引擎类型。 常用的存储引擎有 InnoDB 存储引擎和 MyISAM 存储引擎,InnoDB 是 MySQL 的默认事务引擎。\n8. InnoDB InnoDB 是 MySQL的默认「事务引擎」,被设置用来处理大量短期(short-lived)事务,短期事务大部分情况是正常提交的,很少会回滚\nInnoDB存储引擎采用多版本并发控制(MVCC,MultiVersion Concurrency Control)来支持高并发。并且实现了四个标准的隔离级别,通过间隙锁next-key locking策略防止幻读的出现。\n引擎的表基于聚簇索引建立,聚簇索引对主键查询有很高的性能。不过它的二级索引secondary index非主键索引中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。因此,若表上的索引较多的话,主键应当尽可能的小。另外InnoDB的存储格式是平台独立。\nInnoDB做了很多优化,比如:磁盘读取数据方式采用的可预测性预读、自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。\nInnoDB通过一些机制和工具支持真正的热备份,MySQL 的其他存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。\n9. InnoDB 引擎的四大特性是什么? 插入缓冲(Insert buffer) Insert Buffer 用于非聚集索引的插入和更新操作。先判断插入的非聚集索引是否在缓存池中,如果在则直接插入,否则插入到 Insert Buffer 对象里。再以一定的频率进行 Insert Buffer 和辅助索引叶子节点的 merge 操作,将多次插入合并到一个操作中,提高对非聚集索引的插入性能。\n二次写 (Double write) Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为 2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过 memcpy 函数将脏页先复制到内存中的该区域,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。\n自适应哈希索引 (Adaptive Hash Index) InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。索引通过缓存池的 B+ 树页构造而来,因此建立速度很快,InnoDB存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,所以叫做自适应哈希索引。\n缓存池 为了提高数据库的性能,引入缓存池的概念,通过参数 innodb_buffer_pool_size 可以设置缓存池的大小,参数 innodb_buffer_pool_instances 可以设置缓存池的实例个数。缓存池主要用于存储以下内容:\n缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲 (insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息 (lock info)和数据字典信息 (data dictionary)。\n10. MyISAM存储引擎 MyISAM 是 MySQL 5.1 及之前的版本的默认的存储引擎。MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM 不「支持事务和行级锁」,对于只读数据,或者表比较小、可以容忍修复操作,依然可以使用它。\nMyISAM「不支持行级锁而是对整张表加锁」。读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入。\nMyISAM 表可以手工或者自动执行检查和修复操作。但是和事务恢复以及崩溃恢复不同,可能导致一些「数据丢失」,而且修复操作是非常慢的。\n对于 MyISAM 表,即使是BLOB和TEXT等长字段,也可以基于其前 500 个字符创建索引,MyISAM 也支持「全文索引」,这是一种基于分词创建的索引,可以支持复杂的查询。\n如果指定了DELAY_KEY_WRITE选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成「索引损坏」,需要执行修复操作。\n11. MyISAM 与 InnoDB 存储引擎 5 大区别 InnoDB支持事务,而MyISAM不支持事务\rInnoDB支持行级锁,而MyISAM支持表级锁\rInnoDB支持MVCC, 而MyISAM不支持\rInnoDB支持外键,而MyISAM不支持\rInnoDB不支持全文索引,而MyISAM支持\r 12. SELECT COUNT(*) 在哪个引擎执行更快 SELECT COUNT(*) 常用于统计表的总行数,在 MyISAM 存储引擎中执行更快,前提是不能加有任何WHERE条件。\n这是因为 MyISAM 对于表的行数做了优化,内部用一个变量存储了表的行数,如果查询条件没有 WHERE 条件则是查询表中一共有多少条数据,MyISAM 可以迅速返回结果,如果加 WHERE 条件就不行。\nInnoDB 的表也有一个存储了表行数的变量,但这个值是一个估计值,所以并没有太大实际意义。\n14. 数据库设计三范式 1范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;(只要是关系型数据库都满足1NF)\n2范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;\n3范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。没有冗余的数据库设计可以做到\n但是,没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据,具体做法是:在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑,降低范式就是增加字段,允许冗余。\n15 数据库删除操作中的 delete、drop、 truncate 区别在哪? 当不再需要该表时可以用 drop 来删除表; 当仍要保留该表,但要删除所有记录时, 用 truncate来删除表中记录。 当要删除部分记录时(一般来说有 WHERE 子句约束) 用 delete来删除表中部分记录。\n16. 视图 视图是虚拟表,并不储存数据,只包含定义时的语句的动态数据。\nCREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] [DEFINER = user] [SQL SECURITY { DEFINER | INVOKER }] VIEW view_name [(column_list)] AS select_statement [WITH [CASCADED | LOCAL] CHECK OPTION] 17. 存储过程 delimiter 分隔符 create procedure|proc proc_name() begin sql语句 end 分隔符 delimiter ; --还原分隔符,为了不影响后面的语句的使用 --默认的分隔符是;但是为了能在整个存储过程中重用,因此一般需要自定义分隔符(除\\外) show procedure status like \u0026#34;\u0026#34;; --查询存储过程,可以不适用like进行过滤 drop procedure if exists;--删除存储过程 存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。\n相同点\n存储过程和函数都是为了可重复的执行操作数据库的 SQL 语句的集合。\r存储过程和函数都是一次编译后缓存起来,下次使用就直接命中已经编译好的 sql 语句,减少网络交互提高了效率。\r 不同点\n标识符不同,函数的标识符是 function,存储过程是 procedure。\r函数返回单个值或者表对象,而存储过程没有返回值,但是可以通过OUT参数返回多个值。\r函数限制比较多,比如不能用临时表,只能用表变量,一些函数都不可用等,而存储过程的限制相对就比较少。\r一般来说,存储过程实现的功能要复杂一点,而函数的实现的功能针对性比较强\r函数的参数只能是 IN 类型,存储过程的参数可以是IN OUT INOUT三种类型。\r存储函数使用 select 调用,存储过程需要使用 call 调用。\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/natip/",
"title": "NAT网关配置虚拟机静态IP",
"tags": [],
"description": "Linux",
"content": " 1. 编辑虚拟机NAT网络配置 选择NAT模式,编辑Nat模式下子网地址,网关以及子网掩码的配置(关闭DHCP)\n2. 新建虚拟机时选择NAT网络模式 3. 编辑虚拟机网络配置文件 vi /etc/sysconfig/network-scripts/ifcfg-ens33 #先修改IP为静态 BOOTPROTO=\u0026#34;static\u0026#34;# 默认为DHCP # 再在末尾写上这几句 IPADDR=192.168.24.131 #这里对应上面ipconfig输出的网段192.168.24.3 ~ 192.168.24.254范围随便选一个即可,但不要跟主机相同就行 NETMASK=255.255.255.0 GATEWAY=192.168.24.2 #这里对应上面ipconfig输出的默认网关 然后重启网络服务\nsystemctl restart network 接下来虚拟机就可以和同网段的主机互相通信了\nsystemctl stop firewalld.service Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/powershell/",
"title": "Powershell",
"tags": [],
"description": "Powershell",
"content": " 1. 查看笔记本电脑电池信息 powercfg /batteryreport /output eport.html Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/script/",
"title": "Script命令",
"tags": [],
"description": "Linux",
"content": " 1. script 初级用法 [root@master script]# script -h Usage: script [options] [file] Options: -a, --append append the output # 追加输出 -c, --command \u0026lt;command\u0026gt; run command rather than interactive shell # 只记录执行命令的这一条的记录 -e, --return return exit code of the child process # -f, --flush run flush after each write # 每次写入都刷新 --force use output file even when it is a link -q, --quiet be quiet # 后台执行 -t, --timing[=\u0026lt;file\u0026gt;] output timing data to stderr (or to FILE) # 记录时序数据,从stderr -V, --version output version information and exit # 版本 -h, --help display this help and exit # 帮助 执行记录\n[liuzeng01@master ~]$ script script.txt Script started, file is script.txt [liuzeng01@master ~]$ ls Desktop Documents Downloads Music Pictures Public script.txt Templates Videos [liuzeng01@master ~]$ ps PID TTY TIME CMD 89603 pts/2 00:00:00 bash 89713 pts/2 00:00:00 ps [liuzeng01@master ~]$ time real 0m0.000s user 0m0.000s sys 0m0.000s [liuzeng01@master ~]$ exit exit Script done, file is script.txt 查看输出文件\n[liuzeng01@master ~]$ cat script.txt Script started on Thu 30 Jul 2020 03:49:27 AM PDT [liuzeng01@master ~]$ ls Desktop Documents Downloads Music Pictures Public script.txt Templates Videos [liuzeng01@master ~]$ ps PID TTY TIME CMD 89603 pts/2 00:00:00 bash 89713 pts/2 00:00:00 ps [liuzeng01@master ~]$ time real 0m0.000s user 0m0.000s sys 0m0.000s [liuzeng01@master ~]$ exit exit Script done on Thu 30 Jul 2020 03:49:50 AM PDT 高级用法 服务器安全审计 直接操作线上的服务器有很大隐患,所以一般都是通过登录跳板机,然后连接线上服务器,跳板机可以访问控制和安全审计,查看记录每个人对线上服务器的操作 用户家目录下,修改环境变量,使得用户登录就会触发录像\nvi ~/.profile script -t -f -q 2\u0026gt;/wow/$USER-$UID-`date +%Y%m%d%H%M%S`.time -a /wow/$USER-$UID-`date +%Y%m%d%H%M%S`.his 这样搞完后,发现有一个问题是每次退出,我习惯Ctrl+D,然后按第一遍停止录像,第二遍才能用户退出,如何解决在后面添加\nif [ \u0026#34;$SHLVL\u0026#34; = 1 ]; then exit fi 这样就可以一遍Ctrl+D停止录像(exit)和退出用户\n2. scriptplay 基本用法 [root@master script]# scriptreplay -h Usage: scriptreplay [-t] timingfile [typescript] [divisor] Options: -t, --timing \u0026lt;file\u0026gt; script timing output file # 记录的时序文件 -s, --typescript \u0026lt;file\u0026gt; script terminal session output file -d, --divisor \u0026lt;num\u0026gt; speed up or slow down execution with time divisor # 加速或者减速 -V, --version output version information and exit # 版本信息 -h, --help display this help and exit # 帮助 安装方法\nyum install util-linux 使用方法\nscript -t 2\u0026gt; test.time -a test.txt scriptreplay test.time test.txt 高级用法 vi ~/.profile script -t -f -q 2\u0026gt;$USER.time -a $USER.his # 记录用户登陆后的操作,可以回放显示 实时共享 第一个命令框 mkfifo scriptfifo cat scriptfifo 其他命令框 script -f scriptfifo 可以实现实时同步演示 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/ssh/",
"title": "SSH免密登陆",
"tags": [],
"description": "SSH",
"content": " 1. linux命令行配置方法 ssh-keygen ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.24.132 # 如果给不同主机的不同用户分发的话,加上用户的名字 ssh-copy-id -i ~/.ssh/id_rsa.pub liuzeng@192.168.24.132 2. windows配置免密登陆 # 在CMD里面运行ssh-keygen # 然后把id_rsa里面的内容复制到登陆主机用户的 authorized_keys 的后面 # 接着ssh登陆一次就行了 3. id_rsa权限问题 # linux sudo chmod 600 ~/.ssh/id_rsa # windows10 # 对id_rsa文件:右击-属性-安全-高级,首先,点击窗口左下角的“禁用继承”,然后删除“权限条目”里自己以外的人,接着重新登陆就行了 --- Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/gocookbook/string/",
"title": "String of Go",
"tags": [],
"description": "Cook Book of Golang",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/tag/",
"title": "Tag",
"tags": [],
"description": "Golang",
"content": " 1. tag 结构体字段上还可以额外添加属性,用反引号(Esc键下面的那个键)包含的字符串,称之为 Tag,也就是标签\nTag 由反引号包含,由一对或几对的键值对组成,通过空格来分割键值。格式如下\n`key01:\u0026#34;value01\u0026#34; key02:\u0026#34;value02\u0026#34; key03:\u0026#34;value03\u0026#34;` 2. 利用反射获取结构体字段 获取 Tag 可以分为三个步骤: 获取字段 field 获取标签 tag 获取键值对 key:value\nfield := reflect.TypeOf(obj).FieldByName(\u0026#34;Name\u0026#34;) //field := reflect.ValueOf(obj).Type().Field(i) // i 表示第几个字段 //field := reflect.ValueOf(\u0026amp;obj).Elem().Type().Field(i) // i 表示第几个字段 // 获取 Tag tag := field.Tag // 获取键值对 labelValue := tag.Get(\u0026#34;label\u0026#34;) labelValue,ok := tag.Lookup(\u0026#34;label\u0026#34;) 示例:\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;reflect\u0026#34; ) type person struct { Name string `label:\u0026#34;my name is\u0026#34;` Age int `label:\u0026#34;my age is \u0026#34;\u0026#39;` Addr string `label:\u0026#34;my address is \u0026#34; default:\u0026#34;unknown\u0026#34;` } func main() { p := person{ Name: \u0026#34;liuzeng\u0026#34;, Age: 25, } field ,_:= reflect.TypeOf(p).FieldByName(\u0026#34;Addr\u0026#34;) tag := field.Tag labelValue := tag.Get(\u0026#34;default\u0026#34;) fmt.Println(labelValue) } 3. 利用tag与Json package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) type Person struct { Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` Addr string `json:\u0026#34;addr,omitempty\u0026#34;` } func main() { p1 := Person{ Name: \u0026#34;Jack\u0026#34;, Age: 22, } data1, err := json.Marshal(p1) if err != nil { panic(err) } // p1 没有 Addr,就不会打印了 fmt.Printf(\u0026#34;%s\\n\u0026#34;, data1) // ================ p2 := Person{ Name: \u0026#34;Jack\u0026#34;, Age: 22, Addr: \u0026#34;China\u0026#34;, } data2, err := json.Marshal(p2) if err != nil { panic(err) } // p2 则会打印所有 fmt.Printf(\u0026#34;%s\\n\u0026#34;, data2) } Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/tran/",
"title": "tran",
"tags": [],
"description": "Golang",
"content": " Tran工具\r\r 使用了百度翻译的API,进行单词或者短句的翻译 主要是为了方便自己平时写一些英文文档时,词穷的情况。所以设计成了命令行程序,随时使用。 \r\r\r 1. 入口模块 利用了cobra来进行设计\nmain.go package main import \u0026#34;tran/cmd\u0026#34; func main() { cmd.Execute() } //执行入口 cmd/root.go package cmd import ( \u0026#34;GoStudy/week17/tran/internal\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; \u0026#34;os\u0026#34; homedir \u0026#34;github.com/mitchellh/go-homedir\u0026#34; \u0026#34;github.com/spf13/viper\u0026#34; ) var cfgFile string var rootCmd = \u0026amp;cobra.Command{ Use: \u0026#34;tran\u0026#34;, Short: \u0026#34;The applicaiton to help you translate the word in the terminal\u0026#34;, Long: `The Manual method : Input your query string ,and input your target language code, E.g. Input : hello,zh And the output : hello --\u0026gt; 你好 . The support language As follow as: 中文\tzh 英语\ten 粤语\tyue 文言文\twyw 日语\tjp 韩语\tkor 法语\tfra 西班牙语\tspa 泰语\tth 阿拉伯语\tara 俄语\tru 葡萄牙语\tpt 德语\tde 意大利语\tit 希腊语\tel 荷兰语\tnl 波兰语\tpl 保加利亚语\tbul 爱沙尼亚语\test 丹麦语\tdan 芬兰语 fin 捷克语\tcs 罗马尼亚语\trom 斯洛文尼亚语\tslo 瑞典语\tswe 匈牙利语\thu 繁体中文\tcht 越南语\tvie `, Run: func(cmd *cobra.Command, args []string) { if len(querystr) == 0 { cmd.Help() return } internal.TranString(querystr, tostr) }, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } var querystr string var tostr string func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(\u0026amp;cfgFile, \u0026#34;config\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;config file (default is $HOME/.tran.yaml)\u0026#34;) rootCmd.Flags().BoolP(\u0026#34;toggle\u0026#34;, \u0026#34;t\u0026#34;, false, \u0026#34;Help message for toggle\u0026#34;) rootCmd.Flags().StringVarP(\u0026amp;querystr, \u0026#34;query\u0026#34;, \u0026#34;q\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;Please input your query string :\u0026#34;) rootCmd.Flags().StringVarP(\u0026amp;tostr, \u0026#34;dst\u0026#34;, \u0026#34;d\u0026#34;, \u0026#34;en\u0026#34;, \u0026#34;Please input your destination languange to transform :\u0026#34;) } //这个文件也是命令行的入口,介绍主要用法,以及根据输入调用函数 subcommand package cmd import ( \u0026#34;GoStudy/week17/tran/internal\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/spf13/cobra\u0026#34; ) var m bool var i bool // moreCmd represents the more command var moreCmd = \u0026amp;cobra.Command{ Use: \u0026#34;more\u0026#34;, Short: \u0026#34;Sub Command \u0026#34;, Long: `This sub command is help you to translate in another way.`, Run: func(cmd *cobra.Command, args []string) { if m { //internal.MoreTasks() \tfmt.Println(\u0026#34;Wait for support\u0026#34;) } else if i { internal.Interactive() } else { fmt.Println(\u0026#34;Please select the function pattern !\u0026#34;) } }, } func init() { rootCmd.AddCommand(moreCmd) moreCmd.Flags().BoolVarP(\u0026amp;m, \u0026#34;tasks\u0026#34;, \u0026#34;m\u0026#34;, false, \u0026#34;Use this command to translate a lot in one time \u0026#34;) moreCmd.Flags().BoolVarP(\u0026amp;i, \u0026#34;interactive\u0026#34;, \u0026#34;i\u0026#34;, false, \u0026#34;use this command to translate in a interactive line.\u0026#34;) } //子命令的设计,一个是交互命令行模式下进行翻译,这个我比较中意,比较方便,另一个是多任务翻译,开发的比较失败,回头再继续吧 2. 逻辑模块 构建请求url func buildurl(querystr string, tostr string) string { md5Ctx := md5.New() rand.Seed(time.Now().Unix()) randnum := strconv.Itoa(rand.Int()) signstr := appid + querystr + randnum + key md5Ctx.Write([]byte(signstr)) cipherStr := md5Ctx.Sum(nil) signresult := hex.EncodeToString(cipherStr) url := fmt.Sprintf(\u0026#34;%s?q=%s\u0026amp;from=auto\u0026amp;to=%s\u0026amp;appid=%s\u0026amp;salt=%s\u0026amp;sign=%s\u0026#34;, baseurl, querystr, tostr, appid, randnum, signresult, ) return url } //根据百度翻译API对接入的url的要求,进行了加密和拼接的步骤。中间用到了md5对称加密 结构体和全局变量 package internal type Tran struct { From string `json:\u0026#34;from\u0026#34;` To string `json:\u0026#34;to\u0026#34;` TransResult []TranResult `json:\u0026#34;trans_result\u0026#34;` } type TranResult struct { Src string `json:\u0026#34;src\u0026#34;` Dst string `json:\u0026#34;dst\u0026#34;` } var appid string var key string var baseurl string //Tran 结构体用来解构百度翻译的返回Json //appid和key是百度翻译的认证应用ID //baseurl是百度翻译API的网址 进行get请求和response的解构 func BaiduRequest(querystr string, tostr string) *Tran { url := buildurl(querystr, tostr) resp, err := http.Get(url) if err != nil { fmt.Println(err) fmt.Println(\u0026#34;Please retry !\u0026#34;) return nil } defer resp.Body.Close() res, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) fmt.Println(\u0026#34;Please retry !\u0026#34;) return nil } var tranresult = new(Tran) json.Unmarshal(res, \u0026amp;tranresult) return tranresult } //中间要注意的问题:Json解码时要注意对应结构体的字段和类型相同 //注意close response的body,不然可能内存泄漏 结果输出 func TranString(querystr string, tostr string) { tran := BaiduRequest(querystr, tostr) for _, v := range tran.TransResult { fmt.Println(v.Src, \u0026#34;--\u0026gt;\u0026#34;, v.Dst) } } //这个没什么可解释的 3. 子命令模块 interactive 这个子模块其实也是调前面的函数,做了下其他的改造\nfunc Interactive() { fmt.Println(\u0026#34;Enter the Interactive pattern!\u0026#34;) fmt.Println(`Tips: 1. Input your query string ,and input your dst language after \u0026#34;,\u0026#34;; 2. If you dont input the dst language ,the default language is English. 3. If you want to input two or more word in the command line , please Capitalize the initial of word(especially English). 4. After all done, input the \u0026#34;q\u0026#34; or \u0026#34;quit\u0026#34; to exit. `) fmt.Print(\u0026#34;Input Here : \u0026#34;) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { var transtruct []string text := strings.Replace(scanner.Text(), \u0026#34;,\u0026#34;, \u0026#34;,\u0026#34;, -1) text = strings.Replace(text, \u0026#34; \u0026#34;, \u0026#34;\u0026#34;, -1) res := strings.Split(text, \u0026#34;,\u0026#34;) for _, v := range res { if v != \u0026#34;\u0026#34; { transtruct = append(transtruct, v) } } if len(transtruct) \u0026gt; 2 { fmt.Println(\u0026#34;Input error !Please Retry !\u0026#34;) fmt.Print(\u0026#34;Input Here : \u0026#34;) continue } else if len(transtruct) == 1 { if transtruct[0] == \u0026#34;q\u0026#34; || transtruct[0] == \u0026#34;quit\u0026#34; { return } else { TranString(transtruct[0], \u0026#34;en\u0026#34;) fmt.Print(\u0026#34;Input Here : \u0026#34;) continue } } else if len(transtruct) == 2 { TranString(transtruct[0], transtruct[1]) fmt.Print(\u0026#34;Input Here : \u0026#34;) continue } else { fmt.Print(\u0026#34;Input Here : \u0026#34;) continue } } } //主要就是循环读取那边,在结束翻译后,重新进行输入的读取 tasks 本来设计的目标是多任务翻译。比如从文件中读取,但是还没做好,有一些问题。\nwait for support 4. 效果 tran C:\\Users\\15996\u0026gt;tran The Manual method : Input your query string ,and input your target language code, E.g. Input : hello,zh And the output : hello --\u0026gt; 你好 . The support language As follow as: 中文 zh 英语 en 粤语 yue 文言文 wyw 日语 jp 韩语 kor 法语 fra 西班牙语 spa 泰语 th 阿拉伯语 ara 俄语 ru 葡萄牙语 pt 德语 de 意大利语 it 希腊语 el 荷兰语 nl 波兰语 pl 保加利亚语 bul 爱沙尼亚语 est 丹麦语 dan 芬兰语 fin 捷克语 cs 罗马尼亚语 rom 斯洛文尼亚语 slo 瑞典语 swe 匈牙利语 hu 繁体中文 cht 越南语 vie Usage: tran [flags] tran [command] Available Commands: help Help about any command more Sub Command Flags: --config string config file (default is $HOME/.tran.yaml) -d, --dst string Please input your destination languange to transform : (default \u0026#34;en\u0026#34;) -h, --help help for tran -q, --query string Please input your query string : -t, --toggle Help message for toggle Use \u0026#34;tran [command] --help\u0026#34; for more information about a command. tran -q -d C:\\Users\\15996\u0026gt;tran -q hello -d ru hello --\u0026gt; Здравствыйте tran more -i C:\\Users\\15996\u0026gt;tran more -i Enter the Interactive pattern! Tips: 1. Input your query string ,and input your dst language after \u0026#34;,\u0026#34;; 2. If you dont input the dst language ,the default language is English. 3. If you want to input two or more word in the command line , please Capitalize the initial of word(especially English). 4. After all done, input the \u0026#34;q\u0026#34; or \u0026#34;quit\u0026#34; to exit. Input Here : Input Here : Input Here : hello,ru hello --\u0026gt; Здравствыйте Input Here : hello,wyw hello --\u0026gt; 君 Input Here : hello,jp hello --\u0026gt; こんにちは Input Here : hello,cht hello --\u0026gt; 你好 Input Here : hello,vie hello --\u0026gt; Alô. Input Here : Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/cmd/npm/",
"title": "vue + docker",
"tags": [],
"description": "Docker",
"content": " 1. NPM 环境搭建 1.root 登录linux 2.cd /usr/loacl/node/ 没有目录就自己创建一个 3.wget https://npm.taobao.org/mirrors/node/v4.4.7/node-v4.4.7-linux-x64.tar.gz 4.tar -zxvf node-v4.4.7-linux-x64.tar.gz 5.rm -rf node-v4.4.7-linux-x64.tar.gz 6.ln -s /usr/local/node/node-v4.4.7-linux-x64/bin/npm /usr/local/bin/npm 7.ln -s /usr/local/node/node-v4.4.7-linux-x64/bin/node /usr/local/bin/node 8.npm -v 2. Vue项目初始化 1. 安装vue npm install -g vue 2. 安装vue脚手架 npm install -g @vue/cli 3. 直接使用 Vue 脚手架构建项目 vue create docker-demo 4. 安装下yarn yum install yarn/npm install -g yarn 5. 启动下项目 yarn serve 6. 项目打包 yarn build 3. 制作docker镜像 1. cd docker-demo \u0026amp;\u0026amp; touch Dockerfile 2. docker pull nginx 3. touch default.conf 4. vi default.conf ## 写入 server { listen 80; server_name localhost; #charset koi8-r; access_log /var/log/nginx/host.access.log main; error_log /var/log/nginx/error.log error; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } 5. vi Dockerfile # 写入 FROM nginx COPY dist/ /usr/share/nginx/html/ COPY default.conf /etc/nginx/conf.d/default.conf 6. 制作镜像 docker build -t vue-docker-demo . -t 参数给镜像命名 vue-docker-demo . 是基于当前目录的 Dockerfile 来构建镜像 7. docker image ls | grep vue-docker-demo 8. 运行容器 docker run -d -p 3000:80 --name docker-vue vue-docker-demo 4. 将项目跑到kubernetes集群上 1. 推送镜像 docker login --username=liuzeng_icloud registry.cn-hangzhou.aliyuncs.com docker tag vue-docker-demo registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:v0.1 docker push registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:v0.1 2. 创建deployment文件 vi vueapp.yml # 输入下面的内容 apiVersion: apps/v1 kind: Deployment metadata: name: vue-app spec: replicas: 1 selector: matchLabels: app: vue-app template: metadata: labels: app: vue-app spec: containers: - name: vue-app-container image: registry.cn-hangzhou.aliyuncs.com/liuzeng01/myrepo:v0.1 resources: limits: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;500m\u0026#34; ports: - containerPort: 80 --- kind: Service apiVersion: v1 metadata: name: vue-app-service spec: type: NodePort ports: - name: http port: 80 nodePort: 30002 selector: app: vue-app # 分为两个部分,创建deployment对象,以及创建服务对象,用来给外面访问 3. 部署和删除服务的命令 kubectl create -f vueapp.yml kubectl delete -f vueapp.yml 4. 获取服务状态 kubectl get pods kubectl get svc kubectl get pods -A netstat -an|grep 30002 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/dockerfile/godockerfile/",
"title": "分阶段构建减小go镜像体积",
"tags": [],
"description": "Golang,docker",
"content": " 分阶段构建\r\r主要思路 第一阶段 使用golang镜像构建出可执行文件 第二阶段 使用busybox:glibc小镜像作为基础镜像\r\r\r\r镜像文件 第一阶段 FROMgolang AS my_build_stageCOPY hello.go .RUN go build hello.go第二阶段 FROMbusybox:glibcCOPY --from=my_build_stage hello .// COPY --from=0 hello . 与COPY --from=my_build_stage hello .等效 0表示第一阶段CMD [\u0026#34;./hello\u0026#34;] Go项目应用的Dockerfile通常大概类似这样,但是每个项目的细节可能有所不同。\nFROM golang:alpine指定了开始阶段的基础映像(其中包含Go工具和库,用于构建程序),AS build是给这个阶段取名为build。 golang:alpine指定了Go基础映像的alpine版本, alpine是专门为容器设计的小型Linux发行版。这个Dockerfile中使用了两次FROM指令,第二条FROM scratch行,它告诉Docker从一个全新的,完全空的容器镜像重新开始,然后将上个阶段编译好的程序复制到其中。这个才是我们随后将用于运行的Go应用程序的容器镜像。\nscratch镜像是Docker项目预定义的最小的镜像。 Docker用于Go程序的多阶段构建很常见,使用scratch镜像可以节省大量空间,因为我们实际上不需要Go工具或其他任何东西来运行我们的编译好的程序,这可能也是Go在容器时代的一个优势吧。\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/scanner/",
"title": "循环读取输入",
"tags": [],
"description": "Golang",
"content": " 循环读取用户输入 package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { scanner() } func scanner() { fmt.Printf(\u0026#34;----\u0026gt;\u0026#34;) scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { fmt.Println(scanner.Text()) fmt.Printf(\u0026#34;----\u0026gt;\u0026#34;) } } "
},
{
"uri": "https://liuzeng01.github.io/booknote/",
"title": "BookNote",
"tags": [],
"description": "Note",
"content": " algorithm \r\rSRE \r\rDesign Pattern \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/c/",
"title": "C/C++",
"tags": [],
"description": "C/C++",
"content": " Makefile \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/python/",
"title": "python",
"tags": [],
"description": "python",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/gocookbook/",
"title": "Cook Book",
"tags": [],
"description": "Golang",
"content": " String of Go \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/javascript/",
"title": "JavaScript",
"tags": [],
"description": "JavaScript",
"content": " August \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/tips/proxy/",
"title": "kube proxy",
"tags": [],
"description": "Kubernetes",
"content": " 1. 利用kubectl proxy kubectl proxy curl -v 127.0.0.1:8001 2. 在容器中访问 # 在外部允许匿名访问api kubectl create clusterrolebinding test:anonymous --clusterrole=cluster-admin --user=system:anonymous # 容器内需要有curl curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes # 这个cacert证书指定的是ca证书的位置 ,后面的kubernetes指定的是k8s集群中默认的服务,在pod里面可以通过环境变量或者dns直接访问他 export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt # 可以直接指定ca在环境变量里面,这样容器就可以直接访问api了 3. 利用sidecar container访问 这个其实就是启动了一个类似于proxy的代理容器,和第一个方法是非常相同的 首先把系统的kubectl拷贝到当前目录下,写个简单的启动脚本和dockerfile\n#!/bin/sh API_SERVER=\u0026#34;https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT\u0026#34; CA_CRT=\u0026#34;/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\u0026#34; TOKEN=\u0026#34;$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; /kubectl proxy --server=\u0026#34;$API_SERVER\u0026#34; --certificate-authority=\u0026#34;$CA_CRT\u0026#34; --token=\u0026#34;$TOKEN\u0026#34; --accept-paths=\u0026#39;^.*\u0026#39; dockerfile\nFROMalpineADD . .RUN chmod +x proxy.sh \u0026amp;\u0026amp; chmod +x kubectlENTRYPOINT /proxy.sh然后 docker build docker push就行了 编一个pod的yaml\napiVersion: v1 kind: Pod metadata: name: curl-with-ambassador spec: containers: - name: main image: nginx command: [\u0026#34;sleep\u0026#34;, \u0026#34;9999999\u0026#34;] - name: proxy image: 192.168.24.131:5000/public/kube-proxy 然后在容器内访问127.0.0.1:8001就行了,可以访问kubernetes的API组合\n4. 利用curl访问 curl -v https://192.168.24.131:6443 --cacret /etc/kubernetes/pki/ca.crt # 这个就是ca证书的位置,访问的网址是apiserver对外暴露的地址 5. swagger-ui 这个暂时还没有搞定\n 参考文章 调用Kubernetes API操作Kubernetes Erik_Xu 2018-04-15 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/openstack/",
"title": "OpenStack",
"tags": [],
"description": "OpenStack",
"content": " OpenStack部署 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/openstack/deploy/",
"title": "OpenStack部署",
"tags": [],
"description": "OpenStack",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/supervisor/",
"title": "Supervisor",
"tags": [],
"description": "Linux",
"content": " 进程管理工具\r\rSupervisor是一个进程管理工具,当进程中断的时候Supervisor能自动重新启动它。可以运行在各种类unix的机器上,supervisor就是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。\r\r\r\r1. 基本介绍 supervisord\n运行 Supervisor 时会启动一个进程 supervisord,它负责启动所管理的进程,并将所管理的进程作为自己的子进程来启动,而且可以在所管理的进程出现崩溃时自动重启。\n supervisorctl\n是命令行管理工具,可以用来执行 stop、start、restart 等命令,来对这些子进程进行管理。\nsupervisor是所有进程的父进程,管理着启动的子进展,supervisor以子进程的PID来管理子进程,当子进程异常退出时supervisor可以收到相应的信号量。\n 2. 安装方法及配置 1. 安装 yum instal supervisor # 查看安装是否成功 echo_supervisord_conf 2. 配置 # 创建目录,初始化配置文件 mkdir /usr/supervisor echo_supervisord_conf \u0026gt; /usr/supervisor/supervisord.conf # 新增配置信息文件夹 mkdir /usr/supervisor/supervisord.d/ # 修改系统配置文件 vim /usr/supervisor/supervisord.conf # 添加配置文件路径 [include] files = /usr/supervisor/supervisord.d/*.conf files = /usr/supervisor/supervisord.d/*.ini # [inet_http_server] port=127.0.0.1:9001 ;IP按需配置 username=user password=123 # 启动supervisord supervisord -c /usr/supervisor/supervisord.conf # 重启supervisor supervisorctl -c /usr/supervisor/supervisord.conf 3. 添加配置的监控进程 [program:test_one]\rcommand=java -jar /data/smallvideo/supervisor/taskApp-exec.jar TaskTestOne ; 被监控的进程路径\rpriority=1 ; 数字越高,优先级越高\rnumprocs=1 ; 启动几个进程\rautostart=true ; 随着supervisord的启动而启动\rautorestart=true ; 自动重启\rstartretries=10 ; 启动失败时的最多重试次数\rexitcodes=0 ; 正常退出代码\rstopsignal=KILL ; 用来杀死进程的信号\rstopwaitsecs=10 ; 发送SIGKILL前的等待时间\rredirect_stderr=true ; 重定向stderr到stdout\r[program:test_two]\rcommand=java -jar /data/smallvideo/supervisor/taskApp-exec.jar TaskTestTwo ; 被监控的进程路径\rpriority=1 ; 数字越高,优先级越高\rnumprocs=1 ; 启动几个进程\rautostart=true ; 随着supervisord的启动而启动\rautorestart=true ; 自动重启\rstartretries=10 ; 启动失败时的最多重试次数\rexitcodes=0 ; 正常退出代码\rstopsignal=KILL ; 用来杀死进程的信号\rstopwaitsecs=10 ; 发送SIGKILL前的等待时间\rredirect_stderr=true ; 重定向stderr到stdout\rsupervisor配置文件详解 command:启动程序使用的命令,可以是绝对路径或者相对路径 process_name:一个python字符串表达式,用来表示supervisor进程启动的这个的名称,默认值是%(program_name)s numprocs:Supervisor启动这个程序的多个实例,如果numprocs\u0026gt;1,则process_name的表达式必须包含%(process_num)s,默认是1 numprocs_start:一个int偏移值,当启动实例的时候用来计算numprocs的值 priority:权重,可以控制程序启动和关闭时的顺序,权重越低:越早启动,越晚关闭。默认值是999 autostart:如果设置为true,当supervisord启动的时候,进程会自动重启。 autorestart:值可以是false、true、unexpected。false:进程不会自动重启,unexpected:当程序退出时的退出码不是exitcodes中定义的时,进程会重启,true:进程会无条件重启当退出的时候。 startsecs:程序启动后等待多长时间后才认为程序启动成功 startretries:supervisord尝试启动一个程序时尝试的次数。默认是3 exitcodes:一个预期的退出返回码,默认是0,2。 stopsignal:当收到stop请求的时候,发送信号给程序,默认是TERM信号,也可以是 HUP, INT, QUIT, KILL, USR1, or USR2。 stopwaitsecs:在操作系统给supervisord发送SIGCHILD信号时等待的时间 stopasgroup:如果设置为true,则会使supervisor发送停止信号到整个进程组 killasgroup:如果设置为true,则在给程序发送SIGKILL信号的时候,会发送到整个进程组,它的子进程也会受到影响。 user:如果supervisord以root运行,则会使用这个设置用户启动子程序 redirect_stderr:如果设置为true,进程则会把标准错误输出到supervisord后台的标准输出文件描述符。 stdout_logfile:把进程的标准输出写入文件中,如果stdout_logfile没有设置或者设置为AUTO,则supervisor会自动选择一个文件位置。 stdout_logfile_maxbytes:标准输出log文件达到多少后自动进行轮转,单位是KB、MB、GB。如果设置为0则表示不限制日志文件大小 stdout_logfile_backups:标准输出日志轮转备份的数量,默认是10,如果设置为0,则不备份 stdout_capture_maxbytes:当进程处于stderr capture mode模式的时候,写入FIFO队列的最大bytes值,单位可以是KB、MB、GB stdout_events_enabled:如果设置为true,当进程在写它的stderr到文件描述符的时候,PROCESS_LOG_STDERR事件会被触发 stderr_logfile:把进程的错误日志输出一个文件中,除非redirect_stderr参数被设置为true stderr_logfile_maxbytes:错误log文件达到多少后自动进行轮转,单位是KB、MB、GB。如果设置为0则表示不限制日志文件大小 stderr_logfile_backups:错误日志轮转备份的数量,默认是10,如果设置为0,则不备份 stderr_capture_maxbytes:当进程处于stderr capture mode模式的时候,写入FIFO队列的最大bytes值,单位可以是KB、MB、GB stderr_events_enabled:如果设置为true,当进程在写它的stderr到文件描述符的时候,PROCESS_LOG_STDERR事件会被触发 environment:一个k/v对的list列表 directory:supervisord在生成子进程的时候会切换到该目录 umask:设置进程的umask serverurl:是否允许子进程和内部的HTTP服务通讯,如果设置为AUTO,supervisor会自动的构造一个url 4. 其他配置 开机自动启动 1. 在目录/usr/lib/systemd/system/ 新建文件supervisord.service,并添加配置内容 [Unit]\rDescription=Process Monitoring and Control Daemon\rAfter=rc-local.service nss-user-lookup.target\r[Service]\rType=forking\rExecStart=/usr/bin/supervisord -c /usr/supervisor/supervisord.conf ;开机启动时执行\rExecStop=/usr/bin/supervisord shutdown\rExecReload=/usr/bin/supervisord reload\rkillMode=process\rRestart=on-failure\rRestartSec=42s\r[Install]\rWantedBy=multi-user.target\r2. 启动服务 systemctl enable supervisord 3. 验证一下是否为开机启动 systemctl is-enabled supervisord 4. supervisor常用命令 update 更新新的配置到supervisord(不会重启原来已运行的程序) reload,载入所有配置文件,并按新的配置启动、管理所有进程(会重启原来已运行的程序) start xxx: 启动某个进程 restart xxx: 重启某个进程 stop xxx: 停止某一个进程(xxx),xxx为[program:theprogramname]里配置的值 stop groupworker: 重启所有属于名为groupworker这个分组的进程(start,restart同理) stop all,停止全部进程,注:start、restart、stop都不会载入最新的配置文 reread,当一个服务由自动启动修改为手动启动时执行一下就ok tips:\n 注意配置文件中的supervisor.sock,supervisor.pid,以及logfile的配置 注意include里面配置文件的路径不能配置多条 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/essay/august/xhr/",
"title": "一次XHR错误的排查过程",
"tags": [],
"description": "OpenStack",
"content": " EROR\r\r问题 : 在使用VSCode打开远程SSH服务器的时候,迟迟打不开终端,MobaXterm使用是正常的。报错为XHR error\r\r\r\r1. 排查是否属于密钥的问题 cat /root/.ssh/authorized_keys # 查看与本机密钥是否一致 2. 排查时区问题 yum install ntp cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ntpdate cn.pool.ntp.org date 3. 排查下载插件的问题 将时区与本地的时间校对之后还是打不开,怀疑是之前插件的时区设置也有问题 ,于是删除了 vscode下面的bin,重新加载后解决\n搜索历史命令: ctrl +r "
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/glusterfs/",
"title": "GlusterFS",
"tags": [],
"description": "GlusterFS",
"content": " 简介\r\rGlusterfs是一个开源分布式文件系统,具有强大的横向扩展能力,可支持数PB存储容量和数千客户端,通过Infiniband RDMA 或Tcp/Ip 方式将许多廉价的x86 主机,通过网络互联成一个并行的网络文件系统。具有可扩展性、高性能、高可用性等特点\r\r\r\rglusterfs堆栈式结构 Glusterfs是根据fuse提供的接口实现的一个用户态的文件系统,主要包括gluster、glusterd、glusterfs和glusterfsd四大模块组成:\n gluster:是cli命令执行工具,主要功能是解析命令行参数,然后把命令发送给glusterd模块执行。 glusterd:是一个管理模块,处理gluster发过来的命令,处理集群管理、存储池管理、brick管理、负载均衡、快照管理等。集群信息、存储池信息和快照信息等都是以配置文件的形式存放在服务器中,当客户端挂载存储时,glusterd会把存储池的配置文件发送给客户端。 glusterfsd:是服务端模块,存储池中的每个brick都会启动一个glusterfsd进程。此模块主要是处理客户端的读写请求,从关联的brick所在磁盘中读写数据,然后返回给客户端。 glusterfs:是客户端模块,负责通过mount挂载集群中某台服务器的存储池,以目录的形式呈现给用户。当用户从此目录读写数据时,客户端根据从glusterd模块获取的存储池的配置文件信息,通过DHT算法计算文件所在服务器的brick位置,然后通过Infiniband RDMA 或Tcp/Ip 方式把数据发送给brick,等brick处理完,给用户返回结果。存储池的副本、条带、hash、EC等逻辑都在客户端处理。 glusterfs几种volume模式 一、 默认模式,既DHT, 也叫 分布卷: 将文件已hash算法随机分布到 一台服务器节点中存储。 gluster volume create test-volume server1:/exp1 server2:/exp2 二、 复制模式,既AFR, 创建volume 时带 replica x 数量: 将文件复制到 replica x 个节点中。 gluster volume create test-volume replica 2 transport tcp server1:/exp1 server2:/exp2 三、 条带模式,既Striped, 创建volume 时带 stripe x 数量: 将文件切割成数据块,分别存储到 stripe x 个节点中 ( 类似raid 0 )。 gluster volume create test-volume stripe 2 transport tcp server1:/exp1 server2:/exp2 四、 分布式条带模式(组合型),最少需要4台服务器才能创建。 创建volume 时 stripe 2 server = 4 个节点: 是DHT 与 Striped 的组合型。 gluster volume create test-volume stripe 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4 五、 分布式复制模式(组合型), 最少需要4台服务器才能创建。 创建volume 时 replica 2 server = 4 个节点:是DHT 与 AFR 的组合型。 gluster volume create test-volume replica 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4 六、 条带复制卷模式(组合型), 最少需要4台服务器才能创建。 创建volume 时 stripe 2 replica 2 server = 4 个节点: 是 Striped 与 AFR 的组合型。 gluster volume create test-volume stripe 2 replica 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4 七、 三种模式混合, 至少需要8台 服务器才能创建。 stripe 2 replica 2 , 每4个节点 组成一个 组。 gluster volume create test-volume stripe 2 replica 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4 server5:/exp5 server6:/exp6 server7:/exp7 server8:/exp8 部署 # 主机信息 三节点 192.168.24.131 # master 192.168.24.132 # k8s-node1 192.168.24.133 # k8s-node2 # 在三个节点都安装glusterfs yum install centos-release-gluster yum install -y glusterfs glusterfs-server glusterfs-fuse glusterfs-rdma # 配置 GlusterFS 集群: # 启动 glusterFS systemctl enable glusterd.service systemctl start glusterd.service # 在master节点上配置加入节点 gluster peer probe k8s-node1 gluster peer probe k8s-node2 # 结果 [root@master ~]# gluster peer probe k8s-node1 peer probe: success. [root@master ~]# gluster peer probe k8s-node2 peer probe: success. [root@master ~]# gluster peer status Number of Peers: 2 Hostname: k8s-node1 Uuid: 7cad5fd9-91e9-4d34-b0c7-bfc30790460f State: Peer in Cluster (Connected) Hostname: k8s-node2 Uuid: 50224007-889f-4f50-bfb1-3cccef052fa6 State: Peer in Cluster (Connected) # 在三个节点上都创建挂载目录 mkdir -p /opt/gluster/data # 创建gluster磁盘 gluster volume create models replica 3 master:/opt/gluster/data k8s-node1:/opt/gluster/data k8s-node2:/opt/gluster/data force # 三个节点上都可以执行 # 接下来启动models gluster volume start models # 接下来挂载一个路径,挂载到云盘上 mkdir -p /opt/gfsmnt mount -t glusterfs swarm-manager:models /opt/gfsmnt/ # 向上述的路径写入文件的时候,可以发现已经可以同步了 调优 # 开启 指定 volume 的配额: (models 为 volume 名称) gluster volume quota models enable # 限制 models 中 / (既总目录) 最大使用 80GB 空间 gluster volume quota models limit-usage / 80GB # 设置 cache 4GB gluster volume set models performance.cache-size 4GB # 开启 异步 , 后台操作 gluster volume set models performance.flush-behind on # 设置 io 线程 32 gluster volume set models performance.io-thread-count 32 # 设置 回写 (写数据时间,先写入缓存内,再写入硬盘) gluster volume set models performance.write-behind on 运维 # 查看GlusterFS中所有的volume: gluster volume list # 删除GlusterFS磁盘: gluster volume stop models #停止名字为 models 的磁盘 gluster volume delete models #删除名字为 models 的磁盘 注: 删除 磁盘 以后,必须删除 磁盘( /opt/gluster/data ) 中的 ( .glusterfs/ .trashcan/ )目录。 否则创建新 volume 相同的 磁盘 会出现文件 不分布,或者 类型 错乱 的问题。 # 卸载某个节点GlusterFS磁盘 gluster peer detach k8s-node2 # 设置访问限制,按照每个volume 来限制 gluster volume set models auth.allow 10.6.0.*,10.7.0.* # 添加GlusterFS节点: gluster peer probe k8s-node1 gluster volume add-brick models k8s-node1:/opt/gluster/data 注:如果是复制卷或者条带卷,则每次添加的Brick数必须是replica或者stripe的整数倍 # 配置卷 gluster volume set # 缩容volume: # 先将数据迁移到其它可用的Brick,迁移结束后才将该Brick移除: gluster volume remove-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data start # 在执行了start之后,可以使用status命令查看移除进度: gluster volume remove-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data status # 不进行数据迁移,直接删除该Brick: gluster volume remove-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data commit # 注意,如果是复制卷或者条带卷,则每次移除的Brick数必须是replica或者stripe的整数倍。 # 扩容: gluster volume add-brick models k8s-node2:/opt/gluster/data # 修复命令: gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data commit -force # 迁移volume: gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data start # pause 为暂停迁移 gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data pause # abort 为终止迁移 gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data abort # status 查看迁移状态 gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data status # 迁移结束后使用commit 来生效 gluster volume replace-brick models k8s-node2:/opt/gluster/data k8s-node1:/opt/gluster/data commit # 均衡volume: gluster volume models lay-outstart gluster volume models start gluster volume models startforce gluster volume models status gluster volume models stop kubernetes中使用glusterfs文件系统 1. 将GlusterFS的存储节点声明为endpoint 编写glusterfs-endpoint.json { \u0026#34;kind\u0026#34;: \u0026#34;Endpoints\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;glusterfs-cluster\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;subsets\u0026#34;: [{ \u0026#34;addresses\u0026#34;: [{ \u0026#34;ip\u0026#34;: \u0026#34;192.168.24.131\u0026#34; }], \u0026#34;ports\u0026#34;: [{ \u0026#34;port\u0026#34;: 9200 }] }, { \u0026#34;addresses\u0026#34;: [{ \u0026#34;ip\u0026#34;: \u0026#34;192.168.24.132\u0026#34; }], \u0026#34;ports\u0026#34;: [{ \u0026#34;port\u0026#34;: 9200 }] }, { \u0026#34;addresses\u0026#34;: [{ \u0026#34;ip\u0026#34;: \u0026#34;192.168.24.133\u0026#34; }], \u0026#34;ports\u0026#34;: [{ \u0026#34;port\u0026#34;: 9200 }] } ] } 然后声明这个endpoint\nkubectl creat -f glusterfs-endpoint.json 2. 在endpoint的基础上,声明一个PV资源,获取到我们之前创建的分布式存储 编写一个glusterfs-pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv001 spec: capacity: storage: 2Gi accessModes: - ReadWriteMany glusterfs: endpoints: \u0026#34;glusterfs-cluster\u0026#34; path: \u0026#34;models\u0026#34; readOnly: false 然后声明并使用这个PV资源\nkubectl create -f glusterfs-pv.yaml 3. 声明一个pvc资源,将上一步获取到的PV资源提供一个可直接供pod使用的路径 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc001 spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi 同样,声明使用\nkubectl create -f glusterfs-pvc.yaml\r4. 在应用的yaml文件中引用这个盘 apiVersion: extensions/v1beta1 kind: Deployment metadata: name: hugosite spec: replicas: 1 template: metadata: labels: name: hugosite spec: containers: - name: hugosite image: 192.168.24.131:5000/public/staticsite:v0.1 ports: - containerPort: 80 volumeMounts: - name: storage001 mountPath: \u0026#34;/app/dist\u0026#34; subPath: dist volumes: - name: storage001 persistentVolumeClaim: claimName: pvc001 声明使用\nkubectl create -f hugosite.yaml\r中间碰到个小小的问题,就是所有node节点都需要去配置私有仓库的insecure参数。 然后切入到pod中查看状态,发现已经同步了进来。 可以用subPath这个来指定使用挂载云盘的一个子路径\n Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/dockerfile/practice/",
"title": "Go项目分阶段构建实践",
"tags": [],
"description": "Docker Dockerfile Go",
"content": " 1. main.go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;path/filepath\u0026#34; ) func main() { p, _ := filepath.Abs(filepath.Dir(\u0026#34;dist/\u0026#34;)) http.Handle(\u0026#34;/\u0026#34;, http.FileServer(http.Dir(p))) err := http.ListenAndServe(\u0026#34;:80\u0026#34;, nil) if err != nil { fmt.Println(err) } } 一个很简单的静态文件服务器,可以将dist下面的静态网页展示出去。没有用到第三方库\n2. Dockerfile FROMgolang:alpine AS build-envADD . /srcRUN cd /src \u0026amp;\u0026amp; go build -o app# final stageMAINTAINERliuzeng01FROMalpineWORKDIR/appRUN mkdir distCOPY --from=build-env /src/app /appENTRYPOINT ./app分两个阶段构建,第一个阶段编译Go程序,第二个阶段将编译好的执行性文件拷贝到运行目录下。 执行下构建\n[root@master hugosite]# docker build -t staticsite . Sending build context to Docker daemon 12.6MB Step 1/9 : FROM golang:alpine AS build-env ---\u0026gt; 30df784d6206 Step 2/9 : ADD . /src ---\u0026gt; Using cache ---\u0026gt; cb4d46c21896 Step 3/9 : RUN cd /src \u0026amp;\u0026amp; go build -o app ---\u0026gt; Using cache ---\u0026gt; ba87a3e9c61c Step 4/9 : MAINTAINER liuzeng01 ---\u0026gt; Using cache ---\u0026gt; 6e5f5bcf4490 Step 5/9 : FROM alpine ---\u0026gt; a24bb4013296 Step 6/9 : WORKDIR /app ---\u0026gt; Using cache ---\u0026gt; fc3192d381b1 Step 7/9 : RUN mkdir dist ---\u0026gt; Using cache ---\u0026gt; 1832dd0ae65a Step 8/9 : COPY --from=build-env /src/app /app ---\u0026gt; Using cache ---\u0026gt; 0acf7b185b41 Step 9/9 : ENTRYPOINT ./app ---\u0026gt; Running in a27dd7fd7048 Removing intermediate container a27dd7fd7048 ---\u0026gt; 486b92fffdbc Successfully built 486b92fffdbc Successfully tagged staticsite:latest 看下build之后的镜像大小\n[root@master hugosite]# docker images|grep staticsite staticsite latest 486b92fffdbc About a minute ago 13.2MB 只有十几兆,非常小的一个镜像。运行一下\n[root@master hugosite]# docker run -d -p 8088:80 -v /mydocker/hugosite/liuzeng01.github.io:/app/dist --name hugosite staticsite 3fcd620c4ff2d199311f962c4dd8cbd3133d229d9c06fd0cc83c1bfd3c6e8d72 [root@master hugosite]# docker exec -it hugosite /bin/sh /app # ls -lrth total 7M drwxr-xr-x 16 root root 286 Aug 3 06:51 dist -rwxr-xr-x 1 root root 7.2M Aug 3 07:11 app /app # exit # 编译后的程序就有7.2M,说明基础镜像只有6M左右 补充:构建镜像时区存在问题如何解决 COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfoENV TZ=Asia/Shanghai# 添加这两行,可以读取构建的主机上的时区补充:清理退出的容器 # 停止所有 Exited 的容器 docker ps -a | grep \u0026#34;Exited\u0026#34; | awk \u0026#39;{print $1 }\u0026#39;|xargs docker stop # 删除所有 Exited 的容器 docker ps -a | grep \u0026#34;Exited\u0026#34; | awk \u0026#39;{print $1 }\u0026#39;|xargs docker rm 3. 推送到私有仓库 搭建私有harbor的方法,点这里\n[root@master hugosite]# docker tag staticsite:latest 192.168.24.131:5000/public/staticsite:v0.1 [root@master hugosite]# docker login 192.168.24.131:5000 Authenticating with existing credentials... WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded [root@master hugosite]# docker push 192.168.24.131:5000/public/staticsite:v0.1 The push refers to repository [192.168.24.131:5000/public/staticsite] 5e5a9eef2cc4: Pushed 4a05096b4101: Pushed 17dae035bf4c: Pushed 50644c29ef5a: Pushed v0.1: digest: sha256:9d2ab4d7e0f9ebb4ee8e032822151e0497699943851ab045c9134aacaf3e49ab size: 1153 4.挂载应用到kubernetes上,配置定时任务等 \r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/xujiajun/nutsdb/",
"title": "nutsdb",
"tags": [],
"description": "nutsdb",
"content": " Description\r\rNutsDB是纯Go语言编写一个简单、高性能、内嵌型、持久化的key-value数据库。\nNutsDB支持事务,从v0.2.0之后的版本开始支持ACID的特性,建议使用最新的release版本。v0.2.0之前的版本,保持高性能,没有作sync,但是具备高性能的写(本地测试,百万数据写入达40~50W+/s)。所有的操作都在事务中执行。NutsDB从v0.2.0版本开始支持多种数据结构,如列表(list)、集合(set)、有序集合(sorted set)。从0.4.0版本开始增加自定义配置读写方式、启动时候的文件载入方式、sync是否开启等\n\r\r\rgithub.com/xujiajun/nutsdb 官方文档\n1. 安装 go get -u github.com/xujiajun/nutsdb 2. 使用 开启数据库 package main import ( \u0026#34;log\u0026#34; \u0026#34;github.com/xujiajun/nutsdb\u0026#34; ) func main() { opt := nutsdb.DefaultOptions opt.Dir = \u0026#34;/tmp/nutsdb\u0026#34; //这边数据库会自动创建这个目录文件 \tdb, err := nutsdb.Open(opt) if err != nil { log.Fatal(err) } defer db.Close() ... } 这里的默认配置是\nvar DefaultOptions = Options{ EntryIdxMode: HintKeyValAndRAMIdxMode, //EntryIdxMode 代表索引entry的模式. EntryIdxMode 包括选项: HintKeyValAndRAMIdxMode 、HintKeyAndRAMIdxMode和 HintBPTSparseIdxMode。其中HintKeyValAndRAMIdxMode 代表纯内存索引模式(key和value都会被cache)。 HintKeyAndRAMIdxMode 代表内存+磁盘的索引模式(只有key被cache)。 HintBPTSparseIdxMode(v0.4.0之后的版本支持) 是专门节约内存的设计方案,单机10亿条数据,只要80几M内存。但是读性能不高,需要自己加缓存来加速。 \tSegmentSize: defaultSegmentSize, NodeNum: 1, //代表节点的号码. \tRWMode: FileIO, //RWMode 代表读写模式. RWMode 包括两种选项: FileIO and MMap. FileIO 用标准的 I/O读写。 MMap 代表使用mmap进行读写 \tSyncEnable: true,//SyncEnable 代表调用了 Sync() 方法. 如果 SyncEnable 为 false, 写性能会很高,但是如果遇到断电或者系统奔溃,会有数据丢失的风险。 如果 SyncEnable 为 true,写性能会相比false的情况慢很多,但是数据更有保障,每次事务提交成功都会落盘 \tStartFileLoadingMode: MMap,//StartFileLoadingMode 代表启动数据库的载入文件的方式。参数选项同RWMode } 2. 使用方法 使用bucket buckets中文翻译过来是桶的意思,你可以理解成类似mysql的table表的概念,也可以理解成命名空间,或者多租户的概念。 所以你可以用他存不同的key的键值对,也可以存相同的key的键值对。所有的key在一个bucket里面不能重复。\nkey := []byte(\u0026#34;key001\u0026#34;) val := []byte(\u0026#34;val001\u0026#34;) bucket001 := \u0026#34;bucket001\u0026#34; if err := db.Update( func(tx *nutsdb.Tx) error { if err := tx.Put(bucket001, key, val, 0); err != nil { return err } return nil }); err != nil { log.Fatal(err) } bucket002 := \u0026#34;bucket002\u0026#34; if err := db.Update( func(tx *nutsdb.Tx) error { if err := tx.Put(bucket002, key, val, 0); err != nil { return err } return nil }); err != nil { log.Fatal(err) } 写入数据 if err := db.Update( func(tx *nutsdb.Tx) error { key := []byte(\u0026#34;name1\u0026#34;) val := []byte(\u0026#34;val1\u0026#34;) bucket := \u0026#34;bucket1\u0026#34; if err := tx.Put(bucket, key, val, 0); err != nil { return err } return nil }); err != nil { log.Fatal(err) } 读取数据 if err := db.View( func(tx *nutsdb.Tx) error { key := []byte(\u0026#34;name1\u0026#34;) bucket := \u0026#34;bucket1\u0026#34; if e, err := tx.Get(bucket, key); err != nil { return err } else { fmt.Println(string(e.Value)) // \u0026#34;val1-modify\u0026#34; \t} return nil }); err != nil { log.Println(err) } 删除数据 if err := db.Update( func(tx *nutsdb.Tx) error { key := []byte(\u0026#34;name1\u0026#34;) bucket := \u0026#34;bucket1\u0026#34; if err := tx.Delete(bucket, key); err != nil { return err } return nil }); err != nil { log.Fatal(err) } 更新数据 if err := db.Update( func(tx *nutsdb.Tx) error { key := []byte(\u0026#34;name1\u0026#34;) val := []byte(\u0026#34;val1-modify\u0026#34;) // 更新值 \tbucket := \u0026#34;bucket1\u0026#34; if err := tx.Put(bucket, key, val, 0); err != nil { return err } return nil }); err != nil { log.Fatal(err) } //仍然可以使用添加数据的方法 TTL时间设置 if err := db.Update( func(tx *nutsdb.Tx) error { key := []byte(\u0026#34;name1\u0026#34;) val := []byte(\u0026#34;val1\u0026#34;) bucket := \u0026#34;bucket1\u0026#34; // 如果设置 ttl = 0 or Persistent, 这个key就会永久不删除 \t// 这边 ttl = 60 , 60s之后就会过期。 \tif err := tx.Put(bucket, key, val, 60); err != nil { return err } return nil }); err != nil { log.Fatal(err) } 扫描key key在一个bucket里面按照byte-sorted有序排序的,所以对于keys的扫描操作,在NutsDB里是很高效的。\n前缀扫描 对于前缀的扫描,我们可以用PrefixScan 方法, 使用参数 offSet和limitNum 来限制返回的结果的数量,比方下面例子限制100个entries: if err := db.View( func(tx *nutsdb.Tx) error { prefix := []byte(\u0026#34;user_\u0026#34;) bucket := \u0026#34;user_list\u0026#34; // 从offset=0开始 ,限制 100 entries 返回 \tif entries, err := tx.PrefixScan(bucket, prefix, 0, 100); err != nil { return err } else { for _, entry := range entries { fmt.Println(string(entry.Key), string(entry.Value)) } } return nil }); err != nil { log.Fatal(err) } 前缀后的正则扫描 对于前缀后的扫描,可以通过正则表达式对键的第二部分进行搜索来遍历一个键前缀,我们可以使用PrefixSearchScan方法,用参数reg来编写正则表达式,使用参数offsetNum、limitNum 来约束返回的条目的数量:\nif err := db.View( func(tx *nutsdb.Tx) error { prefix := []byte(\u0026#34;user_\u0026#34;) // 定义前缀 \treg := \u0026#34;99\u0026#34; // 定义正则表达式 \tbucket := \u0026#34;user_list\u0026#34; // 从offset=25开始,限制 100 entries 返回 \tif entries, _, err := tx.PrefixSearchScan(bucket, prefix, reg, 25, 100); err != nil { return err } else { for _, entry := range entries { fmt.Println(string(entry.Key), string(entry.Value)) } } return nil }); err != nil { log.Fatal(err) } 范围扫描 对于范围的扫描,我们可以用RangeScan方法。\nif err := db.View( func(tx *nutsdb.Tx) error { // 假设用户key从 user_0000000 to user_9999999. \t// 执行区间扫描类似这样一个start和end作为主要参数. \tstart := []byte(\u0026#34;user_0010001\u0026#34;) end := []byte(\u0026#34;user_0010010\u0026#34;) bucket := \u0026#34;user_list\u0026#34; if entries, err := tx.RangeScan(bucket, start, end); err != nil { return err } else { for _, entry := range entries { fmt.Println(string(entry.Key), string(entry.Value)) } } return nil }); err != nil { log.Fatal(err) } 读取全部数据 if err := db.View( func(tx *nutsdb.Tx) error { bucket := \u0026#34;user_list\u0026#34; entries, err := tx.GetAll(bucket) if err != nil { return err } for _, entry := range entries { fmt.Println(string(entry.Key),string(entry.Value)) } return nil }); err != nil { log.Println(err) 3. 补充概念 从v0.3.0版本起,NutsDB支持(A)原子性、C(一致性)、I(隔离性),并保证(D)持久化。以下参考wiki百科的对ACID定义分别讲一下。\n1、(A)原子性\n所谓原子性,一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。一般的做法是类似数据快照的方案。关于这一点,NutsDB支持回滚操作。NutsDB的作法是先实际预演一边所有要执行的操作,这个时候数据其实还是uncommitted状态,一直到所有环节都没有问题,才会作commit操作,如果中间任何环节一旦发生错误,直接作rollback回滚操作,保证原子性。 就算发生错误的时候已经有数据进磁盘,下次启动也不会被索引到这些数据。\r 2、(C)一致性\n在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的数据必须完全符合预期的。NutsDB基于读写锁实现锁机制,在高并发场景下,一个读写事务具有排他性的,比如一个goroutine需要执行一个读写事务,其他不管想要读写的事务或者只读的只能等待,直到这个锁释放为止。保证了数据的一致性。所以这一点NutsDB满足一致性。\r 3、(I)隔离性\n数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。如上面的一致性所说,NutsDB基于读写锁实现锁机制。不会出现数据串的情况。所以也是满足隔离性的。\r关于事务的隔离级别,我们也来对照wiki百科,来看下NutsDB属于哪一个级别:\r隔离级别低到高:\r1)未提交读(READ UNCOMMITTED)\r这个是最低的隔离级别。允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。很明显nutsDB是避免脏读的。\r2)在提交读(READ COMMITTED)\r定义:这个隔离级别中,基于锁机制并发控制的DBMS需要对选定对象的写锁一直保持到事务结束,但是读锁在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生)。 看下“不可重复读”的定义:在一次事务中,当一行数据获取两遍得到不同的结果表示发生了“不可重复读”。\rnutsDB不会出现“不可重复读”这种情况,当高并发的时候,正在进行读写操作,一个goroutine刚好先拿到只读锁,这个时候要完成一个读写事务操作的那个goroutine要阻塞等到只读锁释放为止。也就避免上面的问题。\r3)在可重复读(REPEATABLE READS)\r定义:这个隔离级别中,基于锁机制并发控制的DBMS需要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁”,因此可能会发生“幻影读”。\r关于幻影读定义,指在事务执行过程中,当两个完全相同的查询语句执行得到不同的结果集。这种现象称为“幻影读(phantom read)”,有些人也叫他幻读,正如上面所说,在nutsDB中,当进行只读操作的时候,同一时间只能并发只读操作,其他有关“写”的事务是被阻塞的,直到这些只读锁释放为止,因此不会出现“幻影读”的情况。\r4)可串行化 (Serializable)\r定义:这个隔离级别是最高的。避免了所有上面的“脏读”、不可重复读”、“幻影读”现象。\r在nutsDB中,一个只读事务和一个写(读写)事务,是互斥的,需要串行执行,不会出现并发执行。nutsDB属于这个可串行化级别。 这个级别的隔离一般来说在高并发场景下性能会受到影响。但是如果锁本身性能还可以,也不失为一个简单有效的方法。当前版本nutsDB基于读写锁,在并发读多写少的场景下,性能会好一点。\r4、(D)持久化\r事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/",
"title": "Programming",
"tags": [],
"description": "Programming",
"content": " Go \r\rSQL \r\rJava \r\rC/C\u0026#43;\u0026#43; \r\rpython \r\rJavaScript \r\rFlutter \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/dockerfile/tips/",
"title": "dockerfile使用注意事项",
"tags": [],
"description": "docker,golang",
"content": " 1. 使用官方镜像 官方镜像往往更加稳定,安全\n2. From合适的镜像 注意alpine ,runtime ,sdk等等镜像的区别。尽量使用alpine镜像,体积更小。SDK 往往集成了开发的所有环境,这个时候如果为了减小镜像体积的话,可以使用Runtime镜像作为应用的运行镜像。\n3. 只Copy需要的文件,而不是拷贝所有文件 4. 多阶段构建 5. 多利用Cache,来减小重复构建时候的耗时 6. 合理使用Run命令。将需要更新软件或者安装应用的步骤放到一起 这也是为了尽量减小构建的中间镜像的层数和大小。同时注意和经常修改的指令分开,这个主要是为了利用缓存,提升构建速度。\n7. 使用.dockerignore文件来忽略不需要构建或者缓存的文件 8. 注意指令顺序。将不变的放在前面,经常修改的放在后面 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/docker/dockerfile/",
"title": "dockerfile构建镜像",
"tags": [],
"description": "docker,golang",
"content": " dockerfile基础命令 \r\r分阶段构建减小go镜像体积 \r\rGo项目分阶段构建实践 \r\rdockerfile使用注意事项 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/flutter/",
"title": "Flutter",
"tags": [],
"description": "Flutter",
"content": " \r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/gitlab/",
"title": "Gitlab+kubernetes",
"tags": [],
"description": "Kubernetes CI/CD实践笔记",
"content": " 1. 在kubernetes集群中部署Gitlab 2. 在kubernetes集群中部署Jenkins 3. 利用Jenkins配置Gitlab的CICD流程 4. 持续部署镜像应用到Kubernetes Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/golang%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/",
"title": "Go数据结构",
"tags": [],
"description": "Daily Note",
"content": " golang本质是全局按照值传递的,也就是copy值,那么你必须知道你的内存对象是包含哪些内容,才能清楚掌握你在copy值的时候复制了哪些东西 num bool string array slice map channel interface package main func main () { var n int64 = 11 //n就是一个8字节的数据块。 var b bool = true //是一个1字节的数据块。 var s string = \u0026#34;test-string-1\u0026#34; //x var a [3]bool = [3]bool{true, false, true} var sl []int = []int{1,2,3,4} var m map[int]string var c chan int var in interface{} _, _, _, _, _, _, _, _ = n, b, s, a, sl, m, c , in } string类型 (gdb) pt s type = struct string { uint8 *str; int len; } //string类型在go里是一个复合类型,s变量是一个16字节的变量。其中str字段指向字符串存储的地方 //s就是代表了一个16字节的数据块。所以我们每次定义一个string变量,除了字符序列,s的本身结构是分配16个字节在栈上。 数组类型 //数组类型,就是在地址空间中连续的一段内存块,和c一样(旁白:和c一样,都是平坦的内存结构)。 (gdb) p \u0026amp;a $13 = ([3]bool *) 0xc00003070d (gdb) x/6bx 0xc00003070d 0xc00003070d: 0x01 0x00 0x01 0x0b 0x00 0x00 切片类型 // 是个复合类型,变量本身是一个管理结构(和string一样),这个管理结构管理着一段连续的内存 (gdb) pt sl type = struct []int { int *array; int len; int cap; } map 类型 和 channel 类型 map类型和channel类型特别提一点,变量本身本质上是一个指针类型。也就是说上面我们定义了两个变量m,c,从内存分配的角度来讲,只在栈上分配了一个指针变量,并且这个指针还是nil值,所以我们经常看到 go 的一个特殊说明:slice,map,channel 这三种类型必须使用make来创建,就是这个道理。因为如果仅仅定义了类型变量,那仅仅是代表了分配了这个变量本身的内存空间,并且初始化是nil,一旦你直接用,那么就会导致非法地址引用的问题。slice 的24个字节的管理空间,map和channel的一个指针8个字节的空间。那么如果是调用了make,其实就会把下面的结构分配并初始化出来。\n(gdb) pt m type = struct hash\u0026lt;int, string\u0026gt; { int count; uint8 flags; uint8 B; uint16 noverflow; uint32 hash0; struct bucket\u0026lt;int, string\u0026gt; *buckets; struct bucket\u0026lt;int, string\u0026gt; *oldbuckets; uintptr nevacuate; struct runtime.mapextra *extra; } * (gdb) pt c type = struct hchan\u0026lt;int\u0026gt; { uint qcount; uint dataqsiz; void *buf; uint16 elemsize; uint32 closed; runtime._type *elemtype; uint sendx; uint recvx; struct waitq\u0026lt;int\u0026gt; recvq; struct waitq\u0026lt;int\u0026gt; sendq; runtime.mutex lock; } Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/sqlnodefine/",
"title": "Go语言查询不定字段",
"tags": [],
"description": "select * from xxx",
"content": "package main import ( \u0026#34;database/sql\u0026#34; \u0026#34;fmt\u0026#34; _ \u0026#34;github.com/go-sql-driver/mysql\u0026#34; ) func main() { //连接数据库 \tdb, err := sql.Open(\u0026#34;mysql\u0026#34;, \u0026#34;golang:golang@tcp(localhost:3306)/gostudy?charset=utf8\u0026#34;) if err != nil { fmt.Println(\u0026#34;连接数据库失败\u0026#34;, err.Error()) return } defer db.Close() //查询数据库 \tquery, err := db.Query(\u0026#34;select * from event_filelist\u0026#34;) if err != nil { fmt.Println(\u0026#34;查询数据库失败\u0026#34;, err.Error()) return } defer query.Close() //读出查询出的列字段名 \tcols, _ := query.Columns() //values是每个列的值,这里获取到byte里 \tvalues := make([][]byte, len(cols)) //query.Scan的参数,因为每次查询出来的列是不定长的,用len(cols)定住当次查询的长度 \tscans := make([]interface{}, len(cols)) //让每一行数据都填充到[][]byte里面 \tfor i := range values { scans[i] = \u0026amp;values[i] } //最后得到的map \tresults := make(map[int]map[string]string) i := 0 for query.Next() { //循环,让游标往下推 \tif err := query.Scan(scans...); err != nil { //query.Scan查询出来的不定长值放到scans[i] = \u0026amp;values[i],也就是每行都放在values里 \tfmt.Println(err) return } row := make(map[string]string) //每行数据 for k, v := range values { //每行数据是放在values里面,现在把它挪到row里 \tkey := cols[k] row[key] = string(v) } results[i] = row //装入结果集中 \ti++ } //查询出来的数组 \tfor k, v := range results { fmt.Println(k, v) } db.Close() //用完关闭 } "
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/august/design/",
"title": "Go语言设计模式",
"tags": [],
"description": "golang",
"content": " 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。\n1. 创建型模式 单例模式 单例模式算是23中设计模式里最简单的一个了,它主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。\n在程序设计中,有一些对象通常我们只需要一个共享的实例,比如线程池、全局缓存、对象池等,这种场景下就适合使用单例模式。\n但是,并非所有全局唯一的场景都适合使用单例模式。比如,考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。 如何判断一个对象是否应该被建模成单例?\n通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,这个对象是一个中心点吗?\n在对某个对象实现单例模式时,有两个点必须要注意:(1)限制调用者直接实例化该对象;(2)为该对象的单例提供一个全局唯一的访问方法。\n我们可以利用Go语言package的访问规则来实现,将单例结构体设计成首字母小写,就能限定其访问范围只在当前package下,模拟了C++/Java中的私有构造函数;再在当前package下实现一个首字母大写的访问函数,就相当于static方法的作用了。\n在实际开发中,我们经常会遇到需要频繁创建和销毁的对象。频繁的创建和销毁一则消耗CPU,二则内存的利用率也不高,通常我们都会使用对象池技术来进行优化。考虑我们需要实现一个消息对象池,因为是全局的中心点,管理所有的Message实例,所以将其实现成单例,实现代码如下\npackage msgpool ... // 消息池 type messagePool struct { pool *sync.Pool } // 消息池单例 var msgPool = \u0026amp;messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: \u0026amp;sync.Pool{New: func() interface{} { return \u0026amp;Message{Count: 0} }}, } // 访问消息池单例的唯一方法 func Instance() *messagePool { return msgPool } // 往消息池里添加消息 func (m *messagePool) AddMsg(msg *Message) { m.pool.Put(msg) } // 从消息池里获取消息 func (m *messagePool) GetMsg() *Message { return m.pool.Get().(*Message) } ... 以上的单例模式就是典型的“饿汉模式”,实例在系统加载的时候就已经完成了初始化。对应地,还有一种“懒汉模式”,只有等到对象被使用的时候,才会去初始化它,从而一定程度上节省了内存。众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验锁来优化。对于“懒汉模式”,Go语言有一个更优雅的实现方式,那就是利用sync.Once,它有一个Do方法,其入参是一个方法,Go语言会保证仅仅只调用一次该方法。\n// 单例模式的“懒汉模式”实现 package msgpool type messagePool struct { pool *sync.Pool } var once = \u0026amp;sync.Once{} // 消息池单例,在首次调用时初始化 var msgPool *messagePool // 全局唯一获取消息池pool到方法 func Instance() *messagePool { // 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次 once.Do(func() { msgPool = \u0026amp;messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: \u0026amp;sync.Pool{New: func() interface{} { return \u0026amp;Message{Count: 0} }}, } }) return msgPool } 建造者模式 在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。 对于Go语言来说,最常见的表现就是多层的嵌套实例化:\ntype Message struct { Header Body } type Header struct { IPAddr string Length int Tags } type Tags struct { Num int TimeStamp time.Time } type Body struct { Content string TTL int64 } 对象创建方法有两个明显的缺点:(1)对对象使用者不友好,使用者在创建对象时需要知道的细节太多;(2)代码可读性很差。 针对这种对象成员较多,创建对象逻辑较为繁琐的场景,就适合使用建造者模式来进行优化。 建造者模式的作用有如下几个: 1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。 2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。 3、对多个对象复用同样的对象创建逻辑。\n实现代码\ntype builder struct { msg *Message } func NewBuilder() *builder{ return \u0026amp;builder{ msg: \u0026amp;Message{ Header: Header{}, Body: Body{}, }, } } func (b *builder)WithIpaddr(str string) *builder{ b.msg.IPAddr = str return b } func (b *builder)WithLength(len int) *builder{ b.msg.Length = len return b } func (b *builder)WithNum(num int) *builder{ b.msg.Num = num return b } func (b *builder)WithTimeStamp(now time.Time) *builder{ b.msg.TimeStamp = now return b } func (b *builder)WithContent(str string) *builder{ b.msg.Content = str return b } func (b *builder)WithTTL(ttl int64) *builder{ b.msg.TTL = ttl return b } func (b *builder) Build() *Message { return b.msg } // 将创建对象,赋予结构体值的部分全部都封装起来 使用方法\nfunc main() { msg := msgbuild.NewBuilder(). WithContent(\u0026#34;This is a builder message\u0026#34;). WithIpaddr(\u0026#34;127.0.0.1\u0026#34;). WithLength(65536). WithTimeStamp(time.Now()). WithNum(1). WithTTL(5). Build() fmt.Println(msg) time.Sleep(10*time.Second) if time.Now().Unix() \u0026gt;msg.TimeStamp.Unix()+msg.TTL{ fmt.Println(\u0026#34;Now the message is invalid.\u0026#34;) } } 工厂方法模式 工厂方法模式跟上一节讨论的建造者模式类似,都是将对象创建的逻辑封装起来,为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别,建造者模式更常用于需要传递多个参数来进行实例化的场景。 使用工厂方法来创建对象主要有两个好处: 1、代码可读性更好。相比于使用C++/Java中的构造函数,或者Go中的{}来创建对象,工厂方法因为可以通过函数名来表达代码含义,从而具备更好的可读性。比如,使用工厂方法productA := CreateProductA()创建一个ProductA对象,比直接使用productA := ProductA{}的可读性要好。 2、与使用者代码解耦。很多情况下,对象的创建往往是一个容易变化的点,通过工厂方法来封装对象的创建过程,可以在创建逻辑变更时,避免霰弹式修改。 工厂方法模式也有两种实现方式:(1)提供一个工厂对象,通过调用工厂对象的工厂方法来创建产品对象;(2)将工厂方法集成到产品对象中(C++/Java中对象的static方法,Go中同一package下的函数)\n首先是第一种的模式,可以利用这种模式来创建一系列实现了共同接口的对象。 实现方法 首先是结构体定义,接口,以及接口实现\npackage internal type ERRCODE int const ( OutTime ERRCODE = iota InvaildContent NullReturn ) type ERROR interface { GetContent() string GetErrCode() ERRCODE } type OutOfTimeError struct{ content string errcode ERRCODE } type InvalidContentError struct { content string errcode ERRCODE } type NullReturnError struct { content string errcode ERRCODE } func (e *OutOfTimeError)GetContent() string{return e.content} func (e *OutOfTimeError)GetErrCode() ERRCODE{return e.errcode} func (e *InvalidContentError)GetContent() string{return e.content} func (e *InvalidContentError)GetErrCode() ERRCODE{return e.errcode} func (e *NullReturnError)GetContent() string{return e.content} func (e *NullReturnError)GetErrCode() ERRCODE{return e.errcode} 接下来是工厂创建的方法\npackage internal type Factory struct{} func (f *Factory) Create(errcode ERRCODE) ERROR{ if errcode == OutTime{ return \u0026amp;OutOfTimeError{ content: \u0026#34;This is a out of time error\u0026#34;, errcode: OutTime, } }else if errcode == InvaildContent{ return \u0026amp;InvalidContentError{ content: \u0026#34;The content is invalid \u0026#34;, errcode: InvaildContent, } }else { return \u0026amp;NullReturnError{ content: \u0026#34;The Content is null\u0026#34;, errcode: NullReturn, } } } 使用方法\nfunc main() { f := internal.Factory{} errs := f.Create(internal.InvaildContent) fmt.Println(errs.GetContent()) errs = f.Create(internal.OutTime) fmt.Println(errs.GetContent()) errs = f.Create(10) fmt.Println(errs.GetContent()) } 第二种的实现方法\n//简单的在上面的基础上创建下 func NewOutOfTimeError() ERROR{ return \u0026amp;OutOfTimeError{ content: \u0026#34;This is a out of time error\u0026#34;, errcode: OutTime, } } func NewInvaildContentError() ERROR{ return \u0026amp;InvalidContentError{ content: \u0026#34;The content is invalid \u0026#34;, errcode: InvaildContent, } } func NewNullContentError() ERROR{ return \u0026amp;NullReturnError{ content: \u0026#34;The Content is null\u0026#34;, errcode: NullReturn, } } 使用方法\nfunc main() { errs := internal.NewInvaildContentError() fmt.Println(errs.GetContent()) errs = internal.NewOutOfTimeError() fmt.Println(errs.GetContent()) errs = internal.NewNullContentError() fmt.Println(errs.GetContent()) } 抽象工厂模式 抽象工厂模式(英语:Abstract factory pattern)是一种软件开发设计模式。抽象工厂模式提供了一种方式,可以将一组具有同一主题的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。\n工厂模式用来创建一组相关或者相互依赖的对象,与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构, 我们可以将一种产品等级想象为一个产品族,所谓的产品族,是指位于不同产品等级结构中功能相关联的产品组成的家族。\n注意理解这个多个等级的概念\n在工厂方法模式中,我们通过一个工厂对象来创建一个产品族,具体创建哪个产品,则通过swtich-case的方式去判断。这也意味着该产品组上,每新增一类产品对象,都必须修改原来工厂对象的代码;而且随着产品的不断增多,工厂对象的职责也越来越重,违反了单一职责原则。 抽象工厂模式通过给工厂类新增一个抽象层解决了该问题,如上图所示,FactoryA和FactoryB都实现·抽象工厂接口,分别用于创建ProductA和ProductB。如果后续新增了ProductC,只需新增一个FactoryC即可,无需修改原有的代码;因为每个工厂只负责创建一个产品,因此也遵循了单一职责原则。\n实现方法\npackage internal type Store interface { GetPublishBook() Product GetCDProduct() Product } type Product interface { Information() string } type Book struct { Name string } func (p *Book) Information() string{ return p.Name } type CD struct { Maintainer string } func (p *CD)Information() string{ return p.Maintainer } 定义个store叫BookStore\ntype BookStore struct {} func (s *BookStore) GetPublishBook() Product{return \u0026amp;Book{Name: \u0026#34;BookStore\u0026#39;s Book\u0026#34;}} func (s *BookStore) GetCDProduct() Product {return \u0026amp;CD{Maintainer: \u0026#34;BookStore\u0026#39;s Owner\u0026#34;}} 使用工厂方法\nfunc main() { var f internal.Store f = new(internal.BookStore) book := f.GetPublishBook() fmt.Println(book.Information()) CD := f.GetCDProduct() fmt.Println(CD.Information()) } 接下来就可以动态的往里面加store而不用像工厂模式那样修改switch case了\ntype MyStore struct {} func (s *MyStore) GetPublishBook() Product{return \u0026amp;Book{Name: \u0026#34;MyStore\u0026#39;s Book\u0026#34;}} func (s *MyStore) GetCDProduct() Product {return \u0026amp;CD{Maintainer: \u0026#34;MyStore\u0026#39;s Owner\u0026#34;}} 使用方法\nfunc main() { var f internal.Store f = new(internal.MyStore) book := f.GetPublishBook() fmt.Println(book.Information()) CD := f.GetCDProduct() fmt.Println(CD.Information()) } 或者往里面加Product都是一样的,只要这个产品实现了Information即可。\n原型模式 原型模式主要解决对象复制的问题,它的核心就是clone()方法,返回Prototype对象的复制品。在程序设计过程中,往往会遇到有一些场景需要大量相同的对象,如果不使用原型模式,那么我们可能会这样进行对象的创建:新创建一个相同对象的实例,然后遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这种方法的缺点很明显,那就是使用者必须知道对象的实现细节,导致代码之间的耦合。另外,对象很有可能存在除了对象本身以外不可见的变量,这种情况下该方法就行不通了。\n对于这种情况,更好的方法就是使用原型模式,将复制逻辑委托给对象本身,这样,上述两个问题也都迎刃而解了。\npackage internal import \u0026#34;strconv\u0026#34; type Person struct { PersonInfo WorkExperience } type PersonInfo struct { name string age int address string education string } type WorkExperience struct { Company string Duration string } func (p *Person)SetName(name string){p.name = name} func (p *Person)SetAge(age int ){p.age = age} func (p *Person)SetAddress(address string ){p.address =address} func (p *Person)SetEducation(education string){p.education=education} func (p *Person)SetWorkExperience(company ,duration string){ p.Company=company p.Duration=duration } func (p *Person)GetPersonInfo() string{ return \u0026#34;Name : \u0026#34;+p.name +\u0026#34; Age :\u0026#34;+ strconv.Itoa(p.age)+\u0026#34; Address : \u0026#34;+ p.address + \u0026#34; Education :\u0026#34; + p.education} func (p *Person)GetWorkExperience() string{return p.Company + \u0026#34; Duration :\u0026#34; + p.Duration} func (p *Person)Clone() *Person { person := *p //注意这个地方,对指针取指针 \treturn \u0026amp;person } 使用方法\npackage main import ( \u0026#34;GoStudy/week18/prototype/internal\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { var p = new(internal.Person) p.SetAddress(\u0026#34;南京\u0026#34;) p.SetName(\u0026#34;liuzeng\u0026#34;) p.SetAge(23) p.SetEducation(\u0026#34;NUAA\u0026#34;) p.SetWorkExperience(\u0026#34;浩鲸科技\u0026#34;,\u0026#34;2019/6-2020/8\u0026#34;) p2 := p.Clone() p2.SetName(\u0026#34;陈伟\u0026#34;) p2.SetAge(24) p3 := p2.Clone() p3.SetName(\u0026#34;胡振飞\u0026#34;) fmt.Println(p.GetPersonInfo()) fmt.Println(p2.GetPersonInfo()) fmt.Println(p3.GetPersonInfo()) fmt.Println(p3.GetWorkExperience()) } 结果\nName : liuzeng Age :23 Address : 南京 Education :NUAA Name : 陈伟 Age :24 Address : 南京 Education :NUAA Name : 胡振飞 Age :24 Address : 南京 Education :NUAA 浩鲸科技 Duration :2019/6-2020/8 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/shell/shellexample/grep/",
"title": "grep/sed/awk",
"tags": [],
"description": "shell",
"content": " 1. grep 常用命令\n-v 取反 -i 忽略大小写 -c 符合条件的行数 -n 输出的同时打印行号 ^* 以*开头 *$ 以*结尾 ^$ 空行 使用示例\n# 查找在目标文件中含有scsi的行 grep scsi test.txt # 查看目录下所有含有scsi的文件 grep scsi -rl grep scsi -rl * grep scsi * # 反查 grep -v scsci grep -v scsci test.txt # 统计行数 grep -c kworker test.txt # 忽略大小写统计行数 grep -ic kworker test.txt # 输出行号 grep -n kworker test.txt # 输出所有以 ] 结尾的行 grep \u0026#34;]$\u0026#34; test.txt # 输出所有以 root 开始的行 grep \u0026#34;^root\u0026#34; -c test.txt 2. sed sed是一种流编辑器,是一款处理文本比较优秀的工具,可以结合正则表达式一起使用。 d 删除选择的行 s 查找 y 替换 i 当前行前面插入一行 a 当前行后面插入一行 p 打印行 q 退出 替换符: 数字 :替换第几处 g : 全局替换 \\1: 子串匹配标记,前面搜索可以用元字符集\\(..\\) \u0026amp;: 保留搜索刀的字符用来替换其他字符 使用方式:\n# 替换 sed \u0026#39;s/kworker/work/\u0026#39; test.txt # 注意不是在原文件中替换,而是输出到屏幕 # 删除第二行到第四行,同时显示行数 sed \u0026#39;=;2,4d\u0026#39; test.txt # 替换第n行 sed \u0026#39;2c\\hello kitty\u0026#39; word # 替换第2行到结尾为hello Kitty (但不是每行) sed \u0026#39;2,$c\\hello kitty\u0026#39; word awk Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/ip/",
"title": "IP",
"tags": [],
"description": "Daily Note",
"content": " 在cobra里面写了一个IP地址转16进制的脚本,用到了net包 package internal import ( \u0026#34;encoding/hex\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;net\u0026#34; \u0026#34;strings\u0026#34; ) func ParseIPV4(ipaddr string){ if len(ipaddr) == 0 { return } hexaddr := strings.ToUpper(hex.EncodeToString(net.ParseIP(ipaddr).To4())) fmt.Println(hexaddr) } func ParseIPV6(ipaddr string){ if len(ipaddr) == 0 { return } hexaddr := strings.ToUpper(hex.EncodeToString(net.ParseIP(ipaddr).To16())) fmt.Println(hexaddr) } //光有转16进制是不行的,还需要从16进制转回ip地址 func ParseHexToIpv4(hexstring string) { if len(hexstring) == 0 { return } var ip net.IP ip, err := hex.DecodeString(hexstring) if err != nil { fmt.Println(err) return } fmt.Println(ip.To4()) } func ParseHexToIpv6(hexstring string) { if len(hexstring) == 0 { return } var ip net.IP ip, err := hex.DecodeString(hexstring) if err != nil { fmt.Println(err) return } fmt.Println(ip.To16()) } "
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/tips/",
"title": "K8S使用技巧",
"tags": [],
"description": "Kubernetes",
"content": " kubectl命令用法 \r\rkube proxy \r\rkubectl 使用技巧 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/service/",
"title": "K8S概念",
"tags": [],
"description": "Kubernetes",
"content": " NSQ部署实践 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/deploy/",
"title": "K8S环境配置记录",
"tags": [],
"description": "Kubernetes",
"content": " Golang of linux \r\rKubernetes搭建 \r\rNAT网关配置虚拟机静态IP \r\rGlusterFS \r\rGitlab\u0026#43;kubernetes \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/template/",
"title": "K8S资源模板",
"tags": [],
"description": "Kubernetes",
"content": " Kubernetes集群中Pod获取自身信息 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/tips/kubeapi/",
"title": "kubectl 使用技巧",
"tags": [],
"description": "Kubernetes",
"content": " 1. kubectl 工作原理 从技术角度上看,kubectl是Kubernetes API的客户端。Kubernetes API是一个 HTTP REST API。这个API是真正的Kubernetes用户界面。Kubernetes完全受这个API控制,这意味着每个Kubernetes操作都作为API端点公开,并且可以由对此端点的HTTP请求执行。因此,kubectl的主要工作是执行对Kubernetes API的HTTP请求 Kubernetes是一个完全以资源为中心的系统。这意味着,Kubernetes维护资源的内部状态并且所有的Kubernetes操作都是针对这些资源的CRUD(增加、读取、更新、删除)操作。你完全可以通过操控这些资源(Kubernetes会根据资源的当前状态找出解决方案)来控制Kubernetes。因此,Kubernetes API reference主要是资源类型及其相关操作的列表。\nKubernetes API对于内部组件和外部用户的重复操作是Kubernetes的基本设计概念。现在,我们来总结一下Kubernetes如何工作的:\n 存储后端存储(如,资源)Kubernetes的状态 API server以Kubernetes API的形式提供到存储后端的接口 所有其他Kubernetes的组件和用户通过Kubernetes API读取、监视以及操控Kubernetes的状态(如资源)。 2. 配置bash补全 sudo apt-get install bash-completion # or yum install bash-completion kubectl completion bash bash-completion安装完成之后,你需要进行一些设置,以便在所有的shell会话中获得kubectl补全脚本。一种方法是将以下命令行添加到~/.bashrc文件中:\nsource \u0026lt;(kubectl completion bash) 另一种可能性是将kubectl补充脚本添加到/etc/bash_completion.d目录中(如果不存在,则需要先创建它):\nkubectl completion bash \u0026gt;/etc/bash_completion.d/kubectl # /etc/bash_completion.d目录中的所有补全脚本均由bash-completion自动提供。 以上两种方法都是一样的效果。重新加载shell后,kubectl命令补全就能正常工作啦!\n3. 查看API规范 当你创建YAML资源定义时,你需要知道字段以及这些资源的意义。kubectl提供了kubectl explain命令,它能在你的terminal中正确地输入所有资源规范。\nkubectl explain resource[.field]... # 该命令输出所请求资源或字段的规范。kubectl explain所显示的信息与API reference中的信息相同。默认情况下,kubectl explain仅显示单个级别的字段。你可以使用--recursive标志来显示所有级别的字段: 4. 使用自定义列输出格式 自定义列输出选项的用法如下:\n-o custom-columns=\u0026lt;header\u0026gt;:\u0026lt;jsonpath\u0026gt;[,\u0026lt;header\u0026gt;:\u0026lt;jsonpath\u0026gt;]... 你必须将每个输出列定义为:对:\n是列的名称,你可以选择任何所需的内容。\r是一个选择资源字段的表达式\r示例: # Select all elements of a list kubectl get pods -o custom-columns=\u0026#39;DATA:spec.containers[*].image\u0026#39; # Select a specific element of a list kubectl get pods -o custom-columns=\u0026#39;DATA:spec.containers[0].image\u0026#39; # Select those elements of a list that match a filter expression kubectl get pods -o custom-columns=\u0026#39;DATA:spec.containers[?(@.image!=\u0026#34;nginx\u0026#34;)].image\u0026#39; # Select all fields under a specific location, regardless of their name kubectl get pods -o custom-columns=\u0026#39;DATA:metadata.*\u0026#39; # Select all fields with a specific name, regardless of their location kubectl get pods -o custom-columns=\u0026#39;DATA:..image\u0026#39; kubectl get po -o custom-columns=\u0026#39;name:metadata.name,image:spec.containers[0].image\u0026#39; kubectl get nodes -o yaml # or kubectl get nodes -o json 3. 利用kubectl切换命名空间 当kubectl必须向Kubernetes API发出请求时,它将读取系统上的所谓kubeconfig文件,以获取它需要访问的所有连接参数并向API服务器发出请求。\n默认的kubeconfig文件是~/.kube/config。该文件通常是通过某些命令自动创建或更新的.\n使用多个集群时,在kubeconfig文件中配置了多个集群的连接参数。这意味着,你需要一种方法来告诉kubectl要将其连接到哪些集群中。\n在集群中,您可以设置多个命名空间(命名空间是物理集群中的一种“虚拟”集群)。Kubectl还可确定kubeconfig文件中用于请求的命名空间。因此,你需要一种方法来告诉kubectl你要使用哪个命名空间。\n请注意,您还可以通过在KUBECONFIG环境变量中列出它们,以拥有多个kubeconfig文件。在这种情况下,所有这些文件将在执行时合并为一个有效的配置。你还可以为每个kubectl命令使用**\u0026ndash;kubeconfig**选项覆盖默认的kubeconfig文件。\n# 这里面大部分要使用到fzf。当然也可以不使用,直接写就OK # Get current context alias krc=\u0026#39;kubectl config current-context\u0026#39; # List all contexts alias klc=\u0026#39;kubectl config get-contexts -o name | sed \u0026#34;s/^/ /;\\|^ $(krc)$|s/ /*/\u0026#34;\u0026#39; # Change current context alias kcc=\u0026#39;kubectl config use-context \u0026#34;$(klc | fzf -e | sed \u0026#34;s/^..//\u0026#34;)\u0026#34;\u0026#39; # Get current namespace alias krn=\u0026#39;kubectl config get-contexts --no-headers \u0026#34;$(krc)\u0026#34; | awk \u0026#34;{print \\$5}\u0026#34; | sed \u0026#34;s/^$/default/\u0026#34;\u0026#39; # List all namespaces alias kln=\u0026#39;kubectl get -o name ns | sed \u0026#34;s|^.*/| |;\\|^ $(krn)$|s/ /*/\u0026#34;\u0026#39; # Change current namespace alias kcn=\u0026#39;kubectl config set-context --current --namespace \u0026#34;$(kln | fzf -e | sed \u0026#34;s/^..//\u0026#34;)\u0026#34;\u0026#39; ## 要想快速切换到不同的命名空间,可以设置以下别名: alias kcd=\u0026#39;kubectl config set-context $(kubectl config current-context)--namespace\u0026#39; # 然后,可以使用kcd some-namespace在命名空间之间进行切换。 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/linux/kubernetes/",
"title": "Kubernetes",
"tags": [],
"description": "Kubernetes",
"content": " K8S使用技巧 \r\rK8S概念 \r\rK8S环境配置记录 \r\rK8S资源模板 \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/programing/go/note/july/mod/",
"title": "mod",
"tags": [],
"description": "go modules 使用方法",
"content": " mod\r\rgo mod能管理的依赖包的版本,能保证在不同地方构建,获得的依赖模块是一致的。\r\r\r\r1. 常用代理地址 https://mirrors.aliyun.com/goproxy/ //阿里云镜像 https://goproxy.io https://goproxy.cn //七牛云镜像 https://athens.azurefd.net //微软镜像 2. 常用命令 * go mod init # 初始化当前目录为模块根目录,生成go.mod, go.sum文件 * go mod download # 下载依赖包 * go mod tidy # 整理检查依赖,如果缺失包会下载或者引用的不需要的包会删除 * go mod vendor # 复制依赖到vendor目录下面 * go mod # 可看完整所有的命令 ## 如果发现编译不过,可以通过下面命令列出指定mod的版本,然后go get拉指定版本号的包 go list -m -versions rsc.io/sampler go get rsc.io/sampler@v1.3.1 补充 用 go get 拉取新的依赖\r拉取最新的版本(优先择取 tag):go get golang.org/x/text@latest\r拉取 master 分支的最新 commit:go get golang.org/x/text@master\r拉取 tag 为 v0.3.2 的 commit:go get golang.org/x/text@v0.3.2\r拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2:go get golang.org/x/text@342b2e\r用 go get -u 更新现有的依赖\r用 go mod tidy 整理现有的依赖\r用 go mod graph 查看现有的依赖结构\r用 go get -u 更新现有的依赖\r用 go mod download 下载 go.mod 文件中指明的所有依赖\r用 go mod tidy 整理现有的依赖\r用 go mod edit 编辑 go.mod 文件\r用 go mod vendor 导出现有的所有依赖 (事实上 Go modules 正在淡化 Vendor 的概念)\r用 go mod verify 校验一个模块是否被篡改过\r"
},
{
"uri": "https://liuzeng01.github.io/essay/july/toml/",
"title": "toml",
"tags": [],
"description": "Daily Note",
"content": " TOML是前GitHub CEO, Tom Preston-Werner,于2013年创建的语言,其目标是成为一个小规模的易于使用的语义化配置文件格式。TOML被设计为可以无二义性的转换为一个哈希表(Hash table)。TOML是大小写敏感的,必须是UTF-8编码。\ntitle = \u0026#34;TOML Example\u0026#34; [owner] name = \u0026#34;Lance Uppercut\u0026#34; dob = 1979-05-27T07:32:00-08:00 # 日期是一等公民 [database] server = \u0026#34;192.168.1.1\u0026#34; ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true [servers] #你可以使用空格、制表符进行缩进,或者根本不缩进。TOML不关心缩进。 [servers.alpha] ip = \u0026#34;10.0.0.1\u0026#34; dc = \u0026#34;eqdc10\u0026#34; [servers.beta] ip = \u0026#34;10.0.0.2\u0026#34; dc = \u0026#34;eqdc10\u0026#34; [clients] data = [ [\u0026#34;gamma\u0026#34;, \u0026#34;delta\u0026#34;], [1, 2] ] # 数组内可以混入换行符 hosts = [ \u0026#34;alpha\u0026#34;, \u0026#34;omega\u0026#34; ] 注释 注释使用#开头\n基本字符串 字符串使用 \u0026quot; \u0026quot;包裹,所有Unicode字符均可出现,除了双引号、反斜线、控制字符(U+0000 to U+001F)需要转义。\n多行字符串 多行字符串使用\u0026quot;\u0026quot;\u0026quot;包裹,换行会进行保留\n字面量字符串 使用' ' 包裹,内部不允许转义,例如 winpath = 'C:\\Users\\nodejs\\templates'\n多行-字面量字符串 使用''' ''' 进行包裹\n日期 使用此种格式 date1 = 1979-05-27T07:32:00Z \n数组 数组使用方括号包裹。空格会被忽略,包括换行符。元素使用逗号分隔。\narr1 = [ 1, 2, 3 ] arr2 = [ \u0026#34;red\u0026#34;, \u0026#34;yellow\u0026#34;, \u0026#34;green\u0026#34; ] arr3 = [ [ 1, 2 ], [3, 4, 5] ] 表格(注意概念) 表格叶称为哈希表或字典,用来存储键值对。表格名由方括号包裹,且自成一行。\n[database] server = \u0026#34;192.168.1.1\u0026#34; ports = [ 8001, 8001, 8002 ] connection_max = 5000 enabled = true [servers] #你可以使用空格、制表符进行缩进,或者根本不缩进。TOML不关心缩进。 [servers.alpha] ip = \u0026#34;10.0.0.1\u0026#34; dc = \u0026#34;eqdc10\u0026#34; [servers.beta] ip = \u0026#34;10.0.0.2\u0026#34; dc = \u0026#34;eqdc10\u0026#34; "
},
{
"uri": "https://liuzeng01.github.io/programing/go/github.com/rivo/tview/",
"title": "tview",
"tags": [],
"description": "github.com/rivo/tview",
"content": " github.com/rivo/tview 1. 常用的控件 TextView: A scrollable window that display multi-colored text. Text may also be highlighted. Table: A scrollable display of tabular data. Table cells, rows, or columns may also be highlighted. TreeView: A scrollable display for hierarchical data. Tree nodes can be highlighted, collapsed, expanded, and more. List: A navigable text list with optional keyboard shortcuts. InputField: One-line input fields to enter text. DropDown: Drop-down selection fields. Checkbox: Selectable checkbox for boolean values. Button: Buttons which get activated when the user selects them. Form: Forms composed of input fields, drop down selections, checkboxes, and buttons. Modal: A centered window with a text message and one or more buttons. Grid: A grid based layout manager. Flex: A Flexbox based layout manager. Pages: A page based layout manager. "
},
{
"uri": "https://liuzeng01.github.io/linux/shell/vi/",
"title": "Vim快捷键",
"tags": [],
"description": "Shell",
"content": " 1. 基本操作 i :插入 :命令行模式 esc :退出到普通模式 2. 方向快捷键 j 向上一行 k 向下一行 h 向左一个字节 l 向右一个字节 0 跳转到行头 $ 跳转行尾 w 向后一个词 b 向前一个词 G 移动到文件末尾 gg 移动到文件开头 3. 文本快捷键 ( 跳转到上一句 ) 跳转到下一句 [[ 跳转到上一部分 ]] 跳转到下一部分 [] 上一部分的末尾 ][ 上一部分的开头 4. 插入文本快捷键 a 光标后插入 A 行末插入 i 光标前插入 o 下方新开一行 O 上方新开一行 5. 特殊插入 :r [filename] 在光标后方插入文件filename的内容 :r ![command] 执行命令 [command] ,并将输出插入至光标下方 6. 删除文本 dd 删除一行 d0 删至行首 d$ 删至行尾 3dd 删除三行 dgg 删至开头 dG 删至末尾 dw 删除一个词 R 进入复写模式 7. 撤销操作 u 撤销上一步操作 ctrl + r 取消撤销 yy 复制一行 8. 搜索操作 /search 在之后的文本搜索 ?search 在之前的文本搜索 n 移动到下一个搜索结果 N 移动到前一个搜索结果 :%s/original/replacement/g 检索并将所有的 “original” 替换为 “replacement” :%s/original/replacement\t检索第一个 “original” 字符串并将其替换成 “replacement” :%s/original/replacement/gc 检索出所有的 “original” 字符串,但在替换成 “replacement” 前,先询问是否替换 9. 书签操作 m {a-zA-Z} 在当前光标位置设置书签,书签名可用一个大小写字母({a-zA-Z}) :marks 列出所有书签 {a-zA-Z} 跳转到书签 10. 退出命令 :q\t退出 Vim,如果文件已被修改,将退出失败 :w\t保存文件 :w new_name\t用 new_name 作为文件名保存文件 :wq\t保存文件并退出 Vim :q!\t退出 Vim,不保存文件改动 ZZ\t退出 Vim,如果文件被改动过,保存改动内容 ZQ\t与 :q! 相同,退出 Vim,不保存文件改动 Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/categories/",
"title": "Categories",
"tags": [],
"description": "",
"content": ""
},
{
"uri": "https://liuzeng01.github.io/",
"title": "My Document",
"tags": [],
"description": "",
"content": " Linux \r\rEssay \r\rBookNote \r\rProgramming \r\r\r Back\r\r"
},
{
"uri": "https://liuzeng01.github.io/tags/",
"title": "Tags",
"tags": [],
"description": "",
"content": ""
}]