micro package 是整个go-micro最核心的模块, 除了引用别的包之外, 它由function
, micro
, options
, publisher
,service
, wrapper
这些文件组成。 而这些文件实现了可拔插式功能的微服务架构, 她主要对底层数据实现简单快速开始的封装。 可拔插式指的的可以对默认的实现进行剥离然后自行实现自己想要的, 比如前言里的etcd实际就是插件(只是其他人已经帮我们实现了)。
这个是官网的图。她提供了简单快速的开始, new ===> run。
![6b47488c661d47e7e287fe51ab91c577.png](https://i-blog.csdnimg.cn/blog_migrate/d2424151867e97466c214ce022f33abf.jpeg)
options.go
趁热先看下这个文件, 这个结构体对应的实例即是我们拥有一切配置参数的实例
type Options struct {
Broker broker.Broker//Broker: interface只要有那些功能即可
Cmd cmd.Cmd//同上
Client client.Client//..
Server server.Server//..
Registry registry.Registry//..
Transport transport.Transport//..
BeforeStart []func() error
BeforeStop []func() error
AfterStart []func() error
AfterStop []func() error
Context context.Context//这要是用来存数据的
}
func newOptions(opts ...Option) Options {
//新建实例
opt := Options{
Broker: broker.DefaultBroker,
Cmd: cmd.DefaultCmd,
Client: client.DefaultClient,
Server: server.DefaultServer,
Registry: registry.DefaultRegistry,
Transport: transport.DefaultTransport,
Context: context.Background(),
}
//根据传入的函数修改实例
for _, o := range opts {
o(&opt)
}
//返回实例
return opt
}
这个源码里面的其他内容都是返回的option函数的函数。 就是newOptions所需要传入的函数的返回值。
wrap
这里还有WrapHandler``WrapCall
,两个函数。 这两个函数比较特殊, 我们以其中一个为例子看下他是干什么。
// WrapCall is a convenience method for wrapping a Client CallFunc
func WrapCall(w ...client.CallWrapper) Option {
return func(o *Options) {
//Init是传入函数, 来修改她的实例Client。
o.Client.Init(client.WrapCall(w...))//Init(...Option) error
}
}
//这个是client包里面的
// Adds a Wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
return func(o *Options) {
//从这里面可以看到, 最终会将CallWrapper函数压成数组
o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
}
}
看到这个数组, 如果有开发经验, 你一定想到了中间件, 无论是gin, 还是koa等web框架, 都会维护一个数组来做链式调用.
在client/rpc_client.go 文件的Call函数里面我们能找见这个
for i := len(callOpts.CallWrappers); i > 0; i-- {
//rcall是真正的调用函数, 但是在调用之前, 我们需要先把wrapper函数跑一遍
rcall = callOpts.CallWrappers[i-1](rcall)
}
如果熟悉go-kit, 这个东西跟endpoint middleware的实现的方式是一模一样的. 我们可以在里面写一些通用的操作, 比如, 当前服务跑了多少秒, 限流, 熔断什么的
// 记录开始时间
// 调用正真的处理函数
// 处理函数返回后, 打印花费的时间
micro.go
上面图片显示, 启动micro永远都是先NewService
, 而这个将会返回一个Service
实例。
//只要实现这么多方法的都叫Service实例。 也就是NewService返回的实例会实现这么多方法。
type Service interface {
Init(...Option)
Options() Options
Client() client.Client
Server() server.Server
Run() error
String() string
}
service.go
NewService的方法在这里面定义着
func newService(opts ...Option) Service {
//newOptions传入opts就知道这个用来修改options的默认参数的
options := newOptions(opts...)
options.Client = &clientWrapper{
options.Client,
metadata.Metadata{//是个map[string]string类型
HeaderPrefix + "From-Service": options.Server.Options().Name,
},
}
return &service{
opts: options,
}
}
接下来看下Run函数
func (s *service) Run() error {
if err := s.Start(); err != nil {
return err
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
select {
// wait on kill signal
case <-ch:
// wait on context cancel
case <-s.opts.Context.Done():
}
return s.Stop()
}
func (s *service) Start() error {
for _, fn := range s.opts.BeforeStart {
if err := fn(); err != nil {
return err
}
}
if err := s.opts.Server.Start(); err != nil {
return err
}
for _, fn := range s.opts.AfterStart {
if err := fn(); err != nil {
return err
}
}
return nil
}
Run函数主要是开启服务, 而这个服务就是service里面Server服务, 对于Grpc来说,他就是grpc服务, 对http来说就是http服务. 但他们都需要在Server的Start函数里面实现. 然后就停住代码, 等待异常退出
wrapper.go
这个文件夹的内容比较简单, 就是将客户端进行一个封装, 将参数变量放在context.Value里面
type clientWrapper struct {
client.Client
headers metadata.Metadata
}
func (c *clientWrapper) setHeaders(ctx context.Context) context.Context {
// copy metadata
mda, _ := metadata.FromContext(ctx)
md := metadata.Copy(mda)
// set headers
for k, v := range c.headers {
if _, ok := md[k]; !ok {
md[k] = v
}
}
//返回context.Value()的Context
return metadata.NewContext(ctx, md)
}
如果用过GRPC我们都会知道, 除了在pb文件定义请求参数跟返回参数之外, 一般情况下我们还需要定义metadata, metadata里面的数据一般会在整个请求周期都能看见, 比如token什么的都会放在那. 那么如果想使用GRPC将这些东西(headers)发送给其他服务, 那么一定会有一个将metadata(上面自定义的metadata)转化为GRPC(google rpc里面的metadata), 他是在客户端调用的时候需要的, 所以肯定会在client包里面
在client/grpc/grpc.go中, call函数可以看见如下代码
func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
...
if md, ok := metadata.FromContext(ctx); ok {
for k, v := range md {
header[k] = v
}
}
// set timeout in nanoseconds
header["timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// set the content type for the request
header["x-content-type"] = req.ContentType()
md := gmetadata.New(header)
ctx = gmetadata.NewOutgoingContext(ctx, md)
...
}
metadata是自定义的metadata包 , gmetadata是grpc的metadata包. 看到NewOutgoingContext
用过grpc应该可以看懂.