├── .gitignore
├── README.md
├── code
├── base
│ ├── concurrent
│ │ ├── channel
│ │ │ └── base-channel.go
│ │ ├── goroutine
│ │ │ ├── base-goroutine.go
│ │ │ ├── trace
│ │ │ │ └── trace.go
│ │ │ └── trace2
│ │ │ │ ├── trace2
│ │ │ │ └── trace2.go
│ │ ├── lock
│ │ │ ├── base-lock.go
│ │ │ ├── cond
│ │ │ │ └── base-cond.go
│ │ │ ├── mutex
│ │ │ │ └── base-mutex.go
│ │ │ ├── once
│ │ │ │ └── base-once.go
│ │ │ ├── rwlock
│ │ │ │ └── base-rwlock.go
│ │ │ └── waitgroup
│ │ │ │ └── base-waitgroup.go
│ │ └── timer
│ │ │ └── base-timer.go
│ ├── keyword
│ │ ├── defer
│ │ │ ├── order
│ │ │ │ └── defer-order.go
│ │ │ └── pre-param
│ │ │ │ └── defer-param.go
│ │ ├── for-range
│ │ │ └── for-range.go
│ │ ├── make-new
│ │ │ └── base-make-new.go
│ │ ├── panic-recover
│ │ │ ├── multi
│ │ │ │ └── panic-recover.go
│ │ │ └── recover
│ │ │ │ ├── base-recover.go
│ │ │ │ └── base-recover2.go
│ │ └── select
│ │ │ └── base-select.go
│ ├── map
│ │ ├── base_map.go
│ │ └── struct
│ │ │ └── map_struct.go
│ ├── object
│ │ ├── interface
│ │ │ └── base-interface.go
│ │ └── struct
│ │ │ └── base-struct.go
│ ├── string
│ │ ├── base_string.go
│ │ ├── equal
│ │ │ └── string-equal.go
│ │ └── string-join
│ │ │ └── string-join.go
│ └── test
│ │ └── go-test.md
├── middleware
│ └── gorm
│ │ └── base
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
├── reflect
│ └── proxy
│ │ └── main.go
└── test
│ └── map_test.go
├── md
├── base
│ ├── array
│ │ └── array-slice.md
│ ├── concurrent
│ │ ├── channel.md
│ │ ├── goroutine.md
│ │ ├── lock.md
│ │ ├── net-poller.md
│ │ └── timer.md
│ ├── keyword
│ │ ├── defer.md
│ │ ├── for-range.md
│ │ ├── make-vs-new.md
│ │ ├── panic-and-recover.md
│ │ └── select.md
│ ├── map
│ │ └── map.md
│ ├── object
│ │ ├── interface.md
│ │ └── struct.md
│ ├── reflect
│ │ ├── reflect-base.md
│ │ └── static-proxy.md
│ ├── source
│ │ └── debug.md
│ └── string
│ │ └── string.md
├── c-cpp-golang
│ ├── base-c++.md
│ ├── base-c.md
│ └── c-c++-golang.md
├── middleware
│ ├── es
│ │ ├── elk.md
│ │ └── es-base.md
│ ├── kafka
│ │ ├── kafka-base.md
│ │ ├── kafka-bigdata.md
│ │ └── kafka-log.md
│ ├── mongo
│ │ └── mongo-base.md
│ ├── mysql
│ │ ├── mysql-advance.md
│ │ ├── mysql-base.md
│ │ ├── mysql-debug-source.md
│ │ ├── mysql-ha.md
│ │ ├── mysql-probles.md
│ │ ├── mysql-select-flow.md
│ │ ├── mysql-viewer.md
│ │ └── mysql8-optimize.md
│ ├── nginx
│ │ ├── nginx-1.24.0.md
│ │ ├── nginx-base.md
│ │ └── nginx-http.md
│ ├── pgsql
│ │ └── pgsql-base.md
│ ├── rabbitmq
│ │ └── rabbitmq-bases.md
│ ├── redis
│ │ ├── redis-base.md
│ │ ├── redis-data-structure.md
│ │ ├── redis-db.md
│ │ ├── redis-statistic-analysis.md
│ │ └── redis4-data-structure.md
│ └── rpc
│ │ ├── c-grpc-go.md
│ │ └── socket.md
├── other
│ ├── cgo.md
│ ├── electron.md
│ ├── go-ebpf.md
│ ├── linux-core-debug.md
│ ├── prometheus-grafana.md
│ └── ubuntu-kernel-debug.md
├── performence
│ └── performance-test.md
└── web
│ ├── gin
│ ├── gin-bind.md
│ ├── gin-middleware.md
│ └── gin-router.md
│ └── gorm
│ ├── base-gorm.md
│ └── flow-gorm.md
└── res
├── B+Tree-Structure.png
├── C++IO关系图.png
├── GMP-model.jpg
├── MySQL-Triggers.png
├── SZT-bigdata-2+.png
├── adminer.png
├── adminer_create_table.png
├── adminer_insert_data.png
├── canal.png
├── centos-clion.png
├── channel-struct.jpg
├── clion-redis-makefile.png
├── const与指针.jpg
├── cpp代码存储区域.png
├── data
└── SZT-bigdata-master.zip
├── debug-redis.png
├── debug_go_source1.png
├── debug_source.png
├── ebpf-1.png
├── ebpf.png
├── electron-demo-1.png
├── elk-structure.png
├── es-head.png
├── exchange1-length.png
├── fiddle.png
├── flink-demo.png
├── flink-shouye.png
├── gdb_map.png
├── go-map-ds.jpg
├── go-trace-pic.png
├── golang
├── mysql_start_error_1.jpg
├── mysql_start_error_2.png
└── mysql_start_error_3.jpg
├── gorm filed.png
├── gotest-debug.png
├── hash-table-struct.png
├── hash_map.png
├── ht-table.png
├── hyperlog对比.png
├── if与switch对比.jpg
├── innodb-architecture.png
├── kafka-base-gainian.png
├── kafka-eagle.png
├── kafka-runtime-an.png
├── kernel-debug1.png
├── kernel-debug2.png
├── kernel-debug3.png
├── kernel-debug4.png
├── kernel-debug5.png
├── kibana.png
├── list-push.png
├── map_ast.png
├── model.png
├── mysql-debug.png
├── mysql-engine-feature.png
├── mysql-tool-1.jpeg
├── mysql-wireshark.png
├── nginx
└── nginx结构.png
├── other
├── cgo-err.png
├── cgo.png
├── fluentd.png
├── it.png
├── prometheus-1.png
├── prometheus-10.png
├── prometheus-11.png
├── prometheus-12.png
├── prometheus-13.png
├── prometheus-14.png
├── prometheus-15.png
├── prometheus-16.png
├── prometheus-17.png
├── prometheus-18.png
├── prometheus-2.png
├── prometheus-3.png
├── prometheus-4.png
├── prometheus-5.png
├── prometheus-6.png
├── prometheus-7.png
├── prometheus-8.png
├── prometheus-9.png
└── simple-scalable-test-environment.png
├── pgAdmin.png
├── pprof-block.png
├── pprof-data.png
├── pprof-file.png
├── pprof-gorotine.png
├── pprof-html.gif
├── pprof-mem.png
├── pprof-pdf.png
├── pprof-web-cpu.png
├── pretty-zoo.png
├── redis-hash-coding.png
├── redis-hash-ziplist.png
├── redis-hash.png
├── redis-ht-entry.png
├── redis-pipeline.png
├── redis-push-sub.png
├── redis4-hash.png
├── siphash.png
├── slice的调试切片.png
├── socket-struct.png
├── socket-tcp.png
├── stl-relationship.png
├── stl标准库.png
├── string-copy.png
├── string-modify.png
├── studio-3t.png
├── test-mysql-tables.png
├── ubuntu-clion.png
├── unique_ptr_delete.png
├── vector_struct.png
├── vs2015-vt.png
├── vscode-log.png
├── wiki-hash-table1.png
├── wiki-hash-table2.png
├── zookeeper-cli-eagle.png
├── zookeeper-cmd.png
├── zookeeper-eagle.png
├── 三种基本的分支语句.png
├── 大数据处理.png
├── 智能指针的分类.jpg
├── 栈与堆的对比.png
├── 结构体与共用体内存分布.png
├── 结构体内存布局.png
├── 结构体和共用体内存布局.jpg
├── 递归调用.jpg
├── 队列参数设置.png
└── 静态区存储.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | .idea
17 | .DS_Store
18 | __debug*
19 | .vscode
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | - **Golang基础知识**
2 | - [**源码调试**](./md/base/source/debug.md)
3 | - [**从汇编角度理解go**](https://github.com/ymm135/TD4-4BIT-CPU/blob/master/go-asm.md)
4 | - [**go/c/c++常用功能对应的汇编指令**](https://github.com/ymm135/go-build/blob/master/gouse-assembly.md)
5 |
6 | - **数据结构**
7 | - [内建容器简介](https://github.com/ymm135/go-coding/blob/main/docs/3_%E5%86%85%E5%BB%BA%E5%AE%B9%E5%99%A8.md)
8 | - [array/slice](md/base/array/array-slice.md)
9 | - [map](./md/base/map/map.md)
10 | - [字符串](./md/base/string/string.md)
11 | - [结构体](md/base/object/struct.md)
12 | - [接口](md/base/object/interface.md)
13 |
14 | - **常用关键字**
15 | - [for和range实现](md/base/keyword/for-range.md)
16 | - [defer数据结构及实现](md/base/keyword/defer.md)
17 | - [panic和recover实现](md/base/keyword/panic-and-recover.md)
18 | - [make和new差异](md/base/keyword/make-vs-new.md)
19 | - [select实现](md/base/keyword/select.md)
20 |
21 | - **并发编程**
22 | - [goroutine数据结构](md/base/concurrent/goroutine.md)
23 | - [channel实现](md/base/concurrent/channel.md)
24 | - [锁](md/base/concurrent/lock.md)
25 | - [定时器](md/base/concurrent/timer.md)
26 | - [网络轮询器(NetPoller)](md/base/concurrent/net-poller.md)
27 |
28 | - **反射**
29 | - [反射基础介绍](md/base/reflect/reflect-base.md)
30 | - [静态代理](md/base/reflect/static-proxy.md)
31 |
32 | - [**go编译器和链接器**](https://github.com/ymm135/go-build)
33 | - [**cgo调用c/c++**](https://github.com/ymm135/go-coding/blob/main/lang/c_cpp/README.md)
34 |
35 | - **反射**
36 | - [**gotest**]()
37 |
38 | - **Golang Web**
39 | - **gin**
40 | - [gin参数绑定](./md/web/gin/gin-bind.md)
41 | - [gin路由](./md/web/gin/gin-router.md)
42 | - [gin中间件](./md/web/gin/gin-middleware.md)
43 |
44 | - **gorm**
45 | - [gorm基础](md/web/gorm/base-gorm.md)
46 | - [gorm实现原理](md/web/gorm/flow-gorm.md)
47 |
48 | - **中间件**
49 | - **mysql**
50 | - [mysql日常问题整理](md/middleware/mysql/mysql-probles.md)
51 | - [mysql 5.7源码调试](md/middleware/mysql/mysql-debug-source.md)
52 | - [myql 基础知识](md/middleware/mysql/mysql-base.md)
53 | - [myql 高级进阶](md/middleware/mysql/mysql-advance.md)
54 | - [myql 5.7 查询流程分析](md/middleware/mysql/mysql-select-flow.md)
55 | - [mysql 5.7性能测试](https://github.com/ymm135/unixsoket-mysql-prof)
56 | - [mysql 8.0性能优化](md/middleware/mysql/mysql8-optimize.md)
57 | - [mysql 双机热备部署](md/middleware/mysql/mysql-ha.md)
58 | - **nginx**
59 | - [nginx基础](md/middleware/nginx/nginx-base.md)
60 | - [nginx http核心模块](md/middleware/nginx/nginx-http.md)
61 | - [nginx 1.24.0源码调试](md/middleware/nginx/nginx-1.24.0.md)
62 |
63 | - **es**
64 | - [es基础](md/middleware/es/es-base.md)
65 | - [ELK](md/middleware/es/elk.md)
66 |
67 | - **rabbitmq**
68 | - [rabbitmq基础](md/middleware/rabbitmq/rabbitmq-bases.md)
69 |
70 | - **kafka**
71 | - [kafka基础](md/middleware/kafka/kafka-base.md)
72 | - [日志采集及分析](md/middleware/kafka/kafka-log.md)
73 | - [大数据统计及分析](md/middleware/kafka/kafka-bigdata.md)
74 |
75 | - **redis**
76 | - [redis基础](md/middleware/redis/redis-base.md)
77 | - [redis3.0数据结构](./md/middleware/redis/redis-data-structure.md)
78 | - [redis4.x数据结构](./md/middleware/redis/redis4-data-structure.md)
79 | - [redis内存及性能测试](./md/middleware/redis/redis-db.md)
80 | - [redis数据统计分析](./md/middleware/redis/redis-statistic-analysis.md)
81 |
82 | - **mongo**
83 | - [mongo基础](md/middleware/mongo/mongo-base.md)
84 |
85 | - **pgsql**
86 | - [pgsql基础](md/middleware/pgsql/pgsql-base.md)
87 |
88 | - **RPC通信**
89 | - [ubuntu20+vscode调试linux内核](md/other/ubuntu-kernel-debug.md)
90 | - [Socket通信](md/middleware/rpc/socket.md)
91 | - [**gRPC实现原理**](https://github.com/ymm135/go-coding/blob/main/lang/rpc/grpc/README.md)
92 | - [c语言通过grpc与go通信](md/middleware/rpc/c-grpc-go.md)
93 |
94 | - **c/c++/go对比**
95 | - [c基础及进阶](md/c-cpp-golang/base-c.md)
96 | - [c++基础及进阶](md/c-cpp-golang/base-c++.md)
97 | - [c/c++/go对比](md/c-cpp-golang/c-c++-golang.md)
98 |
99 | - **性能**
100 | - [性能测试](md/performence/performance-test.md)
101 | - [性能优化go-perfbook](https://github.com/ymm135/go-perfbook)
102 | - [perf-tools](https://github.com/ymm135/perf-tools)
103 |
104 | - **扩展**
105 | - [go-ebpf](md/other/go-ebpf.md)
106 | - [cgo](md/other/cgo.md)
107 | - [prometheus + grafana](md/other/prometheus-grafana.md)
108 | - [electron](md/other/electron.md)
--------------------------------------------------------------------------------
/code/base/concurrent/channel/base-channel.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | ch := make(chan string, 1)
10 |
11 | go func() {
12 | ch <- "hello channel"
13 | ch <- "!"
14 | ch <- "quit"
15 | }()
16 |
17 | //需要延迟一段时间,要不然接收不到数据
18 | time.Sleep(time.Second)
19 |
20 | str := <-ch
21 | fmt.Println("recv:", str)
22 |
23 | for { //
24 | select {
25 | case str := <-ch:
26 | fmt.Println(str)
27 | if str == "quit" {
28 | goto end
29 | }
30 | default:
31 | fmt.Println("default")
32 | }
33 | }
34 | end:
35 | }
36 |
--------------------------------------------------------------------------------
/code/base/concurrent/goroutine/base-goroutine.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | for i := 0; i < 10; i++ {
10 | tag := i
11 |
12 | go func() {
13 | for {
14 | fmt.Println("goroutine:", tag)
15 | time.Sleep(time.Microsecond * 100)
16 | }
17 | }()
18 | }
19 | time.Sleep(time.Minute)
20 | }
21 |
--------------------------------------------------------------------------------
/code/base/concurrent/goroutine/trace/trace.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "runtime/trace"
7 | )
8 |
9 | func main() {
10 |
11 | //创建trace文件
12 | f, err := os.Create("trace.out")
13 | if err != nil {
14 | panic(err)
15 | }
16 |
17 | defer f.Close()
18 |
19 | //启动trace goroutine
20 | err = trace.Start(f)
21 | if err != nil {
22 | panic(err)
23 | }
24 | defer trace.Stop()
25 |
26 | //main
27 | fmt.Println("Hello World")
28 | }
29 |
--------------------------------------------------------------------------------
/code/base/concurrent/goroutine/trace2/trace2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/code/base/concurrent/goroutine/trace2/trace2
--------------------------------------------------------------------------------
/code/base/concurrent/goroutine/trace2/trace2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | for i := 0; i < 20; i++ {
10 | time.Sleep(time.Second)
11 | fmt.Println("Hello Go")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/base-lock.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | func main() {
10 | mutex := sync.Mutex{}
11 |
12 | go func() {
13 | // 不可重入锁
14 | mutex.Lock() // 同一个协程不能加锁多次
15 | mutex.Lock() // 一直循环获取锁
16 |
17 | fmt.Println("Write ...")
18 | }()
19 |
20 | go func() {
21 | time.Sleep(time.Second * 2)
22 | defer mutex.Unlock() // 不管Lock多少次,只需要解锁一次即可
23 |
24 | fmt.Println("Read ...")
25 | }()
26 |
27 | time.Sleep(time.Second * 4)
28 | }
29 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/cond/base-cond.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/signal"
7 | "sync"
8 | "sync/atomic"
9 | "time"
10 | )
11 |
12 | var status int64
13 |
14 | func main() {
15 | c := sync.NewCond(&sync.Mutex{})
16 | for i := 0; i < 10; i++ {
17 | go listen(c)
18 | }
19 | time.Sleep(1 * time.Second)
20 | go broadcast(c)
21 |
22 | ch := make(chan os.Signal, 1)
23 | signal.Notify(ch, os.Interrupt)
24 | <-ch
25 | }
26 |
27 | func broadcast(c *sync.Cond) {
28 | c.L.Lock()
29 | atomic.StoreInt64(&status, 1)
30 | c.Broadcast()
31 | c.L.Unlock()
32 | }
33 |
34 | func listen(c *sync.Cond) {
35 | c.L.Lock()
36 | for atomic.LoadInt64(&status) != 1 {
37 | c.Wait()
38 | }
39 | fmt.Println("listen")
40 | c.L.Unlock()
41 | }
42 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/mutex/base-mutex.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | func main() {
10 | mutex := &sync.Mutex{}
11 |
12 | go func() {
13 | fmt.Println("goroutine 1 enter")
14 | mutex.Lock()
15 | defer mutex.Unlock()
16 | time.Sleep(time.Second * 2)
17 | }()
18 |
19 | go func() {
20 | time.Sleep(time.Second)
21 | fmt.Println("goroutine 2 enter")
22 |
23 | mutex.Lock()
24 | defer mutex.Unlock()
25 | time.Sleep(time.Second * 4)
26 | }()
27 |
28 | go func() {
29 | time.Sleep(time.Second)
30 | fmt.Println("goroutine 3 enter")
31 |
32 | mutex.Lock()
33 | defer mutex.Unlock()
34 | time.Sleep(time.Second * 5)
35 | }()
36 |
37 | time.Sleep(time.Minute)
38 | }
39 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/once/base-once.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func main() {
9 | once := sync.Once{}
10 | for i := 0; i < 10; i++ {
11 | once.Do(func() {
12 | fmt.Println("once run ")
13 | })
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/rwlock/base-rwlock.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "time"
7 | )
8 |
9 | func main() {
10 | mutex := sync.RWMutex{}
11 | go func() {
12 | mutex.RLock()
13 | fmt.Println("RLock")
14 | }()
15 |
16 | go func() {
17 | time.Sleep(time.Second)
18 | mutex.Lock()
19 | fmt.Println("Lock")
20 | }()
21 |
22 | time.Sleep(time.Minute)
23 | }
24 |
--------------------------------------------------------------------------------
/code/base/concurrent/lock/waitgroup/base-waitgroup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | func main() {
9 | group := sync.WaitGroup{}
10 | num := 5
11 | group.Add(num)
12 |
13 | for i := 0; i < num; i++ {
14 | index := i
15 | go func() {
16 | fmt.Println("run goroutine ", index)
17 | group.Done()
18 | }()
19 | }
20 |
21 | group.Wait()
22 | }
23 |
--------------------------------------------------------------------------------
/code/base/concurrent/timer/base-timer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | // 1.延时执行
10 | fmt.Println("currTime=", time.Now().Format("2006-01-02 15:04:05"))
11 | // create a nobuf channel and a goroutine `timer` will write it after 2 seconds
12 | timeAfterTrigger := time.After(time.Second * 2)
13 | // will be suspend but we have `timer` so will be not deadlocked
14 | curTime, _ := <-timeAfterTrigger
15 | // print current time
16 | fmt.Println("timeAfter=", curTime.Format("2006-01-02 15:04:05"))
17 |
18 | // 2.定时执行
19 | // 创建一个计时器
20 | timeTicker := time.NewTicker(time.Second * 2)
21 | i := 0
22 | for {
23 | if i > 5 {
24 | break
25 | }
26 |
27 | fmt.Println("timeTicker=", time.Now().Format("2006-01-02 15:04:05"))
28 | i++
29 | <-timeTicker.C // 下次触发的时间
30 |
31 | }
32 | // 清理计时器
33 | timeTicker.Stop()
34 | }
35 |
--------------------------------------------------------------------------------
/code/base/keyword/defer/order/defer-order.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {
4 | defer println("A")
5 | defer println("B")
6 | defer println("C")
7 | }
8 |
--------------------------------------------------------------------------------
/code/base/keyword/defer/pre-param/defer-param.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | testParam1()
10 | testParam2()
11 | }
12 |
13 | func testParam1() {
14 | startedAt := time.Now()
15 | defer fmt.Println("testParam1=", time.Since(startedAt))
16 |
17 | time.Sleep(time.Second)
18 | }
19 |
20 | func testParam2() {
21 | startedAt := time.Now()
22 | defer func() { fmt.Println("testParam1=", time.Since(startedAt)) }()
23 |
24 | time.Sleep(time.Second)
25 | }
26 |
--------------------------------------------------------------------------------
/code/base/keyword/for-range/for-range.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | // string
10 | str := "I am String!"
11 | for i, s := range str {
12 | fmt.Println("[string](", i, ")=", string(s))
13 | }
14 |
15 | // array slice
16 | array := []int{1, 3, 5, 7, 9}
17 | for i, v := range array {
18 | // 也可以使用array[i]
19 | fmt.Println("array(", i, ")=", v)
20 | }
21 |
22 | // hash
23 | hashTable := make(map[string]string, 10)
24 | hashTable["a"] = "array"
25 | hashTable["b"] = "bar"
26 | hashTable["c"] = "car"
27 | for k, v := range hashTable {
28 | fmt.Println("[hash]", k, ":", v)
29 | }
30 |
31 | //channel
32 | ch := make(chan string, 10)
33 | go func() {
34 | ch <- "hello"
35 | ch <- "go"
36 | ch <- "!"
37 | }()
38 |
39 | time.Sleep(time.Second)
40 |
41 | // 如果不在协程中开启, fatal error: all goroutines are asleep - deadlock!
42 | go func() {
43 | for c := range ch {
44 | fmt.Println("[channel]", c)
45 | }
46 | }()
47 |
48 | time.Sleep(time.Second)
49 | }
50 |
--------------------------------------------------------------------------------
/code/base/keyword/make-new/base-make-new.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | // make
7 | slice := make([]int, 0, 100)
8 | hash := make(map[int]bool, 10)
9 | ch := make(chan int, 5)
10 |
11 | // new
12 | i := new(int)
13 | var v int
14 | i = &v
15 |
16 | fmt.Println(slice, hash, ch, i)
17 | }
18 |
--------------------------------------------------------------------------------
/code/base/keyword/panic-recover/multi/panic-recover.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | defer fmt.Println("in main")
7 | defer func() {
8 | defer func() {
9 | fmt.Println("panic again and again")
10 | panic("panic again and again")
11 | }()
12 | fmt.Println("panic again")
13 | panic("panic again")
14 | }()
15 |
16 | panic("panic once")
17 | }
18 |
--------------------------------------------------------------------------------
/code/base/keyword/panic-recover/recover/base-recover.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | defer fmt.Println("in main")
7 | defer func() {
8 | if err := recover(); err != nil {
9 | fmt.Println(err)
10 | }
11 | }()
12 |
13 | panic("unknown err")
14 | }
15 |
--------------------------------------------------------------------------------
/code/base/keyword/panic-recover/recover/base-recover2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | defer fmt.Println("in main")
7 | if err := recover(); err != nil { //不生效
8 | fmt.Println(err)
9 | }
10 |
11 | panic("unknown err")
12 | }
13 |
--------------------------------------------------------------------------------
/code/base/keyword/select/base-select.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | func main() {
9 | ch := make(chan string)
10 |
11 | go func() {
12 | ch <- "hello channel"
13 | ch <- "!"
14 | ch <- "quit"
15 | }()
16 |
17 | //需要延迟一段时间,要不然接收不到数据
18 | time.Sleep(time.Second)
19 |
20 | for { //
21 | select {
22 | case str := <-ch:
23 | fmt.Println(str)
24 | if str == "quit" {
25 | goto end
26 | }
27 | default:
28 | fmt.Println("default")
29 | }
30 | }
31 | end:
32 | }
33 |
--------------------------------------------------------------------------------
/code/base/map/base_map.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | m := map[string]string{
7 | "name": "ccmouse",
8 | "course": "golang",
9 | "site": "imooc",
10 | "quality": "notbad",
11 | }
12 | // map[]
13 | m2 := make(map[string]int) // m2 == empty map
14 | // map[]
15 | var m3 map[string]int // m3 == nil
16 |
17 | fmt.Println("m, m2, m3:")
18 | fmt.Println(m, m2, m3)
19 |
20 | name2Everything := make(map[string]interface{})
21 | name2Everything["xiaoming"] = 2
22 | name2Everything["xiaohong"] = "girl"
23 | name2Everything["age"] = 100
24 | name2Everything["1"] = 100
25 | name2Everything["2"] = 100
26 | name2Everything["3"] = 100
27 | name2Everything["4"] = 100
28 | name2Everything["5"] = 100
29 | name2Everything["6"] = 100
30 | name2Everything["7"] = 100
31 | name2Everything["8"] = 100
32 |
33 | fmt.Println(name2Everything)
34 | }
35 |
--------------------------------------------------------------------------------
/code/base/map/struct/map_struct.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | )
7 |
8 | func main() {
9 | m := map[string]string{
10 | "name": "ccmouse",
11 | "course": "golang",
12 | "site": "imooc",
13 | "quality": "notbad",
14 | }
15 |
16 | for i := 0; i < 21; i++ {
17 | m[strconv.Itoa(i)] = strconv.Itoa(i)
18 | }
19 |
20 | delete(m, "name")
21 | fmt.Println("Hello Go")
22 | }
23 |
--------------------------------------------------------------------------------
/code/base/object/interface/base-interface.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "unsafe"
6 | )
7 |
8 | type IMan interface {
9 | walk() int
10 | }
11 |
12 | type Man struct {
13 | name string
14 | age int
15 | }
16 |
17 | func (man *Man) walk() int {
18 | fmt.Println("walk name:", man.name, ",age:", man.age)
19 | return man.age
20 | }
21 |
22 | func main() {
23 | var test_interface interface{}
24 | man := Man{name: "xiaoming", age: 18}
25 | var iman IMan
26 |
27 | iman = &man
28 | iman.walk()
29 | man.walk()
30 |
31 | fmt.Printf("iman %T 占中的字节数是 %d \n", iman, unsafe.Sizeof(iman))
32 | fmt.Println(man, iman, test_interface)
33 | }
34 |
--------------------------------------------------------------------------------
/code/base/object/struct/base-struct.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Man struct {
6 | name string
7 | age int
8 | }
9 |
10 | func (man *Man) Walk() int {
11 | fmt.Println("man Walk")
12 | return 0
13 | }
14 |
15 | func main() {
16 | man := Man{name: "xiaoming", age: 18}
17 |
18 | var walkFun func() int
19 | walkFun = man.Walk
20 | walkFun()
21 | }
22 |
--------------------------------------------------------------------------------
/code/base/string/base_string.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | s := "HelloWorld!"
7 | sm := modifyString(s)
8 | fmt.Println("source(", &s, "):", s, ",midify(", &s, "):", sm)
9 | }
10 |
11 | func modifyString(s string) string {
12 | bs := []byte(s)
13 | bs[0] = 77
14 | return string(bs)
15 | }
16 |
--------------------------------------------------------------------------------
/code/base/string/equal/string-equal.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | s1 := "Hello World"
7 | s2 := s1
8 | s1 = "Hello Go"
9 | fmt.Println("s1:", s1, ",s2:", s2)
10 | }
11 |
--------------------------------------------------------------------------------
/code/base/string/string-join/string-join.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | s1 := "Hello "
7 | s2 := "World!"
8 | s := s1 + s2
9 | fmt.Println(s)
10 | }
11 |
--------------------------------------------------------------------------------
/code/base/test/go-test.md:
--------------------------------------------------------------------------------
1 | - # go test
2 |
3 | Go 以内置单元测试。具体来说,使用命名约定、Go 的`testing`包和`go test`命令,您可以快速编写和执行测试。
4 |
5 | ## 创建目录,文件已`*_test.go`命名
6 |
7 | `greetings_test.go`
8 |
9 | ```go
10 | package greetings
11 |
12 | import (
13 | "testing"
14 | "regexp"
15 | )
16 |
17 | // TestHelloName calls greetings.Hello with a name, checking
18 | // for a valid return value.
19 | func TestHelloName(t *testing.T) {
20 | name := "Gladys"
21 | want := regexp.MustCompile(`\b`+name+`\b`)
22 | msg, err := Hello("Gladys")
23 | if !want.MatchString(msg) || err != nil {
24 | t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
25 | }
26 | }
27 |
28 | // TestHelloEmpty calls greetings.Hello with an empty string,
29 | // checking for an error.
30 | func TestHelloEmpty(t *testing.T) {
31 | msg, err := Hello("")
32 | if msg != "" || err == nil {
33 | t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
34 | }
35 | }
36 | ```
37 |
38 | `Hello`函数
39 | ```go
40 | // Hello returns a greeting for the named person.
41 | func Hello(name string) (string, error) {
42 | // If no name was given, return an error with a message.
43 | if name == "" {
44 | return name, errors.New("empty name")
45 | }
46 | // Create a message using a random format.
47 | message := fmt.Sprintf(randomFormat(), name)
48 | return message, nil
49 | }
50 | ```
51 |
52 | ## 测试函数`TestXxx`
53 | 测试函数格式
54 | ```go
55 | func TestHelloEmpty(t *testing.T) {
56 | // ToDo
57 | }
58 | ```
59 |
60 | ## `go test`运行测试
61 |
62 | ```shell
63 | $ go test
64 | PASS
65 | ok example.com/greetings 0.364s
66 |
67 | $ go test -v
68 | === RUN TestHelloName
69 | --- PASS: TestHelloName (0.00s)
70 | === RUN TestHelloEmpty
71 | --- PASS: TestHelloEmpty (0.00s)
72 | PASS
73 | ok example.com/greetings 0.372s
74 | ```
75 |
76 | 测试指定函数
77 | ```shell
78 | go test -v greetings_test.go -test.run TestHelloEmpty
79 | ```
80 |
81 | ## 测试失败时函数断点
82 | `greetings.Hello`
83 |
84 | 可以Debug模式启动测试,在想要测试的地方进行断点调式
85 |
86 |
87 |
88 |

89 |
90 |
91 |
92 |
93 | ## 增加Flag
94 |
95 | ```go
96 | var (
97 | enableSysTests = flag.Bool("run_system_tests", false, "Run tests that operate against the live kernel")
98 | )
99 | ```
100 |
101 | ```shell
102 | go test -v -run_system_tests true -cpuprofile=prof.out
103 | ```
104 |
105 | 内置flag
106 | -cpu 1,2,4
107 | 指定执行测试或基准的 GOMAXPROCS 值的列表。默认为GOMAXPROCS的当前值。
108 |
109 | -failfast
110 | 第一次测试失败后,不要再开始新的测试。
111 |
112 | -list regexp
113 | 列出与正则表达式匹配的测试、基准或示例。
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/code/middleware/gorm/base/go.mod:
--------------------------------------------------------------------------------
1 | module ymm/gorm-base
2 |
3 | go 1.16
4 |
5 | require (
6 | gorm.io/driver/mysql v1.2.3
7 | gorm.io/gorm v1.22.5
8 | )
9 |
--------------------------------------------------------------------------------
/code/middleware/gorm/base/go.sum:
--------------------------------------------------------------------------------
1 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
2 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
3 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
4 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
5 | github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
6 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
7 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
8 | gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
9 | gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
10 | gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
11 | gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU=
12 | gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
13 |
--------------------------------------------------------------------------------
/code/middleware/gorm/base/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "gorm.io/driver/mysql"
7 | "gorm.io/gorm"
8 | )
9 |
10 | type User struct {
11 | Name string
12 | Age int
13 | Id int
14 | }
15 |
16 | func main() {
17 | dsn := "root:root@tcp(127.0.0.1:3306)/one?charset=utf8mb4&parseTime=True&loc=Local"
18 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
19 |
20 | if err != nil {
21 | return
22 | }
23 |
24 | user := &User{}
25 | db.Raw("SELECT id, name, age FROM users WHERE id = ?", 1).Scan(user)
26 | fmt.Println(user)
27 |
28 | user1 := &User{}
29 | result1 := db.Where("id = ?", 11).First(user1)
30 | fmt.Println(result1.RowsAffected, user1)
31 |
32 | // check error ErrRecordNotFound
33 | errors.Is(result1.Error, gorm.ErrRecordNotFound)
34 |
35 | // Create
36 | userCreate := User{Name: "xiaoming222", Age: 28}
37 | result := db.Create(&userCreate)
38 | resultId := userCreate.Id // 获取创建成功的ID
39 | fmt.Println(result, resultId)
40 |
41 | // Read
42 | var user2 User
43 | // user2 的值会被修改为 id = 1的记录,所有的值都会被修改
44 | db.First(&user2, resultId) // find user2 with integer primary key,
45 | // user2已经对应一条记录,加入id为8,这时查询语句为: SELECT * FROM `users` WHERE age = 20 AND `users`.`id` = 8 ORDER BY `users`.`id` LIMIT 1
46 | db.First(&user2, "age = ?", 20) // find user2 with code D42
47 | fmt.Println(user2)
48 |
49 | // Update - update product's price to 200
50 | db.Model(&user2).Update("Age", 19) // user2也会被修改 age = 19
51 | // Update - update multiple fields
52 | db.Model(&user2).Updates(User{Name: "xiaohong2", Age: 20}) // non-zero fields
53 | db.Model(&user2).Updates(map[string]interface{}{"Name": "mingming", "Age": 26})
54 |
55 | // Delete - delete product
56 | db.Delete(&user2)
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/code/reflect/proxy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type IMan interface {
6 | Walk() int
7 | }
8 |
9 | type ManProxy struct {
10 | manProxy IMan // 不能是指针
11 | }
12 |
13 | func (proxy *ManProxy) setProxy(man IMan) {
14 | proxy.manProxy = man
15 | }
16 |
17 | func (proxy *ManProxy) Walk() int {
18 | proxy.manProxy.Walk()
19 | fmt.Println("proxy Walk")
20 | return 0
21 | }
22 |
23 | type Man struct {
24 | name string
25 | age int
26 | }
27 |
28 | func (man *Man) Walk() int {
29 | fmt.Println("man Walk")
30 | return 0
31 | }
32 |
33 | func main() {
34 | manProxy := &ManProxy{} // 是不是指针都行
35 | var manImpl IMan
36 | man := Man{name: "xiaoming", age: 18}
37 | manImpl = &man //需要取地址, 调用man具体实现,而不是复制
38 |
39 | manProxy.setProxy(manImpl)
40 | manProxy.Walk()
41 |
42 | return
43 | }
44 |
--------------------------------------------------------------------------------
/code/test/map_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import "testing"
4 |
5 | func testMapCreate(t *testing.T) {
6 | t.Log("testMapCreate")
7 | }
8 |
9 |
10 |
--------------------------------------------------------------------------------
/md/base/array/array-slice.md:
--------------------------------------------------------------------------------
1 | - # 数组/切片
2 |
3 | 目录:
4 | - [从汇编角度理解数组/切片](#从汇编角度理解数组切片)
5 | - [数组及切片的数据结构](#数组及切片的数据结构)
6 | - [内存中的数组/切片](#内存中的数组切片)
7 |
8 |
9 |
10 | ## [从汇编角度理解数组/切片](https://github.com/ymm135/TD4-4BIT-CPU/blob/master/go-asm.md#go%E6%B1%87%E7%BC%96%E6%8C%87%E4%BB%A4%E5%AD%A6%E4%B9%A0)
11 |
12 | ## 数组及切片的数据结构
13 |
14 | 数组的数据结构就是连续的内存块,如果是值类型,内存块存储的就是数据。
15 |
16 |
17 | `runtime/slice.go`文件中
18 | ```go
19 | type slice struct {
20 | array unsafe.Pointer
21 | len int
22 | cap int
23 | }
24 | ```
25 |
26 | 从数据结构中可以看出切片包含一个指针,指向数组,另外包含length及cap两个计数器。
27 |
28 | ## 内存中的数组/切片
29 |
30 | 测试代码
31 | ```go
32 | package main
33 |
34 | import "fmt"
35 |
36 | func main() {
37 | var a [5]int
38 | a[0] = 1
39 | a[3] = 10
40 | a[4] = 15
41 | aLen := len(a)
42 |
43 | s := make([]int, 5, 10)
44 | s[2] = 2
45 | s[3] = 256
46 | length := len(s)
47 | fmt.Println(a, "arrayLen=", aLen, s, "sliceLen=", length)
48 | }
49 | ```
50 |
51 | 输出结果为:
52 | ```text
53 | [1 0 0 10 15] arrayLen= 5 [0 0 2 256 0] sliceLen= 5
54 | ```
55 |
56 | 首先编译代码
57 | ```shell
58 | go mod init gotest
59 | go mod tidy
60 |
61 | go build -gcflags "-N -l" // 生成gotest二进制文件
62 | ```
63 |
64 | > -gcflags "-N -l"
65 | > -N disable optimizations
66 | > -l disable inlining
67 |
68 | vscode调试配置
69 | 不能使用golang的配置
70 | ```json
71 | {
72 | "version": "0.2.0",
73 | "configurations": [
74 | {
75 | "name": "Launch Package",
76 | "type": "go",
77 | "request": "launch",
78 | "mode": "auto",
79 | "program": "${file}"
80 | }
81 | ]
82 | }
83 | ```
84 |
85 | 需要使用`gdb`调试配置
86 | ```json
87 | {
88 | "version": "0.2.0",
89 | "configurations": [
90 | {
91 | "name": "(gdb) 启动",
92 | "type": "cppdbg",
93 | "request": "launch",
94 | "program": "${workspaceFolder}/gotest",
95 | "args": [],
96 | "stopAtEntry": false,
97 | "cwd": "${fileDirname}",
98 | "environment": [],
99 | "externalConsole": false,
100 | "MIMode": "gdb",
101 | "setupCommands": [
102 | {
103 | "description": "为 gdb 启用整齐打印",
104 | "text": "-enable-pretty-printing",
105 | "ignoreFailures": true
106 | }
107 | ]
108 | }
109 | ]
110 | }
111 | ```
112 |
113 | 数组的内存视图:
114 | ```shell
115 | -exec x/24x &a
116 | 0xc000088ee0: 0x00000001 0x00000000 0x00000000 0x00000000
117 | 0xc000088ef0: 0x00000000 0x00000000 0x0000000a 0x00000000
118 | 0xc000088f00: 0x0000000f 0x00000000 0x00000060 0x00000000
119 | 0xc000088f10: 0x00082000 0x000000c0 0x0003a748 0x000000c0
120 | 0xc000088f20: 0x0054b520 0x00000000 0x00000000 0x00000000
121 | 0xc000088f30: 0x0003a778 0x000000c0 0x00088f78 0x000000c0
122 | ```
123 | `-exec x/24x &a` 使用gdb查看内存视图,`24x`代表是显示24个16进制,每个`int`占用8字节,
124 | 5个`int`占用5个字节,从`0xc000088ee0`-`0xc000088f08`,存储的值依次是`[0x01 0x00 0x00 0x0a 0x0f]`
125 |
126 |
127 | 类似于C语言的调试,通过gdb可以清晰看到slice的内部实现(数据结构),并且可以查看slice的内存视图
128 |
129 | 
130 |
131 | 通过`-exec x/64x &slice` 可以看到slice的内存视图,总占用32个字节,和数据结构是对应的。
132 |
--------------------------------------------------------------------------------
/md/base/concurrent/timer.md:
--------------------------------------------------------------------------------
1 | - # 定时器
2 |
3 | 目录:
4 | - [测试代码](#测试代码)
5 | - [数据结构](#数据结构)
6 |
7 |
8 | ## 测试代码
9 | ```go
10 | func main() {
11 | // 1.延时执行
12 | fmt.Println("currTime=", time.Now().Format("2006-01-02 15:04:05"))
13 | // create a nobuf channel and a goroutine `timer` will write it after 2 seconds
14 | timeAfterTrigger := time.After(time.Second * 2)
15 | // will be suspend but we have `timer` so will be not deadlocked
16 | curTime, _ := <-timeAfterTrigger
17 | // print current time
18 | fmt.Println("timeAfter=", curTime.Format("2006-01-02 15:04:05"))
19 |
20 | // 2.定时执行
21 | // 创建一个计时器
22 | timeTicker := time.NewTicker(time.Second * 2)
23 | i := 0
24 | for {
25 | if i > 5 {
26 | break
27 | }
28 |
29 | fmt.Println("timeTicker=", time.Now().Format("2006-01-02 15:04:05"))
30 | i++
31 | <-timeTicker.C // 下次触发的时间
32 |
33 | }
34 | // 清理计时器
35 | timeTicker.Stop()
36 | }
37 | ```
38 |
39 | 输出结果:
40 | ```shell
41 | currTime= 2022-01-08 09:56:28
42 | timeAfter= 2022-01-08 09:56:30
43 | timeTicker= 2022-01-08 09:56:30
44 | timeTicker= 2022-01-08 09:56:32
45 | timeTicker= 2022-01-08 09:56:34
46 | timeTicker= 2022-01-08 09:56:36
47 | timeTicker= 2022-01-08 09:56:38
48 | timeTicker= 2022-01-08 09:56:40
49 | ```
50 |
51 | 从测试代码可以看出计时触发通过`管道`而不是回调函数,延时/定时任务通过向管道发送数据,另一端在管道接收数据。
52 |
53 |
54 | ## 数据结构
55 | 源码位置`go/src/time/sleep.go`
56 | ```
57 | // The Timer type represents a single event.
58 | // When the Timer expires, the current time will be sent on C,
59 | // unless the Timer was created by AfterFunc.
60 | // A Timer must be created with NewTimer or AfterFunc.
61 | type Timer struct {
62 | C <-chan Time
63 | r runtimeTimer
64 | }
65 | ```
66 |
67 | 定时任务
68 | ```
69 | // A Ticker holds a channel that delivers ``ticks'' of a clock
70 | // at intervals.
71 | type Ticker struct {
72 | C <-chan Time // The channel on which the ticks are delivered.
73 | r runtimeTimer
74 | }
75 | ```
76 |
77 | 首先查看延时任务`timeAfterTrigger := time.After(time.Second * 2)`
78 | 源码路径:`go/src/time/sleep.go`
79 | ```
80 | // After waits for the duration to elapse and then sends the current time
81 | // on the returned channel.
82 | // It is equivalent to NewTimer(d).C.
83 | // The underlying Timer is not recovered by the garbage collector
84 | // until the timer fires. If efficiency is a concern, use NewTimer
85 | // instead and call Timer.Stop if the timer is no longer needed.
86 | func After(d Duration) <-chan Time {
87 | return NewTimer(d).C
88 | }
89 |
90 | //创建一个定时器Timer,包含通知的管道和计时模块`runtimeTimer`
91 | // NewTimer creates a new Timer that will send
92 | // the current time on its channel after at least duration d.
93 | func NewTimer(d Duration) *Timer {
94 | c := make(chan Time, 1)
95 | t := &Timer{
96 | C: c,
97 | r: runtimeTimer{
98 | when: when(d), // runtimeNano() + int64(d) 当下时间+延迟时间=触发时间
99 | f: sendTime,
100 | arg: c,
101 | },
102 | }
103 | startTimer(&t.r)
104 | return t
105 | }
106 |
107 | //到时间后,会往管道发送当下时间
108 | func sendTime(c interface{}, seq uintptr) {
109 | // Non-blocking send of time on c.
110 | // Used in NewTimer, it cannot block anyway (buffer).
111 | // Used in NewTicker, dropping sends on the floor is
112 | // the desired behavior when the reader gets behind,
113 | // because the sends are periodic.
114 | select {
115 | case c.(chan Time) <- Now():
116 | default:
117 | }
118 | }
119 | ```
120 |
121 | `startTimer(&t.r)`的实现在 `go/src/runtime/time.go:208`
122 | ```
123 | // startTimer adds t to the timer heap.
124 | //go:linkname startTimer time.startTimer
125 | func startTimer(t *timer) {
126 | if raceenabled {
127 | racerelease(unsafe.Pointer(t))
128 | }
129 | addtimer(t) //添加到计时器
130 | }
131 |
132 | // addtimer adds a timer to the current P.
133 | // This should only be called with a newly created timer.
134 | // That avoids the risk of changing the when field of a timer in some P's heap,
135 | // which could cause the heap to become unsorted.
136 | func addtimer(t *timer) {
137 | // when must be positive. A negative value will cause runtimer to
138 | // overflow during its delta calculation and never expire other runtime
139 | // timers. Zero will cause checkTimers to fail to notice the timer.
140 | if t.when <= 0 {
141 | throw("timer when must be positive")
142 | }
143 | if t.period < 0 {
144 | throw("timer period must be non-negative")
145 | }
146 | if t.status != timerNoStatus {
147 | throw("addtimer called with initialized timer")
148 | }
149 | t.status = timerWaiting
150 |
151 | when := t.when
152 |
153 | // Disable preemption while using pp to avoid changing another P's heap.
154 | mp := acquirem()
155 |
156 | pp := getg().m.p.ptr()
157 | lock(&pp.timersLock)
158 | cleantimers(pp)
159 | doaddtimer(pp, t) // doaddtimer adds t to the current P's heap.
160 | unlock(&pp.timersLock)
161 |
162 | // 如果它不会在 when 参数之前唤醒,则wakeNetPoller 唤醒在网络轮询器中休眠的线程; 或者它唤醒一个空闲的 P 以服务计时器和网络轮询器(如果还没有的话)。
163 | wakeNetPoller(when)
164 |
165 | releasem(mp)
166 | }
167 |
168 | ```
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/md/base/keyword/defer.md:
--------------------------------------------------------------------------------
1 | - # defer
2 |
3 | 目录:
4 | - [执行顺序](#执行顺序)
5 | - [预计算参数](#预计算参数)
6 | - [数据结构及实现原理](#数据结构及实现原理)
7 |
8 |
9 | ## 执行顺序
10 | 先声明后执行,相当于堆栈,`先进后出`
11 | ```go
12 | func main() {
13 | defer println("A")
14 | defer println("B")
15 | defer println("C")
16 | }
17 | ```
18 |
19 | 输出结果:
20 | ```shell
21 | C
22 | B
23 | A
24 | ```
25 |
26 | ## 预计算参数
27 | [参考文章](https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-defer/)
28 |
29 | ```go
30 | package main
31 |
32 | import (
33 | "fmt"
34 | "time"
35 | )
36 |
37 | func main() {
38 | testParam1()
39 | testParam2()
40 | }
41 |
42 | func testParam1() {
43 | startedAt := time.Now()
44 | defer fmt.Println("testParam1=", time.Since(startedAt))
45 |
46 | time.Sleep(time.Second)
47 | }
48 |
49 | func testParam2() {
50 | startedAt := time.Now()
51 | defer func() { fmt.Println("testParam1=", time.Since(startedAt)) }()
52 |
53 | time.Sleep(time.Second)
54 | }
55 | ```
56 |
57 | 输出结果是:
58 | ```
59 | testParam1= 185ns
60 | testParam1= 1.004880495s
61 | ```
62 |
63 | 从结果可以看出`defer`后会立刻拷贝函数中引用的外部参数,所以 `time.Since(startedAt)` 的结果不是在 `main` 函数退出之前计算的
64 | `defer`关键字后增加匿名函数,虽然调用 defer 关键字时也使用`值传递`,但是因为拷贝的是函数指针,所以 `time.Since(startedAt)`
65 | 会在 `main` 函数返回前调用并打印出符合预期的结果。
66 |
67 | > 匿名函数也是函数,函数执行是只要找到函数指针指向的`代码地址`(引用传递)即可,不会像`值类型`那样固定。
68 |
69 |
70 | ## 数据结构及实现原理
71 |
72 | ```go
73 | // A _defer holds an entry on the list of deferred calls.
74 | // If you add a field here, add code to clear it in freedefer and deferProcStack
75 | // This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
76 | // and cmd/compile/internal/gc/ssa.go:(*state).call.
77 | // Some defers will be allocated on the stack and some on the heap.
78 | // All defers are logically part of the stack, so write barriers to
79 | // initialize them are not required. All defers must be manually scanned,
80 | // and for heap defers, marked.
81 | type _defer struct {
82 | siz int32 // includes both arguments and results
83 | started bool
84 | heap bool
85 | // openDefer indicates that this _defer is for a frame with open-coded
86 | // defers. We have only one defer record for the entire frame (which may
87 | // currently have 0, 1, or more defers active).
88 | openDefer bool
89 | sp uintptr // sp at time of defer
90 | pc uintptr // pc at time of defer
91 | fn *funcval // can be nil for open-coded defers
92 | _panic *_panic // panic that is running defer
93 | link *_defer
94 |
95 | // If openDefer is true, the fields below record values about the stack
96 | // frame and associated function that has the open-coded defer(s). sp
97 | // above will be the sp for the frame, and pc will be address of the
98 | // deferreturn call in the function.
99 | fd unsafe.Pointer // funcdata for the function associated with the frame
100 | varp uintptr // value of varp for the stack frame
101 | // framepc is the current pc associated with the stack frame. Together,
102 | // with sp above (which is the sp associated with the stack frame),
103 | // framepc/sp can be used as pc/sp pair to continue a stack trace via
104 | // gentraceback().
105 | framepc uintptr
106 | }
107 | ```
108 |
109 | defer数据结构
110 | ```shell
111 | ┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐
112 | │ goroutine │ │ defer │ │ defer │ │ defer │
113 | ├─────────────┬────────────┤ ├─────────────┬────────────┤ ├─────────────┬────────────┤ ├─────────────┬────────────┤
114 | │ ... │_defer->link│ │ ... │ link │ │ ... │ link │ │ ... │ link │
115 | └─────────────┴─────┬──────┘ └─────────────┴─────┬──────┘ └─────────────┴──────┬─────┘ └─────────────┴────────────┘
116 | │ ↑ │ ↑ │ ↑
117 | └────────────────────────────┘ └────────────────────────────┘ └───────────────────────────┘
118 | ```
119 |
120 | 后调用的 defer 函数会先执行:
121 | - 后调用的 defer 函数会被追加到 Goroutine _defer 链表的最前面;
122 | - 运行 runtime._defer 时是从前到后依次执行;
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/md/base/keyword/for-range.md:
--------------------------------------------------------------------------------
1 | # for 和 range
2 | [参考文章]()
3 | 循环是所有编程语言都有的控制结构,除了使用经典的三段式循环之外,
4 | `Go` 语言还引入了另一个关键字 `range` 帮助我们快速遍历`数组`、`切片`、`哈希表`以及 `Channel` 等集合类型。
5 |
6 | ## 测试代码
7 |
8 | ```go
9 | func main() {
10 | // string
11 | str := "I am String!"
12 | for i, s := range str {
13 | fmt.Println("[string](", i, ")=", string(s))
14 | }
15 |
16 | // array slice
17 | array := []int{1, 3, 5, 7, 9}
18 | for i, v := range array {
19 | // 也可以使用array[i]
20 | fmt.Println("array(", i, ")=", v)
21 | }
22 |
23 | // hash
24 | hashTable := make(map[string]string, 10)
25 | hashTable["a"] = "array"
26 | hashTable["b"] = "bar"
27 | hashTable["c"] = "car"
28 | for k, v := range hashTable {
29 | fmt.Println("[hash]", k, ":", v)
30 | }
31 |
32 | //channel
33 | ch := make(chan string, 10)
34 | go func() {
35 | ch <- "hello"
36 | ch <- "go"
37 | ch <- "!"
38 | }()
39 |
40 | time.Sleep(time.Second)
41 |
42 | // 如果不在协程中开启, fatal error: all goroutines are asleep - deadlock!
43 | go func() {
44 | for c := range ch {
45 | fmt.Println("[channel]", c)
46 | }
47 | }()
48 |
49 | time.Sleep(time.Second)
50 | }
51 | ```
52 |
53 | 结果输出:
54 | ```shell
55 | [string]( 0 )= I
56 | [string]( 1 )=
57 | [string]( 2 )= a
58 | [string]( 3 )= m
59 | [string]( 4 )=
60 | [string]( 5 )= S
61 | [string]( 6 )= t
62 | [string]( 7 )= r
63 | [string]( 8 )= i
64 | [string]( 9 )= n
65 | [string]( 10 )= g
66 | [string]( 11 )= !
67 | array( 0 )= 1
68 | array( 1 )= 3
69 | array( 2 )= 5
70 | array( 3 )= 7
71 | array( 4 )= 9
72 | [hash] a : array
73 | [hash] b : bar
74 | [hash] c : car
75 | [channel] hello
76 | [channel] go
77 | [channel] !
78 | ```
79 |
80 | ## `for`和`range`的实现
81 | 在编译阶段,会针对不同场景的`range`做不同的解析
82 | ```go
83 | // cmd/compile/internal/gc/walk.go
84 | // The result of walkstmt MUST be assigned back to n, e.g.
85 | // n.Left = walkstmt(n.Left)
86 | func walkstmt(n *Node) *Node {
87 | ...
88 | case ORANGE:
89 | n = walkrange(n)
90 | ...
91 | }
92 |
93 | //cmd/compile/internal/gc/range.go
94 | // walkrange transforms various forms of ORANGE into
95 | // simpler forms. The result must be assigned back to n.
96 | // Node n may also be modified in place, and may also be
97 | // the returned node.
98 | func walkrange(n *Node) *Node {
99 | switch t.Etype {
100 | default:
101 | Fatalf("walkrange")
102 |
103 | case TARRAY, TSLICE:
104 | ...
105 | case TMAP:
106 | ...
107 | case TCHAN:
108 | ...
109 | case TSTRING:
110 | ...
111 | }
112 | ```
113 | 从`range.go`的实现来看,针对`TARRAY/TSLICE`、`TMAP`、`TCHAN`、`TSTRING` 5中数据类型都有具体的实现
114 |
115 | ### `TARRAY/TSLICE`类型处理
116 |
117 | ```go
118 | func walkrange(n *Node) *Node {
119 | ...
120 | case TARRAY, TSLICE:
121 | if arrayClear(n, v1, v2, a) {
122 | lineno = lno
123 | return n
124 | }
125 |
126 | // order.stmt arranged for a copy of the array/slice variable if needed.
127 | ha := a
128 |
129 | hv1 := temp(types.Types[TINT])
130 | hn := temp(types.Types[TINT])
131 |
132 | init = append(init, nod(OAS, hv1, nil))
133 | init = append(init, nod(OAS, hn, nod(OLEN, ha, nil)))
134 |
135 | n.Left = nod(OLT, hv1, hn)
136 | n.Right = nod(OAS, hv1, nod(OADD, hv1, nodintconst(1)))
137 |
138 | // for range ha { body }
139 | if v1 == nil {
140 | break
141 | }
142 |
143 | // for v1 := range ha { body }
144 | if v2 == nil {
145 | body = []*Node{nod(OAS, v1, hv1)}
146 | break
147 | }
148 |
149 | // for v1, v2 := range ha { body }
150 | if cheapComputableIndex(n.Type.Elem().Width) {
151 | // v1, v2 代表index, value, v2还是数组+索引 a[hv1]
152 | // v1, v2 = hv1, ha[hv1]
153 | tmp := nod(OINDEX, ha, hv1)
154 | tmp.SetBounded(true)
155 | // Use OAS2 to correctly handle assignments
156 | // of the form "v1, a[v1] := range".
157 | a := nod(OAS2, nil, nil)
158 | a.List.Set2(v1, v2)
159 | a.Rlist.Set2(hv1, tmp)
160 | body = []*Node{a}
161 | break
162 | }
163 |
164 | // TODO(austin): OFORUNTIL is a strange beast, but is
165 | // necessary for expressing the control flow we need
166 | // while also making "break" and "continue" work. It
167 | // would be nice to just lower ORANGE during SSA, but
168 | // racewalk needs to see many of the operations
169 | // involved in ORANGE's implementation. If racewalk
170 | // moves into SSA, consider moving ORANGE into SSA and
171 | // eliminating OFORUNTIL.
172 |
173 | // TODO(austin): OFORUNTIL inhibits bounds-check
174 | // elimination on the index variable (see #20711).
175 | // Enhance the prove pass to understand this.
176 | ifGuard = nod(OIF, nil, nil)
177 | ifGuard.Left = nod(OLT, hv1, hn)
178 | translatedLoopOp = OFORUNTIL
179 |
180 | hp := temp(types.NewPtr(n.Type.Elem()))
181 | tmp := nod(OINDEX, ha, nodintconst(0))
182 | tmp.SetBounded(true)
183 | init = append(init, nod(OAS, hp, nod(OADDR, tmp, nil)))
184 |
185 | // Use OAS2 to correctly handle assignments
186 | // of the form "v1, a[v1] := range".
187 | a := nod(OAS2, nil, nil)
188 | a.List.Set2(v1, v2)
189 | a.Rlist.Set2(hv1, nod(ODEREF, hp, nil))
190 | body = append(body, a)
191 |
192 | // Advance pointer as part of the late increment.
193 | //
194 | // This runs *after* the condition check, so we know
195 | // advancing the pointer is safe and won't go past the
196 | // end of the allocation.
197 | a = nod(OAS, hp, addptr(hp, t.Elem().Width))
198 | a = typecheck(a, ctxStmt)
199 | n.List.Set1(a)
200 | ...
201 | ```
202 |
203 | 最终转化成
204 | ```go
205 | ha := a
206 | hv1 := 0
207 | hn := len(ha)
208 |
209 | v1 := hv1
210 | v2 := nil
211 |
212 | for ; hv1 < hn; hv1++ {
213 | tmp := ha[hv1]
214 | v1, v2 = hv1, tmp
215 | ...
216 | }
217 | ```
218 |
219 | 可以看到`range`语句最终还是会转化为`for`循环,转换的过程是通过编译器实现的,通过修改`语法树`。
220 |
221 |
--------------------------------------------------------------------------------
/md/base/keyword/make-vs-new.md:
--------------------------------------------------------------------------------
1 | - # make vs new
2 | - make 的作用是初始化内置的数据结构,也就是我们在前面提到的切片、哈希表和 Channel;
3 | - new 的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针;
4 |
5 |
6 | 示例
7 | ```go
8 | 5: func main() {
9 | 6: // make
10 | 7: slice := make([]int, 0, 100) //调用runtime.makeslice
11 | 8: hash := make(map[int]bool, 10) //调用runtime.makemap
12 | => 9: ch := make(chan int, 5) //调用runtime.makechan
13 | 10:
14 | 11: // new
15 | 12: i := new(int) //调用runtime.newobject
16 | 13: var v int //调用runtime.newobject
17 | 14: i = &v
18 | }
19 | ```
20 |
21 | 对应的汇编程序
22 | ```
23 | (dlv) disass
24 | TEXT main.main(SB) /Users/ymm/work/mygithub/golang-cookbook/code/base/keyword/make-new/base-make-new.go
25 | base-make-new.go:5 0x10cbaa0 65488b0c2530000000 mov rcx, qword ptr gs:[0x30]
26 | base-make-new.go:5 0x10cbaa9 488d442490 lea rax, ptr [rsp-0x70]
27 | base-make-new.go:5 0x10cbaae 483b4110 cmp rax, qword ptr [rcx+0x10]
28 | base-make-new.go:5 0x10cbab2 0f869c020000 jbe 0x10cbd54
29 | base-make-new.go:5 0x10cbab8* 4881ecf0000000 sub rsp, 0xf0
30 | base-make-new.go:5 0x10cbabf 4889ac24e8000000 mov qword ptr [rsp+0xe8], rbp
31 | base-make-new.go:5 0x10cbac7 488dac24e8000000 lea rbp, ptr [rsp+0xe8]
32 | base-make-new.go:7 0x10cbacf 488d056aae0000 lea rax, ptr [rip+0xae6a]
33 | base-make-new.go:7 0x10cbad6 48890424 mov qword ptr [rsp], rax
34 | base-make-new.go:7 0x10cbada 48c744240800000000 mov qword ptr [rsp+0x8], 0x0
35 | base-make-new.go:7 0x10cbae3 48c744241064000000 mov qword ptr [rsp+0x10], 0x64
36 | base-make-new.go:7 0x10cbaec e8af50f8ff call $runtime.makeslice
37 | base-make-new.go:7 0x10cbaf1 488b442418 mov rax, qword ptr [rsp+0x18]
38 | base-make-new.go:7 0x10cbaf6 4889442478 mov qword ptr [rsp+0x78], rax
39 | base-make-new.go:7 0x10cbafb 48c784248000000000000000 mov qword ptr [rsp+0x80], 0x0
40 | base-make-new.go:7 0x10cbb07 48c784248800000064000000 mov qword ptr [rsp+0x88], 0x64
41 | base-make-new.go:8 0x10cbb13 488d0526ef0000 lea rax, ptr [rip+0xef26]
42 | base-make-new.go:8 0x10cbb1a 48890424 mov qword ptr [rsp], rax
43 | base-make-new.go:8 0x10cbb1e 48c74424080a000000 mov qword ptr [rsp+0x8], 0xa
44 | base-make-new.go:8 0x10cbb27 48c744241000000000 mov qword ptr [rsp+0x10], 0x0
45 | base-make-new.go:8 0x10cbb30 e8eb30f4ff call $runtime.makemap
46 | base-make-new.go:8 0x10cbb35 488b442418 mov rax, qword ptr [rsp+0x18]
47 | base-make-new.go:8 0x10cbb3a 4889442438 mov qword ptr [rsp+0x38], rax
48 | => base-make-new.go:9 0x10cbb3f 488d05faa70000 lea rax, ptr [rip+0xa7fa]
49 | base-make-new.go:9 0x10cbb46 48890424 mov qword ptr [rsp], rax
50 | base-make-new.go:9 0x10cbb4a 48c744240805000000 mov qword ptr [rsp+0x8], 0x5
51 | base-make-new.go:9 0x10cbb53 e84890f3ff call $runtime.makechan
52 | base-make-new.go:9 0x10cbb58 488b442410 mov rax, qword ptr [rsp+0x10]
53 | base-make-new.go:9 0x10cbb5d 4889442440 mov qword ptr [rsp+0x40], rax
54 | base-make-new.go:12 0x10cbb62 488d05d7ad0000 lea rax, ptr [rip+0xadd7]
55 | base-make-new.go:12 0x10cbb69 48890424 mov qword ptr [rsp], rax
56 | base-make-new.go:12 0x10cbb6d e8ee21f4ff call $runtime.newobject
57 | base-make-new.go:12 0x10cbb72 488b442408 mov rax, qword ptr [rsp+0x8]
58 | base-make-new.go:12 0x10cbb77 4889442430 mov qword ptr [rsp+0x30], rax
59 | base-make-new.go:13 0x10cbb7c 488d05bdad0000 lea rax, ptr [rip+0xadbd]
60 | base-make-new.go:13 0x10cbb83 48890424 mov qword ptr [rsp], rax
61 | base-make-new.go:13 0x10cbb87 e8d421f4ff call $runtime.newobject
62 | base-make-new.go:13 0x10cbb8c 488b442408 mov rax, qword ptr [rsp+0x8]
63 | base-make-new.go:13 0x10cbb91 4889442470 mov qword ptr [rsp+0x70], rax
64 | base-make-new.go:13 0x10cbb96 48c70000000000 mov qword ptr [rax], 0x0
65 | base-make-new.go:14 0x10cbb9d 488b442470 mov rax, qword ptr [rsp+0x70]
66 | base-make-new.go:14 0x10cbba2 4889442430 mov qword ptr [rsp+0x30], rax
67 | ```
68 |
69 | 从汇编实现中可以看出`new(int)`和`var v int`都是调用`newobject`, 最终返回的是内存地址
70 | ```go
71 | // implementation of new builtin
72 | // compiler (both frontend and SSA backend) knows the signature
73 | // of this function
74 | func newobject(typ *_type) unsafe.Pointer {
75 | return mallocgc(typ.size, typ, true)
76 | }
77 | ```
78 |
79 |
80 |
--------------------------------------------------------------------------------
/md/base/keyword/panic-and-recover.md:
--------------------------------------------------------------------------------
1 | - # panic and recover
2 |
3 | 目录:
4 | - [样例](#样例)
5 | - [panic](#panic)
6 | - [recover](#recover)
7 | - [数据结构](#数据结构)
8 |
9 |
10 | 两个关键字的作用:
11 | - `panic` 能够改变程序的控制流,调用 `panic` 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
12 | - `recover` 可以中止 `panic` 造成的程序崩溃(`recover` 只有在发生 `panic` 之后调用才会生效)。它是一个只能在 `defer` 中发挥作用的函数,在其他作用域中调用不会发挥作用;
13 |
14 | ## 样例
15 | ### panic
16 | ```go
17 | func main() {
18 | defer fmt.Println("in main")
19 | defer func() {
20 | defer func() {
21 | fmt.Println("panic again and again")
22 | panic("panic again and again")
23 | }()
24 | fmt.Println("panic again")
25 | panic("panic again")
26 | }()
27 |
28 | panic("panic once")
29 | }
30 | ```
31 | `panic`嵌套的情况:
32 | 打印输出
33 | ```
34 | panic again
35 | panic again and again
36 | in main
37 | panic: panic once
38 | panic: panic again
39 | panic: panic again and again
40 | ```
41 | 从上述程序输出的结果,我们可以确定程序多次调用 `panic` 也不会影响 `defer` 函数的正常执行,
42 | 所以使用 `defer` 进行收尾工作一般来说都是安全的。
43 |
44 | ### recover
45 |
46 | ```go
47 | func main() {
48 | defer fmt.Println("in main")
49 | if err := recover(); err != nil {
50 | fmt.Println(err)
51 | }
52 |
53 | panic("unknown err")
54 | }
55 | ```
56 | `recover`没有在`panic`之后调用,不生效,输出
57 | ```
58 | in main
59 | panic: unknown err
60 |
61 | goroutine 1 [running]:
62 | main.main()
63 | /Users/xxx/work/mygithub/golang-cookbook/code/base/keyword/panic-recover/recover/base-recover2.go:11 +0x125
64 | ```
65 |
66 | ```go
67 | func main() {
68 | defer fmt.Println("in main")
69 | defer func() {
70 | if err := recover(); err != nil {
71 | fmt.Println(err)
72 | }
73 | }()
74 |
75 | panic("unknown err")
76 | }
77 | ```
78 | recover生效,打印输出是
79 | ```shell
80 | unknown err
81 | in main
82 | ```
83 |
84 | ## 数据结构
85 |
86 | `go/src/runtime/runtime2.go`文件中
87 | ```go
88 | // A _panic holds information about an active panic.
89 | //
90 | // A _panic value must only ever live on the stack.
91 | //
92 | // The argp and link fields are stack pointers, but don't need special
93 | // handling during stack growth: because they are pointer-typed and
94 | // _panic values only live on the stack, regular stack pointer
95 | // adjustment takes care of them.
96 | type _panic struct {
97 | argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
98 | arg interface{} // argument to panic
99 | link *_panic // link to earlier panic 可以有多个
100 | pc uintptr // where to return to in runtime if this panic is bypassed
101 | sp unsafe.Pointer // where to return to in runtime if this panic is bypassed
102 | recovered bool // whether this panic is over
103 | aborted bool // the panic was aborted
104 | goexit bool
105 | }
106 | ```
107 |
108 |
109 |
--------------------------------------------------------------------------------
/md/base/keyword/select.md:
--------------------------------------------------------------------------------
1 | - # Select实现
2 | [参考文章](https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-select/)
3 | `select` 是操作系统中的系统调用,我们经常会使用 `select`、`poll` 和 `epoll` 等函数构建
4 | I/O 多路复用模型提升程序的性能。Go 语言的 `select` 与操作系统中的 `select` 比较相似。
5 |
6 | 目录:
7 | - [测试demo](#测试demo)
8 | - [数据结构](#数据结构)
9 | - [实现原理](#实现原理)
10 |
11 |
12 | ## 测试demo
13 | [code](code/base/keyword/select/base-select.go)
14 | ```go
15 | package main
16 |
17 | import (
18 | "fmt"
19 | "time"
20 | )
21 |
22 | func main() {
23 | ch := make(chan string)
24 |
25 | go func() {
26 | ch <- "hello channel"
27 | ch <- "!"
28 | ch <- "quit"
29 | }()
30 |
31 | //需要延迟一段时间,要不然接收不到数据
32 | time.Sleep(time.Second)
33 |
34 | for { //
35 | select {
36 | case str := <-ch:
37 | fmt.Println(str)
38 | if str == "quit" {
39 | goto end
40 | }
41 | default:
42 | fmt.Println("default")
43 | }
44 | }
45 | end:
46 | }
47 | ```
48 |
49 | 输出结果:
50 | ```shell
51 | hello channel
52 | !
53 | default
54 | default
55 | default
56 | default
57 | default
58 | default
59 | default
60 | default
61 | default
62 | default
63 | default
64 | default
65 | default
66 | default
67 | default
68 | default
69 | default
70 | quit
71 | ```
72 |
73 | ## 数据结构
74 | `chan`的数据结构在`go/src/runtime/chan.go`
75 | ```go
76 | type hchan struct {
77 | qcount uint // total data in the queue
78 | dataqsiz uint // size of the circular queue
79 | buf unsafe.Pointer // points to an array of dataqsiz elements
80 | elemsize uint16
81 | closed uint32
82 | elemtype *_type // element type
83 | sendx uint // send index
84 | recvx uint // receive index
85 | recvq waitq // list of recv waiters
86 | sendq waitq // list of send waiters
87 |
88 | // lock protects all fields in hchan, as well as several
89 | // fields in sudogs blocked on this channel.
90 | //
91 | // Do not change another G's status while holding this lock
92 | // (in particular, do not ready a G), as this can deadlock
93 | // with stack shrinking.
94 | lock mutex
95 | }
96 | ```
97 |
98 | ## 实现原理
99 | `select` 语句在编译期间会被转换成 `OSELECT` 节点。每个 `OSELECT` 节点都会持有一组 `OCASE` 节点,
100 | 每一个 `OCASE` 既包含执行条件也包含满足条件后执行的代码。如果 `OCASE` 的执行条件是空,那就意味着这是一个 `default` 节点。
101 |
102 | 具体实现代码在`cmd/compile/internal/gc/select.go`的`func walkselectcases(cases *Nodes) []*Node` 方法
103 | ```go
104 | ncas := cases.Len()
105 | sellineno := lineno
106 |
107 | // optimization: zero-case select
108 | if ncas == 0 {
109 | return []*Node{mkcall("block", nil, nil)}
110 | }
111 |
112 | // optimization: one-case select: single op.
113 | if ncas == 1 {
114 | cas := cases.First()
115 | setlineno(cas)
116 | l := cas.Ninit.Slice()
117 | if cas.Left != nil { // not default:
118 | n := cas.Left
119 | l = append(l, n.Ninit.Slice()...)
120 | n.Ninit.Set(nil)
121 | switch n.Op {
122 | default:
123 | Fatalf("select %v", n.Op)
124 |
125 | case OSEND:
126 | // already ok
127 |
128 | case OSELRECV, OSELRECV2:
129 | }
130 | l = append(l, cas.Nbody.Slice()...)
131 | l = append(l, nod(OBREAK, nil, nil))
132 | return l
133 | }
134 |
135 | // optimization: two-case select but one is default: single non-blocking op.
136 | if ncas == 2 && dflt != nil {
137 | ...
138 |
139 | case OSEND:
140 | // if selectnbsend(c, v) { body } else { default body }
141 | ch := n.Left
142 | r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right)
143 |
144 | case OSELRECV:
145 | // if selectnbrecv(&v, c) { body } else { default body }
146 | ch := n.Right.Left
147 | elem := n.Left
148 | if elem == nil {
149 | elem = nodnil()
150 | }
151 | r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch)
152 | ...
153 | }
154 | ```
155 |
156 | 在编译阶段会根据具体情况做不同的优化,我们这里关注`channel`的收发情况。`go/src/runtime/chan.go`源码中写的很明白
157 | ```go
158 | // compiler implements
159 | //
160 | // select {
161 | // case c <- v:
162 | // ... foo
163 | // default:
164 | // ... bar
165 | // }
166 | //
167 | // as
168 | //
169 | // if selectnbsend(c, v) {
170 | // ... foo
171 | // } else {
172 | // ... bar
173 | // }
174 | //
175 | func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
176 | return chansend(c, elem, false, getcallerpc())
177 | }
178 |
179 | // compiler implements
180 | //
181 | // select {
182 | // case v = <-c:
183 | // ... foo
184 | // default:
185 | // ... bar
186 | // }
187 | //
188 | // as
189 | //
190 | // if selectnbrecv(&v, c) {
191 | // ... foo
192 | // } else {
193 | // ... bar
194 | // }
195 | //
196 | func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
197 | selected, _ = chanrecv(c, elem, false)
198 | return
199 | }
200 |
201 | // compiler implements
202 | //
203 | // select {
204 | // case v, ok = <-c:
205 | // ... foo
206 | // default:
207 | // ... bar
208 | // }
209 | //
210 | // as
211 | //
212 | // if c != nil && selectnbrecv2(&v, &ok, c) {
213 | // ... foo
214 | // } else {
215 | // ... bar
216 | // }
217 | //
218 | func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
219 | // TODO(khr): just return 2 values from this function, now that it is in Go.
220 | selected, *received = chanrecv(c, elem, false)
221 | return
222 | }
223 | ```
224 |
225 | 从代码中可以看出,`select`中的`channel`相关操作,最终在编译阶段都会转换为`go/src/runtime/chan.go`的发送和接收操作。
226 | - selectnbsend
227 | - selectnbrecv 、 selectnbrecv2
228 |
229 | `select`相当于`channel`收发操作的一层封装。
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/md/base/object/interface.md:
--------------------------------------------------------------------------------
1 | - # 接口
2 |
3 | 目录:
4 |
5 | - [实例](#实例)
6 | - [数据结构](#数据结构)
7 |
8 |
9 | ## 实例
10 | [code](../../../code/base/object/interface/base-interface.go)
11 | ```go
12 | package main
13 |
14 | import (
15 | "fmt"
16 | "unsafe"
17 | )
18 |
19 | type IMan interface {
20 | walk() int
21 | }
22 |
23 | type Man struct {
24 | name string
25 | age int
26 | }
27 |
28 | func (man *Man) walk() int {
29 | fmt.Println("walk name:", man.name, ",age:", man.age)
30 | return man.age
31 | }
32 |
33 | func main() {
34 | var test_interface interface{}
35 | man := Man{name: "xiaoming", age: 18}
36 | var iman IMan
37 |
38 | iman = &man
39 | iman.walk()
40 | man.walk()
41 |
42 | fmt.Printf("iman %T 占中的字节数是 %d \n", iman, unsafe.Sizeof(iman))
43 | fmt.Println(man, iman, test_interface)
44 | }
45 | ```
46 |
47 | 打印输出
48 | ```
49 | walk name: xiaoming ,age: 18
50 | walk name: xiaoming ,age: 18
51 | iman *main.Man 占中的字节数是 16
52 | {xiaoming 18} &{xiaoming 18}
53 | ```
54 |
55 | ## 数据结构
56 | `runtime/runtime2.go`定义`iface`
57 | ```go
58 | type iface struct {
59 | tab *itab
60 | data unsafe.Pointer
61 | }
62 |
63 | // layout of Itab known to compilers
64 | // allocated in non-garbage-collected memory
65 | // Needs to be in sync with
66 | // ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs.
67 | type itab struct {
68 | inter *interfacetype
69 | _type *_type
70 | hash uint32 // copy of _type.hash. Used for type switches.
71 | _ [4]byte
72 | fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
73 | }
74 | ```
75 |
76 | `interfacetype`定义在文件`runtime/type.go`
77 | ```go
78 | type interfacetype struct {
79 | typ _type
80 | pkgpath name
81 | mhdr []imethod // method handler
82 | ```
83 |
84 | `接口`数据结构`data unsafe.Pointer`中会存储实现者的`指针`。
85 | 通过`dlv`查看内存结构可以证明这一点:
86 | ```shell
87 | (dlv) print &man
88 | (*main.Man)(0xc00000c030)
89 | (dlv) print &iman
90 | (*main.IMan)(0xc000065ee0)
91 | (dlv) x -fmt hex -count 32 -size 1 0xc000065ee0
92 | 0xc000065ee0: 0x78 0xf6 0x0f 0x01 0x00 0x00 0x00 0x00
93 | 0xc000065ee8: 0x30 0xc0 0x00 0x00 0xc0 0x00 0x00 0x00 #(*main.Man)(0xc00000c030)
94 | 0xc000065ef0: 0x08 0x01 0x40 0x01 0x00 0x00 0x00 0x00
95 | 0xc000065ef8: 0xb8 0x02 0x00 0x00 0xc0 0x00 0x00 0x00
96 | ```
97 | 内存图`iman`的`data`指向实现的结构体`man`
98 | ```
99 | ┌──────────────────────────┐
100 | │ interface(iman) │
101 | ├─────────────┬────────────┤
102 | │ tab │ data │
103 | └──────┬──────┴─────┬──────┘
104 | │ │ ┌─────────────┐ ┌─────────────┐
105 | ↓ └────────→│ 0xc00000c030├────────→│ struct(man) │
106 | ┌─────────────┐ └─────────────┘ └─────────────┘
107 | │ 0x10ff678 │
108 | └─────────────┘
109 | ```
110 |
111 | 也可以从汇编实现看出结构体赋值给指针`iman = &man`
112 | ```go
113 | 22: func main() {
114 | 23: var test_interface interface{}
115 | 24: man := Man{name: "xiaoming", age: 18}
116 | 25: var iman IMan
117 | 26:
118 | => 27: iman = &man
119 | 28: iman.walk()
120 | 29: man.walk()
121 | ```
122 | 汇编实现如下:
123 | ```shell
124 | base-interface.go:24 0x10cdd7a 488d05df370100 lea rax, ptr [rip+0x137df]
125 | base-interface.go:24 0x10cdd81 48890424 mov qword ptr [rsp], rax
126 | base-interface.go:24 0x10cdd85 e8d6fff3ff call $runtime.newobject
127 | base-interface.go:24 0x10cdd8a 488b442408 mov rax, qword ptr [rsp+0x8]
128 | base-interface.go:24 0x10cdd8f 4889442470 mov qword ptr [rsp+0x70], rax #[rsp+0x70] 就是变量man的地址
129 | base-interface.go:24 0x10cdd94 0f57c0 xorps xmm0, xmm0
130 | base-interface.go:24 0x10cdd97 0f118424b8000000 movups xmmword ptr [rsp+0xb8], xmm0
131 | base-interface.go:24 0x10cdd9f 48c78424c800000000000000 mov qword ptr [rsp+0xc8], 0x0
132 | base-interface.go:24 0x10cddab 488d05385d0200 lea rax, ptr [rip+0x25d38]
133 | base-interface.go:24 0x10cddb2 48898424b8000000 mov qword ptr [rsp+0xb8], rax
134 | base-interface.go:24 0x10cddba 48c78424c000000008000000 mov qword ptr [rsp+0xc0], 0x8
135 | base-interface.go:24 0x10cddc6 48c78424c800000012000000 mov qword ptr [rsp+0xc8], 0x12
136 | base-interface.go:24 0x10cddd2 488b7c2470 mov rdi, qword ptr [rsp+0x70]
137 | base-interface.go:24 0x10cddd7 48c7470808000000 mov qword ptr [rdi+0x8], 0x8
138 | base-interface.go:24 0x10cdddf 48c7471012000000 mov qword ptr [rdi+0x10], 0x12
139 | base-interface.go:24 0x10cdde7 833d02760d0000 cmp dword ptr [runtime.writeBarrier], 0x0
140 | base-interface.go:24 0x10cddee 7405 jz 0x10cddf5
141 | base-interface.go:24 0x10cddf0 e946030000 jmp 0x10ce13b
142 | base-interface.go:24 0x10cddf5 488907 mov qword ptr [rdi], rax
143 | base-interface.go:24 0x10cddf8 eb00 jmp 0x10cddfa
144 | base-interface.go:25 0x10cddfa 0f57c0 xorps xmm0, xmm0
145 | base-interface.go:25 0x10cddfd 0f11842488000000 movups xmmword ptr [rsp+0x88], xmm0 #[rsp+0x88]是iman变量的地址
146 | => base-interface.go:27 0x10cde05* 488b442470 mov rax, qword ptr [rsp+0x70] #[rsp+0x70]存储的就是man变量的地址
147 | base-interface.go:27 0x10cde0a 4889442448 mov qword ptr [rsp+0x48], rax
148 | base-interface.go:27 0x10cde0f 488d0d823c0300 lea rcx, ptr [rip+0x33c82]
149 | base-interface.go:27 0x10cde16 48898c2488000000 mov qword ptr [rsp+0x88], rcx #把`tab *itab`赋值给iman变量
150 | base-interface.go:27 0x10cde1e 4889842490000000 mov qword ptr [rsp+0x90], rax #就是把man变量的地址赋给iman.data变量,也就是指向结构体的实现
151 | base-interface.go:28 0x10cde26 488b842488000000 mov rax, qword ptr [rsp+0x88]
152 | ```
153 |
154 | 从汇编实现可以看出接口赋值需要填充两部分,一部分是`tab *itab`,另一部分是结构体实现`data unsafe.Pointer`部分。
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/md/base/object/struct.md:
--------------------------------------------------------------------------------
1 | - # 结构体
2 |
3 | 目录:
4 | - [示例](#示例)
5 | - [数据结构](#数据结构)
6 | - [结构体如何调用方法](#结构体如何调用方法)
7 |
8 |
9 | ## 示例
10 | [code](code/base/object/struct/base-struct.go)
11 | ```go
12 | package main
13 |
14 | import "fmt"
15 |
16 | type Man struct {
17 | name string
18 | age int
19 | }
20 |
21 | func (man *Man) Walk() int {
22 | fmt.Println("man Walk")
23 | return 0
24 | }
25 |
26 | func main() {
27 | man := Man{name: "xiaoming", age: 18}
28 |
29 | var walkFun func() int
30 | walkFun = man.Walk
31 | walkFun()
32 | }
33 | ```
34 |
35 | ## 数据结构
36 |
37 | 在go源码中暂时没有搜索到`struct`的定义,先使用`dlv`debug看看
38 |
39 | ```shell
40 | dlv debug main.go
41 |
42 | (dlv) print man
43 | main.Man {
44 | name: "xiaoming",
45 | age: 18,}
46 | (dlv) print &man
47 | (*main.Man)(0xc0000b6018)
48 | (dlv) print &man.name
49 | (*string)(0xc0000b6018) #从这看出man与man.name地址相同,结构体开始就是"string"
50 | (dlv) print &man.age
51 | (*int)(0xc0000b6028) #偏移16个字节
52 | ```
53 |
54 | 查看结构体内存视图:
55 | ```shell
56 | (dlv) x -fmt hex -count 32 -size 1 0xc0000b6018 # 查看man结构体
57 | 0xc0000b6018: 0x8f 0x14 0x0f 0x01 0x00 0x00 0x00 0x00
58 | 0xc0000b6020: 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00
59 | 0xc0000b6028: 0x12 0x00 0x00 0x00 0x00 0x00 0x00 0x00
60 | 0xc0000b6030: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
61 |
62 | # 在查看name指向的字符串地址(0x010f148f)
63 | # xiaoming 对应16进制 78 69 61 6f 6d 69 6e 67
64 | (dlv) x -fmt hex -count 32 -size 1 0x010f148f
65 | 0x10f148f: 0x78 0x69 0x61 0x6f 0x6d 0x69 0x6e 0x67
66 | 0x10f1497: 0x20 0x28 0x66 0x6f 0x72 0x63 0x65 0x64
67 | 0x10f149f: 0x29 0x20 0x2d 0x3e 0x20 0x6e 0x6f 0x64
68 | 0x10f14a7: 0x65 0x3d 0x20 0x62 0x6c 0x6f 0x63 0x6b
69 |
70 | ```
71 |
72 | 那么man结构体数据结构为(图形为特殊符号):
73 | ```shell
74 | ┌──────────────────────────┬─────────────┐
75 | │ string │ int │
76 | ├─────────────┬────────────┼─────────────┤
77 | │ data │ len │ │
78 | └─────────────┴────────────┴─────────────┘
79 | ```
80 |
81 | 这样看来结构体中的成员变量就是顺序排列的,如果是引用类型,那就是**引用类型的数据结构,不是一个指针**
82 |
83 | ## 结构体如何调用方法
84 |
85 | ```shell
86 | (dlv) c
87 | > main.main() ./base-struct.go:20 (hits goroutine(1):1 total:1) (PC: 0x10cbbea)
88 | 15: func main() {
89 | 16: man := Man{name: "xiaoming", age: 18}
90 | 17:
91 | 18: var walkFun func() int
92 | 19: walkFun = man.Walk
93 | => 20: walkFun()
94 | 21: }
95 | ```
96 | 把函数作为参数传递给`walkFun`,查看`walkFun`的内存视图:
97 |
98 | ```shell
99 | (dlv) print &man
100 | (*main.Man)(0xc00009df60)
101 | (dlv) print &walkFun
102 | (*func() int)(0xc00009df40)
103 | (dlv) print walkFun
104 | main.(*Man).Walk-fm
105 | ```
106 |
107 | waklFun的地址为`0xc00009df40`,指向的地址为`0x0c0009df50`->`0x10cbc20`
108 | ```shell
109 | (dlv) x -fmt hex -count 32 -size 1 0xc00009df40
110 | 0xc00009df40: 0x50 0xdf 0x09 0x00 0xc0 0x00 0x00 0x00
111 | 0xc00009df48: 0x50 0xdf 0x09 0x00 0xc0 0x00 0x00 0x00
112 | 0xc00009df50: 0x20 0xbc 0x0c 0x01 0x00 0x00 0x00 0x00
113 | 0xc00009df58: 0x60 0xdf 0x09 0x00 0xc0 0x00 0x00 0x00
114 |
115 | (dlv) x -fmt hex -count 32 -size 1 0xc00009df50
116 | 0xc00009df50: 0x20 0xbc 0x0c 0x01 0x00 0x00 0x00 0x00
117 | 0xc00009df58: 0x60 0xdf 0x09 0x00 0xc0 0x00 0x00 0x00
118 | 0xc00009df60: 0x0f 0x12 0x0f 0x01 0x00 0x00 0x00 0x00
119 | 0xc00009df68: 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00
120 |
121 | (dlv) x -fmt hex -count 32 -size 1 0x10cbc20
122 | 0x10cbc20: 0x65 0x48 0x8b 0x0c 0x25 0x30 0x00 0x00
123 | 0x10cbc28: 0x00 0x48 0x3b 0x61 0x10 0x76 0x51 0x48
124 | 0x10cbc30: 0x83 0xec 0x28 0x48 0x89 0x6c 0x24 0x20
125 | 0x10cbc38: 0x48 0x8d 0x6c 0x24 0x20 0x48 0x8b 0x59
126 | ```
127 |
128 | 到这里并不能看出`方法`是什么?结构体`Man`并未拥有方法,但是可以调用它?? 从这看出`0x10cbc20`
129 | 的内存并没有什么特殊,有没有可能是`代码地址`呢?
130 |
131 | 在函数`func (man *Man) Walk() int `里增加个断点
132 | ```shell
133 | (dlv) b base-struct.go:11
134 | (dlv) c # 调到下个断点
135 |
136 | (dlv) ls
137 | > main.(*Man).Walk() ./base-struct.go:11 (hits goroutine(1):1 total:1) (PC: 0x10cbaca)
138 | 6: name string
139 | 7: age int
140 | 8: }
141 | 9:
142 | 10: func (man *Man) Walk() int {
143 | => 11: fmt.Println("man Walk")
144 | 12: return 0
145 | 13: }
146 | 14:
147 | 15: func main() {
148 | 16: man := Man{name: "xiaoming", age: 18}
149 |
150 | ```
151 | 查看汇编代码,确认代码块位置`base-struct.go:10`
152 | ```shell
153 | (dlv) disassemble -a 0x10cbc00 0x10cbc40
154 | TEXT main.main(SB) /Users/ymm/work/mygithub/golang-cookbook/code/base/object/struct/base-struct.go
155 | base-struct.go:15 0x10cbc00 e81b13faff call $runtime.morestack_noctxt
156 | .:0 0x10cbc05 e956ffffff jmp $main.main
157 | .:0 0x10cbc0a cc int3
158 | .:0 0x10cbc0b cc int3
159 | .:0 0x10cbc0c cc int3
160 | .:0 0x10cbc0d cc int3
161 | .:0 0x10cbc0e cc int3
162 | .:0 0x10cbc0f cc int3
163 | .:0 0x10cbc10 cc int3
164 | .:0 0x10cbc11 cc int3
165 | .:0 0x10cbc12 cc int3
166 | .:0 0x10cbc13 cc int3
167 | .:0 0x10cbc14 cc int3
168 | .:0 0x10cbc15 cc int3
169 | .:0 0x10cbc16 cc int3
170 | .:0 0x10cbc17 cc int3
171 | .:0 0x10cbc18 cc int3
172 | .:0 0x10cbc19 cc int3
173 | .:0 0x10cbc1a cc int3
174 | .:0 0x10cbc1b cc int3
175 | .:0 0x10cbc1c cc int3
176 | .:0 0x10cbc1d cc int3
177 | .:0 0x10cbc1e cc int3
178 | .:0 0x10cbc1f cc int3
179 | => base-struct.go:10 0x10cbc20 65488b0c2530000000 mov rcx, qword ptr gs:[0x30]
180 | base-struct.go:10 0x10cbc29 483b6110 cmp rsp, qword ptr [rcx+0x10]
181 | base-struct.go:10 0x10cbc2d 7651 jbe 0x10cbc80
182 | base-struct.go:10 0x10cbc2f 4883ec28 sub rsp, 0x28
183 | base-struct.go:10 0x10cbc33 48896c2420 mov qword ptr [rsp+0x20], rbp
184 | base-struct.go:10 0x10cbc38 488d6c2420 lea rbp, ptr [rsp+0x20]
185 | base-struct.go:10 0x10cbc3d 48 rex.w
186 | base-struct.go:10 0x10cbc3e 8b prefix(0x8b)
187 | base-struct.go:10 0x10cbc3f 59 pop rcx
188 | ```
189 |
190 | 这样就可以确定函数代表的是`代码地址`,也就是要执行和跳转的地址,这里`walkFun()`代表要执行
191 | `base-struct.go:10 0x10cbc20`代码块的内容,也就是指定`func (man *Man) Walk() int`
192 | 这个`(man *Man)`会把`Man`的实现当做参数传入函数,相当于`func Walk(man *Man) int`
193 |
194 | > 这就理解`man.walk()`的含义了,相当于吧`man`作为`walk()int`的参数而已,并不向Java那样
195 | > 对象是和方法绑定在一起的,这里是完全分开的,在运行时通过参数传入,效果是一样的。
196 |
197 | 备注: 如果想要查看结构体调用方法是如何被当做参数传入的,可查看汇编。
198 |
199 | > 也可以参看 [go常用语句对应的汇编指令之函数调用](https://github.com/ymm135/go-build/blob/master/gouse-assembly.md)
200 |
201 | ```
202 | (dlv) disassemble
203 | TEXT main.(*Man).Walk(SB) /Users/ymm/work/mygithub/golang-cookbook/code/base/object/struct/base-struct.go
204 | base-struct.go:10 0x10cbaa0 65488b0c2530000000 mov rcx, qword ptr gs:[0x30]
205 | base-struct.go:10 0x10cbaa9 483b6110 cmp rsp, qword ptr [rcx+0x10]
206 | base-struct.go:10 0x10cbaad 0f868d000000 jbe 0x10cbb40
207 | base-struct.go:10 0x10cbab3 4883ec68 sub rsp, 0x68
208 | base-struct.go:10 0x10cbab7 48896c2460 mov qword ptr [rsp+0x60], rbp
209 | base-struct.go:10 0x10cbabc 488d6c2460 lea rbp, ptr [rsp+0x60]
210 | base-struct.go:10 0x10cbac1 48c744247800000000 mov qword ptr [rsp+0x78], 0x0
211 | => base-struct.go:11 0x10cbaca* 0f57c0 xorps xmm0, xmm0
212 | base-struct.go:11 0x10cbacd 0f11442438 movups xmmword ptr [rsp+0x38], xmm0
213 | base-struct.go:11 0x10cbad2 488d442438 lea rax, ptr [rsp+0x38]
214 | base-struct.go:11 0x10cbad7 4889442430 mov qword ptr [rsp+0x30], rax
215 | base-struct.go:11 0x10cbadc 8400 test byte ptr [rax], al
216 | base-struct.go:11 0x10cbade 488d0d5bb40000 lea rcx, ptr [rip+0xb45b]
217 | base-struct.go:11 0x10cbae5 48894c2438 mov qword ptr [rsp+0x38], rcx
218 | base-struct.go:11 0x10cbaea 488d0d17200300 lea rcx, ptr [rip+0x32017]
219 | base-struct.go:11 0x10cbaf1 48894c2440 mov qword ptr [rsp+0x40], rcx
220 | ```
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/md/base/reflect/static-proxy.md:
--------------------------------------------------------------------------------
1 | - # 静态代理
2 |
3 | 目录:
4 | - [示例](#示例)
5 |
6 |
7 | ## 示例
8 | [静态代理代码](../../../code/reflect/proxy/main.go)
9 |
10 | ```go
11 | package main
12 |
13 | import "fmt"
14 |
15 | type IMan interface {
16 | Walk() int
17 | }
18 |
19 | type ManProxy struct {
20 | manProxy IMan // 不能是指针
21 | }
22 |
23 | func (proxy *ManProxy) setProxy(man IMan) () {
24 | proxy.manProxy = man
25 | }
26 |
27 | func (proxy *ManProxy) Walk() int {
28 | proxy.manProxy.Walk()
29 | fmt.Println("proxy Walk")
30 | return 0
31 | }
32 |
33 | type Man struct {
34 | }
35 |
36 | func (man *Man) Walk() int {
37 | fmt.Println("man Walk")
38 | return 0
39 | }
40 |
41 | func main() {
42 | manProxy := &ManProxy{} // 是不是指针都行
43 | var manImpl IMan
44 | var man Man
45 | manImpl = &man //需要取地址, 调用man具体实现,而不是复制
46 |
47 | manProxy.setProxy(manImpl)
48 | manProxy.Walk()
49 |
50 | return
51 | }
52 | ```
53 |
54 | > 疑问: 为什么需要实现类的地址?(&man)以及`manImpl = &man`的原理是啥?
55 |
56 | 这里应该是编译器的解析规则,比如接口包含实现类的指针,我猜`manImpl = &man`语句就是把man结构实现的地址
57 | 赋给`IMan`的指针
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/md/base/source/debug.md:
--------------------------------------------------------------------------------
1 | - # 源码调试
2 |
3 | 目录:
4 | - [编译源码](#编译源码)
5 | - [调试编译过程](#调试编译过程)
6 |
7 |
8 | ## 编译源码
9 | go源码下载地址`https://go.dev/dl/go1.16.9.src.tar.gz`, 放到到路径为`/Users/zero/go/sdk/source`,go源码路径为`/Users/zero/go/sdk/source1.16.9/go`
10 |
11 | 从官网下载源码,修改源码,比如`source1.16.9/go/src/fmt/print.go`下`Println`函数
12 | ```go
13 | func Println(a ...interface{}) (n int, err error) {
14 | println("_xiao_")
15 | return Fprintln(os.Stdout, a...)
16 | }
17 | ```
18 |
19 | 进入源码src目录,执行`./make.bash` 或者 `./all.bash`
20 | ```shell
21 | Building Go cmd/dist using /Users/zero/go/sdk/go1.16.9. (go1.16.9 darwin/amd64)
22 | Building Go toolchain1 using /Users/zero/go/sdk/go1.16.9.
23 | Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
24 | Building Go toolchain2 using go_bootstrap and Go toolchain1.
25 | Building Go toolchain3 using go_bootstrap and Go toolchain2.
26 | Building packages and commands for darwin/amd64.
27 | ---
28 | Installed Go for darwin/amd64 in /Users/zero/Downloads/go
29 | Installed commands in /Users/zero/Downloads/go/bin
30 | ```
31 |
32 | 把当前的GOROOT切换为编译好的路径: `export GOROOT=/Users/zero/go/sdk/source1.16.9/go`
33 |
34 | ```go
35 | package main
36 |
37 | import "fmt"
38 |
39 | func main() {
40 | fmt.Println("Hello World")
41 | }
42 | ```
43 | `go run main.go` 或者 `$GOPATH/src/github.com/golang/go/bin/go run main.go`
44 | ```shell
45 | _xiao_
46 | Hello World
47 | ```
48 |
49 | ## 调试编译过程
50 | 就是用map创建时bmap明确的过程,源码版本是1.16.9,代码位置是 `source1.16.9/go/src/cmd/compile/internal/gc/reflect.go:83`
51 |
52 | 在源码处增加map类型打印`fmt.Println("_bmap_", t)`:
53 |
54 | ```go
55 | // bmap makes the map bucket type given the type of the map.
56 | func bmap(t *types.Type) *types.Type {
57 |
58 | fmt.Println("_bmap_", t)
59 | if t.MapType().Bucket != nil {
60 | return t.MapType().Bucket
61 | }
62 | ...
63 | ```
64 | demo文件:
65 | ```go
66 | package main
67 |
68 | import "fmt"
69 |
70 | func main() {
71 | m := map[string]string{
72 | "name": "ccmouse",
73 | "course": "golang",
74 | "site": "imooc",
75 | "quality": "notbad",
76 | }
77 | delete(m, "name")
78 | fmt.Println(m)
79 | }
80 | ```
81 | `go build -x map.go `编译输出:
82 | > 这个`go build`使用的是源码中的`build`可执行文件,这里通过修改`export GOROOT`实现
83 | ```
84 | # 使用库的位置, 比如fmt : packagefile fmt=/Users/zero/go/sdk/test/pkg/darwin_amd64/fmt.a
85 | zerodeMacBook-Pro:Downloads zero$ go build -x map.go
86 | WORK=/var/folders/1g/cxqzw10d5vz2c8npwkkm1cfr0000gn/T/go-build1238239978
87 | cat /Users/zero/Library/Caches/go-build/19/19476a0a2d49e836f7566ccc51859414f6e2ed77740ccacbb953b4673efc87e1-d # internal
88 | # command-line-arguments
89 | _xiao_
90 | _bmap_ map[string]string
91 | _xiao_
92 | _bmap_ map[string]string
93 | _xiao_
94 | _bmap_ map[string]string
95 | mkdir -p $WORK/b001/
96 | cat >$WORK/b001/importcfg.link << 'EOF' # internal
97 | packagefile command-line-arguments=/Users/zero/Library/Caches/go-build/ba/baef7897677499e2de076bc3e09e05cf86ff26184487063203a25fddd6d7d38c-d
98 | packagefile fmt=/Users/zero/go/sdk/test/pkg/darwin_amd64/fmt.a
99 | packagefile runtime=/Users/zero/go/sdk/test/pkg/darwin_amd64/runtime.a
100 | packagefile errors=/Users/zero/go/sdk/test/pkg/darwin_amd64/errors.a
101 | packagefile internal/fmtsort=/Users/zero/go/sdk/test/pkg/darwin_amd64/internal/fmtsort.a
102 |
103 | ...
104 |
105 | ```
106 |
107 | > **实现源码可以使用ide断点调试,编译的源码能不能使用ide断点调试呢?**
108 | > 既然编译器也是使用go写的,那就需要找到编译后的可执行文件(cmd compile),然后传入参数调试?
109 | > 查看cmd/compile的README,可以看到 `cmd/compile/internal/gc` (create compiler AST, type checking, AST transformations, converting to SSA)
110 | > 能不能调试`cmd/compile/main.go`?
111 |
112 | 源码pkg编译后文件列表:
113 |
114 | ```shell
115 | └── pkg
116 | ├── darwin_amd64 #darwin_amd64 平台静态库 current ar archive
117 | │ ├── cmd
118 | │ │ ├── asm
119 | │ │ │ └── internal
120 | │ │ │ └── asm.a
121 | │ │ ├── compile
122 | │ │ │ └── internal
123 | │ │ │ ├── amd64.a
124 | │ │ │ ├── arm.a
125 | │ │ │ ├── arm64.a
126 | │ │ │ ├── gc.a
127 | │ │ │ ├── ssa.a
128 | │ │ │ └── x86.a
129 | │ │ ├── go
130 | │ │ │ └── internal
131 | │ │ │ └── auth.a
132 | │ │ ├── internal
133 | │ │ │ └── obj.a
134 | │ │ ├── link
135 | │ │ │ └── internal
136 | │ │ │ ├── amd64.a
137 | │ │ │ └── x86.a
138 | │ │ └── vendor
139 | │ │ ├── github.com
140 | │ │ │ ├── google
141 | │ │ │ │ └── pprof
142 | │ │ │ │ └── driver.a
143 | │ │ │ └── ianlancetaylor
144 | │ │ │ └── demangle.a
145 | │ │ └── golang.org
146 | │ │ └── x
147 | │ ├── fmt.a
148 | │ └── go
149 | │ ├── ast.a
150 | │ └── types.a
151 | ├── include
152 | │ ├── asm_ppc64x.h
153 | │ ├── funcdata.h
154 | │ └── textflag.h
155 | ├── obj
156 | └── tool
157 | └── darwin_amd64 # 工具
158 | ├── addr2line
159 | ├── api
160 | ├── asm
161 | ├── buildid
162 | ├── cgo
163 | ├── compile # 编译工具,对应源码为cmd/compile/main.go
164 | ├── cover
165 | ├── dist
166 | ├── doc
167 | ├── fix
168 | ├── link
169 | ├── nm
170 | ├── objdump
171 | ├── pack
172 | ├── pprof
173 | ├── test2json
174 | ├── trace
175 | └── vet
176 | ```
177 |
178 | 其中.a的是go编译过程中的静态库,可以查看静态库的内容:
179 | ```shell
180 | $ ar -v -t pkg/darwin_amd64/fmt.a
181 | rw-r--r-- 0/0 11894 Jan 1 08:00 1970 __.PKGDEF
182 | rw-r--r-- 0/0 754130 Jan 1 08:00 1970 _go_.o
183 |
184 | # 解压后,查看_go_.o 格式
185 | $ ar -x pkg/darwin_amd64/fmt.a
186 |
187 | # 如果不解压,也可以直接查看fmt.a文件,基本一样的:vim fmt.a
188 | 1 ! # 压缩文件格式
189 | 2 __.PKGDEF 0 0 0 644 11894 ` # __.PKGDEF文件
190 | 3 go object darwin amd64 go1.16.9 X:none
191 | 4 build id "RlfCTK6S1eFmY7vzQP2e/HhLM8RlkNVokumyWo3n0" # build id
192 |
193 | 115 _go_.o 0 0 0 644 754130 ` # _go_.o文件
194 | 116 go object darwin amd64 go1.16.9 X:none
195 | 117 build id "RlfCTK6S1eFmY7vzQP2e/HhLM8RlkNVokumyWo3n0"
196 |
197 | # 用go tool compile 编译出二进制文件
198 | # 用go tool pack 打包成静态库
199 | # go tool compile
200 |
201 | go build || go run
202 | ```
203 |
204 | Go 语言的**编译器**入口在 `src/cmd/compile/internal/gc/main.go` 文件中,
205 | 其中 600 多行的 `cmd/compile/internal/gc.Main` 就是 Go 语言编译器的主程序,
206 | 该函数会先获取命令行传入的参数并更新编译选项和配置,随后会调用 `cmd/compile/internal/gc.parseFiles`
207 | 对输入的文件进行词法与语法分析得到对应的抽象语法树:
208 |
209 | ```go
210 | func Main(archInit func(*Arch)) {
211 | ...
212 |
213 | lines := parseFiles(flag.Args())
214 | ```
215 |
216 | 可以使用pkg/tool/compile的可执行文件编译指定文件
217 | ```shell
218 | ./compile ~/Downloads/map.go
219 | ```
220 |
221 | 如果把src/cmd在idea中打开,调试compile源码:
222 | ```shell
223 | # 包导入的有问题
224 | use of internal package cmd/compile/internal/amd64 not allowed
225 | ```
226 |
227 | 如果把当前模块的名称从`cmd`改为`cmd.local`, 然后把依赖替换为`cmd.local/compile/internal/amd64`
228 | 但是依赖于源码其他文件也会提示不能使用内部包,这个该如何解决呢?比如目前依赖`exec "internal/execabs"`可以把internal
229 | 复制一份,修改为`internal.local`,导入这个包
230 |
231 | 问题是使用的不是GOROOT/src下的internal,而是vendor中的依赖库
232 | ```shell
233 | ../internal/dwarf/dwarf.go:15:2: cannot find package "." in:
234 | /Users/zero/work/mygithub/go/src/cmd_local/vendor/internal_local/execabs
235 | ```
236 |
237 | 然后把文件夹放到vendor下
238 | ```shell
239 | drwxr-xr-x 4 zero staff 128 Nov 23 15:31 github.com
240 | drwxr-xr-x 3 zero staff 96 Nov 23 15:31 golang.org
241 | drwxr-xr-x 28 zero staff 896 Nov 23 15:01 internal_local
242 | -rw-r--r-- 1 zero staff 3626 Nov 23 15:31 modules.txt
243 | ```
244 |
245 | 这样就行了,可以直接编译,断点调试
246 | [可调试的golang源码](git@github.com:ymm135/go.git)
247 |
248 | 
249 |
250 |
251 |
252 | 
253 |
254 |
255 |
256 | [仓库地址](https://github.com/ymm135/go/tree/debug.1.16.9) 使用IDEA打开工程`src/cmd_local/`, 就开始源码编译模块debug吧! :smiley:
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/md/c-cpp-golang/base-c.md:
--------------------------------------------------------------------------------
1 | - # c基础
2 | [学习课程](https://coding.imooc.com/learn/list/463.html)
3 |
4 | 目录:
5 | - [环境搭建](#环境搭建)
6 | - [数据类型](#数据类型)
7 | - [函数与程序结构](#函数与程序结构)
8 | - [预处理和宏](#预处理和宏)
9 | - [数组](#数组)
10 | - [指针](#指针)
11 | - [聚合数据类型](#聚合数据类型)
12 | - [字符串你](#字符串你)
13 | - [时间的应用](#时间的应用)
14 | - [文本输入输出](#文本输入输出)
15 | - [线程与并发](#线程与并发)
16 | - [编译、链接和库](#编译链接和库)
17 | - [GUI编程](#gui编程)
18 |
19 |
20 | ## 环境搭建
21 | ## 数据类型
22 | ## 函数与程序结构
23 | ## 预处理和宏
24 | ## 数组
25 | ## 指针
26 | ## 聚合数据类型
27 | ## 字符串你
28 | ## 时间的应用
29 | ## 文本输入输出
30 | ## 线程与并发
31 | ## 编译、链接和库
32 | ## GUI编程
33 |
34 |
35 |
--------------------------------------------------------------------------------
/md/c-cpp-golang/c-c++-golang.md:
--------------------------------------------------------------------------------
1 | # c/c++/golang对比
2 |
3 | 目录:
4 | - [c/c++/golang对比](#ccgolang对比)
5 | - [变量声明及内存分配](#变量声明及内存分配)
6 | - [内存回收](#内存回收)
7 | - [字符串及容器实现](#字符串及容器实现)
8 | - [并发实现](#并发实现)
9 | - [文件及IO](#文件及io)
10 |
11 |
12 | ## 变量声明及内存分配
13 |
14 | ## 内存回收
15 |
16 | ## 字符串及容器实现
17 |
18 | ## 并发实现
19 |
20 | ## 文件及IO
21 |
22 |
23 |
--------------------------------------------------------------------------------
/md/middleware/es/es-base.md:
--------------------------------------------------------------------------------
1 | - # ES基础
2 |
3 | 目录:
4 | - [docker搭建es 6.8](#docker搭建es-68)
5 | - [使用elasticsearch head插件](#使用elasticsearch-head插件)
6 | - [查看节点信息](#查看节点信息)
7 | - [索引](#索引)
8 | - [使用kibana](#使用kibana)
9 | - [安装](#安装)
10 |
11 | - ## [官网安装](https://www.elastic.co/guide/en/elastic-stack/6.8/installing-elastic-stack.html)
12 |
13 | ## docker搭建es 6.8
14 |
15 | ```shell
16 | # 拉取镜像
17 | docker pull docker.elastic.co/elasticsearch/elasticsearch:6.8.20
18 |
19 | # 运行镜像
20 | docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.8.20
21 | ```
22 |
23 | 访问`http://localhost:9200/`
24 | ```json
25 | {
26 | "name" : "cefBBBH",
27 | "cluster_name" : "docker-cluster",
28 | "cluster_uuid" : "UsdiqEMZSNO-AqsOBtxF_Q",
29 | "version" : {
30 | "number" : "6.8.20",
31 | "build_flavor" : "default",
32 | "build_type" : "docker",
33 | "build_hash" : "c859302",
34 | "build_date" : "2021-10-07T22:00:24.085009Z",
35 | "build_snapshot" : false,
36 | "lucene_version" : "7.7.3",
37 | "minimum_wire_compatibility_version" : "5.6.0",
38 | "minimum_index_compatibility_version" : "5.0.0"
39 | },
40 | "tagline" : "You Know, for Search"
41 | }
42 | ```
43 |
44 | es集群`docker-compose.yml`
45 | ```yaml
46 | version: '3'
47 | services:
48 | elasticsearch_n0:
49 | image: elasticsearch:6.8.20
50 | container_name: elasticsearch_n0
51 | privileged: true
52 | environment:
53 | - cluster.name=elasticsearch-cluster
54 | - node.name=node0
55 | - node.master=true
56 | - node.data=true
57 | - bootstrap.memory_lock=true
58 | - http.cors.enabled=true
59 | - http.cors.allow-origin=*
60 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
61 | - "discovery.zen.ping.unicast.hosts=elasticsearch_n0,elasticsearch_n1,elasticsearch_n2"
62 | - "discovery.zen.minimum_master_nodes=2"
63 | ulimits:
64 | memlock:
65 | soft: -1
66 | hard: -1
67 | ports:
68 | - 9200:9200
69 | elasticsearch_n1:
70 | image: elasticsearch:6.8.20
71 | container_name: elasticsearch_n1
72 | privileged: true
73 | environment:
74 | - cluster.name=elasticsearch-cluster
75 | - node.name=node1
76 | - node.master=true
77 | - node.data=true
78 | - bootstrap.memory_lock=true
79 | - http.cors.enabled=true
80 | - http.cors.allow-origin=*
81 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
82 | - "discovery.zen.ping.unicast.hosts=elasticsearch_n0,elasticsearch_n1,elasticsearch_n2"
83 | - "discovery.zen.minimum_master_nodes=2"
84 | volumes:
85 | - ./data/node1:/usr/share/elasticsearch/data
86 | - ./logs/node1:/usr/share/elasticsearch/logs
87 | ports:
88 | - 9201:9200
89 | elasticsearch_n2:
90 | image: elasticsearch:6.8.20
91 | container_name: elasticsearch_n2
92 | privileged: true
93 | environment:
94 | - cluster.name=docker-cluster
95 | - node.name=node2
96 | - node.master=true
97 | - node.data=true
98 | - bootstrap.memory_lock=true
99 | - http.cors.enabled=true
100 | - http.cors.allow-origin=*
101 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
102 | - "discovery.zen.ping.unicast.hosts=elasticsearch_n0,elasticsearch_n1,elasticsearch_n2"
103 | - "discovery.zen.minimum_master_nodes=2"
104 | ulimits:
105 | memlock:
106 | soft: -1
107 | hard: -1
108 | ports:
109 | - 9202:9200
110 | ```
111 |
112 | ## 使用elasticsearch head插件
113 |
114 | ### 查看节点信息
115 | 
116 |
117 | ### 索引
118 |
119 | ## 使用kibana
120 | ### 安装
121 |
122 | ```shell
123 | # 拉取镜像
124 | docker pull docker.elastic.co/kibana/kibana:6.8.20
125 |
126 | # 启动并绑定es (--link elasticsearch 是容器名)
127 | docker run -d --name kibana --link elasticsearch -p 5601:5601 docker.elastic.co/kibana/kibana:6.8.20
128 | ```
129 |
130 | 访问页面`http://localhost:5601/` , 访问manager,可以查看索引及端口
131 |
132 | 
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/md/middleware/kafka/kafka-base.md:
--------------------------------------------------------------------------------
1 | - # kafka基础
2 | [官网](https://kafka.apache.org/)
3 |
4 | 目录:
5 | - [docker 安装](#docker-安装)
6 | - [kafka基本操作及指令](#kafka基本操作及指令)
7 | - [基本概念](#基本概念)
8 | - [实时分析模型](#实时分析模型)
9 | - [基本操作](#基本操作)
10 | - [应用](#应用)
11 |
12 |
13 | ## docker 安装
14 |
15 | 或者通过[kafka-eagle](https://github.com/ymm135/docker_kafka_eagle) ,直接通过docker安装
16 |
17 | zookeeper状态
18 | 
19 |
20 | zookeeper命令行
21 | 
22 |
23 | kafka信息
24 | 
25 |
26 | 也可以通过 [PrettyZoo](https://github.com/vran-dev/PrettyZoo) 测试`zookeeper`
27 |
28 | 
29 |
30 | 命令行操作:
31 | 
32 |
33 | ## kafka基本操作及指令
34 | ### 基本概念
35 | 
36 |
37 | ### 实时分析模型
38 |
39 | 
40 |
41 | 
42 |
43 | 大数据处理技术栈[来自慕课](https://class.imooc.com/sale/bigdata)
44 | 
45 |
46 | ### 基本操作
47 | [kafka-go](https://github.com/segmentio/kafka-go)
48 | [kafka-spring](https://github.com/spring-projects/spring-kafka)
49 |
50 | ### 应用
51 | kafka特点
52 | 1. 消息系统:生存者消费者模型,先入先出(FIFO)。Partition内部是FIFO的,partition之间呢不是FIFO的,当然我们可以把topic设为一个partition,这样就是严格的FIFO。
53 |
54 | 2. 持久化:可进行持久化操作。将消息持久化到磁盘,因此可用于批量消费,例如ETL,以及实时应用程序。通过将数据持久化到硬盘以及replication防止数据丢失。直接append到磁盘里去,这样的好处是直接持久化,数据不会丢失,第二个好处是顺序写,消费数据也是顺序读,所以持久化的同时还能保证顺序。
55 |
56 | 3. 分布式:易于向外扩展。所有的producer、broker和consumer都会有多个,均为分布式的。无需停机即可扩展机器。
57 |
58 | 4. 高吞吐量:同时为发布和订阅提供高吞吐量。据了解,Kafka每秒可以生产约25万消息(50 MB),每秒处理55万消息(110 MB)。
59 | - 零拷贝技术
60 | - 分布式存储
61 | - 顺序读顺序写
62 | - 批量读批量写
63 |
64 | Kafka的使用场景
65 |
66 | 1. 消息系统
67 | Kafka被当作传统消息中间件的替代品。与大多数消息系统相比,Kafka具有更好的吞吐量,内置的分区,多副本和容错性,这使其成为大规模消息处理应用程序的良好解决方案。
68 | 在我们的经验中,消息的使用通常是相对较低的吞吐量,但可能需要较低的端到端延迟,并且通常需要强大的持久性保证,这些Kafka都能提供。
69 |
70 |
71 | 2. 网站行为跟踪
72 | Kafka的另一个应用场景是跟踪用户浏览页面、搜索及其他行为,以发布-订阅的模式实时记录到对应的topic里。那么这些结果被订阅者拿到后,就可以做进一步的实时处理,或实时监控,或放到hadoop/离线数据仓库里处理。
73 |
74 |
75 | 3. 指标
76 | 用Kafka采集应用程序和服务器健康相关的指标,如CPU占用率、IO、内存、连接数、TPS、QPS等,然后将指标信息进行处理,从而构建一个具有监控仪表盘、曲线图等可视化监控系统。例如,很多公司采用Kafka与ELK(ElasticSearch、Logstash和Kibana)整合构建应用服务监控系统。
77 |
78 |
79 | 4. 日志聚合
80 | 其实开源产品有很多,包括Scribe、Apache Flume,很多人使用Kafka代替日志聚合(log aggregation)。
81 | 日志聚合一般来说是从服务器上收集日志文件,然后放到一个集中的位置(文件服务器或HDFS)进行处理。然而Kafka忽略掉文件的细节,将其更清晰地抽象成一个个日志或事件的消息流。这就让Kafka处理过程延迟更低,更容易支持多数据源和分布式数据处理。比起以日志为中心的系统比如Scribe或者Flume来说,Kafka提供同样高效的性能和副本机制确保了更强的耐用性保,并且端到端延迟更低。
82 |
83 |
84 | 5. 流处理
85 | 保存收集流数据,以提供之后对接的Storm或其他流式计算框架进行处理。很多用户会将那些从原始topic来的数据进行 阶段性处理,汇总,扩充或者以其他的方式转换到新的topic下再继续后面的处理。
86 | 例如一个文章推荐的处理流程,可能是先从RSS数据源中抓取文章的内 容,然后将其丢入一个叫做“文章”的topic中,后续操作可能是需要对这个内容进行清理,比如回复正常数据或者删除重复数据,最后再将内容匹配的结果返还给用户。
87 | 从0.10.0.0版本开始,Apache Kafka提供了一个名为Kafka Streams的轻量级,但功能强大的流处理库,可执行如上所述的数据处理。除了Kafka Streams之外,替代开源流处理工具还包括Apache Storm和Apache Samza。
88 |
89 |
90 | 6. 事件源
91 | 事件源是一种应用程序设计的方式,该方式的状态转移被记录为按时间顺序排序的记录序列。Kafka可以存储大量的日志数据,这使得它成为一个对这种方式的应用来说绝佳的后台。比如动态汇总(News feed)。
92 |
93 |
94 | 7. 提交日志
95 | Kafka可以为一种外部的持久性日志的分布式系统提供服务。这种日志可以在节点间备份数据,并为故障节点数据回复提供一种重新同步的机制。Kafka中日志压缩功能为这种用法提供了条件。在这种用法中,Kafka类似于Apache BookKeeper项目。
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/md/middleware/kafka/kafka-bigdata.md:
--------------------------------------------------------------------------------
1 | - # 大数据统计与分析Demo
2 | - ## [SZT-bigdata](https://github.com/ymm135/SZT-bigdata)
3 | 该项目主要分析深圳通刷卡数据,通过大数据技术角度来研究深圳地铁客运能力,探索深圳地铁优化服务的方向;
4 |
5 | 目录:
6 | - [架构图](#架构图)
7 | - [环境搭建](#环境搭建)
8 | - [Flume fluːm](#flume-fluːm)
9 | - [HBase `Hadoop Database`](#hbase-hadoop-database)
10 | - [HDFS](#hdfs)
11 | - [Hive haɪv](#hive-haɪv)
12 | - [Hue](#hue)
13 | - [Impala](#impala)
14 | - [Kafka Zookeeper](#kafka-zookeeper)
15 | - [Oozie](#oozie)
16 | - [Spark](#spark)
17 | - [YARN](#yarn)
18 | - [配置及运行](#配置及运行)
19 |
20 |
21 | ## 架构图
22 |
23 |
24 |
25 |

26 |
27 |
28 |
29 | ```shell
30 | 数字标记不分先后顺序,对应代码:
31 | 1-cn.java666.sztcommon.util.SZTData
32 | 2-cn.java666.etlflink.app.Jsons2Redis
33 | 3-cn.java666.etlspringboot.controller.RedisController#get
34 | 4-cn.java666.etlflink.app.Redis2ES
35 | 5-cn.java666.etlflink.app.Redis2Csv
36 | 6-Hive sql 脚本(开发维护成本最低)
37 | 7-Saprk 程序(开发维护成本最高,但是功能更强)
38 | 8-HUE 方便查询和展示 Hive 数据
39 | 9-cn.java666.etlflink.app.Redis2HBase
40 | 10、14-cn.java666.szthbase.controller.KafkaListen#sink2Hbase
41 | 11-cn.java666.etlflink.app.Redis2HBase
42 | 12-CDH HDFS+HUE+Hbase+Hive 一站式查询
43 | 13-cn.java666.etlflink.app.Redis2Kafka
44 | 15-cn.java666.sztflink.realtime.Kafka2MyCH
45 | 16-cn.java666.sztflink.realtime.sink.MyClickhouseSinkFun
46 | ```
47 |
48 | ## 环境搭建
49 | - Java-1.8/Scala-2.11 `brew install scala@2.11`
50 | - Flink-1.10
51 | - Redis-3.2
52 | - Kafka-2.1
53 | - Zookeeper-3.4.5
54 | - CDH-6.2
55 | - Docker-19
56 | - SpringBoot-2.13
57 | - Elasticsearch-7
58 | - Kibana-7.4
59 | - ClickHouse
60 | - MongoDB-4.0
61 | - Spark-2.3
62 | - Mysql-5.7
63 | - Hadoop3.0
64 |
65 | 通过docker搭建开发环境
66 |
67 | ### Flume [fluːm]()
68 | Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
69 |
70 | ### HBase `Hadoop Database`
71 | HBase是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的BigTable建模,实现的编程语言为 Java。它是Apache软件基金会的Hadoop项目的一部分,运行于HDFS文件系统之上,为 Hadoop 提供类似于BigTable 规模的服务。因此,它可以对稀疏文件提供极高的容错率。
72 |
73 | ### HDFS
74 | The Hadoop Distributed File System (HDFS) is a distributed file system designed to run on commodity hardware.
75 | Hadoop分布式文件系统(HDFS)是指被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统(Distributed File System)。它和现有的分布式文件系统有很多共同点。但同时,它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。
76 |
77 | ### Hive [haɪv]()
78 | hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表,并提供SQL查询功能,能将SQL语句转变成MapReduce任务来执行。
79 |
80 | ### Hue
81 |
82 | ### Impala
83 |
84 |
85 | ### Kafka Zookeeper
86 |
87 | [kafka基础中docker安装](kafka-base.md)
88 |
89 | ### Oozie
90 |
91 | ### Spark
92 | [bitnami/spark](https://hub.docker.com/r/bitnami/spark)
93 |
94 |
95 | Apache Spark is a high-performance engine for large-scale computing tasks, such as data processing, machine learning and real-time data streaming. It includes APIs for Java, Python, Scala and R.
96 |
97 | spark 是一个大数据处理技术栈,广义的spark包括 spark sql,spark shell,HDFS 和 YARN。
98 |
99 | `docker-compose.yml` ,执行`docker-compose up`
100 | ```yml
101 | version: '2'
102 |
103 | services:
104 | spark:
105 | image: docker.io/bitnami/spark:2.4.3-r10
106 | environment:
107 | - SPARK_MODE=master
108 | - SPARK_RPC_AUTHENTICATION_ENABLED=no
109 | - SPARK_RPC_ENCRYPTION_ENABLED=no
110 | - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no
111 | - SPARK_SSL_ENABLED=no
112 | ports:
113 | - '8080:8080'
114 | spark-worker:
115 | image: docker.io/bitnami/spark:2.4.3-r10
116 | environment:
117 | - SPARK_MODE=worker
118 | - SPARK_MASTER_URL=spark://spark:7077
119 | - SPARK_WORKER_MEMORY=1G
120 | - SPARK_WORKER_CORES=1
121 | - SPARK_RPC_AUTHENTICATION_ENABLED=no
122 | - SPARK_RPC_ENCRYPTION_ENABLED=no
123 | - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no
124 | - SPARK_SSL_ENABLED=no
125 | ```
126 |
127 |
128 | ### YARN
129 |
130 | ## 配置及运行
131 | IDEA需要安装`Scala`插件,方便开发。
132 | 有些源码的根目录是`scala`,需要标记`SZT-common/src/main/scala`为源码目录,要不然找不到`ParseCardNo.java`类
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/md/middleware/kafka/kafka-log.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/md/middleware/kafka/kafka-log.md
--------------------------------------------------------------------------------
/md/middleware/mongo/mongo-base.md:
--------------------------------------------------------------------------------
1 | - # mongo基础
2 |
3 | 目录:
4 | - [docker安装](#docker安装)
5 | - [studio 3T](#studio-3t)
6 | - [mongo指令](#mongo指令)
7 |
8 |
9 | ## docker安装
10 |
11 | ```shell
12 | docker pull mongo:4.0.26-xenial
13 |
14 | # 不用加 --auth
15 | docker run -itd --name mongo -p 27017:27017 --restart=always mongo:4.0.26-xenial
16 |
17 | # 登录设置密码
18 | docker exec -it mongo mongo admin
19 | # 创建管理员账号
20 | > db.createUser({ user: 'root', pwd: 'root', roles: [ { role: "root", db: "admin" } ] });
21 |
22 | # 使用管理员账号登录
23 | > db.auth('root', 'root')
24 |
25 | # 创建mongo-test数据库
26 | > use mongo-test;
27 |
28 | # 创建一个名为 root,密码为 root 的数据库用户。
29 | # readWrite 所有数据库
30 | > db.createUser({ user: 'root', pwd: 'root', roles: [{ role: "readWrite", db: "mongo-test" }] });
31 |
32 | # 查看表格
33 | > show dbs;
34 |
35 | # 查询所有角色权限(包含内置角色)
36 | > db.runCommand({ rolesInfo: 1, showBuiltinRoles: true })
37 | ```
38 |
39 |
40 | ## [studio 3T](https://studio3t.com/download/)
41 | 连接测试数据库:
42 | studio-3t.png
43 |
44 | 
45 |
46 |
47 | ## mongo指令
48 |
49 | ```shell
50 | ## 基本指令
51 | mongo 进入mongodb命令行
52 | show dbs; 显示数据库列表
53 | use dbname; 切换/创建dbname数据库,大小写敏感
54 | show collections; 显示数据库中的集合
55 | db.createCollection('users'); 创建users集合
56 | db.users.drop()或db.runCommand({"drop","users"}); 删除集合users
57 | db.runCommand({"dropDatabase": 1}); 删除当前数据库
58 |
59 |
60 | ## help指令
61 | odb.help();
62 | odb.yourColl.help();
63 | odb.youColl.find().help();
64 | db.dropDatabase(); 删除当前数据库
65 | db.cloneDatabase(“127.0.0.1”); 从指定主机上克隆数据库
66 | db.copyDatabase("mydb", "temp", "127.0.0.1"); 从指定的机器上复制指定数据库数据到某个数据库,这里将本机的mydb的数据复制到temp数据库中
67 | db 查看当前数据库
68 | db.version(); 查看数据库版本
69 | db.getMongo(); 查看当前db的链接机器
70 |
71 |
72 | ## 创建&新增save()
73 | db.users.save({"name":"lecaf"});
74 | 创建了名为users的集合,并新增了一条{"name":"lecaf"}的数据
75 | {
76 | "_id" : ObjectId("61b8936bd2ef28a43b6598f8"),
77 | "name" : "lecaf"
78 | }
79 |
80 | ## insert()
81 | db.users.insert({"name":"ghost", "age":10});
82 | 在users集合中插入一条新数据,,如果没有users这个集合,mongodb会自动创建
83 |
84 | ## 删除
85 | db.users.remove(); 删除users集合下所有数据
86 | db.users.remove({"name": "lecaf"}); 删除users集合下name=lecaf的数据
87 |
88 | ## 查找
89 | db.users.find(); 查找users集合中所有数据
90 | db.users.find({“name”:”feng”}); 查找users集合中name=feng的所有数据
91 | db.users.findOne(); 查找users集合中的第一条数据
92 | db.users.find({“name”:”feng”}); 查找users集合中name=feng的数据集合中的第一条数据
93 |
94 |
95 | # 修改
96 | db.users.update({"name":"lecaf"}, {"age":10})
97 | 修改name=lecaf的数据为age=10,第一个参数是查找条件,第二个参数是修改内容,除了主键,其他内容会被第二个参数的内容替换,主键不能修改,如图
98 |
99 | ```
100 |
101 | > save()和insert()也存在着些许区别:若新增的数据主键已经存在,insert()会不做操作并提示错误,而save() 则更改原来的内容为新内容。
--------------------------------------------------------------------------------
/md/middleware/mysql/mysql-advance.md:
--------------------------------------------------------------------------------
1 | - # mysql 高级功能
2 |
3 | - [MySQL Triggers](#mysql-triggers)
4 | - [MySQL Views](#mysql-views)
5 | - [MySQL Index](#mysql-index)
6 | - [创建索引](#创建索引)
7 | - [MySQL Full-Text Search](#mysql-full-text-search)
8 | - [MySQL Tips](#mysql-tips)
9 | - [MySQL用户定义变量介绍](#mysql用户定义变量介绍)
10 | - [MySQLSELECT INTO Variable语法](#mysqlselect-into-variable语法)
11 | - [正则表达式搜索](#正则表达式搜索)
12 | - [MySQL Administration](#mysql-administration)
13 |
14 |
15 | ## MySQL Triggers
16 | MySQL 触发器是自动执行的存储程序,以响应与表相关的特定事件,例如插入、更新或删除。本节向您展示如何有效地使用 MySQL 触发器。
17 | 在 MySQL 中,触发器是自动调用的存储程序,以响应关联表中发生的插入、更新或删除等事件。例如,您可以定义在将新行插入表之前自动调用的触发器。
18 |
19 | MySQL 支持为响应INSERT、UPDATE或DELETE事件而调用的触发器。
20 |
21 | SQL 标准定义了两种类型的触发器:行级触发器和语句级触发器。
22 |
23 | - 为插入、更新或删除的每一行激活一个行级触发器。例如,如果一个表插入、更新或删除了 100 行,则触发器会自动为受影响的 100 行调用 100 次。
24 | - 无论插入、更新或删除多少行,每个事务都会执行一次语句级触发器。
25 |
26 | MySQL 仅支持行级触发器。它不支持语句级触发器。
27 |
28 |
29 |
30 |
31 |

32 |
33 |
34 |
35 | 触发器的优点
36 | - 触发器提供了另一种检查数据完整性的方法。
37 | - 触发器处理来自数据库层的错误。
38 | - 触发器提供了另一种运行计划任务的方法。通过使用触发器,您不必等待计划的事件运行,因为触发器会在对表中的数据进行更改之前或之后自动调用。
39 | - 触发器可用于审计表中的数据更改。
40 |
41 | 触发器的缺点
42 | - 触发器只能提供扩展验证,而不是所有验证。对于简单的验证,您可以使用、NOT NULL和UNIQUE约束。CHECKFOREIGN KEY
43 | - 触发器可能难以排除故障,因为它们会在数据库中自动执行,而客户端应用程序可能无法看到这些触发器。
44 | - 触发器可能会增加 MySQL 服务器的开销。
45 |
46 | MySQLCREATE TRIGGER语句介绍
47 | 该`CREATE TRIGGER`语句创建一个新触发器。以下是该`CREATE TRIGGER`语句的基本语法:
48 |
49 | ```sql
50 | CREATE TRIGGER trigger_name
51 | {BEFORE | AFTER} {INSERT | UPDATE| DELETE }
52 | ON table_name FOR EACH ROW
53 | trigger_body;
54 | ```
55 |
56 | 下表说明了OLDandNEW修饰符的可用性:
57 |
58 | | 触发事件 | OLD | NEW |
59 | | ------- | --- | --- |
60 | | INSERT | 不 | 是的 |
61 | | UPDATE | 是的 | 是的 |
62 | | DELETE | 是的 | 不 |
63 |
64 | 示例:
65 | 首先,创建一个名为`employees_audit`以保留对表的更改的新`employees` 表:
66 | ```sql
67 | CREATE TABLE employees_audit (
68 | id INT AUTO_INCREMENT PRIMARY KEY,
69 | employeeNumber INT NOT NULL,
70 | lastname VARCHAR(50) NOT NULL,
71 | changedat DATETIME DEFAULT NULL,
72 | action VARCHAR(50) DEFAULT NULL
73 | );
74 | ```
75 | 接下来,创建一个在对表BEFORE UPDATE进行更改之前调用的触发器。`employees`
76 | ```sql
77 | CREATE TRIGGER before_employee_update
78 | BEFORE UPDATE ON employees
79 | FOR EACH ROW
80 | INSERT INTO employees_audit
81 | SET action = 'update',
82 | employeeNumber = OLD.employeeNumber,
83 | lastname = OLD.lastname,
84 | changedat = NOW();
85 | ```
86 |
87 | 查看触发器:
88 | ```shell
89 | # 展示
90 | SHOW TRIGGERS;
91 |
92 | # 删除
93 | DROP TRIGGER before_employee_update
94 | ```
95 |
96 | 新建数据,然后更新,最后查看trigger表中数据
97 | ```sql
98 | INSERT INTO employees VALUES (0, 10, 'xiao', 'ming', 'email@qq.com')
99 |
100 | SELECT * FROM employees
101 |
102 | UPDATE employees SET lastname = 'baibai' WHERE employeeNumber = 10
103 |
104 | SELECT * FROM employees
105 | ```
106 |
107 | ```
108 | mysql> SELECT * FROM employees_audit LIMIT 1;
109 | +----+----------------+----------+---------------------+--------+
110 | | id | employeeNumber | lastname | changedat | action |
111 | +----+----------------+----------+---------------------+--------+
112 | | 1 | 10 | ming | 2022-05-12 11:22:18 | update |
113 | +----+----------------+----------+---------------------+--------+
114 | ```
115 |
116 | ## MySQL Views
117 | 本教程向您介绍 MySQL 视图,它是存储在数据库中的命名查询,并逐步向您展示如何有效地管理视图。
118 |
119 | [视图原理](mysql-viewer.md)
120 |
121 | ## MySQL Index
122 |
123 | MySQL 使用索引来快速查找具有特定列值的行。如果没有索引,MySQL 必须扫描整个表以定位相关行。表越大,搜索越慢。
124 |
125 | 在本节中,您将了解 MySQL 索引,包括创建索引、删除索引、列出表的所有索引以及 MySQL 中索引的其他重要特性。
126 |
127 | - 创建索引——向您介绍索引概念,并向您展示如何为表的一个或多个列创建索引。
128 | - 删除索引——向您展示如何删除表的现有索引。
129 | - 列出表索引——为您提供列出表的所有索引或特定索引的语句。
130 | - 唯一索引——使用唯一索引来确保存储在列中的不同值。
131 | - 前缀索引——向您展示如何使用前缀索引为字符串列创建索引。
132 | - 不可见索引——涵盖索引可见性并向您展示如何使索引可见或不可见。
133 | - 降序索引——向您展示如何使用降序索引来提高查询性能。
134 | - 复合索引——说明复合索引的应用,并向您展示何时使用它们来加快查询速度。
135 | - 聚集索引——解释 InnoDB 表中的聚集索引。
136 | - 索引基数——解释索引基数并向您展示如何使用 show index 命令查看它。
137 | - USE INDEX 提示——向您展示如何使用 USE INDEX 提示来指示查询优化器使用指定- 索引的唯一列表来查找表中的行。
138 | - FORCE INDEX 提示——向您展示如何使用 FORCE INDEX 提示来强制查询优化器使用- 指定的索引从表中选择数据。
139 |
140 | ### 创建索引
141 | 索引是一种数据结构,例如 B-Tree,它提高了对表的数据检索速度,但需要额外的写入和存储来维护它。
142 | 查询优化器可以使用索引来快速定位数据,而不必为给定查询扫描表中的每一行。
143 | 当您使用主键或唯一键创建表时,MySQL 会自动创建一个名为. 该索引称为聚集索引。PRIMARY
144 | 索引之所以特殊,PRIMARY是因为索引本身与数据一起存储在同一张表中。聚集索引强制执行表中的行顺序。
145 | 索引以外的其他索引PRIMARY称为二级索引或非聚集索引。
146 |
147 | - 索引的优点
148 |
149 | 大大的提高查询速度
150 | 可以显著的减少查询和排序的时间。
151 |
152 | - 索引的缺点
153 |
154 | 当对表中的数据进行增加,修改,删除的时候,索引要同时进行维护,数据量越大维护时间越长
155 |
156 | ## MySQL Full-Text Search
157 | - 定义
158 | 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。全文搜索搜索引擎数据库中的数据。
159 |
160 | 在本节中,您将学习如何使用 MySQL 全文搜索功能。MySQL全文搜索提供了一种简单的方法来实现各种高级搜索技术,例如自然语言搜索、布尔文本搜索和查询扩展。
161 |
162 | 如果您使用过 Google 或 Bing 等搜索引擎,那么您使用的是全文搜索或 FTS。搜索引擎将网站上的内容收集到数据库中,并允许您根据关键字进行搜索。
163 |
164 | 除了搜索引擎之外,FTS 还为博客、新闻、电子商务等网站上的搜索结果提供支持。
165 |
166 | 全文搜索是一种搜索与搜索条件不完全匹配的文档的技术。文档是包含文本数据的数据库实体,例如产品描述、博客文章和文章。
167 |
168 | 比如你可以搜索 `Wood and Metal`,FTS可以返回单独包含搜索词的结果,只是Wood或Metal,或者包含不同顺序的词的结果`Word and Metal`,或者`Metal and Wood`。
169 |
170 | 从技术上讲,MySQL 支持使用LIKE 运算符和正则表达式进行部分文本搜索。但是,当文本列很大,表中的行数增加时,使用LIKEor正则表达式有一些限制:
171 |
172 |
173 | ## MySQL Tips
174 | 我们为您提供先进的 MySQL 技术和技巧,帮助您有效解决 MySQL 中最困难的挑战。
175 |
176 | ### MySQL用户定义变量介绍
177 | 有时,您希望将值从 SQL 语句传递到另一个 SQL 语句。为此,您将值存储在第一个语句中的 MySQL 用户定义变量中,并在后续语句中引用它。
178 |
179 | 要创建用户定义的变量,请使用 format @variable_name,其中variable_name由字母数字字符组成。自 MySQL 5.7.5 起,用户定义变量的最大长度为 64 个字符
180 |
181 | 用户定义的变量不区分大小写。这意味着@id和@ID是相同的。
182 |
183 | 您可以将用户定义的变量分配给特定的数据类型,例如整数、浮点、小数、字符串或NULL。
184 |
185 | 由一个客户端定义的用户定义变量对其他客户端不可见。换句话说,用户定义的变量是特定于会话的。
186 |
187 | 请注意,用户定义的变量是 MySQL 特定的 SQL 标准扩展。它们可能在其他数据库系统中不可用。
188 |
189 | - MySQL 变量赋值
190 | ```sql
191 | SET @variable_name := value;
192 | ```
193 |
194 | 您可以使用 := 或 = 作为 SET 语句中的赋值运算符。例如,该语句将数字 100 分配给变量 @counter。
195 |
196 | ```sql
197 | SET @counter := 100;
198 | ```
199 |
200 | 给变量赋值的第二种方法是使用SELECT 语句。在这种情况下,您必须使用 := 赋值运算符,因为在 SELECT 语句中,MySQL 将 = 运算符视为等于运算符。
201 |
202 | ```sql
203 | SELECT @variable_name := value;
204 | ```
205 | 赋值后,您可以在允许表达式的后续语句中使用该变量,例如,在WHERE子句、INSERT或UPDATE语句中。
206 |
207 | - MySQL 变量示例
208 | 以下语句获取表中最昂贵的产品products并将价格分配给用户定义的变量@msrp:
209 |
210 | ```sql
211 | SELECT
212 | @msrp:=MAX(msrp)
213 | FROM
214 | products;
215 | ```
216 | 以下语句使用@msrp 变量查询最贵产品的信息。
217 |
218 | ```sql
219 | SELECT
220 | productCode, productName, productLine, msrp
221 | FROM
222 | products
223 | WHERE
224 | msrp = @msrp;
225 | ```
226 |
227 | 有时,您想在表中插入一行,获取最后一个插入 id,并将其用于将数据插入到另一个表中。在这种情况下,您可以使用用户定义的变量来存储由AUTO_INCREMENT列生成的最新 id ,如下所示。
228 |
229 | ```sql
230 | SELECT @id:=LAST_INSERT_ID();
231 | ```
232 |
233 | 用户定义的变量只能保存一个值。如果 SELECT 语句返回多个值,则该变量将采用结果中最后一行的值。
234 |
235 | ```sql
236 | SELECT
237 | @buyPrice:=buyprice
238 | FROM
239 | products
240 | WHERE
241 | buyprice > 95
242 | ORDER BY buyprice;
243 | ```
244 |
245 | ### MySQLSELECT INTO Variable语法
246 | 要将查询结果存储在一个或多个变量中,请使用以下SELECT INTO variable语法:
247 | ```sql
248 | SELECT
249 | c1, c2, c3, ...
250 | INTO
251 | @v1, @v2, @v3,...
252 | FROM
253 | table_name
254 | WHERE
255 | condition;
256 | ```
257 | 在这种语法中:
258 |
259 | - c1、c2 和 c3 是要选择并存储到变量中的列或表达式。
260 | - @v1、@v2 和@v3 是存储来自c1、c2 和c3 的值的变量。
261 | 变量数必须与选择列表中的列数或表达式数相同。此外,查询必须返回零或一行。
262 |
263 | 如果查询没有返回任何行,MySQL 会发出没有数据的警告,并且变量的值保持不变。
264 |
265 | 如果查询返回多行,MySQL 会发出错误。为确保查询始终返回最多一行,您使用该LIMIT 1子句将结果集限制为单行。
266 |
267 | ### 正则表达式搜索
268 |
269 | ```
270 | SELECT
271 | *
272 | FROM
273 | employees
274 | WHERE
275 | email REGEXP '^(e|B|C)'
276 | ```
277 |
278 | email 其实为e/B/C的记录:
279 | ```
280 | +----+----------------+-----------+----------+--------------+
281 | | id | employeeNumber | firstname | lastname | email |
282 | +----+----------------+-----------+----------+--------------+
283 | | 1 | 10 | xiao | baibai | email@qq.com |
284 | +----+----------------+-----------+----------+--------------+
285 | ```
286 |
287 | ## MySQL Administration
288 | 在本节中,您将找到许多有用的 MySQL 管理教程,包括 MySQL 服务器启动和关闭、MySQL 服务器安全、MySQL 数据库维护、备份和恢复。
289 |
290 |
291 |
292 |
--------------------------------------------------------------------------------
/md/middleware/mysql/mysql-select-flow.md:
--------------------------------------------------------------------------------
1 | # Mysql 查询流程分析
2 | > 主要是了解mysql查询语句,通过主键索引查看ID后,查询表文件的具体流程是什么?耗时的主要原因?
3 |
4 | [前提知识>>InnoDB整体结构](mysql-base.md)
5 |
6 | - [Mysql 查询流程分析](#mysql-查询流程分析)
7 | - [测试数据](#测试数据)
8 | - [Select执行步骤](#select执行步骤)
9 | - [\[1万\]测试ID不同的单条数据查询及耗时](#1万测试id不同的单条数据查询及耗时)
10 | - [\[500万\]测试ID不同的单条数据查询及耗时](#500万测试id不同的单条数据查询及耗时)
11 |
12 |
13 | ## 测试数据
14 |
15 | ```sql
16 | DROP TABLE IF EXISTS employees;
17 | CREATE TABLE employees (
18 | emp_no INT NOT NULL,
19 | birth_date DATE NOT NULL,
20 | first_name VARCHAR(14) NOT NULL,
21 | last_name VARCHAR(16) NOT NULL,
22 | gender ENUM ('M','F') NOT NULL,
23 | hire_date DATE NOT NULL,
24 | PRIMARY KEY (emp_no)
25 | );
26 | ```
27 |
28 | 数据样本:
29 | ```
30 | mysql> select * from employees ;
31 | +--------+------------+------------+-------------+--------+------------+
32 | | emp_no | birth_date | first_name | last_name | gender | hire_date |
33 | +--------+------------+------------+-------------+--------+------------+
34 | | 10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 |
35 | | 10002 | 1964-06-02 | Bezalel | Simmel | F | 1985-11-21 |
36 | | 10003 | 1959-12-03 | Parto | Bamford | M | 1986-08-28 |
37 | | 10004 | 1954-05-01 | Chirstian | Koblick | M | 1986-12-01 |
38 | | 10005 | 1955-01-21 | Kyoichi | Maliniak | M | 1989-09-12 |
39 | | 10006 | 1953-04-20 | Anneke | Preusig | F | 1989-06-02 |
40 | ```
41 |
42 | 查询的日志信息:
43 | ```shell
44 | do_command: info: Command on socket (52) = 3 (Query)
45 | do_command: info: packet: ''; command: 3
46 | dispatch_command: info: command: 3
47 | dispatch_command: query: select * from employees where emp_no = 10006
48 | gtid_pre_statement_checks: info: gtid_next->type=0 owned_gtid.{sidno,gno}={0,0}
49 | mysql_execute_command: info: derived: 0 view: 0
50 | column_bitmaps_signal: info: read_set: 0x7fffc800f348 write_set: 0x7fffc800f368
51 | Field_iterator_table_ref::set_field_iterator: info: field_it for 'employees' is Field_iterator_table
52 | SELECT_LEX::prepare: info: setup_ref_array this 0x7fffc8005930 45 : 0 0 6 1 2 0
53 | setup_fields: info: thd->mark_used_columns: 1
54 | setup_fields: info: thd->mark_used_columns: 1
55 | SELECT_LEX::setup_conds: info: thd->mark_used_columns: 1
56 | get_lock_data: info: count 1
57 | get_lock_data: info: sql_lock->table_count 1 sql_lock->lock_count 0
58 | mysql_lock_tables: info: thd->proc_info System lock
59 | lock_external: info: count 1
60 | THD::decide_logging_format: info: query: select * from employees where emp_no = 10006
61 | THD::decide_logging_format: info: variables.binlog_format: 2
62 | THD::decide_logging_format: info: lex->get_stmt_unsafe_flags(): 0x0
63 | THD::decide_logging_format: info: decision: no logging since mysql_bin_log.is_open() = 0 and (options & OPTION_BIN_LOG) = 0x40000 and binlog_format = 2 and binlog_filter->db_ok(db) = 1
64 | THD::is_classic_protocol: info: type=0
65 |
66 | WHERE:(after const change) 0x7fffc80170f8 multiple equal(10006, `test`.`employees`.`emp_no`)
67 | add_key_fields: info: add_key_field for field emp_no
68 | get_lock_data: info: count 1
69 | get_lock_data: info: sql_lock->table_count 1 sql_lock->lock_count 0
70 |
71 | WHERE:(after substitute_best_equal) 0x7fffc8018138 1
72 |
73 | WHERE:(constants) 0x7fffc8018138 1
74 |
75 | Info about JOIN
76 | employees type: const q_keys: 1 refs: 1 key: 0 len: 4
77 | refs: 10006
78 | JOIN::make_tmp_tables_info: info: Using end_send
79 | JOIN::exec: info: Sending data
80 | Protocol_classic::start_result_metadata: info: num_cols 6, flags 5
81 | Protocol_classic::end_result_metadata: info: num_cols 6, flags 5
82 | do_select: info: Using end_send
83 | do_select: info: 1 records output
84 | ha_commit_trans: info: all=0 thd->in_sub_stmt=0 ha_info=0x7fffc80020d8 is_real_trans=1
85 | close_thread_tables: info: thd->open_tables: 0x7fffc800f240
86 | MDL_context::release_locks_stored_before: info: found lock to release ticket=0x7fffc800ed80
87 | dispatch_command: info: query ready
88 | net_send_ok: info: affected_rows: 0 id: 0 status: 2 warning_count: 0
89 | net_send_ok: info: OK sent, so no more error sending allowed
90 | ```
91 |
92 | ### [Select执行步骤](https://dev.mysql.com/doc/internals/en/selects.html)
93 | 每个选择都在以下基本步骤中执行:
94 |
95 | - JOIN::prepare
96 | - Initialization and linking JOIN structure to `st_select_lex`.
97 | - fix_fields() for all items (after fix_fields(), we know everything about item).
98 | - Moving HAVING to WHERE if possible.
99 | - Initialization procedure if there is one.
100 |
101 | - JOIN::optimize
102 | - Single select optimization.
103 | - Creation of first temporary table if needed.
104 |
105 | - JOIN::exec
106 | - Performing select (a second temporary table may be created).
107 |
108 | - JOIN::cleanup
109 | - Removing all temporary tables, other cleanup.
110 |
111 | - JOIN::reinit
112 | - Prepare all structures for execution of SELECT (with JOIN::exec).
113 |
114 |
115 | [官方查询结构说明](https://dev.mysql.com/doc/internals/en/select-structure.html)
116 | 复杂的查询结构
117 | 有两种描述选择的结构:
118 |
119 | - st_select_lex ( SELECT_LEX) 代表 SELECT自己
120 | - st_select_lex_unit ( SELECT_LEX_UNIT) 用于将多个选择分组
121 |
122 | 后一项表示`UNION`操作(没有`UNION`是只有一个的联合, `SELECT`并且在任何情况下都存在此结构)。将来,这种结构也将用于 `EXCEPT`和`INTERSECT`。
123 |
124 | 例如:
125 | ```
126 | (SELECT ...) UNION (SELECT ... (SELECT...)...(SELECT...UNION...SELECT))
127 | 1 2 3 4 5 6 7
128 | ```
129 |
130 | 将表示为:
131 | ```
132 | ------------------------------------------------------------------------
133 | level 1
134 | SELECT_LEX_UNIT(2)
135 | |
136 | +---------------+
137 | | |
138 | SELECT_LEX(1) SELECT_LEX(3)
139 | |
140 | --------------- | ------------------------------------------------------
141 | | level 2
142 | +-------------------+
143 | | |
144 | SELECT_LEX_UNIT(4) SELECT_LEX_UNIT(6)
145 | | |
146 | | +--------------+
147 | | | |
148 | SELECT_LEX(4) SELECT_LEX(5) SELECT_LEX(7)
149 |
150 | ------------------------------------------------------------------------
151 | ```
152 |
153 | > 注意:单个子查询 4 有自己的 SELECT_LEX_UNIT.
154 |
155 |
156 |
157 |
158 | `sql/sql_optimizer.h`
159 | ```c++
160 |
161 | class JOIN :public Sql_alloc
162 | {
163 | JOIN(const JOIN &rhs); /**< not implemented */
164 | JOIN& operator=(const JOIN &rhs); /**< not implemented */
165 |
166 | /// Query block that is optimized and executed using this JOIN
167 | SELECT_LEX *const select_lex;
168 | /// Query expression referring this query block
169 | SELECT_LEX_UNIT *const unit;
170 | /// Thread handler
171 | THD *const thd;
172 |
173 | int optimize();
174 | void reset();
175 | void exec();
176 | bool prepare_result();
177 | bool destroy();
178 | void restore_tmp();
179 | bool alloc_func_list();
180 | bool make_sum_func_list(List- &all_fields,
181 | List
- &send_fields,
182 | bool before_group_by, bool recompute= FALSE);
183 | }
184 | ```
185 |
186 | `sql/sql_lex.h`
187 | ```c++
188 | class st_select_lex: public Sql_alloc
189 | {
190 | void set_query_result(Query_result *result) { m_query_result= result; }
191 | Query_result *query_result() const { return m_query_result; }
192 | bool change_query_result(Query_result_interceptor *new_result,
193 | Query_result_interceptor *old_result);
194 | /// Result of this query block
195 | Query_result *m_query_result;
196 | }
197 | ```
198 |
199 | 调用栈:
200 | ```
201 | end_send(JOIN * join, QEP_TAB * qep_tab, bool end_of_records) (mysql-server/sql/sql_executor.cc:2933)
202 | do_select(JOIN * join) (mysql-server/sql/sql_executor.cc:902)
203 | JOIN::exec(JOIN * const this) (mysql-server/sql/sql_executor.cc:206) 同步调用 st_select_lex::set_query_result
204 | handle_query(THD * thd, LEX * lex, Query_result * result, ulonglong added_options, ulonglong removed_options) (mysql-server/sql/sql_select.cc:191)
205 | execute_sqlcom_select(THD * thd, TABLE_LIST * all_tables) (mysql-server/sql/sql_parse.cc:5167)
206 | mysql_execute_command(THD * thd, bool first_level) (mysql-server/sql/sql_parse.cc:2829)
207 | mysql_parse(THD * thd, Parser_state * parser_state) (mysql-server/sql/sql_parse.cc:5600)
208 | dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) (mysql-server/sql/sql_parse.cc:1493)
209 | do_command(THD * thd) (mysql-server/sql/sql_parse.cc:1032)
210 | handle_connection(void * arg) (mysql-server/sql/conn_handler/connection_handler_per_thread.cc:313)
211 | pfs_spawn_thread(void * arg) (mysql-server/storage/perfschema/pfs.cc:2197)
212 | libpthread.so.0!start_thread (未知源:0)
213 | libc.so.6!clone (未知源:0)
214 | ```
215 |
216 | 目前主要想知道通过主键索引查询到数据位置,如何到磁盘中查询对应记录的?
217 |
218 |
219 |
220 | [测试数据](https://github.com/datacharmer/test_db/blob/master/load_employees.dump)
221 |
222 | ## [1万]测试ID不同的单条数据查询及耗时
223 | `select * from employees where emp_no = 10006 `
224 |
225 |
226 |
227 | ## [500万]测试ID不同的单条数据查询及耗时
228 |
--------------------------------------------------------------------------------
/md/middleware/mysql/mysql-viewer.md:
--------------------------------------------------------------------------------
1 | # mysql 5.7 视图原理
2 |
3 | [mysql优化官方文档](https://dev.mysql.com/doc/refman/5.7/en/optimization.html)
4 |
5 | - [mysql 5.7 视图原理](#mysql-57-视图原理)
6 | - [视图介绍](#视图介绍)
7 | - [视图的操作](#视图的操作)
8 | - [视图性能分析](#视图性能分析)
9 |
10 |
11 | ## 视图介绍
12 | MySQL从5.0.1版本开始提供视图功能。
13 |
14 | `视图`(`view`)是一种虚拟存在的表,是一个逻辑表,本身并不包含数据。作为一个select语句保存在数据字典中的。
15 | 通过视图,可以展现基表的部分数据;视图数据来自定义视图的查询中使用的表,使用视图动态生成。
16 | `基表`:用来创建视图的表叫做基表 `base table`
17 |
18 |
19 | Q:为什么要使用视图?
20 |
21 | A:因为视图的诸多优点,如下
22 |
23 | - 1)简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。
24 |
25 | - 2)安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。
26 |
27 | - 3)数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
28 |
29 |
30 | ## 视图的操作
31 |
32 | - ### 测试数据准备
33 |
34 | `https://github.com/datacharmer/test_db` 测试数据来源, 导入`employees.sql`数据
35 |
36 | 关系如下:
37 |
38 |
39 |
40 |

41 |
42 |
43 |
44 | 其中表格和视图如下:
45 | ```shell
46 | mysql> show tables;
47 | +----------------------+
48 | | Tables_in_employees |
49 | +----------------------+
50 | | current_dept_emp |
51 | | departments |
52 | | dept_emp |
53 | | dept_emp_latest_date |
54 | | dept_manager |
55 | | employees |
56 | | salaries |
57 | | titles |
58 | +----------------------+
59 | 8 rows in set (0.00 sec)
60 |
61 | mysql> show table status where comment='view';
62 | +----------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------+----------+----------------+---------+
63 | | Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment |
64 | +----------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------+----------+----------------+---------+
65 | | current_dept_emp | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | VIEW |
66 | | dept_emp_latest_date | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | VIEW |
67 | +----------------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------+----------+----------------+---------+
68 | 2 rows in set (0.01 sec)
69 | ```
70 |
71 |
72 |
73 | 导入`load_employees.dump`近30万条数据
74 |
75 | - ### 创建视图
76 |
77 | ```shell
78 | CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
79 | VIEW view_name [(column_list)]
80 | AS select_statement
81 | [WITH [CASCADED | LOCAL] CHECK OPTION]
82 | ```
83 |
84 | 单表视图示例-同一天入职的
85 | ```shell
86 | create view count_emp_hire (聘用日期, 员工个数)
87 | as (select hire_date, count(*) from employees GROUP BY hire_date ORDER BY hire_date)
88 | ```
89 |
90 | 数据示例:
91 | ```shell
92 | mysql> select * from count_emp_hire limit 2;
93 | +--------------+--------------+
94 | | 聘用日期 | 员工个数 |
95 | +--------------+--------------+
96 | | 1985-01-01 | 9 |
97 | | 1985-01-14 | 1 |
98 | +--------------+--------------+
99 | 2 rows in set (3.89 sec)
100 | ```
101 |
102 |
103 | ## 视图性能分析
104 | - ### explain
105 |
106 | ```shell
107 | mysql> EXPLAIN select * from count_emp_hire limit 2;
108 | +----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
109 | | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
110 | +----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
111 | | 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 299335 | 100.00 | NULL |
112 | | 2 | DERIVED | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299335 | 100.00 | Using temporary; Using filesort |
113 | +----+-------------+------------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
114 | 2 rows in set, 1 warning (0.01 sec)
115 | ```
116 |
117 | 通过分析,说明每次视图都会全表扫描,并使用了临时表和文件排序,那视图仅仅是`sql语句别名`,最终还是要查看视图对应的语句 :sob:
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/md/middleware/nginx/nginx-1.24.0.md:
--------------------------------------------------------------------------------
1 | - # nginx-1.24.0 调试
2 |
3 | 目录:
4 |
5 | ## 源码调试
6 | ### 环境搭建
7 |
8 | https://github.com/nginx/nginx
9 |
10 | 在 Ubuntu 20.04 上编译 Nginx 源码,您需要按照以下步骤操作:
11 |
12 | 1. **安装依赖库**:安装编译 Nginx 所需的依赖库。打开终端,使用以下命令:
13 | ```
14 | sudo apt update
15 | sudo apt install build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev
16 | ```
17 |
18 | 2. **获取 Nginx 源码**:从 [Nginx 的 GitHub 仓库](https://github.com/nginx/nginx) 克隆或下载源代码。
19 |
20 | 3. **配置编译选项**:解压源码(如果是压缩包),然后进入源码目录。运行 `./configure` 脚本,根据需要指定选项。例如:
21 | ```
22 | ./configure --with-debug
23 | ```
24 | `--with-debug` 选项会开启调试支持。
25 |
26 | 4. **编译源码**:运行以下命令来编译 Nginx:
27 | ```
28 | make
29 | ```
30 |
31 | 5. **安装 Nginx**(可选):编译完成后,可以选择安装 Nginx。使用以下命令安装:
32 | ```
33 | sudo make install
34 | ```
35 |
36 | nginx是多进程架构:一个Master进程,若干个Worker进程。
37 |
38 | Master进程负责管理 Worker 进程,处理nginx命令行指令
39 |
40 | Worker进程负责接收处理客户端请求
41 |
42 |
43 |
44 |

45 |
46 |
47 |
48 | ### master 进程调试
49 |
50 | ```sh
51 | # 关闭Master守护进程的功能
52 | daemon off;
53 | # 便于调试只启动一个Worker进程
54 | worker_processes 1;
55 | ```
56 |
57 | `vscode`配置
58 | ```json
59 | {
60 | "version": "0.2.0",
61 | "configurations": [
62 | {
63 | "name": "(gdb) Launch",
64 | "type": "cppdbg",
65 | "request": "launch",
66 | "program": "${workspaceFolder}/objs/nginx",
67 | "args": [
68 | "-c",
69 | "${workspaceFolder}/conf/nginx.conf"
70 | ],
71 | "stopAtEntry": false,
72 | "cwd": "${workspaceFolder}",
73 | "environment": [],
74 | "MIMode": "gdb",
75 | "miDebuggerPath": "/usr/bin/gdb",
76 | "setupCommands": [
77 | {
78 | "description": "Enable pretty-printing for gdb",
79 | "text": "-enable-pretty-printing",
80 | "ignoreFailures": true
81 | }
82 | ]
83 | }
84 | ]
85 | }
86 | ```
87 |
88 |
89 | ### worker进程调试
90 |
91 | `ps aux | grep nginx` 查看nginx work进程id
92 |
93 |
94 | ```json
95 | {
96 | "version": "0.2.0",
97 | "configurations": [
98 | /* ... */
99 | {
100 | "name": "(gdb) Attach Worker",
101 | "type": "cppdbg",
102 | "request": "attach",
103 | "program": "${workspaceFolder}/objs/nginx",
104 | "MIMode": "gdb",
105 | "miDebuggerPath": "/usr/bin/gdb",
106 | "processId": "9133"
107 | }
108 | ]
109 | }
110 | ```
111 |
112 | > 这里的进程id填进去就行了
113 |
114 |
115 |
116 | ## 配置调试
117 |
118 | 通过调试,配置是否生效
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/md/middleware/nginx/nginx-base.md:
--------------------------------------------------------------------------------
1 | # nginx基础
2 | ## [官网](https://nginx.org/en/)
3 |
4 | ## [快速入门](https://nginx.org/en/docs/beginners_guide.html)
5 |
6 | ## 基础知识
7 |
8 | ### 查看nginx版本及编译参数
9 | ```sh
10 | nginx -V
11 | nginx version: nginx/1.24.0
12 | built by gcc 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
13 | built with OpenSSL 1.1.1f 31 Mar 2020
14 | TLS SNI support enabled
15 | configure arguments: --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module
16 | ```
17 |
18 | ### nginx配置测试`nginx -t`
19 | ```sh
20 | nginx -t
21 | nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
22 | nginx: configuration file /etc/nginx/nginx.conf test is successful
23 | ```
24 |
25 | ### 常用指令
26 |
27 | Nginx 常用的命令包括:
28 |
29 | 1. **启动 Nginx**:
30 | ```
31 | sudo nginx
32 | ```
33 |
34 | 2. **停止 Nginx**:
35 | - 快速停止:
36 | ```
37 | sudo nginx -s stop
38 | ```
39 | - 优雅停止:
40 | ```
41 | sudo nginx -s quit
42 | ```
43 |
44 | 3. **重新加载配置文件**:
45 | ```
46 | sudo nginx -s reload
47 | ```
48 |
49 | 4. **重新打开日志文件**:
50 | ```
51 | sudo nginx -s reopen
52 | ```
53 |
54 | 5. **检查配置文件**:
55 | ```
56 | sudo nginx -t
57 | ```
58 |
59 | 6. **查看 Nginx 版本和编译配置**:
60 | ```
61 | nginx -v
62 | nginx -V
63 | ```
64 |
65 | 这些命令涵盖了 Nginx 的基本操作,包括启动、停止、配置管理等。在使用这些命令时,确保你有足够的权限(如使用 `sudo`)。
66 |
67 |
--------------------------------------------------------------------------------
/md/middleware/nginx/nginx-http.md:
--------------------------------------------------------------------------------
1 | - # nginx-http-core
2 |
3 | 目录:
4 | - [基础知识](#基础知识)
5 | - [http 模块](#http-模块)
6 | - [server 模块](#server-模块)
7 | - [nginx配置测试`nginx -t`](#nginx配置测试nginx--t)
8 | - [项目一般配置](#项目一般配置)
9 | - [疑问及拓展](#疑问及拓展)
10 | - [HTTP 严格传输安全 (HSTS) 和 NGINX](#http-严格传输安全-hsts-和-nginx)
11 | - [websocket配置](#websocket配置)
12 |
13 |
14 | ## 基础知识
15 |
16 | https://nginx.org/en/docs/http/ngx_http_core_module.html
17 |
18 | ### http 模块
19 | https://nginx.org/en/docs/http/ngx_http_core_module.html#http
20 |
21 | ### server 模块
22 | https://nginx.org/en/docs/http/ngx_http_core_module.html#server
23 |
24 | Sets configuration for a virtual server. There is no clear separation between IP-based (based on the IP address) and name-based (based on the “Host” request header field) virtual servers. Instead, the listen directives describe all addresses and ports that should accept connections for the server, and the server_name directive lists all server names. Example configurations are provided in the “How nginx processes a request” document.
25 |
26 | - ### https://nginx.org/en/docs/http/request_processing.html
27 |
28 | Nginx 处理一个请求的流程大致如下:
29 |
30 | 1. **接收请求:** Nginx 首先接收到客户端的 HTTP 请求。
31 |
32 | 2. **寻找匹配的服务器块:** Nginx 通过请求中的主机名(Host 头)和端口号来寻找相匹配的 `server` 块。
33 |
34 | 3. **选择 location 块:** 在找到的 `server` 块中,Nginx 根据请求的 URI 选择最佳匹配的 `location` 块。
35 |
36 | 4. **执行请求:** 在 `location` 块中,Nginx 根据配置执行相关操作,比如代理传递、重定向或返回静态文件内容。
37 |
38 | 5. **生成响应:** Nginx 处理请求后,生成 HTTP 响应并发送给客户端。
39 |
40 | Nginx 的配置极其灵活,可以根据需要进行高度定制。更详细的处理流程和配置选项可以在 [Nginx 官方文档](https://nginx.org/en/docs/http/request_processing.html)中找到。
41 |
42 |
43 |
44 | ### nginx配置测试`nginx -t`
45 | ```sh
46 | nginx -t
47 | nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
48 | nginx: configuration file /etc/nginx/nginx.conf test is successful
49 | ```
50 |
51 | ### 项目一般配置
52 | ```sh
53 | user root;
54 | worker_processes auto;
55 |
56 | error_log /var/log/nginx/error.log notice;
57 | pid /var/run/nginx.pid;
58 |
59 | events {
60 | worker_connections 1024;
61 | }
62 |
63 |
64 | http {
65 | include mime.types;
66 | default_type application/octet-stream;
67 |
68 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
69 | '$status $body_bytes_sent "$http_referer" '
70 | '"$http_user_agent" "$http_x_forwarded_for"';
71 |
72 | access_log /var/log/nginx/access.log main;
73 |
74 | sendfile on;
75 | #tcp_nopush on;
76 |
77 | keepalive_timeout 65;
78 |
79 | ssl_protocols TLSv1.2;
80 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK';
81 |
82 | #gzip on;
83 |
84 | #include /etc/nginx/conf.d/*.conf;
85 |
86 | client_max_body_size 1024m;
87 | proxy_connect_timeout 3600; #单位秒
88 | proxy_send_timeout 3600; #单位秒
89 | proxy_read_timeout 3600; #单位秒
90 |
91 | server {
92 | listen 443 ssl;
93 | server_name localhost;
94 | root /opt/app/frontend;
95 |
96 | # ssl证书
97 | ssl_certificate /etc/nginx/crt/ssl.crt;
98 | ssl_certificate_key /etc/nginx/crt/ssl.key;
99 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK';
100 |
101 | location / {
102 | try_files $uri $uri/ /index.html;
103 | index index.html;
104 | add_header Cache-Control no-cache;
105 | }
106 |
107 | location /api {
108 | proxy_set_header Host $http_host;
109 | proxy_set_header X-Real-IP $remote_addr;
110 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
111 | proxy_set_header X-Forwarded-Proto $scheme;
112 |
113 | proxy_pass http://127.0.0.1:8000;
114 | proxy_cookie_path / /api;
115 | proxy_redirect default;
116 | rewrite ^/api/(.*) /$1 break;
117 | client_max_body_size 500m;
118 | }
119 |
120 | location ^~ /ws/ {
121 | proxy_pass http://127.0.0.1:8000/ws/;
122 | proxy_set_header X-Real-IP $remote_addr;
123 | proxy_set_header Host $host:8000;
124 | proxy_http_version 1.1;
125 | proxy_set_header Connection keep-alive;
126 | proxy_set_header Keep-Alive 600;
127 | proxy_set_header Upgrade $http_upgrade;
128 | proxy_set_header Connection "upgrade";
129 | proxy_connect_timeout 60;
130 | proxy_read_timeout 600;
131 | }
132 | }
133 | }
134 | ```
135 |
136 | > 其中包含资源配置/后端配置/websocket配置
137 |
138 | ## 疑问及拓展
139 | ### HTTP 严格传输安全 (HSTS) 和 NGINX
140 |
141 | add_header指令的继承规则
142 | NGINX 配置块add_header从其封闭块继承指令,因此您只需将该add_header指令放置在顶级server块中即可。有一个重要的例外:如果块add_header本身包含指令,它不会从封闭块继承标头,并且您需要重新声明所有add_header指令:
143 |
144 | > 意思就是顶级和子级的配置是独立,需要独立配置
145 |
146 | 漏洞详情:
147 | ```sh
148 | Attack Details
149 | URLs where HSTS is not enabled:
150 | https://10.25.30.126/
151 | https://10.25.30.126/crossdomain.xml
152 | https://10.25.30.126/sitemap.xml.gz
153 | https://10.25.30.126/smooth
154 | https://10.25.30.126/login
155 | https://10.25.30.126/clientaccesspolicy.xml
156 | https://10.25.30.126/sitemap.xml
157 | ```
158 |
159 | > 导致问题的原因在于静态资源配置
160 |
161 | ```sh
162 | location / {
163 | try_files $uri $uri/ /index.html;
164 | index index.html;
165 | add_header Cache-Control no-cache;
166 | }
167 | ```
168 |
169 | 所以需要在`location /`块中增加`add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;`
170 | ```sh
171 | location / {
172 | try_files $uri $uri/ /index.html;
173 | index index.html;
174 | add_header Cache-Control no-cache;
175 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
176 | }
177 | ```
178 |
179 | 重启nginx后,查看请求头:
180 |
181 | ```sh
182 | $ curl -Ik https://10.25.30.126
183 | HTTP/1.1 200 OK
184 | Server: nginx/1.24.0
185 | Date: Tue, 30 Jan 2024 15:35:20 GMT
186 | Content-Type: text/html
187 | Content-Length: 10961
188 | Last-Modified: Fri, 26 Jan 2024 08:27:15 GMT
189 | Connection: keep-alive
190 | ETag: "65b36ce3-2ad1"
191 | Cache-Control: no-cache
192 | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
193 | Accept-Ranges: bytes
194 | ```
195 |
196 | > `Strict-Transport-Security: max-age=31536000; includeSubDomains; preload`
197 |
198 | ### websocket配置
199 | ```sh
200 | location ^~ /ws/ {
201 | proxy_pass http://127.0.0.1:8000/ws/;
202 | proxy_set_header X-Real-IP $remote_addr;
203 | proxy_set_header Host $host:8000;
204 | proxy_http_version 1.1;
205 | proxy_set_header Connection keep-alive;
206 | proxy_set_header Keep-Alive 600;
207 | proxy_set_header Upgrade $http_upgrade;
208 | proxy_set_header Connection "upgrade";
209 | proxy_connect_timeout 60;
210 | proxy_read_timeout 600;
211 | }
212 | ```
213 |
214 | 在 Nginx 配置中关于 WebSocket 的设置主要通过 `location` 块来实现。这个配置段的作用和原理如下:
215 |
216 | 1. **`proxy_pass` 指令:** 将请求转发到指定的内部服务器地址,这里是 `http://127.0.0.1:8000/ws/`。这意味着所有匹配 `/ws/` 的请求都将被转发到本地的 `8000` 端口上的 WebSocket 服务。
217 |
218 | 2. **设置头部信息:** 通过 `proxy_set_header` 指令设置头部信息,以确保 WebSocket 的正确功能。例如,`X-Real-IP` 设置为客户端的 IP 地址,`Upgrade` 和 `Connection` 头部被设置以支持 WebSocket 协议的升级机制。
219 |
220 | 3. **保持连接活跃:** `keep-alive` 和 `Keep-Alive` 头部用于维持长连接,这对于 WebSocket 连接至关重要。
221 |
222 | 4. **超时设置:** `proxy_connect_timeout` 和 `proxy_read_timeout` 指定了连接和读取的超时时间。
223 |
224 | 这种配置允许 Nginx 作为反向代理处理 WebSocket 连接,确保 WebSocket 请求被正确地路由和处理。
225 |
--------------------------------------------------------------------------------
/md/middleware/pgsql/pgsql-base.md:
--------------------------------------------------------------------------------
1 | # pgsql 基础
2 |
3 | - [pgsql 基础](#pgsql-基础)
4 | - [官网](#官网)
5 | - [docker install](#docker-install)
6 | - [pgAdmin工具](#pgadmin工具)
7 | - [常用指令](#常用指令)
8 | - [pipeline安装及使用](#pipeline安装及使用)
9 |
10 |
11 | ## [官网](https://www.postgresql.org/)
12 | [postgres11文档](https://www.postgresql.org/docs/11/index.html)
13 |
14 | ## docker install
15 | [镜像文档](https://hub.docker.com/_/postgres)
16 |
17 | ```shell
18 | docker run -d -p 5432:5432 --name postgres -e POSTGRES_PASSWORD=root postgres:11.12
19 | ```
20 |
21 | > 默认数据库postgres, 默认用户postgres, 默认端口5432
22 |
23 | docker-compose.yml 示例
24 | ```yaml
25 | # Use postgres/example user/password credentials
26 | version: '3.1'
27 |
28 | services:
29 | db:
30 | image: postgres:11.12
31 | # restart: always
32 | environment:
33 | POSTGRES_PASSWORD: root
34 | ports:
35 | - 5432:5432
36 |
37 | adminer:
38 | image: adminer
39 | # restart: always
40 | ports:
41 | - 8980:8080
42 | ```
43 |
44 | 测试启动`docker-compose up` 或者后台运行`docker-compose up -d`
45 | 访问`http://localhost:8980/` 登录pgsql
46 |
47 | 
48 |
49 |
50 | 
51 |
52 |
53 | 
54 |
55 |
56 | ## [pgAdmin工具](https://www.postgresql.org/download/)
57 |
58 |
59 | 
60 |
61 | > 需要设置密码: set master password ,不然连接时会有
62 |
63 | ## 常用指令
64 |
65 | 命令行进入:
66 | ```shell
67 | $ psql mydb
68 |
69 | psql (11.14)
70 | Type "help" for help.
71 |
72 | mydb=>
73 |
74 | mydb=> SELECT version();
75 | version
76 | ------------------------------------------------------------------------------------------
77 | PostgreSQL 11.14 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit
78 | (1 row)
79 | ```
80 |
81 | ```shell
82 | \h:查看SQL命令的解释,比如\h select。
83 | ?:查看psql命令列表。
84 | \l:列出所有数据库。
85 | \c [database_name]:连接其他数据库。
86 | \d:列出当前数据库的所有表格。
87 | \d [table_name]:列出某一张表格的结构。
88 | \du:列出所有用户。
89 | \e:打开文本编辑器。
90 | \conninfo:列出当前数据库和连接的信息。
91 |
92 | ```
93 |
94 | ## [pipeline安装及使用](http://docs.pipelinedb.com/quickstart.html)
95 |
96 | [pipeline安装](http://docs.pipelinedb.com/installation.html)
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/md/middleware/rabbitmq/rabbitmq-bases.md:
--------------------------------------------------------------------------------
1 | # rabbitmq 基础
2 | [官网](https://www.rabbitmq.com/)
3 |
4 | - [rabbitmq 基础](#rabbitmq-基础)
5 | - [docker 安装](#docker-安装)
6 | - [java 发送与订阅](#java-发送与订阅)
7 | - [go 发送与订阅](#go-发送与订阅)
8 | - [队列上线配置](#队列上线配置)
9 |
10 |
11 | ## docker 安装
12 |
13 | ```shell
14 | # 携带管理界面的
15 | docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.7.7-management
16 |
17 | # 进入管理界面
18 | http://localhost:15672
19 | ```
20 |
21 | ## java 发送与订阅
22 |
23 | [java spring demo](https://www.rabbitmq.com/tutorials/tutorial-three-spring-amqp.html)
24 | ```java
25 | @Configuration
26 | public class MQConfig {
27 |
28 | public final static String QUEUE_NAME = "queue.test";
29 | public final static String TOPIC_ROUTINGKEY = "exchange.topic.stream";
30 | public final static String EXCHANGE_STREAM = "exchange.stream";
31 |
32 | @Bean
33 | public TopicExchange topic() {
34 | return new TopicExchange(EXCHANGE_STREAM);
35 | }
36 |
37 | @Bean
38 | public Queue packetQueue() {
39 | return new AnonymousQueue();
40 | }
41 |
42 |
43 | @Bean
44 | public Binding binding1a(TopicExchange topic, Queue packetQueue) {
45 | return BindingBuilder.bind(packetQueue).to(topic).with(TOPIC_ROUTINGKEY);
46 | }
47 |
48 |
49 | @Profile("receiver")
50 | @Bean
51 | public PacketRecvHandler receiver() {
52 | return new PacketRecvHandler();
53 | }
54 |
55 | }
56 | ```
57 |
58 | > 可以直接订阅 `exchange`,但是在绑定时,仍要增加一个`queue name`
59 |
60 | ```java
61 | @RabbitListener(queues = MQConfig.QUEUE_NAME)
62 | public void rawPackerRecv(String msg) { //(byte[] msg)
63 |
64 | }
65 | ```
66 |
67 | > 结束数据的类型要根据发送的数据类型变化,如果发送的是`byte[]`,接收的是`String`,接收的数据可能会有逗号分割符
68 |
69 |
70 | ## go 发送与订阅
71 | [官网demo](https://www.rabbitmq.com/tutorials/tutorial-three-go.html)
72 |
73 | ## 队列上线配置
74 | 目前使用MQ作为网络数据包缓存,可能瞬间流量超过10w+/s,直接导致程序队列溢出,JVM-OOM,通过设置队列大小
75 | 超过队列上线后,直接丢弃早期数据
76 |
77 | [设置参数及策略](https://www.rabbitmq.com/parameters.html#policies)
78 | [队列上限设置](https://www.rabbitmq.com/maxlength.html)
79 |
80 | 修改策略配置(队列最大消息个数为2):
81 | ```shell
82 | rabbitmqctl set_policy my-pol "^two-messages$" \
83 | '{"max-length":2,"overflow":"reject-publish"}' \
84 | --apply-to queues
85 | ```
86 |
87 | manager ui
88 |
89 |
90 |
91 |

92 |
93 |
94 |
95 | 通过ui往队列中push playload为`1,2,3`的三条数据,获取第一条数据是`2`
96 |
97 | 
98 |
99 | exchange设置参数不生效,虽然显示`Lim`
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/md/middleware/redis/redis-base.md:
--------------------------------------------------------------------------------
1 | # redis基础
2 | ## [官网](https://redis.io/)
3 | ### [在线redis](https://try.redis.io/)
4 | ### [所有指令](https://redis.io/commands)
5 |
6 | - ## 目录
7 | - [redis基础](#redis基础)
8 | - [官网](#官网)
9 | - [在线redis](#在线redis)
10 | - [所有指令](#所有指令)
11 | - [docker 安装](#docker-安装)
12 | - [容器](#容器)
13 | - [基本操作](#基本操作)
14 | - [keys](#keys)
15 | - [list](#list)
16 | - [hash](#hash)
17 | - [sets](#sets)
18 | - [pipeline](#pipeline)
19 | - [Redis Pub/Sub](#redis-pubsub)
20 | - [Using Redis as an LRU cache](#using-redis-as-an-lru-cache)
21 | - [过期键删除策略](#过期键删除策略)
22 |
23 |
24 |
25 | ## docker 安装
26 |
27 | ```shell
28 | docker run -p 6379:6379 --name redis -d redis:5.0 --requirepass 'redis'
29 |
30 | > config set requirepass redis
31 | ```
32 |
33 | ## 容器
34 | ### 基本操作
35 | ```sh
36 | # 查看数据类型
37 | > type cpu_used:17:15:17
38 | string
39 |
40 | # 测试与服务器的连接
41 | > ping
42 | PONG
43 |
44 | # 选择数据库
45 | > SELECT 1
46 | OK
47 |
48 | # 清空当前数据库中的所有数据
49 | > FLUSHDB
50 | OK
51 |
52 | # 清空所有数据库中的数据
53 | > FLUSHALL
54 | OK
55 | ```
56 |
57 | ### keys
58 |
59 | `KEYS` 命令用于查找所有符合给定模式的键。Redis 使用以下的一些模式匹配运算符:
60 |
61 | - `*` :匹配所有键。
62 | - `?` :匹配任意一个字符。
63 | - `[]` :匹配括号内的任意一个字符。
64 | - `[a-z]` :匹配 a 到 z 之间的任意一个字符。
65 |
66 | 下面是一些示例:
67 |
68 | 1. **查找所有键**:
69 | ```
70 | > KEYS *
71 | ```
72 |
73 | 2. **查找以 `user` 开头的所有键**:
74 | ```
75 | > KEYS user*
76 | ```
77 |
78 | 3. **查找以 `user` 开头,接着是任意一个字符,然后以 `data` 结尾的所有键**:
79 | ```
80 | > KEYS user?data
81 | ```
82 |
83 | 4. **查找以 `user` 开头,接着是数字(0-9)的所有键**:
84 | ```
85 | > KEYS user[0-9]*
86 | ```
87 |
88 | 5. **查找以 `a` 或 `b` 开头的所有键**:
89 | ```
90 | > KEYS [ab]*
91 | ```
92 |
93 | 请注意,`KEYS` 命令在生产环境中应当谨慎使用,特别是当 Redis 实例中有大量的键时。这是因为 `KEYS` 是一个阻塞性命令,可能会对性能产生影响。为了避免这种情况,在生产环境中,你应该考虑使用 `SCAN` 命令进行迭代操作。
94 |
95 |
96 |
97 | ### [list](https://redis.io/commands#list)
98 | ```shell
99 | 127.0.0.1:6379> LPUSH mylist "world"
100 | (integer) 1
101 | 127.0.0.1:6379> LPUSH mylist "hello"
102 | (integer) 2
103 | 127.0.0.1:6379> LRANGE mylist 0 -1
104 | 1) "hello"
105 | 2) "world"
106 | 127.0.0.1:6379> LPOP mylist
107 | "hello"
108 | 127.0.0.1:6379> RPUSH mylist "one"
109 | (integer) 2
110 | 127.0.0.1:6379> RPUSH mylist "two"
111 | (integer) 3
112 | 127.0.0.1:6379>
113 | ```
114 |
115 | ### [hash](https://redis.io/commands#hash)
116 |
117 | ```shell
118 | 127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
119 | OK
120 | 127.0.0.1:6379> HGET myhash field1
121 | "Hello"
122 | 127.0.0.1:6379> HKEYS myhash
123 | 1) "field1"
124 | 2) "field2"
125 | 127.0.0.1:6379> HVALS myhash
126 | 1) "Hello"
127 | 2) "World"
128 | 127.0.0.1:6379> HLEN myhash
129 | (integer) 2
130 | ```
131 |
132 | ### [sets](https://redis.io/commands#set)
133 |
134 | ```shell
135 | 127.0.0.1:6379> SADD myset "one"
136 | (integer) 1
137 | 127.0.0.1:6379> SADD myset "two"
138 | (integer) 1
139 | 127.0.0.1:6379> SADD myset "three"
140 | (integer) 1
141 | 127.0.0.1:6379> SPOP myset
142 | "two"
143 | 127.0.0.1:6379> SMEMBERS myset
144 | 1) "three"
145 | 2) "one"
146 | 127.0.0.1:6379> SADD myotherset "three"
147 | (integer) 1
148 | 127.0.0.1:6379> SADD myset "two"
149 | (integer) 1
150 | 127.0.0.1:6379> SMEMBERS myset
151 | 1) "two"
152 | 2) "three"
153 | 3) "one"
154 | 127.0.0.1:6379> SMOVE myset myotherset "two"
155 | (integer) 1
156 | 127.0.0.1:6379> SMEMBERS myset
157 | 1) "three"
158 | 2) "one"
159 | ```
160 |
161 |
162 | ## [pipeline](https://redis.io/topics/pipelining)
163 |
164 | 
165 |
166 | ## [Redis Pub/Sub](https://redis.io/topics/pubsub)
167 | 
168 |
169 | - 订阅的不是字段/key, 而是channel
170 |
171 | ```shell
172 | SUBSCRIBE foo bar [channel ...]
173 | ```
174 |
175 | - 如果没有订阅,发布消息到channel会失败
176 | ```shell
177 | 127.0.0.1:6379> PUBLISH foo redis
178 | (integer) 1
179 | 127.0.0.1:6379> PUBLISH foo 2
180 | (integer) 1
181 | 127.0.0.1:6379> PUBLISH foo 2
182 | (integer) 0
183 | 127.0.0.1:6379> PUBLISH foo 3 //执行 SUBSCRIBE foo
184 | (integer) 0
185 | 127.0.0.1:6379> PUBLISH foo 3
186 | (integer) 1
187 | 127.0.0.1:6379>
188 | ```
189 |
190 | ## [Using Redis as an LRU cache](https://redis.io/topics/lru-cache)
191 | 在Redis的配置文件redis.conf文件中,配置maxmemory的大小参数如下所示:
192 | ```
193 | maxmemory 100mb
194 | ```
195 |
196 | 命令行设置
197 | ```shell
198 | 127.0.0.1:6379> config get maxmemory
199 | 1) "maxmemory"
200 | 2) "0"
201 | 127.0.0.1:6379> config set maxmemory 100mb
202 | OK
203 | 127.0.0.1:6379> config get maxmemory
204 | 1) "maxmemory"
205 | 2) "104857600"
206 | ```
207 |
208 | 倘若实际的存储中超出了Redis的配置参数的大小时,Redis中有淘汰策略,把需要淘汰的key给淘汰掉,整理出干净的一块内存给新的key值使用。
209 |
210 | Redis提供了6种的淘汰策略,其中默认的是noeviction,这6中淘汰策略如下:
211 |
212 | - noeviction(默认策略):若是内存的大小达到阀值的时候,所有申请内存的**指令都会报错**。
213 | - allkeys-lru:所有key都是使用LRU算法进行淘汰。
214 | - volatile-lru:所有设置了过期时间的key使用LRU算法进行淘汰。
215 | - allkeys-random:所有的key使用随机淘汰的方式进行淘汰。
216 | - volatile-random:所有设置了过期时间的key使用随机淘汰的方式进行淘汰。
217 | - volatile-ttl:所有设置了过期时间的key根据过期时间进行淘汰,越早过期就越快被淘汰。
218 |
219 | 假如在Redis中的数据有一部分是热点数据,而剩下的数据是冷门数据,或者我们不太清楚我们应用的缓存访问分布状况,这时可以使用allkeys-lru。
220 |
221 | 假如所有的数据访问的频率大概一样,就可以使用allkeys-random的淘汰策略。
222 |
223 | 假如要配置具体的淘汰策略,可以在redis.conf配置文件中配置,具体配置如下所示:
224 | ```
225 | maxmemory-policy noeviction
226 | ```
227 |
228 | 命令行设置:
229 | ```shell
230 | 127.0.0.1:6379> config get maxmemory-policy
231 | 1) "maxmemory-policy"
232 | 2) "noeviction"
233 | 127.0.0.1:6379> config set maxmemory-policy allkeys-lru
234 | OK
235 | 127.0.0.1:6379> config get maxmemory-policy
236 | 1) "maxmemory-policy"
237 | 2) "allkeys-lru"
238 | ```
239 |
240 | ## 过期键删除策略
241 |
242 | ```shell
243 | 127.0.0.1:6379> set name xiaoming
244 | OK
245 | 127.0.0.1:6379> get name
246 | "xiaoming"
247 | 127.0.0.1:6379> TTL name # 没有过期时间, 返回值为-1
248 | (integer) -1
249 | 127.0.0.1:6379> EXPIRE name 10
250 | (integer) 1
251 | 127.0.0.1:6379> TTL name # 设置过期时间后,返回过期剩余时间
252 | (integer) 8
253 | 127.0.0.1:6379> TTL name
254 | (integer) 7
255 | 127.0.0.1:6379> TTL name
256 | (integer) 6
257 | 127.0.0.1:6379> TTL name
258 | (integer) 5
259 | 127.0.0.1:6379> TTL name
260 | (integer) 4
261 | 127.0.0.1:6379> TTL name
262 | (integer) 1
263 | 127.0.0.1:6379> TTL name # 如果过去后,就会返回-2
264 | (integer) -2
265 | ```
266 |
267 | Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。
268 | 惰性删除:Redis的惰性删除策略由 `db.c/expireIfNeeded` 函数实现,所有键读写命令执行之前都会调用 expireIfNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,继续执行原有的命令。
269 |
270 | 定期删除:由`redis.c/activeExpireCycle` 函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
271 |
272 | 注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数量的键。
273 |
274 | 定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis2.8版本后,可以通过修改配置文件redis.conf 的 `hz` 选项来调整这个次数。
275 |
276 |
277 | 过期删除还是需要到对应数据结构中清除`dictDelete(db->dict,key->ptr)`
278 | ```js
279 | /* Delete a key, value, and associated expiration entry if any, from the DB */
280 | int dbSyncDelete(redisDb *db, robj *key) {
281 | /* Deleting an entry from the expires dict will not free the sds of
282 | * the key, because it is shared with the main dictionary. */
283 | if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
284 | if (dictDelete(db->dict,key->ptr) == DICT_OK) {
285 | if (server.cluster_enabled) slotToKeyDel(key);
286 | return 1;
287 | } else {
288 | return 0;
289 | }
290 | }
291 | ```
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
--------------------------------------------------------------------------------
/md/middleware/redis/redis-statistic-analysis.md:
--------------------------------------------------------------------------------
1 | # redis 数据统计分析
2 | 常情况下,我们面临的用户数量以及访问量都是巨大的,比如百万、千万级别的用户数量,或者千万级别、甚至亿级别的访问信息。
3 | 所以,我们必须要选择能够非常高效地统计大量数据(例如亿级)的集合类型。
4 | 如何选择合适的数据集合,我们首先要了解常用的统计模式,并运用合理的数据来解决实际问题。
5 |
6 | 四种统计类型:
7 |
8 | 1. 二值状态统计;
9 | 2. 聚合统计;
10 | 3. 排序统计;
11 | 4. 基数统计。
12 |
13 | 将用到 String、Set、Zset、List、hash 以外的拓展数据类型 Bitmap、HyperLogLog来实现。
14 |
15 | 来看下剩下的三种统计类型。
16 |
17 | 文章涉及到的指令可以通过在线 Redis 客户端运行调试,地址:https://try.redis.io/,超方便的说。
18 |
19 | ## 基数统计
20 | `基数统计:统计一个集合中不重复元素的个数,常见于计算独立用户数(UV)`
21 |
22 | - PV(访问量):即Page View, 即页面浏览量或点击量,用户每次刷新即被计算一次。
23 | - UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只被计算一次。
24 | - IP(独立IP):即Internet Protocol,指独立IP数。00:00-24:00内相同IP地址之被计算一次。
25 |
26 | 实现基数统计最直接的方法,就是采用集合(Set)这种数据结构,当一个元素从未出现过时,便在集合中增加一个元素;如果出现过,那么集合仍保持不变。
27 |
28 | 当页面访问量巨大,就需要一个超大的 Set 集合来统计,将会浪费大量空间。另外,这样的数据也**不需要很精确**,到底有没有更好的方案呢?
29 |
30 | 这个问题问得好,`Redis` 提供了 `HyperLogLog` 数据结构就是用来解决种种场景的统计问题。
31 |
32 | `HyperLogLog` 是一种不精确的去重基数方案,它的统计规则是基于概率实现的,标准误差 `0.81%`,这样的精度足以满足 UV 统计需求了。
33 |
34 | ### Set方案
35 |
36 | ```shell
37 | > sadd uv-set xiao
38 | (integer) 1
39 | > sadd uv-set ming
40 | (integer) 1
41 | > sadd uv-set hong
42 | (integer) 1
43 | > sadd uv-set lan
44 | (integer) 1
45 | > SMEMBERS uv-set
46 | 1) "xiao"
47 | 2) "lan"
48 | 3) "hong"
49 | 4) "ming"
50 | ```
51 |
52 | ### Hash 方案
53 |
54 | `利用 Hash 类型实现,将用户 ID 作为 Hash 集合的 key,访问页面则执行 HSET 命令将 value 设置成 1。`
55 |
56 | 即使用户重复访问,重复执行命令,也只会把这个 userId 的值设置成 “1"。
57 |
58 | 最后,利用 HLEN 命令统计 Hash 集合中的元素个数就是 UV。
59 |
60 |
61 | ```
62 | > hset uv-hset xiao:id5 1
63 | 1
64 | > hset uv-hset ming:id5 1
65 | 1
66 | > hset uv-hset hong:id7 1
67 | 1
68 | > hlen uv-hset
69 | 3
70 | > hkeys uv-hset
71 | 1) "xiao:id5"
72 | 2) "ming:id5"
73 | 3) "hong:id7"
74 | ```
75 |
76 | ### `HyperLogLog` 方案
77 |
78 | 利用 Redis 提供的 HyperLogLog 高级数据结构(不要只知道 Redis 的五种基础数据类型了)。这是一种用于基数统计的数据集合类型,即使数据量很大,计算基数需要的空间也是固定的。
79 |
80 | 每个 HyperLogLog 最多只需要花费 12KB 内存就可以计算 2 的 64 次方个元素的基数。
81 |
82 | Redis 对 HyperLogLog 的存储进行了优化,在计数比较小的时候,存储空间采用系数矩阵,占用空间很小。
83 |
84 | 只有在计数很大,稀疏矩阵占用的空间超过了阈值才会转变成稠密矩阵,占用 12KB 空间。
85 |
86 | > 什么是基数?
87 | > 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
88 |
89 | ```shell
90 | > PFADD hll1 foo bar zap a
91 | (integer) 1
92 | > PFADD hll2 a b c foo
93 | (integer) 1
94 | > PFMERGE hll3 hll1 hll2
95 | "OK"
96 | > PFCOUNT hll3
97 | (integer) 6
98 | ```
99 |
100 | > PFMERGE 可以合并多个
101 |
102 |
103 |
104 |

105 |
106 |
107 |
108 | > 三种方式的内存消耗对比。
109 |
110 |
111 | ## 排序统计
112 |
113 | Redis 的 4 个集合类型中(List、Set、Hash、Sorted Set),List 和 Sorted Set 就是有序的。
114 |
115 | - List:按照元素插入 List 的顺序排序,使用场景通常可以作为 消息队列、最新列表、排行榜;
116 |
117 | - Sorted Set:根据元素的 score 权重排序,我们可以自己决定每个元素的权重值。使用场景(排行榜,比如按照播放量、点赞数)。
118 |
119 | 最新评论列表:
120 | ```
121 | > LPUSH l-sort 1 2 3 4 5 6
122 | (integer) 6
123 | > LRANGE l-sort 0 4
124 | 1) "6"
125 | 2) "5"
126 | 3) "4"
127 | 4) "3"
128 | 5) "2"
129 | ```
130 |
131 | 排行榜:
132 |
133 | `ZADD`
134 | 比如我们将《青花瓷》和《花田错》播放量添加到 musicTop 集合中:
135 | ```shell
136 | ZADD musicTop 100000000 青花瓷 8999999 花田错
137 | ```
138 |
139 | `ZINCRBY`
140 | 《青花瓷》每播放一次就通过 ZINCRBY指令将 score + 1。
141 | ```
142 | ZINCRBY musicTop 1 青花瓷
143 | 100000001
144 | ```
145 |
146 | `ZRANGEBYSCORE`
147 | 语法为:
148 | ```
149 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
150 | ```
151 | 我们需要获取 musicTop 前十播放量音乐榜单,目前最大播放量是 N ,可通过如下指令获取:
152 | ```
153 | # 所有的
154 | > ZRANGEBYSCORE musicTop -inf +inf WITHSCORES
155 | 1) "花田错"
156 | 2) 8999999.0
157 | 3) "青花瓷"
158 | 4) 100000001.0
159 |
160 | # top
161 | ZRANGEBYSCORE musicTop N-9 N WITHSCORES
162 |
163 | ```
164 |
165 | `ZREVRANGE`
166 | 可通过 `ZREVRANGE key start stop [WITHSCORES]`指令。其中元素的排序按 score 值递减(从大到小)来排列。具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。
167 |
168 | ```
169 | # 获取第一个
170 | > ZREVRANGE musicTop 0 0 WITHSCORES
171 | ```
172 |
173 | > 在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议优先考虑使用 Sorted Set。
174 |
175 | ## 聚合统计
176 | 聚合统计就是统计多个集合元素的聚合结果,比如说:
177 |
178 | - 统计多个元素的共有数据(交集);
179 | - 统计两个集合其中的一个独有元素(差集统计);
180 | - 统计多个集合的所有元素(并集统计)。
181 |
182 | Redis 的 Set 类型支持集合内的增删改查,底层使用了 Hash 数据结构,无论是 add、remove 都是 O(1) 时间复杂度。
183 |
184 | 并且支持多个集合间的交集、并集、差集操作,利用这些集合操作,解决上边提到的统计问题。
185 |
186 | ### 交集-共同好友
187 |
188 | 比如 QQ 中的共同好友正是聚合统计中的交集。我们将账号作为 Key,该账号的好友作为 Set 集合的 value。
189 |
190 | ```
191 | > SADD user-ming "hello"
192 | (integer) 1
193 | > SADD user-ming "foo"
194 | (integer) 1
195 | > SADD user-ming "bar"
196 | (integer) 1
197 | > SADD user-hong "hello"
198 | (integer) 1
199 | > SADD user-hong "world"
200 | (integer) 1
201 | > SINTERSTORE commom user-ming user-hong
202 | (integer) 1
203 | > SMEMBERS commom
204 | 1) "hello"
205 | ```
206 |
207 | > Redis Sinterstore 命令将给定集合之间的交集存储在指定的集合中。如果指定的集合已经存在,则将其覆盖。
208 |
209 | ### 差集-每日新增好友数
210 | > Redis Sdiffstore 命令将给定集合之间的差集存储在指定的集合中。如果指定的集合 key 已存在,则会被覆盖。
211 |
212 | 语法: `SDIFFSTORE DESTINATION_KEY KEY1..KEYN`
213 |
214 | ```
215 | > SADD user-ming "hello"
216 | (integer) 1
217 | > SADD user-ming "foo"
218 | (integer) 1
219 | > SADD user-ming "bar"
220 | (integer) 1
221 | > SADD user-hong "hello"
222 | (integer) 1
223 | > SADD user-hong "world"
224 | (integer) 1
225 |
226 | # 用 user-ming 与 user-hong
227 | > SDIFFSTORE diff1 user-ming user-hong
228 | (integer) 2
229 | > SMEMBERS diff1
230 | 1) "foo"
231 | 2) "bar"
232 |
233 | # 用 user-hong 与 user-ming
234 | > SDIFFSTORE diff2 user-hong user-ming
235 | (integer) 2
236 | > SMEMBERS diff2
237 | 1) "world"
238 | ```
239 |
240 | > key的顺序会对结果有影响
241 |
242 |
243 | ### 并集-总共新增好友
244 |
245 | 先求差集,再求并集。
246 |
247 | > Redis Sunionstore 命令将给定集合的并集存储在指定的集合 destination 中。如果 destination 已经存在,则将其覆盖。
248 |
249 | 语法: `SUNIONSTORE destination key [key ...]`
250 |
251 | ```
252 | > SADD key1 "a"
253 | (integer) 1
254 | > SADD key1 "b"
255 | (integer) 1
256 | > SADD key1 "c"
257 | (integer) 1
258 | > SADD key2 "c"
259 | (integer) 1
260 | > SADD key2 "d"
261 | (integer) 1
262 | > SADD key2 "e"
263 | (integer) 1
264 | > SUNIONSTORE key key1 key2
265 | (integer) 5
266 | > SMEMBERS key
267 | 1) "c"
268 | 2) "b"
269 | 3) "e"
270 | 4) "d"
271 | 5) "a"
272 | ```
273 |
274 |
275 | Set 的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致 Redis 实例阻塞。
276 |
277 | 所以,可以专门部署一个集群用于统计,让它专门负责聚合计算,或者是把数据读取到客户端,在客户端来完成聚合统计,这样就可以规避由于阻塞导致其他服务无法响应。
278 |
279 |
280 |
281 |
282 |
283 |
--------------------------------------------------------------------------------
/md/middleware/redis/redis4-data-structure.md:
--------------------------------------------------------------------------------
1 | # redis4.0 数据结构
2 | 下载源码
3 | ```shell
4 | git clone https://github.com/redis/redis -b 4.0
5 | ```
6 |
7 | 具体模块可以查看src/Makefile
8 | ```shell
9 | 比如调试redis-server
10 | REDIS_SERVER_NAME=redis-server
11 |
12 | REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o ...
13 |
14 | ```
15 |
16 | server `main()`方法在`src/server.c`中,首先编译源码`make -j4`,然后在debug的配置`Executable`中选择编译完成的
17 | `redis-sever`文件,然后debug
18 |
19 | 
20 |
21 | ## hash
22 | ```shell
23 | 127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
24 | OK
25 | (351.50s)
26 | 127.0.0.1:6379> HMSET myhash field2 "World1234567890"
27 | OK
28 | (321.42s)
29 | 127.0.0.1:6379> HMSET myhash field0 "Haha"
30 | ```
31 |
32 | 
33 |
34 | 看到的数据结构和网上文章不一样,没有hash过程及存储呀~~?? hash中的key并不是经过hash存储的。
35 |
36 | 编码选项:
37 | ```
38 | /* Objects encoding. Some kind of objects like Strings and Hashes can be
39 | * internally represented in multiple ways. The 'encoding' field of the object
40 | * is set to one of this fields for this object. */
41 | #define OBJ_ENCODING_RAW 0 /* Raw representation */
42 | #define OBJ_ENCODING_INT 1 /* Encoded as integer */
43 | #define OBJ_ENCODING_HT 2 /* Encoded as hash table */
44 | #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
45 | #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
46 | #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
47 | #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
48 | #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
49 | #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
50 | #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
51 | ```
52 |
53 | [网文](https://juejin.cn/post/6844903693075103757)
54 |
55 | 
56 |
57 | 修改`redis.conf`配置
58 | ```
59 | ############################### ADVANCED CONFIG ###############################
60 |
61 | # Hashes are encoded using a memory efficient data structure when they have a
62 | # small number of entries, and the biggest entry does not exceed a given
63 | # threshold. These thresholds can be configured using the following directives.
64 | hash-max-ziplist-entries 512 # 最大存储多少对key value
65 | hash-max-ziplist-value 64 # value超过多少字节之后使用hashtable
66 | ```
67 |
68 | 修改为
69 | ```shell
70 | hash-max-ziplist-entries 512
71 | hash-max-ziplist-value 1
72 | ```
73 |
74 | 启动时增加redis.conf配置,只要存储的value大于一个字节,就会启用hashtable存储
75 | ```shell
76 | /Users/xxx/work/github/redis/src/redis-server /Users/xxx/work/github/redis/redis.conf
77 | 14496:C 15 Dec 11:37:10.798 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
78 | 14496:C 15 Dec 11:37:10.798 # Redis version=4.0.14, bits=64, commit=ff6db5f1, modified=1, pid=14496, just started
79 | 14496:C 15 Dec 11:37:10.798 # Configuration loaded
80 | ```
81 |
82 | 存储指令:`127.0.0.1:6379> HMSET myhash field1 "Hello"`
83 |
84 | 首选会判断存储类型是ziplist还是ht?
85 | ```js
86 | void hashTypeConvertZiplist(robj *o, int enc) {
87 | serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST);
88 |
89 | if (enc == OBJ_ENCODING_ZIPLIST) {
90 | /* Nothing to do... */
91 |
92 | } else if (enc == OBJ_ENCODING_HT) {
93 | hashTypeIterator *hi;
94 | dict *dict;
95 | int ret;
96 |
97 | hi = hashTypeInitIterator(o);
98 | dict = dictCreate(&hashDictType, NULL);
99 |
100 | /* 省略部分 */
101 | } else {
102 | serverPanic("Unknown hash encoding");
103 | }
104 | }
105 | ```
106 |
107 | 数据结构是个字典`dict`
108 | ```
109 | typedef struct dict {
110 | dictType *type;
111 | void *privdata;
112 | dictht ht[2]; # hashtable具体实现,有两个ht,方便rehashing
113 | long rehashidx; /* rehashing not in progress if rehashidx == -1 */
114 | unsigned long iterators; /* number of iterators currently running */
115 | } dict;
116 |
117 | /* This is our hash table structure. Every dictionary has two of this as we
118 | * implement incremental rehashing, for the old to the new table. */
119 | typedef struct dictht {
120 | dictEntry **table;
121 | unsigned long size;
122 | unsigned long sizemask;
123 | unsigned long used;
124 | } dictht;
125 |
126 | # 键值对存储实体
127 | typedef struct dictEntry {
128 | void *key;
129 | union {
130 | void *val;
131 | uint64_t u64;
132 | int64_t s64;
133 | double d;
134 | } v;
135 | struct dictEntry *next;
136 | } dictEntry;
137 |
138 | ```
139 |
140 | 首先查看`dictEntry`:
141 |
142 | 
143 |
144 | > `hset`单个,`hmset`多个,在4.0以后hmset不提倡使用。
145 | > As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code.
146 | > 根据Redis 4.0.0,HMSET被视为已弃用。请在新代码中使用HSET。
147 |
148 | ```shell
149 | $ HMSET myhash field1 "Hello"
150 | $ HMSET myhash field2 "Hello123"
151 |
152 | entry
153 |
154 | key 0x7fbd2b504301 => field2
155 | 0x00007fbd2b504300 | 30 66 69 65 6c 64 32 00 00 00 00 00 00 00 00 00 │ 0field2········· │
156 |
157 | value 0x7fbd2b504591 => Hello123
158 | 0x00007fbd2b504590 | 40 48 65 6c 6c 6f 31 32 33 00 00 00 00 00 00 00 │ @Hello123······· │
159 |
160 | next 0x7fbd2b5042d0 => 0x7fbd2c0041e1
161 | 0x00007fbd2b5042d0 | e1 41 00 2c bd 7f 00 00 f1 42 50 2b bd 7f 00 00 │ ·A·,·····BP+···· │
162 |
163 | 0x7fbd2c0041e1 =>
164 | 0x00007fbd2c0041e0 | 30 66 69 65 6c 64 31 00 00 00 ff 00 00 00 00 00 │ 0field1········· │
165 |
166 | ```
167 |
168 | 通过表达式查看table
169 | ```
170 | t_hash.c // int hashTypeSet(r 函数
171 | (dictht)(((dict*)(o->ptr))->ht[0])
172 |
173 | result = {dictht}
174 | table = {dictEntry **} 0x7fbd2b504510
175 | size = {unsigned long} 8
176 | sizemask = {unsigned long} 7
177 | used = {unsigned long} 6
178 | ```
179 |
180 | 如果一直使用一个`key`,那么所有的数据都会使用链表存储,只有使用不同的`key`才会涉及到hash表
181 | ```shell
182 | # 相当于关系型数据库中用户表的两条记录
183 | hmset user:1 name tom age 23 city beijing
184 | hmset user:2 name tim age 18 city beijing
185 | ```
186 |
187 | 比如执行一下命令,查看hash函数及如何存储
188 | ```shell
189 | hmset user:2 name tom age 23 city beijing
190 | ```
191 |
192 | `hash`函数
193 | ```
194 | #
195 | uint64_t dictGenHashFunction(const void *key, int len) {
196 | return siphash(key,len,dict_hash_function_seed);
197 | }
198 |
199 | key => 0x00007fbd2b504b41 => 存储内容是user:2
200 | 0x00007fbd2b504b40 | 30 75 73 65 72 3a 32 00 00 00 00 00 00 00 00 00 │ 0user:2········· │
201 |
202 | # 种子由util.c中void getRandomHexChars(char *p, unsigned int len) 函数生成。是基于/dev/random
203 | # /dev/random在类UNIX系统中是一个特殊的设备文件,可以用作随机数发生器或伪随机数发生器。
204 | dict_hash_function_seed => 0x0000000102e41b10
205 | 0x0000000102e41b10 | 65 32 61 63 38 30 34 39 62 37 37 33 32 33 32 38 │ e2ac8049b7732328 │
206 | 0x0000000102e41b20 | 00 00 00 00 40 aa 00 00 00 00 00 00 00 00 00 00 │ ····@··········· │
207 |
208 | ```
209 |
210 | 从中可以看出`4.0`使用`SipHash`哈希算法,之前使用的是`MurmurHash2`哈希算法
211 | [参考文章1](https://my.oschina.net/tigerBin/blog/3038044)
212 | [参考文章2](http://cr.yp.to/siphash/siphash-20120918.pdf)
213 |
214 |
215 | 算法部分流程截图:
216 |
217 | 
218 |
219 | 根据hash函数结果存储dictEntry
220 |
221 | 
222 |
223 | 整体结构如下:
224 |
225 | 
226 |
227 | ## sets
228 |
229 | ## zsets
230 |
--------------------------------------------------------------------------------
/md/middleware/rpc/c-grpc-go.md:
--------------------------------------------------------------------------------
1 | # c语言通过grpc与go通信
2 | [参考链接](https://github.com/ymm135/grpc-c)
3 |
4 | - [c语言通过grpc与go通信](#c语言通过grpc与go通信)
5 | - [环境搭建](#环境搭建)
6 | - [protobuf](#protobuf)
7 | - [protobuf-c](#protobuf-c)
8 | - [grpc](#grpc)
9 | - [grpc-c](#grpc-c)
10 | - [protobuf go](#protobuf-go)
11 |
12 |
13 | ## 环境搭建
14 | ### [protobuf](https://github.com/protocolbuffers/protobuf)
15 | [安装文档](https://github.com/protocolbuffers/protobuf/blob/bba446bbf2ac7b0b9923d4eb07d5acd0665a8cf0/src/README.md)
16 |
17 | 依赖安装:
18 | ```shell
19 | # Ubuntu
20 | sudo apt-get install autoconf automake libtool curl make g++ unzip
21 |
22 | # Centos
23 | sudo yum install autoconf automake libtool curl make gcc gcc-c++ unzip
24 | ```
25 |
26 | > curl会下载gmock, 如果虚拟机无法访问,请手动下载:
27 | ```
28 | # autogen.sh
29 | # Check that gmock is present. Usually it is already there since the
30 | # directory is set up as an SVN external.
31 | if test ! -e gmock; then
32 | echo "Google Mock not present. Fetching gmock-1.7.0 from the web..."
33 | curl $curlopts -L -O https://github.com/google/googlemock/archive/release-1.7.0.zip
34 | unzip -q release-1.7.0.zip
35 | rm release-1.7.0.zip
36 | mv googlemock-release-1.7.0 gmock
37 |
38 | curl $curlopts -L -O https://github.com/google/googletest/archive/release-1.7.0.zip
39 | unzip -q release-1.7.0.zip
40 | rm release-1.7.0.zip
41 | mv googletest-release-1.7.0 gmock/gtest
42 | fi
43 | ```
44 |
45 | protobuf安装
46 | ```shell
47 | $ ./autogen.sh
48 |
49 | $ ./configure
50 | $ make
51 | $ make check
52 | $ sudo make install
53 | $ sudo ldconfig # refresh shared library cache.
54 | ```
55 |
56 | 增加两个环境变量
57 | ```
58 | # 依赖检测
59 | export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
60 |
61 | # 依赖库路径
62 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
63 | ```
64 |
65 | 安装成功测试:
66 | ```shell
67 | $ pkg-config --cflags --libs protobuf
68 | -pthread -I/usr/local/include -pthread -L/usr/local/lib -lprotobuf -lpthread
69 | ```
70 |
71 | ### [protobuf-c](https://github.com/protobuf-c/protobuf-c)
72 |
73 | 安装步骤:
74 | ```
75 | ./configure && make && make install
76 |
77 | # 如果git仓库
78 | ./autogen.sh && ./configure && make && make install
79 | ```
80 |
81 | ### [grpc](https://github.com/grpc/grpc)
82 | [安装文档](https://github.com/grpc/grpc/tree/master/src/cpp)
83 |
84 | 安装依赖:
85 | ```shell
86 | # Ubuntu
87 | $ sudo apt-get install build-essential autoconf libtool pkg-config
88 |
89 | # Centos
90 | $ sudo yum install make automake gcc gcc-c++ kernel-devel autoconf libtool pkgconfig.x86_64
91 | ```
92 |
93 | 其中依赖很多第三方包`third_party`,需要下载依赖,在根目录执行`git submodule update --init`,不然会提示`cares/cares does not contain a CMakeLists.txt file.`
94 |
95 |
96 | 使用cmake编译:
97 | ```shell
98 | # 下载依赖
99 | $ git submodule update --init
100 |
101 | # 编译
102 | $ mkdir -p cmake/build
103 | $ cd cmake/build
104 | $ cmake ../..
105 | $ make
106 | $ sudo make install
107 | ```
108 |
109 | > 不同grpc需要指定cmake版本
110 |
111 | 错误解决:
112 | - `grpc-c/third_party/grpc/third_party/zlib/zlib.h`:1758:44: 错误:`va_list`未声
113 |
114 | 文件中增加头文件`#include `
115 |
116 |
117 | ### [grpc-c](https://github.com/Juniper/grpc-c)
118 |
119 | 编译:
120 | ```shell
121 | autoreconf --install
122 | $ mkdir build && cd build
123 | $ ../configure
124 | $ make
125 | $ sudo make install
126 | ```
127 |
128 | 安装的程序有:
129 | ```shell
130 | /usr/local/bin/protoc-gen-grpc-c
131 | /usr/local/lib/libgrpc-c.so
132 | ```
133 |
134 | 构建example:
135 | ```shell
136 | cd build/examples
137 | make gencode
138 | make
139 | ```
140 |
141 | 出现错误:
142 | ```shell
143 | [root@sd1 examples]# make gencode
144 | --grpc-c_out: Unimplemented GenerateAll() method.
145 | --grpc-c_out: Unimplemented GenerateAll() method.
146 | --grpc-c_out: Unimplemented GenerateAll() method.
147 | --grpc-c_out: Unimplemented GenerateAll() method.
148 | make: *** [gencode] 错误 1
149 | ```
150 |
151 | 可以使用makefile(GUN Make)调试模式`SHELL="/bin/bash -vx"`
152 | ```shell
153 | [root@sd1 examples]# make SHELL="/bin/bash -vx" gencode
154 | (for protofile in `ls -1 ../../examples/*.proto` ; do \
155 | protoc -I ../../examples --grpc-c_out=. \
156 | --plugin=protoc-gen-grpc-c=../compiler/protoc-gen-grpc-c \
157 | $protofile; \
158 | done)
159 |
160 | + for protofile in '`ls -1 ../../examples/*.proto`'
161 | + protoc -I ../../examples --grpc-c_out=. --plugin=protoc-gen-grpc-c=../compiler/protoc-gen-grpc-c ../../examples/server_streaming.proto
162 | --grpc-c_out: Unimplemented GenerateAll() method.
163 | make: *** [gencode] 错误 1
164 | ```
165 |
166 | 这里会告诉你makefile执行的语句是什么,相当于shell调试信息,可以看到错误是`protoc`
167 | ```shell
168 | protoc -I .. --grpc-c_out=. --plugin=protoc-gen-grpc-c=..
169 | ```
170 |
171 | 提示`--grpc-c_out`未实现`GenerateAll()`,通过`protoc --help`查看确实没有,有`--cpp_out=OUT_DIR`
172 | grpc的代码生成器目前有两个虚函数`Generate()`和`GenerateAll()`,目前版本需要实现`GenerateAll()`,应该和grpc版本有关系? 把/usr/local/lib和/usr/local/lib64下所有与protobuf相关的库都删除,重新install [参考链接](https://github.com/grpc/grpc/issues/10941)
173 |
174 | 另外需要增加一些库的引用及升级 [openssl](https://github.com/openssl/openssl) 版本,需要在make时指定依赖库,但是在链接openssl会出现问题:
175 |
176 | ```shell
177 | [root@sd1 examples]# /bin/sh ../libtool --tag=CC --mode=link gcc -I. -I../../examples/../lib/h/ -I../../examples/../third_party/protobuf-c -I../../examples/../third_party/grpc/include -g -O2 -o foo_client foo_client.o foo.grpc-c.o ../lib/libgrpc-c.la -lgrpc -lgpr -lprotobuf-c -lpthread -lz -lcares -lm -lssl -lcrypto
178 | libtool: link: gcc -I. -I../../examples/../lib/h/ -I../../examples/../third_party/protobuf-c -I../../examples/../third_party/grpc/include -g -O2 -o .libs/foo_client foo_client.o foo.grpc-c.o ../lib/.libs/libgrpc-c.so -lgrpc -lgpr /usr/local/lib/libprotobuf-c.so -lpthread -lz /usr/local/lib/libcares.so -lm -lssl -lcrypto
179 | //usr/local/lib64/libgrpc.a(ssl_transport_security.c.o): In function `init_openssl':
180 | ssl_transport_security.c:(.text+0x8d): undefined reference to `OpenSSL_add_all_algorithms'
181 | //usr/local/lib64/libgrpc.a(ssl_transport_security.c.o): In function `add_pem_certificate':
182 | ssl_transport_security.c:(.text+0x65e): undefined reference to `BIO_get_mem_data'
183 | //usr/local/lib64/libgrpc.a(ssl_transport_security.c.o): In function `ssl_ctx_use_certificate_chain':
184 | ...
185 | collect2: error: ld returned 1 exit status
186 | ```
187 |
188 | 这里出现问题是编译`/usr/local/lib64/libgrpc.a`时无法找到`OpenSSL_add_all_algorithms`定义,而不是使用`libgrpc.so`动态库,如果是动态库找不到,那说明是`libgrpc.so`编译有问题。
189 |
190 |
191 | ### [protobuf go](https://github.com/golang/protobuf)
192 | 如果使用protobuf 3.0.x的版本,没有内置gen-go插件,需要自定编译并安装
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/md/other/electron.md:
--------------------------------------------------------------------------------
1 | - # Electron
2 |
3 | 目录:
4 | - [小试牛刀](#小试牛刀)
5 | - [electron-api-demos](#electron-api-demos)
6 | - [简介](#简介)
7 | - [环境搭建](#环境搭建)
8 | - [开发](#开发)
9 | - [开发工具](#开发工具)
10 | - [疑问拓展](#疑问拓展)
11 | - [vscode开发工具是`Electron`?](#vscode开发工具是electron)
12 |
13 |
14 | - ## https://www.electronjs.org/
15 |
16 | ## 小试牛刀
17 |
18 | `Electron` 基于 Chromium,而 Chromium 是谷歌开发的一个开源浏览器项目,它同样包括了一个网页渲染引擎。Chromium 最初使用的是 `WebKit`,但谷歌后来开发了自己的一个分支称为 `Blink`。从2013年开始,Chromium 和 Chrome 都转而使用 Blink 作为其网页渲染引擎。
19 | > Electron `/ɪˈlektrɒn/` 电子
20 | > Chromium `/ˈkrəʊmiəm/` [化学]铬(24号元素,符号 Cr)
21 |
22 | WebKit 是一个开源的网页渲染引擎,它负责解析 HTML、CSS 代码和执行 JavaScript,从而在屏幕上呈现网页内容。WebKit 是许多浏览器的基础,包括早期的 Apple Safari 和以前的 Google Chrome。
23 |
24 | 因此,`Electron` 与 `WebKit` 的关系在于它们都是网页渲染引擎,但 Electron 实际上是使用 Chromium 的 Blink 引擎,而不是直接使用 WebKit。不过,由于 Blink 引擎是从 WebKit 分支出来的,两者在许多核心渲染方面共享了相似的技术和代码基础。
25 |
26 | > https://github.com/electron/electron
27 |
28 |
29 | ### electron-api-demos
30 |
31 | https://github.com/electron/electron-api-demos
32 |
33 | 前置条件 `npm install electron -g`
34 | ```sh
35 | $ git clone https://github.com/electron/electron-api-demos
36 | $ cd electron-api-demos
37 | $ npm install
38 | $ npm start
39 | ```
40 |
41 |
42 |
43 |
44 |

45 |
46 |
47 |
48 |
49 | ### 简介
50 |
51 | 如果您是在说使用 Web 技术(HTML, CSS, JavaScript)来创建一个具有图形用户界面的本地 Windows 应用程序,并且想要在点击按钮时显示一个“Hello”对话框,那么通常这涉及到使用 Electron 或 NW.js 这样的框架。这些框架允许开发人员使用 Web 技术来构建跨平台的桌面应用程序。
52 |
53 | 这里有一个基本的 Electron 应用程序示例,它在点击按钮时会显示一个弹出对话框:
54 |
55 | 1. **初始化一个新的 Node.js 项目**:
56 |
57 | ```bash
58 | mkdir my-electron-app
59 | cd my-electron-app
60 | npm init -y
61 | npm install electron --save-dev
62 | ```
63 |
64 | 2. **创建主进程文件** (`index.js`):
65 |
66 | ```javascript
67 | const { app, BrowserWindow, dialog } = require('electron');
68 |
69 | function createWindow() {
70 | const win = new BrowserWindow({
71 | width: 800,
72 | height: 600,
73 | webPreferences: {
74 | nodeIntegration: true
75 | }
76 | });
77 |
78 | win.loadFile('index.html');
79 |
80 | // 打开开发者工具(如果需要)
81 | win.webContents.openDevTools();
82 | }
83 |
84 | app.whenReady().then(createWindow);
85 |
86 | app.on('window-all-closed', () => {
87 | if (process.platform !== 'darwin') {
88 | app.quit();
89 | }
90 | });
91 |
92 | app.on('activate', () => {
93 | if (BrowserWindow.getAllWindows().length === 0) {
94 | createWindow();
95 | }
96 | });
97 | ```
98 |
99 | 3. **创建前端 HTML 文件** (`index.html`):
100 |
101 | ```html
102 |
103 |
104 |
105 | Hello Dialog
106 |
107 |
108 | My Electron App
109 |
110 |
111 |
122 |
123 |
124 | ```
125 |
126 | 4. **在 `package.json` 中设置启动脚本**:
127 |
128 | 在 `package.json` 文件的 `scripts` 部分添加:
129 |
130 | ```json
131 | "start": "electron ."
132 | ```
133 |
134 | 完成以上步
135 |
136 | 骤后,你可以通过在命令行中运行 `npm start` 来启动你的 Electron 应用程序。点击按钮时,会调用 Electron 的 `dialog` API 弹出一个包含“Hello”消息的对话框。
137 |
138 | 请注意,由于 Electron 5.0.0 版本开始,渲染器进程中默认不再启用 Node.js 集成。如果你需要在渲染器进程中使用 Node.js 特性(如上面示例中所做的那样),你需要在 `BrowserWindow` 的 `webPreferences` 中设置 `nodeIntegration: true`。此外,为了提高应用程序的安全性,应该避免在可能的情况下在渲染器进程中启用 Node.js 集成。
139 |
140 | 最后,如果你指的是使用 WebKit 直接开发 Windows 应用程序,那么这通常涉及到更底层的编程,例如使用 C++ 和一个类似于 Qt 的框架来嵌入 WebKit。然而,Electron 已经成为了使用 Web 技术创建桌面应用程序的主流方式,因为它简化了开发过程,并且提供了丰富的 API。
141 |
142 | ### 环境搭建
143 |
144 | ## 开发
145 | ### 开发工具
146 |
147 | https://www.electronjs.org/fiddle
148 |
149 |
150 |
151 |

152 |
153 |
154 |
155 |
156 | 推荐使用`vscode`
157 | https://www.electronjs.org/docs/latest/tutorial/debugging-vscode
158 |
159 |
160 |
161 | ## 疑问拓展
162 | ### vscode开发工具是`Electron`?
163 |
164 | Visual Studio Code(VSCode)是一个由微软开发的开源代码编辑器。它是使用 Electron 框架开发的,Electron 允许使用前端技术如 HTML、CSS 和 JavaScript 来开发跨平台的桌面应用程序。
165 |
166 | VSCode 的开发涉及多种技术和工具:
167 |
168 | 1. **Electron**: 用于将 VSCode 作为一个桌面应用程序打包和运行。
169 |
170 | 2. **Node.js**: 提供后端运行时环境,允许使用 JavaScript 进行系统级的操作。
171 |
172 | 3. **TypeScript**: VSCode 的主要编程语言,是 JavaScript 的一个超集,添加了静态类型检查和更高级的编程特性。
173 |
174 | 4. **Monaco Editor**: 作为 VSCode 的编辑器核心,是一个用于网页应用的代码编辑器,也由微软开发。
175 |
176 | 5. **Git**: 用于版本控制,VSCode 自身也提供了内置的 Git 支持。
177 |
178 | 6. **各种前端技术**: 包括 HTML、CSS 和 JavaScript,用于构建用户界面。
179 |
180 | 7. **npm**: 作为包管理器,用于管理 VSCode 的依赖。
181 |
182 | 8. **Azure DevOps**: 微软的持续集成和持续部署服务,用于 VSCode 的开发流程。
183 |
184 | 由于 VSCode 是开源的,你可以在其[GitHub 仓库](https://github.com/microsoft/vscode)中找到所有源代码和构建脚本。这不仅让人们可以自由地探索和学习 VSCode 是如何被构建的,也允许社区贡献代码和功能。
--------------------------------------------------------------------------------
/md/other/go-ebpf.md:
--------------------------------------------------------------------------------
1 | - # go-ebpf
2 |
3 | 目录:
4 | - [ebpf](#ebpf)
5 | - [原理](#原理)
6 | - [go-ebpf](#go-ebpf)
7 | - [demo-xdp-connect](#demo-xdp-connect)
8 |
9 |
10 | ## ebpf
11 | 近年来,eBPF 在`故障诊断`、`网络优化`、`安全控制`、`性能监控`等领域获得大量应用,项目数量呈爆炸式增长。2021年8月12日, Linux 基金会旗下成立了 eBPF 基金会,一个激动人心的未来正在展开。
12 |
13 | 作为一项革命性的技术,eBPF 的“魔力”在哪里?简单来说,`eBPF 使我们能够在不更改内核代码的前提下,实时获取和修改操作系统的行为。`这就意味着,eBPF 可以帮我们洞悉系统底层的“黑盒”,重新定义了我们思考操作系统的方式。
14 |
15 |
16 | ebpf的学习路线
17 |
18 |
19 |

20 |
21 |
22 |
23 | ### 原理
24 | [what-is-ebpf](https://ebpf.io/what-is-ebpf)
25 |
26 | eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行`沙盒`程序。它用于安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。
27 |
28 | 从历史上看,由于内核具有监督和控制整个系统的特权能力,操作系统一直是实现可观察性、安全性和网络功能的理想场所。同时,操作系统内核由于其核心地位以及对稳定性和安全性的高要求,难以演进。因此,与在操作系统之外实现的功能相比,操作系统级别的创新率传统上较低。
29 |
30 |
31 |
32 |

33 |
34 |
35 |
36 | eBPF 从根本上改变了这个公式。通过允许在操作系统中运行沙盒程序,应用程序开发人员可以运行 eBPF 程序以在运行时向操作系统添加额外的功能。然后,操作系统保证安全性和执行效率,就好像在即时 (JIT) 编译器和验证引擎的帮助下本地编译一样。这引发了一波基于 eBPF 的项目,涵盖了广泛的用例,包括下一代网络、可观察性和安全功能。
37 |
38 | 如今,eBPF 被广泛用于驱动各种用例:在现代数据中心和云原生环境中提供高性能网络和负载均衡,以低开销提取细粒度的安全可观察性数据,帮助应用程序开发人员跟踪应用程序,为性能故障排除、预防性应用程序和容器运行时安全实施等提供见解。可能性是无限的,eBPF 解锁的创新才刚刚开始。
39 |
40 |
41 |
42 | ## go-ebpf
43 | ebpf-go 是一个纯 Go 库,可以加载、编译和调试 eBPF 程序。
44 |
45 | [github仓库](https://github.com/cilium/ebpf)
46 |
47 |
48 | ### demo-xdp-connect
49 |
50 | 编译环境配置
51 | ```shell
52 | # Build all ELF binaries using a containerized LLVM toolchain.
53 | container-all:
54 | ${CONTAINER_ENGINE} run --rm ${CONTAINER_RUN_ARGS} \
55 | -v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \
56 | --env CFLAGS="-fdebug-prefix-map=/ebpf=." \
57 | --env HOME="/tmp" \
58 | --env GO111MODULE="on" \
59 | --env GOPROXY="https://goproxy.cn" \
60 | "${IMAGE}:${VERSION}" \
61 | $(MAKE) all
62 | ```
63 |
64 | > 需要配置go proxy, 不然依赖库无法下载
65 |
66 | 运行方法
67 | ```shell
68 | cd ebpf/examples/
69 | go run -exec sudo [./kprobe, ./uretprobe, ./ringbuffer, ...]
70 | ```
71 |
72 | [demo参考文章](https://www.ebpf.top/post/ebpf_go_translation/)
73 |
74 |
--------------------------------------------------------------------------------
/md/other/linux-core-debug.md:
--------------------------------------------------------------------------------
1 | # Linux 内核调试
2 |
3 | 目录:
4 | - [Linux 内核调试](#linux-内核调试)
5 | - [内核编译](#内核编译)
6 | - [centos](#centos)
7 | - [ubuntu](#ubuntu)
8 | - [**现在内核源码已经编译完成,可通过多种方式学习内核,如果仅仅了解每个功能的流程,可以通过vscode及插件阅读源码,另外可替换当前系统内核进行调试。如果想了解内核流程,可通过虚拟机启动内核进行调试。**](#现在内核源码已经编译完成可通过多种方式学习内核如果仅仅了解每个功能的流程可以通过vscode及插件阅读源码另外可替换当前系统内核进行调试如果想了解内核流程可通过虚拟机启动内核进行调试)
9 | - [centos](#centos-1)
10 | - [ubuntu](#ubuntu-1)
11 |
12 |
13 | ## 内核编译
14 |
15 | centos7 内核版本
16 | ```shell
17 | $ uname -a
18 | Linux d1.localdomain 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
19 | ```
20 |
21 |
22 | - ### 安装环境:
23 | #### centos
24 | ```shell
25 | yum install ncurses-devel bison flex elfutils-libelf-devel openssl-devel
26 | ```
27 |
28 | 下载源码:
29 | 内核版本为: `3.10.0-1127.el7.x86_64`
30 | [下载地址 git clone https://github.com/torvalds/linux.git ](https://github.com/torvalds/linux) ,也可以使用`sudo apt-get source linux-image-$(uname -r)`下载当前内核版本或更小的发行版,缺点:版本不全
31 |
32 | ```
33 | git clone https://github.com/torvalds/linux.git
34 | git tag | grep 3.10.0
35 | git checkout
36 | ```
37 |
38 | 更新GCC版本,最低5.1.0,这里升级到gcc7
39 | ```shell
40 | sudo yum install centos-release-scl
41 | sudo yum install devtoolset-7-gcc # devtoolset-8-gcc
42 |
43 | # 切换对应版本
44 | scl enable devtoolset-7 bash
45 |
46 | # 测试
47 | gcc -v
48 |
49 | # 直接替换旧的gcc(终极解决方案)
50 | mv /usr/bin/gcc /usr/bin/gcc-4.8.5
51 | ln -s /opt/rh/devtoolset-7/root/bin/gcc /usr/bin/gcc
52 | mv /usr/bin/g++ /usr/bin/g++-4.8.5
53 | ln -s /opt/rh/devtoolset-7/root/bin/g++ /usr/bin/g++
54 | gcc --version
55 | g++ --version
56 | ```
57 |
58 | > 也可以 [下载源码](https://ftp.gnu.org/gnu/gcc/) 编译,比较麻烦
59 |
60 | #### ubuntu
61 | 安装依赖
62 | ```
63 | apt install
64 | ```
65 |
66 | gcc9编译有问题,更换为gcc8
67 | ```
68 | # 安装
69 | sudo apt install gcc-8 g++-8
70 |
71 | # 切换为gcc8
72 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100
73 | sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 100
74 |
75 | # 多版本切换
76 | sudo update-alternatives --config gcc
77 | sudo update-alternatives --config g++
78 | ```
79 |
80 | > 找不到库 `libpixman-1.so.0`,可以使用 `apt-cache search pixman` 查找
81 |
82 |
83 |
84 | - ### 编译内核
85 | ```
86 | make x86_64_defconfig # 选择对应平台
87 | make menuconfig
88 | make -j8
89 | ```
90 |
91 | > 要进行打断点调试,需要关闭系统的随机化和开启调试信息
92 | ```shell
93 | Processor type and features --->
94 | [ ] Build a relocatable kernel
95 | [ ] Randomize the address of the kernel image (KASLR) (NEW) # 按键N关闭
96 |
97 |
98 | Kernel hacking --->
99 | Compile-time checks and compiler options --->
100 | [*] Compile the kernel with debug info
101 | [ ] Reduce debugging information
102 | [ ] Produce split debuginfo in .dwo files
103 | [*] Generate dwarf4 debuginfo
104 | [*] Provide GDB scripts for kernel debugging
105 | ```
106 |
107 | 修改配置会保存在.config中,可以自行查看
108 | ```shell
109 | # grep CONFIG_DEBUG_INFO .config
110 |
111 | CONFIG_DEBUG_INFO=y
112 | # CONFIG_DEBUG_INFO_NONE is not set
113 | CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
114 | # CONFIG_DEBUG_INFO_DWARF4 is not set
115 | # CONFIG_DEBUG_INFO_DWARF5 is not set
116 | # CONFIG_DEBUG_INFO_REDUCED is not set
117 | # CONFIG_DEBUG_INFO_COMPRESSED is not set
118 | # CONFIG_DEBUG_INFO_SPLIT is not set
119 | ```
120 |
121 | > 配置文件和之前有差异
122 |
123 |
124 | 编译成功结果:
125 | ```shell
126 | OBJCOPY arch/x86/boot/setup.bin
127 | BUILD arch/x86/boot/bzImage
128 | Kernel: arch/x86/boot/bzImage is ready (#1)
129 | ```
130 |
131 | 查看编译完成后的文件
132 | ```shell
133 | # 未压缩的内核文件,这个在 gdb 的时候需要加载,用于读取 symbol 符号信息,由于包含调试信息所以比较大
134 | $ ls -hl vmlinux
135 | -rwxr-xr-x. 1 root root 348M 3月 28 20:56 vmlinux
136 |
137 | # 压缩后的镜像文件
138 | $ ls -hl ./arch/x86_64/boot/bzImage
139 | lrwxrwxrwx. 1 root root 22 3月 28 20:56 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage
140 |
141 | $ ls -hl ./arch/x86/boot/bzImage
142 | -rw-r--r--. 1 root root 9.7M 3月 28 20:56 ./arch/x86/boot/bzImage
143 | ```
144 |
145 | - ### 启动内存文件系统制作
146 | [busybox](https://busybox.net/about.html)
147 |
148 | BusyBox 将许多常见 UNIX 实用程序的微小版本组合成一个小型可执行文件。它为您通常在 GNU fileutils、shellutils 等中找到的大多数实用程序提供了替代品。BusyBox 中的实用程序通常比它们功能齐全的 GNU 表亲具有更少的选项;但是,包含的选项提供了预期的功能,并且其行为与 GNU 对应项非常相似。BusyBox 为任何小型或嵌入式系统提供了一个相当完整的环境。
149 |
150 | ```shell
151 | # 首先安装静态依赖,否则会有报错,参见后续的排错章节
152 | $ yum install -y glibc-static.x86_64 -y
153 |
154 | $ wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2
155 | $ tar -xvf busybox-1.32.1.tar.bz2
156 | $ cd busybox-1.32.1/
157 |
158 | $ make menuconfig
159 |
160 | # 安装完成后生成的相关文件会在 _install 目录下
161 | $ make && make install
162 |
163 | $ cd _install
164 | $ mkdir proc
165 | $ mkdir sys
166 | $ touch init
167 |
168 | # init 内容见后续章节,为内核启动的初始化程序
169 | $ vim init
170 |
171 | # 必须设置成可执行文件
172 | $ chmod +x init
173 |
174 | $ find . | cpio -o --format=newc > ./rootfs.img
175 | cpio: 文件 ./rootfs.img 增长,34304 新字节未被拷贝
176 | 2055 块
177 |
178 | $ ls -hl rootfs.img
179 | -rw-r--r--. 1 root root 1.1M 3月 28 21:54 rootfs.img
180 | ```
181 |
182 | 其中上述的 `init` 文件内容如下,打印启动日志和系统的整个启动过程花费的时间
183 | ```
184 | #!/bin/sh
185 | echo "{==DBG==} INIT SCRIPT"
186 | mkdir /tmp
187 | mount -t proc none /proc
188 | mount -t sysfs none /sys
189 | mount -t debugfs none /sys/kernel/debug
190 | mount -t tmpfs none /tmp
191 |
192 | mdev -s
193 | echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
194 |
195 | # normal user
196 | setsid /bin/cttyhack setuidgid 1000 /bin/sh
197 | ```
198 |
199 | 到此为止我们已经编译了好了 Linux 内核(vmlinux 和 bzImage)和启动的内存文件系统(rootfs.img)
200 |
201 | > 如果在编译时,没有找到指定库,可以使用 `yum provides */libm.a` 类似语句查询
202 |
203 | #### **现在内核源码已经编译完成,可通过多种方式学习内核,如果仅仅了解每个功能的流程,可以通过vscode及插件阅读源码,另外可替换当前系统内核进行调试。如果想了解内核流程,可通过虚拟机启动内核进行调试。**
204 |
205 | - ### vscode 查看内核源码
206 | 通过vscode的 `Remote SSH` 连接虚拟机,打开编译好的内核源码目录。
207 |
208 | - ### macos 启动内核调试
209 |
210 | ```shell
211 | brew install qemu
212 | ```
213 |
214 | 编译好的文件,直接启动即可:
215 | ```shell
216 | qemu-system-x86_64 -kernel bzImage -initrd rootfs.img
217 | ```
218 |
219 |
220 | - ### 替换内核进行调试
221 |
222 |
223 | - ### Qemu 启动内核调试
224 |
225 | > 无图像启动: -nographic
226 |
227 | 你可能使用的是ubuntu或者centos,都需要搭建 KVM (Kernel-based Virtual Machine).
228 |
229 | KVM 是基于 x86 虚拟化扩展(Intel VT 或者 AMD-V) 技术的虚拟机软件,所以查看 CPU 是否支持 VT 技术,就可以判断是否支持KVM。有返回结果,如果结果中有vmx(Intel)或svm(AMD)字样,就说明CPU的支持的。
230 |
231 | ```shell
232 | cat /proc/cpuinfo | egrep 'vmx|svm'
233 |
234 | flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm arat epb pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc
235 | ```
236 |
237 | #### centos
238 | macos不支持,但是需要增加额外参数
239 | kvm is the linux hypervisor implementation, that isn't going to work. Recent qemu version have support for the macos hypervisor framework, use `accel=hvf` for that.
240 |
241 | ```
242 | qemu-system-x86_64 -m 2G -hda ubuntu.20.qcow2 -accel hvf
243 | ```
244 |
245 | > ERROR 主机不支持 虚拟化类型 'hvm' 架构 'x86_64' 的虚拟机 kvm
246 |
247 |
248 | #### ubuntu
249 | 需要在调试的 Ubuntu 20.04 的系统中安装 Qemu 工具,其中调测的 Ubuntu 系统使用 VirtualBox 安装。
250 |
251 | ```shell
252 | cp busybox-1.32.1/_install/rootfs.img .
253 | cp linux//arch/x86/boot/bzImage .
254 |
255 | # 启动
256 | apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils
257 | ```
258 |
259 | 把上述编译好的 vmlinux、bzImage、rootfs.img 和编译的源码拷贝到我们当前 Unbuntu 机器中。
260 |
261 | 拷贝 Linux 编译的源码主要是在 gdb 的调试过程中查看源码,其中 vmlinux 和 linux 源码处于相同的目录,本例中 vmlinux 位于 linux-4.19.172 源目录中。
262 |
263 | ```shell
264 | qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -s -S -nographic
265 | ```
266 |
267 | 使用上述命令启动调试,启动后会停止在界面处,并等待远程 gdb 进行调试,在使用 GDB 调试之前,可以先使用以下命令进程测试内核启动是否正常。
268 |
269 | ```shell
270 | qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -nographic
271 | ```
272 |
273 | > qemu: could not load PC BIOS 'bios-256k.bin'
274 |
275 | 在安装后seabios,发现有`/usr/share/seabios/bios-256k.bin`, 需要增加路径 `-L /usr/share/seabios`
276 |
277 | > qemu-system-x86_64 -kernel ../../arch/x86/boot/bzImage -initrd ../rootfs.img
278 | > Unable to init server: Could not connect: Connection refused
279 | > gtk initialization failed
280 |
281 | 不能在远程用命令行运行,需要在有图形界面的系统运行该指令。
282 |
283 |
284 |
285 | - ### GDB 调试内核
286 |
287 |
288 |
289 |
--------------------------------------------------------------------------------
/md/web/gin/gin-bind.md:
--------------------------------------------------------------------------------
1 | - # gin参数绑定
2 |
3 | 目录:
4 | - [通过反射解析query参数](#通过反射解析query参数)
5 | - [shouldBindQuery源码](#shouldbindquery源码)
6 | - [自己动手写的demo](#自己动手写的demo)
7 | - [reflect.New实现动态代理](#reflectnew实现动态代理)
8 |
9 |
10 |
11 | ## 通过反射解析query参数
12 | ### shouldBindQuery源码
13 | 比如需要解析query参数,填充的结构体为:
14 | ```go
15 | // 周报搜索结构体
16 | type WtReportsSearch struct {
17 | CurrUserId uint `form:"currUserId"`
18 | UserId uint `form:"userId"`
19 | StartTime string `form:"startTime" example:"2021-11-04 12:36:34"`
20 | EndTime string `form:"endTime"`
21 | Content string `form:"content" example:"xx项目"`
22 | request.PageInfo
23 | }
24 | ```
25 |
26 | gin解析代码和请求参数:
27 | ```go
28 | // url参数: userId=1&content=工作&startTime=2021-11-04 01:11:07&endTime=2021-11-04 03:11:08
29 | func (wtReportsApi *WtReportsApi) GetWtReportsList(c *gin.Context) {
30 | var searchInfo wtReq.WtReportsSearch
31 | _ = c.ShouldBindQuery(&searchInfo)
32 | ...
33 | }
34 | ```
35 |
36 | ShouldBindQuery方法,传入实现: **binding.Query**
37 | ```go
38 | // gin@v1.7.4/context.go
39 | // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
40 | func (c *Context) ShouldBindQuery(obj interface{}) error {
41 | return c.ShouldBindWith(obj, binding.Query)
42 | }
43 |
44 | //调用
45 | // ShouldBindWith binds the passed struct pointer using the specified binding engine.
46 | // See the binding package.
47 | func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
48 | return b.Bind(c.Request, obj)
49 | }
50 |
51 | //调用接口
52 | // Binding describes the interface which needs to be implemented for binding the
53 | // data present in the request such as JSON request body, query parameters or
54 | // the form POST.
55 | type Binding interface {
56 | Name() string
57 | Bind(*http.Request, interface{}) error
58 | }
59 |
60 | //方法实现 gin@v1.7.4/binding/query.go
61 | func (queryBinding) Bind(req *http.Request, obj interface{}) error {
62 | values := req.URL.Query()
63 | if err := mapForm(obj, values); err != nil {
64 | return err
65 | }
66 | return validate(obj)
67 | }
68 |
69 | // gin@v1.7.4/binding/form_mapping.go
70 | // 需要注意"form"表单, 类型也有json
71 | func mapForm(ptr interface{}, form map[string][]string) error {
72 | return mapFormByTag(ptr, form, "form")
73 | }
74 |
75 | // 最终把参数转为map, 通过map匹配struct的tag,通过反射Value、Field设定值。
76 |
77 | ```
78 |
79 | ## 自己动手写的demo
80 | ```go
81 | package main
82 |
83 | import (
84 | "fmt"
85 | "reflect"
86 | "strconv"
87 | "strings"
88 | )
89 |
90 | type WtReportsSearch struct {
91 | CurrUserId uint `form:"currUserId"`
92 | UserId uint `form:"userId"`
93 | StartTime string `form:"startTime" example:"2021-11-04 12:36:34"`
94 | EndTime string `form:"endTime"`
95 | Content string `form:"content" example:"xx项目"`
96 | }
97 |
98 | func bindQuery(obj interface{}) {
99 | var values map[string]interface{}
100 | values = make(map[string]interface{})
101 |
102 | values["currUserId"] = 1
103 | values["userId"] = 2
104 | values["content"] = "项目工作"
105 |
106 | prtObjTyp := reflect.TypeOf(obj)
107 | prtObjVal := reflect.ValueOf(obj)
108 |
109 | // 必须对象是ValueOf ,如果是TypeOf,不能修改
110 | //objVal := reflect.ValueOf(obj)
111 | //field0 := objVal.Elem().Field(0)
112 |
113 | //objVal := reflect.TypeOf(obj)
114 | //field0 := objVal.Elem().Field(0)
115 | //filed0Val := reflect.ValueOf(field0)
116 | //filed0Val.SetUint(2)
117 |
118 | ptrMapVal := reflect.ValueOf(values)
119 |
120 | if ptrMapVal.Kind() == reflect.Ptr {
121 | ptrMapVal = ptrMapVal.Elem()
122 | }
123 |
124 | // Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素
125 | if prtObjVal.Kind() == reflect.Ptr {
126 | prtObjVal = prtObjVal.Elem()
127 | prtObjTyp = prtObjTyp.Elem()
128 | }
129 |
130 | //对象本身是个指针,需要获取原有类型
131 | // Elem returns a type's element type.
132 | numField := prtObjVal.NumField() // 使用反射获取结构体的成员类型 NumField() 和 Field()
133 | for i := 0; i < numField; i++ {
134 |
135 | fieldVal := prtObjVal.Field(i)
136 | fieldTpy := prtObjTyp.Field(i)
137 | fmt.Println(fieldVal, fieldTpy) // {CurrUserId uint form:"currUserId" 0 [0] false}
138 |
139 | for key, value := range values {
140 | mapVal := reflect.ValueOf(value)
141 | fmt.Println("mapVal=", mapVal, ", canSet=", mapVal.CanSet())
142 |
143 | if strings.Compare(fieldTpy.Tag.Get("form"), key) == 0 { // 结构体标签(Struct Tag)
144 | // 值能被修改的条件: 可被寻址, 可被设置
145 | fmt.Println("fileVal interface=", fieldVal.Interface(), ", canSet=", fieldVal.CanSet(), ",CanAddr=", fieldVal.CanAddr())
146 |
147 | if mapVal.Type().Kind() == reflect.String { // Kind用于判断类型
148 | fieldVal.Set(reflect.ValueOf(value))
149 | }
150 |
151 | if mapVal.Type().Kind() == reflect.Int {
152 | str := fmt.Sprintf("%v", value)
153 | atoi, _ := strconv.Atoi(str)
154 | fieldVal.SetUint(uint64(atoi))
155 | }
156 | fmt.Println(value)
157 | }
158 | }
159 |
160 | }
161 | }
162 |
163 | // 通过反射给结构体赋值
164 | func main() {
165 | var searchInfo WtReportsSearch
166 | bindQuery(&searchInfo)
167 |
168 | fmt.Println(searchInfo)
169 | }
170 | ```
171 |
172 | ## reflect.New实现动态代理
173 |
174 | 首先展示通过方法传入接口依赖 [code](../../../code/reflect/proxy/main.go) :
175 | ```go
176 | package main
177 |
178 | import "fmt"
179 |
180 | type IMan interface {
181 | Walk() int
182 | }
183 |
184 | type ManProxy struct {
185 | manProxy IMan // 不能是指针
186 | }
187 |
188 | func (proxy *ManProxy) setProxy(man IMan) () {
189 | proxy.manProxy = man
190 | }
191 |
192 | func (proxy *ManProxy) Walk() int {
193 | proxy.manProxy.Walk()
194 | fmt.Println("proxy Walk")
195 | }
196 |
197 | type Man struct {
198 | }
199 |
200 | func (man *Man) Walk() int {
201 | fmt.Println("man Walk")
202 | }
203 |
204 | func main() {
205 | manProxy := &ManProxy{}
206 | var manImpl IMan
207 | var man Man
208 | manImpl = &man //需要取地址, 调用man具体实现,而不是复制
209 | manProxy.setProxy(manImpl)
210 | }
211 | ```
212 | 如果通过结构名或者文件名动态加载, 那就只能通过自定义配置文件匹配静态绑定。
213 |
214 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/md/web/gin/gin-middleware.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/md/web/gin/gin-middleware.md
--------------------------------------------------------------------------------
/md/web/gin/gin-router.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/md/web/gin/gin-router.md
--------------------------------------------------------------------------------
/md/web/gorm/flow-gorm.md:
--------------------------------------------------------------------------------
1 | # gorm实现原理
--------------------------------------------------------------------------------
/res/B+Tree-Structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/B+Tree-Structure.png
--------------------------------------------------------------------------------
/res/C++IO关系图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/C++IO关系图.png
--------------------------------------------------------------------------------
/res/GMP-model.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/GMP-model.jpg
--------------------------------------------------------------------------------
/res/MySQL-Triggers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/MySQL-Triggers.png
--------------------------------------------------------------------------------
/res/SZT-bigdata-2+.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/SZT-bigdata-2+.png
--------------------------------------------------------------------------------
/res/adminer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/adminer.png
--------------------------------------------------------------------------------
/res/adminer_create_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/adminer_create_table.png
--------------------------------------------------------------------------------
/res/adminer_insert_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/adminer_insert_data.png
--------------------------------------------------------------------------------
/res/canal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/canal.png
--------------------------------------------------------------------------------
/res/centos-clion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/centos-clion.png
--------------------------------------------------------------------------------
/res/channel-struct.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/channel-struct.jpg
--------------------------------------------------------------------------------
/res/clion-redis-makefile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/clion-redis-makefile.png
--------------------------------------------------------------------------------
/res/const与指针.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/const与指针.jpg
--------------------------------------------------------------------------------
/res/cpp代码存储区域.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/cpp代码存储区域.png
--------------------------------------------------------------------------------
/res/data/SZT-bigdata-master.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/data/SZT-bigdata-master.zip
--------------------------------------------------------------------------------
/res/debug-redis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/debug-redis.png
--------------------------------------------------------------------------------
/res/debug_go_source1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/debug_go_source1.png
--------------------------------------------------------------------------------
/res/debug_source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/debug_source.png
--------------------------------------------------------------------------------
/res/ebpf-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/ebpf-1.png
--------------------------------------------------------------------------------
/res/ebpf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/ebpf.png
--------------------------------------------------------------------------------
/res/electron-demo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/electron-demo-1.png
--------------------------------------------------------------------------------
/res/elk-structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/elk-structure.png
--------------------------------------------------------------------------------
/res/es-head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/es-head.png
--------------------------------------------------------------------------------
/res/exchange1-length.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/exchange1-length.png
--------------------------------------------------------------------------------
/res/fiddle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/fiddle.png
--------------------------------------------------------------------------------
/res/flink-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/flink-demo.png
--------------------------------------------------------------------------------
/res/flink-shouye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/flink-shouye.png
--------------------------------------------------------------------------------
/res/gdb_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/gdb_map.png
--------------------------------------------------------------------------------
/res/go-map-ds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/go-map-ds.jpg
--------------------------------------------------------------------------------
/res/go-trace-pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/go-trace-pic.png
--------------------------------------------------------------------------------
/res/golang/mysql_start_error_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/golang/mysql_start_error_1.jpg
--------------------------------------------------------------------------------
/res/golang/mysql_start_error_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/golang/mysql_start_error_2.png
--------------------------------------------------------------------------------
/res/golang/mysql_start_error_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/golang/mysql_start_error_3.jpg
--------------------------------------------------------------------------------
/res/gorm filed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/gorm filed.png
--------------------------------------------------------------------------------
/res/gotest-debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/gotest-debug.png
--------------------------------------------------------------------------------
/res/hash-table-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/hash-table-struct.png
--------------------------------------------------------------------------------
/res/hash_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/hash_map.png
--------------------------------------------------------------------------------
/res/ht-table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/ht-table.png
--------------------------------------------------------------------------------
/res/hyperlog对比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/hyperlog对比.png
--------------------------------------------------------------------------------
/res/if与switch对比.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/if与switch对比.jpg
--------------------------------------------------------------------------------
/res/innodb-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/innodb-architecture.png
--------------------------------------------------------------------------------
/res/kafka-base-gainian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kafka-base-gainian.png
--------------------------------------------------------------------------------
/res/kafka-eagle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kafka-eagle.png
--------------------------------------------------------------------------------
/res/kafka-runtime-an.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kafka-runtime-an.png
--------------------------------------------------------------------------------
/res/kernel-debug1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kernel-debug1.png
--------------------------------------------------------------------------------
/res/kernel-debug2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kernel-debug2.png
--------------------------------------------------------------------------------
/res/kernel-debug3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kernel-debug3.png
--------------------------------------------------------------------------------
/res/kernel-debug4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kernel-debug4.png
--------------------------------------------------------------------------------
/res/kernel-debug5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kernel-debug5.png
--------------------------------------------------------------------------------
/res/kibana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/kibana.png
--------------------------------------------------------------------------------
/res/list-push.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/list-push.png
--------------------------------------------------------------------------------
/res/map_ast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/map_ast.png
--------------------------------------------------------------------------------
/res/model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/model.png
--------------------------------------------------------------------------------
/res/mysql-debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/mysql-debug.png
--------------------------------------------------------------------------------
/res/mysql-engine-feature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/mysql-engine-feature.png
--------------------------------------------------------------------------------
/res/mysql-tool-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/mysql-tool-1.jpeg
--------------------------------------------------------------------------------
/res/mysql-wireshark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/mysql-wireshark.png
--------------------------------------------------------------------------------
/res/nginx/nginx结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/nginx/nginx结构.png
--------------------------------------------------------------------------------
/res/other/cgo-err.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/cgo-err.png
--------------------------------------------------------------------------------
/res/other/cgo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/cgo.png
--------------------------------------------------------------------------------
/res/other/fluentd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/fluentd.png
--------------------------------------------------------------------------------
/res/other/it.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/it.png
--------------------------------------------------------------------------------
/res/other/prometheus-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-1.png
--------------------------------------------------------------------------------
/res/other/prometheus-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-10.png
--------------------------------------------------------------------------------
/res/other/prometheus-11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-11.png
--------------------------------------------------------------------------------
/res/other/prometheus-12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-12.png
--------------------------------------------------------------------------------
/res/other/prometheus-13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-13.png
--------------------------------------------------------------------------------
/res/other/prometheus-14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-14.png
--------------------------------------------------------------------------------
/res/other/prometheus-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-15.png
--------------------------------------------------------------------------------
/res/other/prometheus-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-16.png
--------------------------------------------------------------------------------
/res/other/prometheus-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-17.png
--------------------------------------------------------------------------------
/res/other/prometheus-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-18.png
--------------------------------------------------------------------------------
/res/other/prometheus-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-2.png
--------------------------------------------------------------------------------
/res/other/prometheus-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-3.png
--------------------------------------------------------------------------------
/res/other/prometheus-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-4.png
--------------------------------------------------------------------------------
/res/other/prometheus-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-5.png
--------------------------------------------------------------------------------
/res/other/prometheus-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-6.png
--------------------------------------------------------------------------------
/res/other/prometheus-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-7.png
--------------------------------------------------------------------------------
/res/other/prometheus-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-8.png
--------------------------------------------------------------------------------
/res/other/prometheus-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/prometheus-9.png
--------------------------------------------------------------------------------
/res/other/simple-scalable-test-environment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/other/simple-scalable-test-environment.png
--------------------------------------------------------------------------------
/res/pgAdmin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pgAdmin.png
--------------------------------------------------------------------------------
/res/pprof-block.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-block.png
--------------------------------------------------------------------------------
/res/pprof-data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-data.png
--------------------------------------------------------------------------------
/res/pprof-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-file.png
--------------------------------------------------------------------------------
/res/pprof-gorotine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-gorotine.png
--------------------------------------------------------------------------------
/res/pprof-html.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-html.gif
--------------------------------------------------------------------------------
/res/pprof-mem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-mem.png
--------------------------------------------------------------------------------
/res/pprof-pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-pdf.png
--------------------------------------------------------------------------------
/res/pprof-web-cpu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pprof-web-cpu.png
--------------------------------------------------------------------------------
/res/pretty-zoo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/pretty-zoo.png
--------------------------------------------------------------------------------
/res/redis-hash-coding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-hash-coding.png
--------------------------------------------------------------------------------
/res/redis-hash-ziplist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-hash-ziplist.png
--------------------------------------------------------------------------------
/res/redis-hash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-hash.png
--------------------------------------------------------------------------------
/res/redis-ht-entry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-ht-entry.png
--------------------------------------------------------------------------------
/res/redis-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-pipeline.png
--------------------------------------------------------------------------------
/res/redis-push-sub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis-push-sub.png
--------------------------------------------------------------------------------
/res/redis4-hash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/redis4-hash.png
--------------------------------------------------------------------------------
/res/siphash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/siphash.png
--------------------------------------------------------------------------------
/res/slice的调试切片.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/slice的调试切片.png
--------------------------------------------------------------------------------
/res/socket-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/socket-struct.png
--------------------------------------------------------------------------------
/res/socket-tcp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/socket-tcp.png
--------------------------------------------------------------------------------
/res/stl-relationship.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/stl-relationship.png
--------------------------------------------------------------------------------
/res/stl标准库.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/stl标准库.png
--------------------------------------------------------------------------------
/res/string-copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/string-copy.png
--------------------------------------------------------------------------------
/res/string-modify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/string-modify.png
--------------------------------------------------------------------------------
/res/studio-3t.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/studio-3t.png
--------------------------------------------------------------------------------
/res/test-mysql-tables.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/test-mysql-tables.png
--------------------------------------------------------------------------------
/res/ubuntu-clion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/ubuntu-clion.png
--------------------------------------------------------------------------------
/res/unique_ptr_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/unique_ptr_delete.png
--------------------------------------------------------------------------------
/res/vector_struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/vector_struct.png
--------------------------------------------------------------------------------
/res/vs2015-vt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/vs2015-vt.png
--------------------------------------------------------------------------------
/res/vscode-log.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/vscode-log.png
--------------------------------------------------------------------------------
/res/wiki-hash-table1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/wiki-hash-table1.png
--------------------------------------------------------------------------------
/res/wiki-hash-table2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/wiki-hash-table2.png
--------------------------------------------------------------------------------
/res/zookeeper-cli-eagle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/zookeeper-cli-eagle.png
--------------------------------------------------------------------------------
/res/zookeeper-cmd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/zookeeper-cmd.png
--------------------------------------------------------------------------------
/res/zookeeper-eagle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/zookeeper-eagle.png
--------------------------------------------------------------------------------
/res/三种基本的分支语句.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/三种基本的分支语句.png
--------------------------------------------------------------------------------
/res/大数据处理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/大数据处理.png
--------------------------------------------------------------------------------
/res/智能指针的分类.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/智能指针的分类.jpg
--------------------------------------------------------------------------------
/res/栈与堆的对比.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/栈与堆的对比.png
--------------------------------------------------------------------------------
/res/结构体与共用体内存分布.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/结构体与共用体内存分布.png
--------------------------------------------------------------------------------
/res/结构体内存布局.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/结构体内存布局.png
--------------------------------------------------------------------------------
/res/结构体和共用体内存布局.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/结构体和共用体内存布局.jpg
--------------------------------------------------------------------------------
/res/递归调用.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/递归调用.jpg
--------------------------------------------------------------------------------
/res/队列参数设置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/队列参数设置.png
--------------------------------------------------------------------------------
/res/静态区存储.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ymm135/golang-cookbook/02d1243d829a8b4bb15c81318b9f65d43a996bac/res/静态区存储.png
--------------------------------------------------------------------------------