-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgo_allinone.slide
executable file
·735 lines (588 loc) · 22.6 KB
/
go_allinone.slide
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
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
Go 原理到进阶
2017-9-15
吴小川
CDN产品部
* Gopher
.image images/gopher.svg
* Gopher
.image images/fiveyears.jpg 560 1000
* Go Philosophy
* Go Philosophy
- 简单+可组合
简单:(routine)
每个代码片完成独立的,单一的功能。
代码片放到单独的一个goroutine里面执行。
可组合:(cocurrency)
简单的代码片可相互组合,完成整体功能。
组合的对象可以是struct,也可以是interface。
注:routine之间通信的通道是channel。
* Go原理
- 协程
- Go调度
- Go GC
* Go原理
# 通用协程实现原理
*协程:*
1. 基于函数之间的长跳转setjmp/longjmp。
2. 函数栈分配在堆上,可动态伸缩。
3. 函数之间的上下文切换开销小。
.image images/go_threads.png
#https://stackoverflow.com/questions/5440128/thread-context-switch-vs-process-context-switch
* Go原理
# 当前go调度逻辑
*Go调度:*
┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│ │ │ │ │ │ │ │ │ │
├─┤ ├─┤ ├─┤ ├─┤ ├─┤ Global
│ │ │G│ │ │ │ │ │ │ state
├─┤ ├─┤ ├─┤ ├─┤ ├─┤
│G│ │G│ │G│ │ │ │G│
├─┤ ├─┤ ├─┤ ├─┤ ├─┤
│G│ │G│ │G│ │G│ │G│
└┬┘ └┬┘ └┬┘ └┬┘ └─┘
│ │ │ │
↓ ↓ ↓ ↓
┌─┬──────┐ ┌─┬──────┐ ┌─┬──────┐ ┌─┬──────┐ ┌────┐┌──────┐┌───────┐
│P│mcache│ │P│mcache│ │P│mcache│ │P│mcache│ │heap││timers││netpoll│
└┬┴──────┘ └┬┴──────┘ └┬┴──────┘ └┬┴──────┘ └────┘└──────┘└───────┘
│ │ │ │
↓ ↓ ↓ ↓
┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│M│ │M│ │M│ │M│ │M│ │M│ │M│
└─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
G - goroutine; P - logical processor; M - OS thread (machine)
* Go原理
# go调度图示
地鼠搬砖:
.image images/gopher.png
进程启动的时候生成一个主Gopher(M),并最多生成GOMAXPROCS(不大于系统逻辑核心数)个小车(P)。go命令随后创建goroutine(书,G)。砖太多,小车空闲,就创建新的地鼠M去搬砖。
.image images/gopher2.png
* Go原理
三色标记法:
1. 初始全部节点为white。
2. 将根节点标记为black,子节点标记为grey。
3. 递归将子节点作为根节点并标记为black,子节点标记为grey。
4. 最终节点只有white和black两种,其中white为没有被引用的节点,可以回收。
目标是:
1. 立即恢复用户协程。
2. 专门协程慢慢清理可以回收的内存节点,降低用户协程的阻塞时间。
* Go基础
- 基础语法
- 语句
- 包管理
- 数据类型
- 并发模型
* Go基础
- 基础语法
1.Go变量、结构体和函数声明及定义
变量声明:
.code codes/code.go /go spec1/,/go spec2/
* Go基础
结构体声明:
.code codes/code.go /go struct1/,/go struct2/
* Go基础
函数声明:
.code codes/code.go /go func1/,/go func2/
* Go基础
- 语句:
for语句:
.code codes/code.go /go for1/,/go for2/
* Go基础
defer语句:
.code codes/code.go /go defer1/,/go defer2/
* Go基础
switch语句:
.code codes/code.go /go switch/,/go switch2/
* Go基础
select语句:
.code codes/code.go /go select1/,/go select2/
* Go基础
- 包管理
代码行宽(errors.go):
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
* Go基础
变量及函数命名:
.code codes/code.go /go camel1/,/go camel2/
* Go基础
包初始化:
net/http/race.go
package http
func init() {
raceEnabled = true
}
* Go基础
包文件及目录命名:
net/http/
cgi/
internel/
httputil/
httptest/
header.go
client.go
request.go
response.go
bytes/
buffer.go
bytes.go
reader.go
bufio/
bufio.go
scan.go
尽量简短,少用下划线。
包结构:
包级别的类型放在顶层目录的文件中,文件名一般就是包名称。
* Go基础
- 数据类型
1.channel(goroutine间数据通信以及退出控制)
.play codes/9.go /func.waiter/,/endmain/
* Go基础
.play codes/9b.go /func.worker/,/endmain/
* Go基础
.play codes/10.go /func.worker/,/endmain/
* Go基础
2.map
实现结构是hash,成员无序。go里面对应的tree实现是treemap库。
3.struct
struct匿名字段也叫嵌入字段,类似于C++的继承,字段名称为该类型名称。go会把嵌入字段的成员提升到当前结构体作用域。基类成员类型可以是结构体类型或其指针类型。在声明该结构体并同时初始化时,需要嵌套构造。结构体不能递归嵌套,不管直接或间接。外层字段会覆盖内层相同名称字段。规则:
1). 嵌入字段类型为结构体类型,子类类型可以访问基类或基类指针的方法集合;子类指针可以访问基类指针的方法集合。
2). 嵌入字段类型为结构体指针,子类类型及子类指针都可以访问基类及基类指针的方法集合。
* Go基础
4.interface&method
接口类型包括一个method集合。
只需要该类型或其指针实现该接口定义的方法集合即可。
任意类型都实现空接口对象interface{}。
一个类型可以同时实现多个接口。
接口类型也可以像结构体一样实现嵌入/继承,递归除外。
method可以定义在任何类型上,除了指针和interface接口。
比如,可以在一个函数上定义一个方法。参考http包HandlerFunc。
* Go基础
5.func(闭包closure)
.code codes/code.go /Compose/,/print\(Compose/
.code codes/adder.go /START1/,/STOP1/
.play codes/adder.go /START2/,/STOP2/
* Go基础
- Go并发模型
并发:同时处理很多事情。关于程序结构。
并行:同时在做很多事情。关于程序执行。
Go模型:把程序分解成可独立执行的单元(goroutine);这些单元通过通信进行协作(channel)。
* Go基础
地鼠搬砖:一只地鼠
.image images/gophersimple1.jpg
一个地鼠,时间比较久。
* Go基础
地鼠搬砖:多只地鼠
.image images/gophersimple3.jpg
地鼠够了,小车不够。
* Go基础
地鼠搬砖:多只地鼠,多个小车
.image images/gophersimple2.jpg
砖堆和窑会有瓶颈,还需要协调地鼠。
* Go基础
地鼠搬砖:加倍砖堆和窑
移除瓶颈
.image images/gophersimple4.jpg
当前的并发组合包含两个地鼠过程。
* Go基础
并发组合
当前设计并不自动并行,但是自动可并行的。这也暗示了其他模型。
* Go基础
新的设计
.image images/gophercomplex0.jpg
三只地鼠,每个地鼠独立执行完成相同的工作,相互之间需要协调。
* Go基础
新的设计
新加一只地鼠还车
.image images/gophercomplex1.jpg
比最初一只地鼠工作快4倍。
* Go基础
思考:
我们通过添加并发过程到当前设计来提高性能。
4种不同的并发过程:
#* Concurrent procedures
1.装载
2.运输
3.卸载
4.归还
不同的并发设计产生不同的并行化方式。
* Go基础
进一步并行化:八只地鼠
.image images/gophercomplex2.jpg
即使同时只有一只地鼠在工作,程序也是并发的。
* Go基础
另一种设计:两只地鼠+临时工作台
.image images/gophercomplex3.jpg
* Go基础
正常并行化:垂直
.image images/gophercomplex4.jpg
* Go基础
或者将临时工作台加入到多只地鼠的模型:水平
.image images/gophercomplex5.jpg
* Go基础
彻底优化:垂直+水平
.image images/gophercomplex6.jpg
* Go基础
垂直:
┌──────────────────────────────────────────────────┐
│ │
│ App │
├─────────┬─────────┬─────────┬─────────┬──────────┤
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ gopher1 │ gopher2 │ gopher3 │ gopher4 │ ... │
│ │ │ │ │ │
│ load │ move │ unload │ return │ │
│ │ │ │ │ │
│ │ │ │ │ │
└─────────┴─────────┴─────────┴─────────┴──────────┘
* Go基础
水平:
┌─────┬────────────────────────────────────────────┐
│ │ │
│ │ move books │
│ │ to staging │
│ │ │
│ │ │
│ App ├────────────────────────────────────────────┤
│ │ │
│ │ move books │
│ │ from staging │
│ │ to incinerator │
│ │ │
└─────┴────────────────────────────────────────────┘
* Go基础
垂直+水平
┌─────┬───────────┬───────────┬──────────┬─────────┐
│ │ │ │ │ │
│ │ gopher1 │ gopher2 │ gopher3 │ gopher4 │
│ │ loads │moves books│ unloads │ return │
│ │ books │to staging │ books │ back │ stage 1
│ │ │ │ │ carts │
│ │ │ │ │ │
│ App ├───────────┼───────────┼──────────┼─────────┤
│ │ │ │ │ │
│ │ gopher5 │ gopher6 │ gopher7 │ gopher8 │
│ │loads books│moves books│ unloads │ returns │
│ │ from │ to │ books │ carts │ stage 2
│ │ staging │incinerator│ │ to │
│ │ │ │ │ staging │
└─────┴───────────┴───────────┴──────────┴─────────┘
* Go实战
- go 命令
- cgo
- vendor
- 配置文件
- 命名返回参数
- log
- web framework
* Go实战
- go 命令
1. go build
2. go install
3. go test
4. go run
5. go get
6. go fmt
7. go vet
* Go实战
8. go doc
// Join concatenates the elements of elem to create a single string.
// The separator string sep is placed between elements in the resulting string.
func Join(elem []string, sep string) string {
godoc 会抽取这些注释,显示到web页面:
.image images/godoc.png
包注释放在package前面。
// Package fmt…
package fmt
* Go实战
9. go tool (trace, pprof, present)
.code codes/snippets.go /go pprof1/,/go pprof2/
.play codes/fibonacci.go /fmt.Println\(fibonacci/
(pprof) top 5
10.43s of 10.43s total ( 100%)
flat flat% sum% cum cum%
10.43s 100% 100% 10.43s 100% main.fibonacci
0 0% 100% 10.43s 100% main.main
0 0% 100% 10.43s 100% runtime.goexit
0 0% 100% 10.43s 100% runtime.main
* Go实战
(pprof) list
Total: 10.43s
ROUTINE ======================== main.fibonacci in /home/wuxc/godoc/go/test.go
10.43s 14.33s (flat, cum) 100% of Total
. . 15: pprof.StartCPUProfile(f)
. . 16: fmt.Println(fibonacci(45))
. . 17: pprof.StopCPUProfile()
. . 18:}
. . 19:
3.15s 3.15s 20:func fibonacci(n int) int {
210ms 210ms 21: if n < 2 {
540ms 540ms 22: return n
. . 23: }
6.53s 10.43s 24: return fibonacci(n-1) + fibonacci(n-2)
. . 25:}
ROUTINE ======================== main.main in /home/wuxc/godoc/go/test.go
0 10.43s (flat, cum) 100% of Total
. . 11: f, err := os.Create("cpu-profile.prof")
. . 12: if err != nil {
. . 13: log.Fatal(err)
. . 14: }
. . 15: pprof.StartCPUProfile(f)
. 10.43s 16: fmt.Println(fibonacci(45))
. . 17: pprof.StopCPUProfile()
* Go实战
pprof:
.image images/pprof.png
* Go实战
trace:
.image images/trace.png
* Go实战
- cgo
import "C"之前存放被注释的C代码。详细参考https://golang.org/cmd/cgo/。
使用cgo命令添加C编译选项,如// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo。
- vendor目录
解决各个项目不同版本库代码冲突问题。
在src目录建立vendor目录,项目使用的特定版本库和不同源的包目录。
go优先去vendor目录搜索。
src/
cgi/
internel/
vendor/
github.com/
golang.org/
gopkg.in/
foo.go
bar.go
* Go实战
- 配置文件定义
.code codes/code.go /go jsonconf1/,/go jsonconf2/
* Go实战
配置文件
.code codes/code.go /go jsonfile1/,/go jsonfile2/
* Go实战
使用:
.code codes/code.go /go jsonparse1/,/go jsonparse2/
* Go实战
- 函数:返回命名参数
.code codes/code.go /go no named var1/,/go no named var2/
* Go实战
使用命名返回参数:
.code codes/code.go /go named var1/,/go named var2/
* Go实战
- defer
.code codes/code.go /go defer3/,/go defer4/
* Go实战
defer代码:
.code codes/code.go /go defer5/,/go defer6/
* Go实战
- log库
seelog:
支持同步/异步(获取频率:循环,定时,自适应);支持丰富的输出级别,格式,终端和颜色;支持不同级别log的文件过滤;支持log文件回滚;支持动态更新logger设置。
自定义:
runtime.Caller(1)返回调用函数所在的全文件路径名和行号;添加前缀log.SetPrefix(); os.Getpid()获取PID等。
* Go实战
好的log格式示例:
.image images/goskill_srslog.png 400 1000
说明:基本信息包括标识pid和tid,错误级别,错误号等。需要唯一标识每一个request。
错误号定义:分类定义,如http 1xx-5xx。
* Go实战
- web framework(beego, iris, martini)
Beego:使用MVC+ORM+RESTful,组件齐全,使用方便;框架比较臃肿,实现性能不高。
Iris: 速度最快的web framework,简单。
MVC:
.image images/goskill_mvc.png 400 500
思想:
将数据与表现解耦,提高代码的复用性。
* Go进阶
- 多态(接口)
- Redis 数据结构
- Mysql+ORM
- JWT/OAuth2.0
#- Iris 框架思路
#- Microservices
- Kubernetes
* Go进阶
- 多态(接口)
接口定义:
.code codes/code.go /Interface1/,/Interface2/
接口实现:
.code codes/code.go /Interface2/,/Interface3/
接口可以被任意类型实现。
* Go进阶
函数实现接口
.code codes/code.go /HandlerFunc1/,/HandlerFunc2/
* Go进阶
接口组合:
.code codes/code.go /Interface3/,/Interface4/
接口使用:
.code codes/code.go /Interface4/,/Interface5/
链式组合:
.play codes/chain.go /LogReader{io/
* Go进阶
接口将 _数据_ 和 _行为_ 分离(class混合它们)
函数不仅可以操作 _数据_,还可以操作 _行为_ 。
// Copy copies from src to dst until either EOF is reached
// on src or an error occurs. It returns the number of bytes
// copied and the first error encountered while copying, if any.
func Copy(dst Writer, src Reader) (written int64, err error) {
.play codes/chain.go /LogReader{io/
* Go进阶
动态分发:
.play codes/interface.go /START/,/STOP/
接口类型可指向任意实现该接口的变量。
* Go进阶
- Redis 数据结构
Dict: String, Set, Hash (Int Set使用intset)
Deque: List
Skip-List: Sorted Set
Hash:
┌──┐┌──┐┌──┐┌──┐ ┌──┐
│k1││k2││k3││k4│ ... │km│ M slots
└──┘└──┘└──┘└──┘ └──┘
↓ ↓ ↓ ↓ ↓
┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
│v│ │v│ │v│ │v│ │v│
├─┤ ├─┤ └─┘ ├─┤ ├─┤
│v│ │v│ │v│ │v│
├─┤ ├─┤ └─┘ ... ├─┤ N conflicts
│v│ │.│ │v│
└─┘ │.│ └─┘
│.│
├─┤
│v│
└─┘
#/* This is our hash table structure. Every dictionary has two of this as we
# * implement incremental rehashing, for the old to the new table. */
#typedef struct dictht {
# dictEntry **table;
# unsigned long size;
# unsigned long sizemask;
# unsigned long used;
#} dictht;
* Go进阶
双buffer:
.code codes/code.go /Redis1/,/Redis2/
元素总数/桶数量比值大于阈值(5),进行rehash,size=2*size。
采用增量拷贝的方式完成全部rehash,每次拷贝一个bucket。dict切换到新的hash指针。
#每次查询key时,要查询两个ht。
Zset:
typedef struct zset {
dict *dict; // 快速检索单个member
zskiplist *zsl; // 获取连续范围member
} zset;
dict的score地址指向zsl里面node的score地址。
* Go进阶
- Mysql+ORM
定义:
.code codes/code.go /Model1/,/Model2/
* Go进阶
Create:
.code codes/code.go /Model2/,/Model3/
Retrieve:
.code codes/code.go /Model3/,/Model4/
* Go进阶
Update:
.code codes/code.go /Model4/,/Model5/
Delete:
.code codes/code.go /Model5/,/Model6/
#* Go进阶
#- RPC/RESTful
#http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
#```seq
#Client->Server: username/passwd
#Server->Auth Server: username/passwd
#Note right of Auth Server: check
#Auth Server-->Server: ok
#Note right of Server: create JWT
#Server-->Client: JWT
#Note left of Client: store JWT
#Client->Server: JWT
#Note right of Server: validate
#Server-->Client: data
#```
* Go进阶
- JWT(JSON Web Token)/OAuth2.0
.image images/goadvance_jwt.png
* Go进阶
.image images/jwtio.png 560 1000
#```seq
#Client->Resource Owner: Authorization Request
#Resource Owner-->Client: Authorization Grant
#Client->Authorization Server: Authorization Grant
#Authorization Server-->Client: Access Token
#Client->Resource Server: Access Token
#Resource Server-->Client: Protected Resource
#```
* Go进阶
OAuth2.0:
.image images/go_oauth2.png
* Go进阶
- etcd
一个可靠的,分布式k-v集群存储系统。解决:
1. 网络分区时的leader选举问题。
2. 可以监测配置是否改变,用于配置分发。
3. 可以用于服务发现及负载均衡。
4. 分布式锁。
.link https://github.com/coreos/etcd/ https://github.com/coreos/etcd/
* Go进阶
- Kubernetes(K8s):
Kubernetes是一个自动化部署,伸缩和管理容器化应用的系统。重要概念:
1.Kubernetes Master
k8s主控节点,全部控制的入口。状态结果存入etcd。
2.etcd
存储服务的状态,配置,命名空间和控制信息。
3.scheduler
根据当前集群资源情况和部署配置来生成部署策略。
4.Node/Minion
k8s工作节点,可以是物理机或虚拟机。
5.Pod
容器集合。
6.Container
容器单元,如docker或rocket。
* Go进阶
7.Replication Controller
复制控制器,控制pod数量。
8.Service
一组相同功能pod的抽象。
9.Label
对pod附加属性,方便区分。
10.kubelet
master节点的代理,与master通信,从etcd获取服务配置。
11.kube-proxy
负责Node内网络路由。
12.kubectl
命令行工具,与api server。
* Go进阶
.image images/k8s_arch.png
#https://x-team.com/blog/introduction-kubernetes-architecture/
#https://developer.adroitlogic.com/ips/docs/17.07/architecture/kubernetes-architecture.html
#http://www.360doc.com/content/16/0402/09/32165413_547246334.shtml
#https://mp.weixin.qq.com/s?__biz=MzIzMzExNDQ3MA==&mid=402412524&idx=1&sn=61962944124a5372e7c105c1c6d16a9e&scene=21#wechat_redirect
*
参考资料:
.link https://golang.org/ref/spec https://golang.org/ref/spec (go标准规范)
.link https://golang.org/doc/effective_go.html https://golang.org/doc/effective_go.html (高效go)
.link https://github.com/golang/talks https://github.com/golang/talks (go语言设计文档及会议)
.link https://github.com/golang/oauth2 https://github.com/golang/oauth2
.link https://github.com/golang/perf https://github.com/golang/perf
.link https://godoc.org/golang.org/x/tools/present https://godoc.org/golang.org/x/tools/present (present工具)
.link https://jwt.io/ https://jwt.io(jwt)
.link https://oauth.net/2/ https://oauth.net/2 (oauth2.0)
.link https://tour.golang.org/list https://tour.golang.org/list (golang语法示例)
#.image images/blank.png 100 600
Questions?