Go基础 + 进阶
Go基础 + 进阶
学习路线
基础
http包 与 web服务
03.3. Go 如何使得 Web 工作 | 第三章. Web 基础 |《Go Web 编程》| Go 技术论坛 (learnku.com)
图 3.9 http 包执行流程
创建 Listen Socket, 监听指定的端口,等待客户端请求到来。
Listen Socket 接受客户端的请求,得到 Client Socket, 接下来通过 Client Socket 与客户端通信。
处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端。
源码解读
03.4. Go 的 http 包详解 | 第三章. Web 基础 |《Go Web 编程》| Go 技术论坛 (learnku.com)
路由:
1 |
|
Go 默认的路由添加是通过函数 http.Handle 和 http.HandleFunc 等来添加,底层都是调用了 DefaultServeMux.Handle(pattern string, handler Handler), 这个函数会把路由信息存储在一个 map 信息中 map[string]muxEntry,这就解决了上面说的第一点。
1 |
|
Go 代码的执行流程#
通过对 http 包的分析之后,现在让我们来梳理一下整个的代码执行过程。
- 首先调用 Http.HandleFunc
按顺序做了几件事:
1 调用了 DefaultServeMux 的 HandleFunc
2 调用了 DefaultServeMux 的 Handle
3 往 DefaultServeMux 的 map [string] muxEntry 中增加对应的 handler 和路由规则
- 其次调用 http.ListenAndServe (“:9090”, nil)
按顺序做了几件事情:
1 实例化 Server
2 调用 Server 的 ListenAndServe ()
3 调用 net.Listen (“tcp”, addr) 监听端口
4 启动一个 for 循环,在循环体中 Accept 请求
5 对每个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求进行服务 go c.serve ()
6 读取每个请求的内容 w, err := c.readRequest ()
7 判断 handler 是否为空,如果没有设置 handler(这个例子就没有设置 handler),handler 就设置为 DefaultServeMux
8 调用 handler 的 ServeHttp
9 在这个例子中,下面就进入到 DefaultServeMux.ServeHttp
10 根据 request 选择 handler,并且进入到这个 handler 的 ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 选择 handler:
A 判断是否有路由能满足这个 request(循环遍历 ServeMux 的 muxEntry)
B 如果有路由满足,调用这个路由 handler 的 ServeHTTP
C 如果没有路由满足,调用 NotFoundHandler 的 ServeHTTP
数据库加载原理
05.1. database/sql 接口 | 第五章. 访问数据库 |《Go Web 编程》| Go 技术论坛 (learnku.com)
模拟RESTful 路由
08.3. REST | 第八章. Web 服务 |《Go Web 编程》| Go 技术论坛 (learnku.com)
1 |
|
go RPC
08.4. RPC | 第八章. Web 服务 |《Go Web 编程》| Go 技术论坛 (learnku.com)
RPC 工作原理#
图 8.8 RPC 工作流程图
运行时,一次客户机对服务器的 RPC 调用,其内部操作大致有如下十步:
- 调用客户端句柄;执行传送参数
- 调用本地系统内核发送网络消息
- 消息传送到远程主机
- 服务器句柄得到消息并取得参数
- 执行远程过程
- 执行的过程将结果返回服务器句柄
- 服务器句柄返回结果,调用远程系统内核
- 消息传回本地主机
- 客户句柄由内核接收消息
- 客户接收句柄返回的数据
结构体嵌套
1 |
|
1 |
|
1 |
|
需要根据具体情况选择内嵌指针或内嵌结构体,以达到最优的性能和易用性。
元标记
在Go语言中,结构体字段后面的反引号中可以添加元标记(tags)来指定该字段的元数据。这些标记通常用于提供编译时检查或代码生成的元信息。
下面是一些常见的元标记及其含义:
omitempty
:隐式忽略空值字段(如nil
、0
、false
、""
),以减小生成的JSON、XML或Gob等的输出文件大小。string
、int
、float
等:强制将该字段解析为指定的类型。json
:覆盖JSON输出中的字段名,如:json:"my_field"
表示在输出中使用my_field
而非字段的原始名称。xml
:同上,但是用于XML输出。msgpack
:同上,但是用于MessagePack输出。bson
:同上,但是用于BSON输出。yaml
:同上,但是用于YAML输出。db
:针对数据库模型,覆盖表字段名的名称或类型。validate
:提供快速的参数验证标准,如validate:"required,min=10,max=100"
强制验证字符串的长度,以及值的范围。
这些元标记一般集成在各种库和框架中,以简化开发人员的开发。例如,在 Gin、Echo、Beego 和 Revel 等框架中,都有基于元标记的验证和绑定机制,可以方便地进行参数验证和提供更好的表单,JSON 和 XML 绑定。
标准依赖库
依赖使用介绍:Standard library - Go Packages
源码地址:Sub-repositories - Go Packages
导入包和 导入init方法 (‘’_’’)
1 |
|
go mod 导入依赖
使用go mod进行依赖管理时,常用的命令包括:
- go mod init:初始化一个新的go.mod文件来管理项目依赖。
- go mod tidy:根据go.mod文件中记录的依赖关系,自动添加或删除项目中使用但未在go.mod文件中声明的依赖包,并且保持go.mod和go.sum文件的同步。
- go mod vendor:将依赖包复制到项目的vendor目录中,以便于在没有网络连接的情况下构建项目。
- go mod download:下载go.mod文件中记录的所有依赖包到本地缓存中。
- go mod verify:校验依赖包的完整性,检查依赖包的hash值是否匹配go.sum文件中的记录。
- go mod graph:打印当前项目的模块依赖图。
- go mod why:打印指定的包是为何需要被包含在项目中的原因。
- go mod edit:修改go.mod文件中的依赖关系。
- go mod replace:替换go.mod文件中的特定依赖包。
- go list -m all:列出所有导入路径及其对应的版本。
这些命令可以通过在命令行中输入”go mod”加上相应的命令来执行。
断言表达式
interface.(*type xx struct)
是一个有效的类型断言表达式,其中 xx
是你想要断言的具体类型的名称。
在 Go 语言中,使用类型断言表达式 value.(Type)
来将接口类型的值 value
转换为特定类型 Type
。
具体类型名称应该替换为你想要断言的实际类型的名称。
示例:
1 |
|
在上面的示例中,我们将 x
断言为 Person
类型,并检查断言结果以确定是否成功。如果断言成功,我们可以使用断言后的变量 p
来访问 Person
结构体的字段;如果断言失败,我们可以据此处理。
请确保将 xx
替换为你想要断言的具体类型的名称,以使类型断言有效。
进阶
进阶学习方式
如果想要深入了解 Go 语言的底层逻辑,可以从以下几个方向入手:
Go 语言编译器:了解 Go 语言编译器和编译器优化技术,理解生成的汇编代码和可执行文件的结构。
Go 语言运行时系统:了解 Go 语言的内存管理、垃圾回收、调度器、并发机制、信号处理和系统调用等底层机制。
Go 语言标准库:了解 Go 标准库的设计思想和底层实现。
以下是一些推荐的阅读资料:
《Go语言编程》:一本从基础到进阶都很全面的 Go 语言教材,涉及 Go 语言的基础知识、网络编程、进程和线程、性能分析和调优等方面。
《Go语言高级编程》:一本涉及 Go 语言高级主题的教材,包括 Go 语言运行时系统、内存管理、垃圾回收、网络编程和并发编程等。
《Go源码剖析》:一本介绍 Go 语言源码的书籍,通过深入阅读和分析 Go 语言的源代码,让读者深入了解 Go 语言底层机制和实现。
Go 官方文档:官方文档包括 Go 语言规范、Go 语言标准库文档、Go 语言编译器和运行时系统文档等,是了解 Go 语言底层机制的重要途径。
Go官方代码库:Go 语言的开源代码库提供了大量的实现细节和技术细节,可以通过阅读代码库来了解 Go 语言底层实现。
Go源码分析系列博客:由国内 Go 社区的大佬们写就的针对 Go 源代码的解读分析系列博客,可以更深入地了解 Go 的设计思想和底层实现。
Go 官方博客:官方博客提供了关于 Go 语言开发和技术方面的信息,它涵盖了从新手到专业人士的各种主题。
需要注意的是,Go 语言有其独特的设计思想和底层实现,初学者可能需要逐步了解相关概念和内部实现,尤其是对于底层系统设计和并发机制。因此建议从基础到进阶,一步一步深入学习,注重理解底层原理,通过实践编写代码并进行调试。
init 和 main
在 Go 中,init()
函数的执行顺序与包的导入顺序有关。具体来说,每个文件中的 init()
函数会在文件被编译时被执行,而导入该文件的包的 init()
函数则会在导入包时被执行,按照导入的顺序依次执行。因此,导入该文件的包的 init()
函数的执行顺序是在每个文件内的 init()
函数的前面。
在程序执行时,与包被加载的时间无关,init()
函数会在程序启动时自动执行。具体来说,在程序的 main()
函数执行前,Go 会自动执行所有包中的 init()
函数,包括第三方依赖的包。由于 Go 的包是惰性加载的,在编写程序时可能会导入一些不必要的包,因此需要根据实际情况减少导入不必要的包,以提高程序的启动速度。
需要注意的是,如果一个包被多次导入,那么其中的 init()
函数也会被多次执行。为了避免这种情况,可以使用 sync.Once
等方式来保证 init()
函数只被执行一次。
启动流程
在 Go 中,如果一个包被多次导入,则该包的 init()
函数会被多次执行。这个含义是指在一个程序中,如果多次导入同一个包,该包的 init()
函数会被多次调用,而不是只调用一次。这种情况可能会导致不必要的性能开销,因此应该尽可能避免多次导入同一个包的情况。
Go 的执行方式包含三个阶段:编译、链接和运行。在编译阶段,Go 会将代码编译为目标平台可执行的二进制文件。在链接阶段,Go 会将编译得到的二进制文件与所有的依赖库链接,生成最终的可执行文件。在运行阶段,用户通过执行生成的可执行文件来运行程序。
go build
命令用于编译 Go 程序,可以将指定的 Go 源代码文件编译为可执行文件。该命令会执行编译和链接两个阶段,但不会执行最终的运行阶段。编译后的结果是一个二进制文件,而不是直接执行程序。例如,执行以下命令编译程序:
1 |
|
该命令会将 main.go
编译为可执行文件 main
,可以通过以下命令来运行:
1 |
|
go run
命令用于编译并执行 Go 程序,它执行的是编译、链接和最终的运行三个阶段。例如,执行以下命令编译并执行程序:
1 |
|
go install
命令用于编译 Go 程序,并安装生成的可执行文件到 $GOPATH/bin
目录中。这个命令会在编译、链接和最终的运行三个阶段都执行,并将编译生成的可执行文件安装到系统中。如果在 $GOPATH/bin
和 $PATH
环境变量中配置了该目录,那么就可以在任何地方通过命令行来执行该程序。
在使用以上命令时,需要注意的是,在执行 go build
或 go install
等命令时,如果多次导入同一个包,会导致该包的 init()
函数被多次调用,增加了编译、链接和运行的时间。因此,在编写程序时应尽量避免多次导入同一个包。可以使用 go mod
命令管理依赖关系,减少包的导入次数。
同时,应该注意在安装和执行 Go 程序时,确保系统环境变量 $GOPATH
和 $PATH
都正确配置,以确保程序可以被正确地编译、安装和执行。
Go 本地缓存
golang本地缓存(bigcache/freecache/fastcache等)选型对比及原理总结 - 知乎 (zhihu.com)