接下来的两篇文章,我们一起来学习go-micro
中的Server
和Client
,这篇文章重点介绍Server
.
go-micro
中的Server
也被抽离出一个接口类型,便于我们进行自定义,go-micro
中默认的Server
是rpc_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...)
}
启动的时候,也是启动的newRpcServer
的Start
方法
// 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()
}
所以我们就一起来看看这个默认的Server
该Server
的结构体如下
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,如果你想做些检查,可以在Server
的opts
属性中进行定义,格式为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 := ®istry.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, µTransport{addr: t.listener.Addr().String(), fn: fn})
// start serving
return srv.Serve(t.listener)
}
在Accept
方法中,new一个service
,这个srv
是google.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;
}