先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
在运行 go run 即可开启服务。
2)运行 zookeeper
==============
安装 zookeeper,并运行 zkServer, 默认为 2181 端口。
3)运行 go-client 调用 server 服务
===========================
- 进入 go-client 的源码目录
$ cd go-client/app
- 同理,在 /app 下配置环境变量
$ export CONF_CONSUMER_FILE_PATH=“…/profiles/dev/client.yml”
$ export APP_LOG_CONF_FILE=“…/profiles/dev/log.yml”
配置 go 代理:
$ export GOPROXY=“http://goproxy.io”
- 运行程序
$ go run .
即可在日志中找到打印出的请求结果:
response result: &{A001 Alex Stocks 18 2020-10-28 14:52:49.131 +0800 CST}
同样,在运行的 server 中,也可以在日志中找到打印出的请求:
req:[]interface {}{“A001”}
rsp:main.User{Id:“A001”, Name:“Alex Stocks”, Age:18, Time:time.Time{…}
恭喜!一次基于 dubbo-go 的 rpc 调用成功。
4)常见问题
======
-
当日志开始部分出现 profiderInit 和 ConsumerInit 均失败的日志,检查环境变量中配置路径是否正确,配置文件是否正确。
-
当日志中出现 register 失败的情况,一般为向注册中心注册失败,检查注册中心是否开启,检查配置文件中关于 register 的端口是否正确。
-
sample 的默认开启端口为 20000,确保启动前无占用。
2. 配置环境变量
==========
export APP_LOG_CONF_FILE=“…/profiles/dev/log.yml”
export CONF_CONSUMER_FILE_PATH=“…/profiles/dev/client.yml”
复制代码
3. 服务端源码
=========
1)目录结构
======
dubbo-go 框架的 example 提供的目录如下:
-
app/ 文件夹下存放源码,可以自己编写环境变量配置脚本 buliddev.sh
-
assembly/ 文件夹下存放不同平台的构建脚本
-
profiles/ 文件夹下存放不同环境的配置文件
-
target/ 文件夹下存放可执行文件
2)关键源码
======
源码放置在 app/ 文件夹下,主要包含 server.go 和 user.go 两个文件,顾名思义,server.go 用于使用框架开启服务以及注册传输协议;user.go 则定义了 rpc-service 结构体,以及传输协议的结构。
- user.go
func init() {
config.SetProviderService(new(UserProvider))
// ------for hessian2------
hessian.RegisterPOJO(&User{})
}
type User struct {
Id string
Name string
Age int32
Time time.Time
}
type UserProvider struct {
}
func (u *UserProvider) GetUser(ctx context.Context, req []interface{}) (*User, error) {
复制代码
可以看到,user.go 中存在 init 函数,是服务端代码中最先被执行的部分。User 为用户自定义的传输结构体,UserProvider 为用户自定义的 rpc_service;包含一个 rpc 函数,GetUser。当然,用户可以自定义其他的 rpc 功能函数。
在 init 函数中,调用 config 的 SetProviderService 函数,将当前 rpc_service 注册在框架 config 上。
可以查看 dubbo 官方文档提供的设计图:
service 层下面就是 config 层,用户服务会逐层向下注册,最终实现服务端的暴露。
rpc-service 注册完毕之后,调用 hessian 接口注册传输结构体 User。
至此,init 函数执行完毕。
- server.go
// they are necessary:
// export CONF_PROVIDER_FILE_PATH=“xxx”
// export APP_LOG_CONF_FILE=“xxx”
func main() {
hessian.RegisterPOJO(&User{})
config.Load()
initSignal()
}
func initSignal() {
signals := make(chan os.Signal, 1)
…
复制代码
之后执行 main 函数。
main 函数中只进行了两个操作,首先使用 hessian 注册组件将 User 结构体注册(与之前略有重复),从而可以在接下来使用 getty 打解包。
之后调用 config.Load 函数,该函数位于框架 config/config_loader.go 内,这个函数是整个框架服务的启动点,下面会详细讲这个函数内重要的配置处理过程。执行完 Load() 函数之后,配置文件会读入框架,之后根据配置文件的内容,将注册的 service 实现到配置结构里,再调用 Export 暴露给特定的 registry,进而开启特定的 service 进行对应端口的 tcp 监听,成功启动并且暴露服务。
最终开启信号监听 initSignal() 优雅地结束一个服务的启动过程。
4. 客户端源码
=========
客户端包含 client.go 和 user.go 两个文件,其中 user.go 与服务端完全一致,不再赘述。
- client.go
// they are necessary:
// export CONF_CONSUMER_FILE_PATH=“xxx”
// export APP_LOG_CONF_FILE=“xxx”
func main() {
hessian.RegisterPOJO(&User{})
config.Load()
time.Sleep(3e9)
println(“\n\n\nstart to test dubbo”)
user := &User{}
err := userProvider.GetUser(context.TODO(), []interface{}{“A001”}, user)
if err != nil {
panic(err)
}
println(“response result: %v\n”, user)
initSignal()
}
复制代码
main 函数和服务端也类似,首先将传输结构注册到 hessian 上,再调用 config.Load() 函数。在下文会介绍,客户端和服务端会根据配置类型执行 config.Load() 中特定的函数 loadConsumerConfig() 和 loadProviderConfig(),从而达到“开启服务”、“调用服务”的目的。
加载完配置之后,还是通过实现服务、增加函数 proxy、申请 registry 和 reloadInvoker 指向服务端 ip 等操作,重写了客户端实例 userProvider 的对应函数,这时再通过调用 GetUser 函数,可以直接通过 invoker,调用到已经开启的服务端,实现 rpc 过程。
下面会从 server 端和 client 从两个角度,详细讲解服务启动、registry 注册和调用过程。
5. 自定义配置文件(非环境变量)方法
====================
1)服务端自定义配置文件
============
- var providerConfigStr = xxxxx// 配置文件内容,可以参考 log 和 client。在这里你可以定义配置文件的获取方式,比如配置中心,本地文件读取。
log 地址:github.com/dubbogo/dub…
client 地址:github.com/dubbogo/dub…
- 在 config.Load() 之前设置配置,例如:
func main() {
hessian.RegisterPOJO(&User{})
providerConfig := config.ProviderConfig{}
yaml.Unmarshal([]byte(providerConfigStr), &providerConfig)
config.SetProviderConfig(providerConfig)
defaultServerConfig := dubbo.GetDefaultServerConfig()
dubbo.SetServerConfig(defaultServerConfig)
logger.SetLoggerLevel(“warn”) // info,warn
config.Load()
select {
}
}
复制代码
2)客户端自定义配置文件
============
-
var consumerConfigStr = xxxxx// 配置文件内容,可以参考 log 和 clien。在这里你可以定义配置文件的获取方式,比如配置中心,本地文件读取。
-
在 config.Load() 之前设置配置,例如:
func main() {
p := config.ConsumerConfig{}
yaml.Unmarshal([]byte(consumerConfigStr), &p)
config.SetConsumerConfig§
defaultClientConfig := dubbo.GetDefaultClientConfig()
dubbo.SetClientConf(defaultClientConfig)
logger.SetLoggerLevel(“warn”) // info,warn
config.Load()
user := &User{}
err := userProvider.GetUser(context.TODO(), []interface{}{“A001”}, user)
if err != nil {
log.Print(err)
return
}
log.Print(user)
}
复制代码
Server 端
========
服务暴露过程涉及到多次原始 rpcService 的封装、暴露,网上其他文章的图感觉太过笼统,在此,简要地绘制了一个用户定义服务的数据流图:
1. 加载配置
========
1)框架初始化
=======
在加载配置之前,框架提供了很多已定义好的协议、工厂等组件,都会在对应模块 init 函数内注册到 extension 模块上,以供接下来配置文件中进行选用。
其中重要的有:
- 默认函数代理工厂:common/proxy/proxy_factory/default.go
func init() {
extension.SetProxyFactory(“default”, NewDefaultProxyFactory)
}
复制代码
它的作用是将原始 rpc-service 进行封装,形成 proxy_invoker,更易于实现远程 call 调用,详情可见其 invoke 函数。
- 注册中心注册协议:
registry/protocol/protocol.go
func init() {
extension.SetProtocol(“registry”, GetProtocol)
}
复制代码
它负责将 invoker 暴露给对应注册中心,比如 zk 注册中心。
- zookeeper 注册协议:registry/zookeeper/zookeeper.go
func init() {
extension.SetRegistry(“zookeeper”, newZkRegistry)
}
复制代码
它合并了 base_resiger,负责在服务暴露过程中,将服务注册在 zookeeper 注册器上,从而为调用者提供调用方法。
- dubbo 传输协议:protocol/dubbo/dubbo.go
func init() {
extension.SetProtocol(DUBBO, GetProtocol)
}
复制代码
它负责监听对应端口,将具体的服务暴露,并启动对应的事件 handler,将远程调用的 event 事件传递到 invoker 内部,调用本地 invoker 并获得执行结果返回。
- filter 包装调用链协议:protocol/protocolwrapper/protocol_filter_wrapper.go
func init() {
extension.SetProtocol(FILTER, GetProtocol)
}
复制代码
它负责在服务暴露过程中,将代理 invoker 打包,通过配置好的 filter 形成调用链,并交付给 dubbo 协议进行暴露。
上述提前注册好的框架已实现的组件,在整个服务暴露调用链中都会用到,会根据配置取其所需。
2)配置文件
======
服务端需要的重要配置有三个字段:services、protocols、registries。
profiles/dev/server.yml:
registries :
“demoZk”:
protocol: “zookeeper”
timeout : “3s”
address: “127.0.0.1:2181”
services:
“UserProvider”:
可以指定多个registry,使用逗号隔开;不指定默认向所有注册中心注册
registry: “demoZk”
protocol : “dubbo”
相当于dubbo.xml中的interface
interface : “com.ikurento.user.UserProvider”
loadbalance: “random”
warmup: “100”
cluster: “failover”
methods:
- name: “GetUser”
retries: 1
loadbalance: “random”
protocols:
“dubbo”:
name: “dubbo”
port: 20000
复制代码
其中 service 指定了要暴露的 rpc-service 名("UserProvider)、暴露的协议名(“dubbo”)、注册的协议名(“demoZk”)、暴露的服务所处的 interface、负载均衡策略、集群失败策略及调用的方法等等。
其中,中间服务的协议名需要和 registries 下的 mapkey 对应,暴露的协议名需要和 protocols 下的 mapkey 对应。
可以看到上述例子中,使用了 dubbo 作为暴露协议,使用了 zookeeper 作为中间注册协议,并且给定了端口。如果 zk 需要设置用户名和密码,也可以在配置中写好。
3)配置文件的读入和检查
============
config/config_loader.go:: Load()
在上述 example 的 main 函数中,有 config.Load() 函数的直接调用,该函数执行细节如下:
// Load Dubbo Init
func Load() {
// init router
initRouter()
// init the global event dispatcher
extension.SetAndInitGlobalDispatcher(GetBaseConfig().EventDispatcherType)
// start the metadata report if config set
if err := startMetadataReport(GetApplicationConfig().MetadataType, GetBaseConfig().MetadataReportConfig); err != nil {
logger.Errorf(“Provider starts metadata report error, and the error is {%#v}”, err)
return
}
// reference config
loadConsumerConfig()
// service config
loadProviderConfig()
// init the shutdown callback
GracefulShutdownInit()
}
复制代码
在本文中,我们重点关心 loadConsumerConfig() 和 loadProviderConfig() 两个函数。
对于 provider 端,可以看到 loadProviderConfig() 函数代码如下:
前半部分是配置的读入和检查,进入 for 循环后,是单个 service 的暴露起始点。
前面提到,在配置文件中已经写好了要暴露的 service 的种种信息,比如服务名、interface 名、method 名等等。在图中 for 循环内,会将所有 service 的服务依次实现。
for 循环的第一行,根据 key 调用 GetProviderService 函数,拿到注册的 rpcService 实例,这里对应上述提到的 init 函数中,用户手动注册的自己实现的 rpc-service 实例:
这个对象也就成为了 for 循环中的 rpcService 变量,将这个对象注册通过 Implement 函数写到 sys(ServiceConfig 类型)上,设置好 sys 的 key 和协议组,最终调用了 sys 的 Export 方法。
此处对应流程图的部分:
至此,框架配置结构体已经拿到了所有 service 有关的配置,以及用户定义好的 rpc-service 实例,它触发了 Export 方法,旨在将自己的实力暴露出去。这是 Export 调用链的起始点。
2. 原始 service 封装入 proxy_invoker
=================================
config/service_config.go :: Export()
接下来进入 ServiceConfig.Export() 函数.
这个函数进行了一些细碎的操作,比如为不同的协议分配随机端口,如果指定了多个中心注册协议,则会将服务通过多个中心注册协议的 registryProtocol 暴露出去,我们只关心对于一个注册协议是如何操作的。还有一些操作比如生成调用 url 和注册 url,用于为暴露做准备。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
sys 的 key 和协议组,最终调用了 sys 的 Export 方法。
此处对应流程图的部分:
至此,框架配置结构体已经拿到了所有 service 有关的配置,以及用户定义好的 rpc-service 实例,它触发了 Export 方法,旨在将自己的实力暴露出去。这是 Export 调用链的起始点。
2. 原始 service 封装入 proxy_invoker
=================================
config/service_config.go :: Export()
接下来进入 ServiceConfig.Export() 函数.
这个函数进行了一些细碎的操作,比如为不同的协议分配随机端口,如果指定了多个中心注册协议,则会将服务通过多个中心注册协议的 registryProtocol 暴露出去,我们只关心对于一个注册协议是如何操作的。还有一些操作比如生成调用 url 和注册 url,用于为暴露做准备。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-1reaOYSt-1713711950210)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!