既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
当拿到一款框架之后,一种不错的源码阅读方式大致如下:从运行最基础的 helloworld demo 源码开始 —> 再查看配置文件 —> 开启各种依赖服务(比如zk、consul) —> 开启服务端 —> 再到通过 client 调用服务端 —> 打印完整请求日志和回报。调用成功之后,再根据框架的设计模型,从配置文件解析开始,自顶向下递阅读整个框架的调用栈。
对于 C/S 模式的 rpc 请求来说,整个调用栈被拆成了 client 和 server 两部分,所以可以分别从 server 端的配置文件解析阅读到 server 端的监听启动,从 client 端的配置文件解析阅读到一次 invoker Call 调用。这样一次完整请求就明晰了起来。
运行官网提供的 helloworld-demo
=======================
官方 demo 相关链接:github.com/dubbogo/dub…
1. dubbo-go 2.7 版本 QuickStart
==============================
1)开启一个 go-server 服务
===================
- 将仓库 clone 到本地
$ git clone github.com/dubbogo/dub…
- 进入 dubbo 目录
$ cd dubbo-samples/golang/helloworld/dubbo
进入目录后可看到四个文件夹,分别支持 go 和 java 的 client 以及 server,我们尝试运行一个 go 的 server。进入 app 的文件夹内,可以看到里面保存了 go 文件。
$ cd go-server/app
- sample 文件结构
可以在 go-server 里面看到三个文件夹:app、assembly、profiles。
其中 app 文件夹下保存 go 源码,assembly 文件夹下保存可选的针对特定环境的 build 脚本,profiles 下保存配置文件。对于 dubbo-go 框架,配置文件非常重要,没有文件将导致服务无法启动。
- 设置指向配置文件的环境变量
由于 dubbo-go 框架依赖配置文件启动,让框架定位到配置文件的方式就是通过环境变量来找。对于 server 端需要两个必须配置的环境变量:CONF_PROVIDER_FILE_PATH、APP_LOG_CONF_FILE,分别应该指向服务端配置文件、日志配置文件。
在 sample 里面,我们可以使用 dev 环境,即 profiles/dev/log.yml 和 profiles/dev/server.yml 两个文件。在 app/ 下,通过命令行中指定好这两个文件:
$ export CONF_PROVIDER_FILE_PATH=“…/profiles/dev/server.yml”
$ export APP_LOG_CONF_FILE=“…/profiles/dev/log.yml”
- 设置 go 代理并运行服务
$ go run .
如果提示 timeout,则需要设置 goproxy 代理。
$ export GOPROXY=“http://goproxy.io”
在运行 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() 函数的直接调用,该函数执行细节如下:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
“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() 函数的直接调用,该函数执行细节如下:
[外链图片转存中…(img-gYgqgUYc-1715502805034)]
[外链图片转存中…(img-uQFFaVUB-1715502805034)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!