├── .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 | ![slice的调试切片](../../../res/slice的调试切片.png) 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 | ![go源码调试1](../../../res/debug_source.png) 249 | 250 |
251 | 252 | ![go源码调试2](../../../res/debug_go_source1.png) 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 | ![es-head](../../../res/es-head.png) 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 | ![kibana](../../../res/kibana.png) 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 | ![zookeeper-eagle.png](../../../res/zookeeper-eagle.png) 19 | 20 | zookeeper命令行 21 | ![zookeeper-cli-eagle.png ](../../../res/zookeeper-cli-eagle.png) 22 | 23 | kafka信息 24 | ![kafka-eagle.png](../../../res/kafka-eagle.png) 25 | 26 | 也可以通过 [PrettyZoo](https://github.com/vran-dev/PrettyZoo) 测试`zookeeper` 27 | 28 | ![pretty-zoo.png](../../../res/pretty-zoo.png) 29 | 30 | 命令行操作: 31 | ![zookeeper-cmd.png](../../../res/zookeeper-cmd.png) 32 | 33 | ## kafka基本操作及指令 34 | ### 基本概念 35 | ![kafka-base-gainian.png](../../../res/kafka-base-gainian.png) 36 | 37 | ### 实时分析模型 38 | 39 | ![flink-demo.png](../../../res/flink-demo.png) 40 | 41 | ![flink-shouye.png](../../../res/flink-shouye.png) 42 | 43 | 大数据处理技术栈[来自慕课](https://class.imooc.com/sale/bigdata) 44 | ![大数据处理.png](../../../res/大数据处理.png) 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 | ![studio-3t.png](../../../res/studio-3t.png) 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 | ![adminer](../../../res/adminer.png) 48 |
49 | 50 | ![adminer_create_table](../../../res/adminer_create_table.png) 51 |
52 | 53 | ![adminer_insert_data](../../../res/adminer_insert_data.png) 54 |
55 | 56 | ## [pgAdmin工具](https://www.postgresql.org/download/) 57 | 58 | 59 | ![adminer_insert_data](../../../res/pgAdmin.png) 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 | ![exchange1-length](../../../res/exchange1-length.png) 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 | ![redis-pipeline.png](../../../res/redis-pipeline.png) 165 | 166 | ## [Redis Pub/Sub](https://redis.io/topics/pubsub) 167 | ![redis-push-sub.png](../../../res/redis-push-sub.png) 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 | ![CLion 2021.3通过makefile调试redis](../../../res/clion-redis-makefile.png) 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 | ![redis4-hash.png](../../../res/redis4-hash.png) 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 | ![redis-hash-coding.png](../../../res/redis-hash-coding.png) 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 | ![redis-ht-entry.png](../../../res/redis-ht-entry.png) 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 | ![siphash.png](../../../res/siphash.png) 218 | 219 | 根据hash函数结果存储dictEntry 220 | 221 | ![ht-table.png](../../../res/ht-table.png) 222 | 223 | 整体结构如下: 224 | 225 | ![redis-hash.png](../../../res/redis-hash.png) 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 --------------------------------------------------------------------------------