如何在go-micro中实现Server

658

接下来的两篇文章,我们一起来学习go-micro中的ServerClient,这篇文章重点介绍Server.

go-micro中的Server也被抽离出一个接口类型,便于我们进行自定义,go-micro中默认的Serverrpc_server.

我们首先看一下Server接口主要包含那些方法签名

type Server interface {
	// Initialise options
	Init(...Option) error
	// Retrieve the options
	Options() Options
	// Register a handler
	Handle(Handler) error
	// Create a new handler
	NewHandler(interface{}, ...HandlerOption) Handler
	// Create a new subscriber
	NewSubscriber(string, interface{}, ...SubscriberOption) Subscriber
	// Register a subscriber
	Subscribe(Subscriber) error
	// Start the server
	Start() error
	// Stop the server
	Stop() error
	// Server implementation
	String() string
}

在进行初始化的过程中,其实调用的是newRpcServer.

// Init initialises the default server with options passed in
func Init(opt ...Option) {
	if DefaultServer == nil {
		DefaultServer = newRpcServer(opt...)
	}
	DefaultServer.Init(opt...)
}

启动的时候,也是启动的newRpcServerStart方法

// Start starts the default server
func Start() error {
	config := DefaultServer.Options()
	if logger.V(logger.InfoLevel, logger.DefaultLogger) {
		logger.Infof("Starting server %s id %s", config.Name, config.Id)
	}
	return DefaultServer.Start()
}

所以我们就一起来看看这个默认的ServerServer的结构体如下

type rpcServer struct {
	router *router   // 路由
	exit   chan chan error

	sync.RWMutex
	opts        Options
	handlers    map[string]Handler
	subscribers map[Subscriber][]broker.Subscriber
	// marks the serve as started
	started bool
	// used for first registration
	registered bool
	// subscribe to service name
	subscriber broker.Subscriber
	// graceful exit
	wg *sync.WaitGroup

	rsvc *registry.Service
}

实例化一个newRpcServer实例的方法如下,

func newRpcServer(opts ...Option) Server {
	options := newOptions(opts...)
	router := newRpcRouter()
	router.hdlrWrappers = options.HdlrWrappers  // handler wrappers
	router.subWrappers = options.SubWrappers    // subscriber wrappers

	return &rpcServer{
		opts:        options,
		router:      router,
		handlers:    make(map[string]Handler),
		subscribers: make(map[Subscriber][]broker.Subscriber),
		exit:        make(chan chan error),
		wg:          wait(options.Context),
	}
}

Start方法中,分为以下几个步骤,由于有很多调试代码,我们仅看相关代码

1.在transport上开启一个监听,把监听的地址赋值给server.Options.Address

ts, err := config.Transport.Listen(config.Address)
	if err != nil {
		return err
	}

2.连接broker.

if err := config.Broker.Connect(); err != nil {
		if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
			logger.Errorf("Broker [%s] connect error: %v", bname, err)
		}
		return err
	}

3.执行注册前检查,没问题才会进行Register操作。这个注册前检查有点意思,他是在注册前进行调用的函数,但是默认的检查函数其实什么也没做,直接返回nil,如果你想做些检查,可以在Serveropts属性中进行定义,格式为fn func(context.Context) error类型的函数。

if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
		if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
			logger.Errorf("Server %s-%s register check error: %s", config.Name, config.Id, err)
		}
	} else {
		// announce self to the world
		if err = s.Register(); err != nil {
			if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
				logger.Errorf("Server %s-%s register error: %s", config.Name, config.Id, err)
			}
		}
	}

Register方法的逻辑就是将我们的Server上的subscribers以及handlers放到endpoints里面,具体逻辑是把handler转换为endpointer并存到endpointers列表中.并在Register方法里,使用它来实例化一个service

service := &registry.Service{
		Name:      config.Name,
		Version:   config.Version,
		Nodes:     []*registry.Node{node},
		Endpoints: endpoints,
	}

之后,将service执行注册,该注册方法将该service注册到注册中心,便于客户端服务发现并进行业务调用

// register the service
	if err := regFunc(service); err != nil {
		return err
	}

以上就是整个的注册过程,说完了Start方法中的注册,我们继续看Start方法后面的内容

go func() {
		for {
			// listen for connections
			err := ts.Accept(s.ServeConn)

			// TODO: listen for messages
			// msg := broker.Exchange(service).Consume()

			select {
			// check if we're supposed to exit
			case <-exit:
				return
			// check the error and backoff
			default:
				if err != nil {
					if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
						logger.Errorf("Accept error: %v", err)
					}
					time.Sleep(time.Second)
					continue
				}
			}

			// no error just exit
			return
		}
	}()

为什么在default中判断err是否为空,不为空就continue,这个跟ts.Accept(s.ServeConn)有关.

func (t *grpcTransportListener) Accept(fn func(transport.Socket)) error {
	var opts []grpc.ServerOption

	// setup tls if specified
	if t.secure || t.tls != nil {
		config := t.tls
		if config == nil {
			var err error
			addr := t.listener.Addr().String()
			config, err = getTLSConfig(addr)
			if err != nil {
				return err
			}
		}

		creds := credentials.NewTLS(config)
		opts = append(opts, grpc.Creds(creds))
	}

	// new service
	srv := grpc.NewServer(opts...)

	// register service
	pb.RegisterTransportServer(srv, &microTransport{addr: t.listener.Addr().String(), fn: fn})

	// start serving
	return srv.Serve(t.listener)
}

Accept方法中,new一个service,这个srvgoogle.golang.org/grpc库中的实例化的一个Server.这个Server代表gRpc Server用来处理RPC请求。

启动这个gRpc Server的方法是Serve方法,该方法接收net.Listener类型的参数上进来的connection,创建一个ServerTransport实例和服务于该连接的服务线程。这个服务线程读取gRpc请求,并调用注册在它上的handler回复它,Serve方法将在net.Listener上的Accept方法发生致命错误的时候返回。net.Listener将在Serve方法返回后关闭。

Serve方法将返回一个非空的错误,除非调用了Stop方法或者调用了GracefulStop方法。

这就是为什么在err := ts.Accept(s.ServeConn)后的select中的default里面,会持续的判断err是否为空,如果不是,continue继续Accept.

Start方法还有一个线程,用来检测注册检查,以及在注册检查出现问题时,进行重新注册。直到Server退出。在检测到退出后,会执行Deregister方法,进行取消注册,并持续等到requests完成,之后关闭到transport监听,关闭borker上的连接,以上内容为Start方法的整个内容。

我们通过一个实例来完成的看一下真个服务过程

package main

import (
	"log"

	"github.com/asim/go-micro/examples/v3/server/handler"
	"github.com/asim/go-micro/examples/v3/server/subscriber"
	"github.com/asim/go-micro/v3/cmd"
	"github.com/asim/go-micro/v3/server"
)

func main() {
	// optionally setup command line usage
	cmd.Init()

	// 初始化 Server
	server.Init(
		server.Name("go.micro.srv.example"),
	)

	// 注册 Handlers
	server.Handle(
		server.NewHandler(
			new(handler.Example),
		),
	)

	// 注册 Subscribers
	if err := server.Subscribe(
		server.NewSubscriber(
			"topic.example",
			new(subscriber.Example),
		),
	); err != nil {
		log.Fatal(err)
	}

	if err := server.Subscribe(
		server.NewSubscriber(
			"topic.example",
			subscriber.Handler,
		),
	); err != nil {
		log.Fatal(err)
	}

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

关于如何定义Handler,其实不论你怎么定义,在进行注册的时候,都会帮我们进行转换

server.NewHandler(
			new(handler.Example),
		),

然后才是

func Handle(h Handler) error {
	return DefaultServer.Handle(h)
}

方法帮我们进行注册到Server上。

看看我们实现的Handler

package handler

import (
	"log"

	example "github.com/asim/go-micro/examples/v3/server/proto/example"
	"github.com/asim/go-micro/v3/metadata"
	"github.com/asim/go-micro/v3/server"

	"context"
)

type Example struct{}

func (e *Example) Call(ctx context.Context, req *example.Request, rsp *example.Response) error {
	md, _ := metadata.FromContext(ctx)
	log.Printf("Received Example.Call request with metadata: %v", md)
	rsp.Msg = server.DefaultOptions().Id + ": Hello " + req.Name
	return nil
}

func (e *Example) Stream(ctx context.Context, stream server.Stream) error {
	log.Print("Executing streaming handler")
	req := &example.StreamingRequest{}

	// We just want to receive 1 request and then process here
	if err := stream.Recv(req); err != nil {
		log.Printf("Error receiving streaming request: %v", err)
		return err
	}

	log.Printf("Received Example.Stream request with count: %d", req.Count)

	for i := 0; i < int(req.Count); i++ {
		log.Printf("Responding: %d", i)

		if err := stream.Send(&example.StreamingResponse{
			Count: int64(i),
		}); err != nil {
			return err
		}
	}

	return nil
}

func (e *Example) PingPong(ctx context.Context, stream server.Stream) error {
	for {
		req := &example.Ping{}
		if err := stream.Recv(req); err != nil {
			return err
		}
		log.Printf("Got ping %v", req.Stroke)
		if err := stream.Send(&example.Pong{Stroke: req.Stroke}); err != nil {
			return err
		}
	}
}

proto文件内容如下

syntax = "proto3";

package go.micro.srv.example;

service Example {
	rpc Call(Request) returns (Response) {}
	rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
	rpc PingPong(stream Ping) returns (stream Pong) {}
}

message Message {
	string say = 1;
}

message Request {
	string name = 1;
}

message Response {
	string msg = 1;
}

message StreamingRequest {
	int64 count = 1;
}

message StreamingResponse {
	int64 count = 1;
}

message Ping {
	int64 stroke = 1;
}

message Pong {
	int64 stroke = 1;
}