所有接触过go-micro的同学一定都看过官方的那个greeter的示例吧,这篇文章尝试着探索一下greeter的server和client背后到底做了哪些事情。

Greeter example

greeter代码如下所示:

server

package main

import (
	"log"
	"time"

	hello "github.com/micro/examples/greeter/srv/proto/hello"
	"github.com/micro/go-micro"

	"context"
)

type Say struct{}

func (s *Say) Hello(ctx context.Context, req *hello.Request, rsp *hello.Response) error {
	log.Print("Received Say.Hello request")
	rsp.Msg = "Hello " + req.Name
	return nil
}

func main() {
	service := micro.NewService(
		micro.Name("go.micro.srv.greeter"),
		micro.RegisterTTL(time.Second*30),
		micro.RegisterInterval(time.Second*10),
	)

	// optionally setup command line usage
	service.Init()

	// Register Handlers
	hello.RegisterSayHandler(service.Server(), new(Say))

	// Run server
	if err := service.Run(); err != nil {
		log.Fatal(err)
	}
}

client

package main

import (
	"context"
	"fmt"

	hello "github.com/micro/examples/greeter/srv/proto/hello"
	"github.com/micro/go-micro"
)

func main() {
	// create a new service
	service := micro.NewService()

	// parse command line flags
	service.Init()

	// Use the generated client stub
	cl := hello.NewSayService("go.micro.srv.greeter", service.Client())

	// Make request
	rsp, err := cl.Hello(context.Background(), &hello.Request{
		Name: "John",
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(rsp.Msg)
}

Init

从上面的例子中可以看到,在go-micro中,初始化一个只有一个handler的server只要三行代码:

service := micro.NewService(...)
service.Init(...)
xxx.RegisterXXXHandler(service.Server(), new(XXX))

而初始化一个只和一个server交互的client也只要三行代码:

service := micro.NewService(...)
service.Init(...)
c := xxx.NewXXXService("xxx", service.Client())

其中,对于client和server,它们的前两行是一样的。它的作用是初始化service的Options,其中包括:

接下来,我们先看看这两行初始化service的代码背后在做什么,然后再剖析一下server和client特有的那一行代码的作用。

micro.NewService()

NewService

service.Init()

service.Init

Take Away: options设置的优先级为:cli arguments > env.var. > hardcoded (NewService()/service.Init())

server: xxx.RegisterXXXHandler()

(这个方法由go-micro的proto plugin生成的代码提供)

server注册其rpc实现(即handler)。如果你用的是默认的rpc server(而不是grpc server),它会加入到内部rpc router的serviceMap里面。

client: xxx.NewXXXService()

(这个方法由go-micro的proto plugin生成的代码提供)

拿greeter 的client来说,它的实现如下:

func NewSayService(serviceName string, c client.Client) SayService {
	if c == nil {
		c = client.NewClient()
	}
	if len(serviceName) == 0 {
		serviceName = "go.micro.srv.greeter"
	}
	return &sayService{
		c:           c,
		serviceName: serviceName,
	}
}

一般情况下,应该显式地传入serviceNamec参数。

传入serviceName很好理解,因为server的名字很可能不叫这个默认的名字。

而传入c的原因是因为在初始化service的时候,你可能设置了各种options。这些options被应用到当时的service内部的options.Client对象上。如果,在此时调用xxx.NewXXXService()的时候不使用service中的client,而是new了一个新的client,亦或是传入一个nil让函数自己创建一个默认的client,那么这个client内部的options都是默认的options,而不是你在初始化service时使用的那些options。笔者昨天就犯了这么一个低级错误,在NewXXXService()中调用了client.NewClient(),导致registry的设置(consul)都没生效(而是用的默认的mdns),导致selector无法找到server (server注册在consul上)。结果后来花了大半天debug才发现。这也是促使我阅读源码并且写这篇博客的原因。

Action

看完了基本的初始化流程,我们看看server和client是如何工作的。

service.Run()

service.Run

cl.XXX()

client.Run