How Dubbo-go Client Turns Config Files into Remote Calls – A Deep Dive
This article walks through the Dubbo-go client side, showing how the client configuration file and source code are loaded, how URLs and registry protocols are built, how directories and cluster invokers are created, and how a proxy finally enables seamless remote method invocation.
Client Configuration File
The demo profiles/client.yaml defines a Zookeeper registry named demoZk and a reference to UserProvider with protocol, interface, method, and cluster settings.
registries:
"demoZk":
protocol: "zookeeper"
timeout: "3s"
address: "127.0.0.1:2181"
username: ""
password: ""
references:
"UserProvider":
registry: "demoZk"
protocol: "dubbo"
interface: "com.ikurento.user.UserProvider"
cluster: "failover"
methods:
- name: "GetUser"
retries: 3Client Source Code
In user.go the consumer service is registered and the POJO type is added to Hessian:
func init() {
config.SetConsumerService(userProvider)
hessian.RegisterPOJO(&User{})
}The main.go loads the configuration, sleeps briefly, then calls the remote method:
func main() {
hessian.RegisterPOJO(&User{})
config.Load()
time.Sleep(3e9)
println("
start to test dubbo")
user := &User{}
err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)
if err != nil { panic(err) }
println("response result: %v
", user)
initSignal()
}Loading the Configuration
The call to config.Load() triggers initRouter(), sets up the global event dispatcher, optionally starts a metadata reporter, and finally calls loadConsumerConfig().
Steps in loadConsumerConfig()
Initialize other consumer configs (application name, config‑center refresh, registry checks).
Iterate over consumerConfig.References to create or retrieve a service instance, then call Refer and Implement on each reference.
Wait up to three seconds for an invoker to become available; panic if the timeout expires.
Reference Handling – Building URLs and Invokers
The ReferenceConfig.Refer method constructs a service URL, resolves the registry URL (Zookeeper or user‑provided), and obtains a registryProtocol instance to perform the actual Refer call.
// file: reference_config.go: Refer()
func (c *ReferenceConfig) Refer(_ interface{}) {
// (1) Build service URL
cfgURL := common.NewURLWithOptions(
common.WithPath(c.id),
common.WithProtocol(c.Protocol),
common.WithParams(c.getUrlMap()),
common.WithParamsValue(constant.BEAN_NAME_KEY, c.id),
)
// (2) Resolve registry URL(s)
if c.Url != "" { /* parse user‑specified URLs */ } else {
c.urls = loadRegistries(c.Registry, consumerConfig.Registries, common.CONSUMER)
for _, regUrl := range c.urls { regUrl.SubURL = cfgURL }
}
// (3) Call registry protocol Refer to get an invoker
if len(c.urls) == 1 {
c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(*c.urls[0])
} else {
// multiple registries → cluster strategy
// ... create static directory and join with a cluster
}
// (4) Create proxy for the RPC service
if c.Async { /* async proxy */ } else { c.pxy = extension.GetProxyFactory(...).GetProxy(c.invoker, cfgURL) }
}RegistryProtocol Refer Flow
The registryProtocol.Refer function extracts the service URL from the registry URL, loads (or creates) a Zookeeper registry instance, builds a RegistryDirectory, registers the service URL with Zookeeper, and finally creates a cluster invoker based on the configured cluster strategy.
// file: registry/protocol/protocol.go: Refer(url common.URL) protocol.Invoker
func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker {
serviceUrl := url.SubURL
if url.Protocol == constant.REGISTRY_PROTOCOL {
url.Protocol = url.GetParam(constant.REGISTRY_KEY, "")
}
// get or create registry instance
var reg registry.Registry
if r, loaded := proto.registries.Load(url.Key()); !loaded {
reg = getRegistry(&url)
proto.registries.Store(url.Key(), reg)
} else { reg = r.(registry.Registry) }
// create directory and subscribe
directory, err := extension.GetDefaultRegistryDirectory(&url, reg)
if err != nil { logger.Errorf(...); return nil }
// register service URL on Zookeeper
if err = reg.Register(*serviceUrl); err != nil { logger.Errorf(...) }
// apply cluster strategy (default failover)
cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER))
invoker := cluster.Join(directory)
proto.invokers = append(proto.invokers, invoker)
return invoker
}RegistryDirectory Creation and Subscription
The directory is created with a background goroutine that adds two listeners and subscribes to Zookeeper for service updates.
// file: registry/directory/directory.go: NewRegistryDirectory()
func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster.Directory, error) {
if url.SubURL == nil { return nil, perrors.Errorf("url is invalid, suburl can not be nil") }
dir := &RegistryDirectory{BaseDirectory: directory.NewBaseDirectory(url), registry: registry}
dir.consumerConfigurationListener = newConsumerConfigurationListener(dir)
go dir.subscribe(url.SubURL)
return dir, nil
}
// subscribe()
func (dir *RegistryDirectory) subscribe(url *common.URL) {
dir.consumerConfigurationListener.addNotifyListener(dir)
dir.referenceConfigurationListener = newReferenceConfigurationListener(dir, url)
dir.registry.Subscribe(url, dir)
}Cluster Invoker – Failover Strategy
The default cluster is failover. The failoverClusterInvoker.Invoke method selects an invoker, retries on failure, and respects load‑balancing and cluster policies.
// file: cluster/failover_cluster_invokers.go: Invoke()
func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {
invokers := invoker.directory.List(invocation)
if err := invoker.checkInvokers(invokers, invocation); err != nil { return &protocol.RPCResult{Err: err} }
methodName := invocation.MethodName()
retries := getRetries(invokers, methodName)
loadBalance := getLoadBalance(invokers[0], invocation)
for i := 0; i <= retries; i++ {
if i > 0 { /* re‑select and re‑check */ }
ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked)
if ivk == nil { continue }
result := ivk.Invoke(ctx, invocation)
if result.Error() == nil { return result }
// record failed provider and retry
}
return result
}Proxy Implementation
The proxy converts a user‑defined RPC service method into an Invocation, attaches context data, calls the underlying invoker, and writes back results.
// file: common/proxy/proxy.go: Implement()
func (p *Proxy) Implement(v common.RPCService) {
makeDubboCallProxy := func(methodName string, outs []reflect.Type) func(in []reflect.Value) []reflect.Value {
return func(in []reflect.Value) []reflect.Value {
inv := invocation_impl.NewRPCInvocationWithOptions(
invocation_impl.WithMethodName(methodName),
invocation_impl.WithArguments(inIArr),
invocation_impl.WithReply(reply.Interface()),
invocation_impl.WithCallBack(p.callBack),
invocation_impl.WithParameterValues(inVArr),
)
for k, val := range p.attachments { inv.SetAttachments(k, val) }
// invoke through the cluster invoker
result := p.invoke.Invoke(invCtx, inv)
if len(result.Attachments()) > 0 {
invCtx = context.WithValue(invCtx, constant.AttachmentKey, result.Attachments())
}
// convert result to reflect.Values and return
// ...
}
}
// iterate over struct fields, replace each function with the generated proxy
for i := 0; i < numField; i++ {
t := typeOf.Field(i)
methodName := t.Tag.Get("dubbo")
if methodName == "" { methodName = t.Name }
f := valueOfElem.Field(i)
if f.Kind() == reflect.Func && f.IsValid() && f.CanSet() {
outNum := t.Type.NumOut()
if outNum != 1 && outNum != 2 { continue }
if t.Type.Out(outNum-1) != typError { continue }
funcOuts := make([]reflect.Type, outNum)
for j := 0; j < outNum; j++ { funcOuts[j] = t.Type.Out(j) }
f.Set(reflect.MakeFunc(f.Type(), makeDubboCallProxy(methodName, funcOuts)))
}
}
}Putting It All Together
After config.Load() the client has a fully built invoker chain: Zookeeper registry → RegistryDirectory → FailoverClusterInvoker → Proxy → user‑defined RPC method. The final call in main.go:
config.Load()
user := &User{}
err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)executes the remote GetUser method through the generated proxy, demonstrating how Dubbo-go abstracts the complexities of service discovery, clustering, and RPC invocation.
The listener‑based design mirrors Java’s event model and cleanly decouples each layer of the call stack.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
