Micro (4)

go-micro请求处理过程

上文分析了我们自己创建的service(handler)是如何注册的,那么go-micro又如何知道一个服务查询请求应该怎么处理呢?
上文提到我们添加的service存放在server.rpcServer.handlersmap(类型map[string]server.Handler)中。下面看一下相关的数据结构。

// server.Handler
// server.handler.go
type Handler interface {
    Name() string
    Handler() interface{}
    Endpoints() []*registry.Endpoint
    Options() HandlerOptions
}

// server.rpcHandler, implements server.Handler interface
// server.rpc_handler.go
type rpcHandler struct {
    name      string
    handler   interface{}
    endpoints []*registry.Endpoint
    opts      HandlerOptions
}

接上文go-micro service启动流程,启动后go-micro不断接受用户的远程调用请求,然后调用server.server#ServeRequest来处理请求,接下来流程如下图所示。

go-micro response for request

注明: 水平方向表示内部调用,垂直方向表示顺序调用。
其中需要说明的是service, mtype, ... := server.server#readRequest()调用,该方法调用中从请求头中解析出请求的服务service以及请求的方法method(var mtype, reflect类型)。
然后调用mtype.method.Func()完成远程方法原型的调用。
那么问题又来了,远程调用客户端如何知道自己需要的服务在那台机器上呢?或者说客户端是如何实现远程过程调用的呢?
回顾一下hello_world client的远程调用过程:

type helloWorldClient struct {                                                                                                                                                             
    c           client.Client // micro.client.Client interface, implemented by micro.client.rpcClient                                                                                                                                                           
    serviceName string                                                                                                                                                                       
}

service := micro.NewService(
    micro.Name("hello_world"),
    micro.Version("latest"),
    micro.Metadata(map[string]string{
      "type": "helloworld",
    }),
  )
  service.Init()
  greeter := hello_world.NewHelloWorldClient("hello_world", service.Client())

  rsp, err := greeter.Hello(context.TODO(), &hello_world.HelloWorldRequest{Name: "Alice"})
  if err != nil {
    fmt.Println(err)
    return
  }

与server端不一样的地方就是,初始化后我们会获取一个服务的client对象(类型为helloWorldClient, 该结构体中有一个c micro.client.Client,用来执行真正的方法调用)。

greeter := hello_world.NewHelloWorldClient("hello_world", service.Client())

紧接着我们会使用该client对象greeter进行远程过程调用:

rsp, err := greeter.Hello(context.TODO(), &hello_world.HelloWorldRequest{Name: "Alice"})

该方法具体实现如下:

func (c *helloWorldClient) Hello(ctx context.Context, in *HelloWorldRequest, opts ...client.CallOption) (*HelloWorldResponse, error) {
  req := c.c.NewRequest(c.serviceName, "HelloWorld.Hello", in)
  out := new(HelloWorldResponse)
  err := c.c.Call(ctx, req, out, opts...)
  if err != nil {
    return nil, err
  }
  return out, nil
}

在该方法中,我们会创建一个micro.client.Request(类型micro.client.rpcRequest,实现interface micro.client.Request接口)对象,并且传入service_name, 远程方法名"HelloWorld.Hello"作为参数,然后通过micro.client.rpcClient#call()执行远程方法调用。
该方法具体实现如下:

// micro/go-micro/client/rpc_client.go
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
    // make a copy of call opts
    callOpts := r.opts.CallOptions
    for _, opt := range opts {
        opt(&callOpts)
    }

    // get next nodes from the selector
    next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
    if err != nil && err == selector.ErrNotFound {
        return errors.NotFound("go.micro.client", err.Error())
    } else if err != nil {
        return errors.InternalServerError("go.micro.client", err.Error())
    }

    // check if we already have a deadline
    d, ok := ctx.Deadline()
    if !ok {
        // no deadline so we create a new one
        ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
    } else {
        // got a deadline so no need to setup context
        // but we need to set the timeout we pass along
        opt := WithRequestTimeout(d.Sub(time.Now()))
        opt(&callOpts)
    }

    // should we noop right here?
    select {
    case <-ctx.Done():
        return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
    default:
    }

    // make copy of call method
    rcall := r.call

    // wrap the call in reverse
    for i := len(callOpts.CallWrappers); i > 0; i-- {
        rcall = callOpts.CallWrappers[i-1](rcall)
    }

    // return errors.New("go.micro.client", "request timeout", 408)
    call := func(i int) error {
        // call backoff first. Someone may want an initial start delay
        t, err := callOpts.Backoff(ctx, request, i)
        if err != nil {
            return errors.InternalServerError("go.micro.client", err.Error())
        }

        // only sleep if greater than 0
        if t.Seconds() > 0 {
            time.Sleep(t)
        }

        // select next node
        node, err := next()
        if err != nil && err == selector.ErrNotFound {
            return errors.NotFound("go.micro.client", err.Error())
        } else if err != nil {
            return errors.InternalServerError("go.micro.client", err.Error())
        }

        // set the address
        address := node.Address
        if node.Port > 0 {
            address = fmt.Sprintf("%s:%d", address, node.Port)
        }

        // make the call
        err = rcall(ctx, address, request, response, callOpts)
        r.opts.Selector.Mark(request.Service(), node, err)
        return err
    }

    ch := make(chan error, callOpts.Retries)
    var gerr error

    for i := 0; i < callOpts.Retries; i++ {
        go func() {
            ch <- call(i)
        }()

        select {
        case <-ctx.Done():
            return errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408)
        case err := <-ch:
            // if the call succeeded lets bail early
            if err == nil {
                return nil
            }

            retry, rerr := callOpts.Retry(ctx, request, i, err)
            if rerr != nil {
                return rerr
            }

            if !retry {
                return err
            }

            gerr = err
        }
    }

    return gerr
}

可知该方法的第一步即是向service registry进行服务查询:

r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)

然后执行到目标service所在address的远程过程调用。
至此,分析结束。
题外话: Dubbo和go-micro的服务注册的区别。

Dubbo服务注册和发现

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
    参考文章

go-micro服务注册和发现

  1. 启动service registry服务(etcd, consul)
  2. 服务提供者向service registry进行服务注册
  3. 服务消费者向service registry进行服务查询
  4. service registry返回服务消费者查询服务地址
  5. 服务消费者想服务所在地址发起远程过程调用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,724评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,104评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,142评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,086评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,076评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,914评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,220评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,871评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,318评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,834评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,951评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,574评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,162评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,162评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,383评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,349评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,652评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 1 为什么需要服务发现 简单来说,服务化的核心就是将传统的一站式应用根据业务拆分成一个一个的服务,而微服务在这个基...
    谦小易阅读 25,076评论 4 93
  • 0 准备 安装注册中心:Zookeeper、Dubbox自带的dubbo-registry-simple;安装Du...
    七寸知架构阅读 13,970评论 0 88
  • 沉默啊沉默 原谅我不开口 把眼神当成缄默的感受 看到的你只是朦朦胧胧 索性闭上了眼 让难过临时的停留 嘴角勾笑 泪...
    Sunror阅读 171评论 0 1
  • 英文Slash,从字面上翻译过来,就是斜杠的意思。2007年,这个概念由《纽约时报》的专栏作家Marci Albo...
    Zoe妹子阅读 4,784评论 0 0