导读:有了上一篇文章《Dubbo-go 源码笔记(一)Server 端开启服务过程》的铺垫,可以类比客户端启动于服务端的启动过程。其中最大的区别是服务端通过 zk 注册服务,发布自己的ivkURL并订阅事件开启监听;而客户应该是通过zk注册组件,拿到需要调用的serviceURL,更新invoker并重写用户的RPCService,从而实现对远程过程调用细节的封装。
配置文件和客户端源代码
1. client 配置文件
helloworld 提供的 demo:profiles/client.yaml。
registries :
"demoZk":
protocol: "zookeeper"
timeout : "3s"
address: "127.0.0.1:2181"
username: ""
password: ""
references:
"UserProvider":
# 可以指定多个registry,使用逗号隔开;不指定默认向所有注册中心注册
registry: "demoZk"
protocol : "dubbo"
interface : "com.ikurento.user.UserProvider"
cluster: "failover"
methods :
- name: "GetUser"
retries: 3
可看到配置文件与之前讨论过的 Server 端非常类似,其 refrences 部分字段就是对当前服务要主调的服务的配置,其中详细说明了调用协议、注册协议、接口 id、调用方法、集群策略等,这些配置都会在之后与注册组件交互、重写 ivk、调用的过程中使用到。
2. 客户端使用框架源码
user.go:
func init() {
config.SetConsumerService(userProvider)
hessian.RegisterPOJO(&User{})
}
main.go:
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()
}
在官网提供的 helloworld demo 的源码中,可看到与服务端类似,在 user.go 内注册了 rpc-service,以及需要 rpc 传输的结构体 user。
在 main 函数中,同样调用了 config.Load() 函数,之后就可以通过实现好的 rpc-service:userProvider 直接调用对应的功能函数,即可实现 rpc 调用。
可以猜到,从 hessian 注册结构、SetConsumerService,到调用函数 .GetUser() 期间,用户定义的 rpc-service 也就是 userProvider 对应的函数被重写,重写后的 GetUser 函数已经包含实现了远程调用逻辑的 invoker。
接下来,就要通过阅读源码,看看 dubbo-go 是如何做到的。
实现远程过程调用
1. 加载配置文件
// file: config/config_loader.go :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()
在 main 函数中调用了 config.Load() 函数,进而调用了 loadConsumerConfig,类似于之前讲到的 server 端配置读入函数。
在 loadConsumerConfig 函数中,进行了三步操作:
// config/config_loader.go
func loadConsumerConfig() {
// 1 init other consumer config
conConfigType := consumerConfig.ConfigType
for key, value := range extension.GetDefaultConfigReader() {}
checkApplicationName(consumerConfig.ApplicationConfig)
configCenterRefreshConsumer()
checkRegistries(consumerConfig.Registries, consumerConfig.Registry)
// 2 refer-implement-reference
for key, ref := range consumerConfig.References {
if ref.Generic {
genericService := NewGenericService(key)
SetConsumerService(genericService)
}
rpcService := GetConsumerService(key)
ref.id = key
ref.Refer(rpcService)
ref.Implement(rpcService)
}
// 3 wait for invoker is available, if wait over default 3s, then panic
for {}
}
- 检查配置文件并将配置写入内存
- 在 for 循环内部,依次引用(refer)并且实例化(implement)每个被调 reference
- 等待三秒钟所有 invoker 就绪
其中重要的就是 for 循环里面的引用和实例化,两步操作,会在接下来展开讨论。
至此,配置已经被写入了框架。
2. 获取远程 Service URL,实现可供调用的 invoker
上述的 ref.Refer 完成的就是这部分的操作。