golang context的具体使用场景举例-rpc并发调用

rpc调用是在实际开发场景中经常用到的,假设某个用户请求的处理需要3个RPC并行请求,设置一个整体任务的超时时间,如果到达超时时间后,不管RPC有没有执行完毕任务都返回,实现逻辑见下面代码:

package main

import (
	"context"
	"fmt"
	"strconv"
	"sync"
	"time"
)

//rpc调用
func Rpc(ctx context.Context, url string) string {
	result := make(chan string)
	//开启一个新的协程去执行rpc调用逻辑
	go func() {
		//...balabala
		if url == "http://rpc_2_url" { //假设rpc-2服务比较耗时 就假设要2s吧
			time.Sleep(2*time.Second)
		}
		rpcResult := url+"xxxx" //假设调用rpc得到的数据就是这个样子
		result<-rpcResult
	}()
	//当前协程监听通道事件(整体任务的超时时间到/当前rpc调用完成了)
	select {
	case <- ctx.Done(): //context的超时事件
		// 如果规定的时间到了,没有得到结果也立即返回
		return ""
	case r:=<- result: //本RPC调用成功,返回结果
		return r
	}
}


func main() {
	//设置整个任务超时时间为1s
	ctx, _ := context.WithTimeout(context.Background(),1*time.Second)
	wg := sync.WaitGroup{}
	var lastRes string

	rcpUrl:=[]string{
		"http://rpc_1_url",
		"http://rpc_2_url",
		"http://rpc_3_url",
	}
	wg.Add(len(rcpUrl))
	for i,url := range rcpUrl {
		//并发执行rpc调用
		go func(i int,url string) {
			defer wg.Done()
			res := Rpc(ctx, url)
			if res != ""  {
				lastRes+=res //简单拼接得到最后结果
				fmt.Println("任务index:"+strconv.Itoa(i)+" success end,res="+res)
			} else {
				fmt.Println("超时时间到了,任务:"+strconv.Itoa(i)+" 没有调用完成直接返回了")
			}
		}(i,url)
	}
	wg.Wait()
	fmt.Println("main end,lastRes="+lastRes)
}

同时,context是可以`传递`的,以上面的代码为例,假设传递到Rpc函数的ctx=context.WithTimeout(context.Background(),3*time.Second),即超时时间是3s,那么可能存在这种情况:在某个rpc实例的调用中超时时间要求更严格,比如如果超过1s就需要返回,那么在具体的rpc协程中,可以在基于ctx新建一个,即:ctx2=context.WithTimeout(ctx,1*time.Second) 然后在这个rpc协程中监听ctx2.Done(), 这个事件就会在1s的时间触发返回,稍作修改:

package main

import (
	"context"
	"fmt"
	"strconv"
	"sync"
	"time"
)

//rpc调用
func Rpc(ctx context.Context, url string) string {
	result := make(chan string)
	//开启一个新的协程去执行rpc调用逻辑
	go func() {
		//...balabala
		if url == "http://rpc_2_url" { //假设rpc-2服务比较耗时 就假设要2s吧
			time.Sleep(2*time.Second)
		}
		rpcResult := url+"xxxx" //假设调用rpc得到的数据就是这个样子
		result<-rpcResult
	}()
	//当前协程监听通道事件(整体任务的超时时间到/当前rpc调用完成了)
	select {
	case <- ctx.Done(): //context的超时事件
		// 如果规定的时间到了,没有得到结果也立即返回
		return ""
	case r:=<- result: //本RPC调用成功,返回结果
		return r
	}
}


func main() {
	//设置整个任务超时时间为1s
	ctx, _ := context.WithTimeout(context.Background(),3*time.Second)
	wg := sync.WaitGroup{}
	var lastRes string

	rcpUrl:=[]string{
		"http://rpc_1_url",
		"http://rpc_2_url",
		"http://rpc_3_url",
	}
	wg.Add(len(rcpUrl))
	for i,url := range rcpUrl {
		//并发执行rpc调用
		go func(i int,url string) {
			defer wg.Done()
			if url == "http://rpc_2_url" { //此rpc调用超时时间需要设置更短
				ctx,_=context.WithTimeout(ctx,1*time.Second)
			}
			res := Rpc(ctx, url)
			if res != ""  {
				lastRes+=res //简单拼接得到最后结果
				fmt.Println("任务index:"+strconv.Itoa(i)+" success end,res="+res)
			} else {
				fmt.Println("超时时间到了,任务:"+strconv.Itoa(i)+" 没有调用完成直接返回了")
			}
		}(i,url)
	}
	wg.Wait()
	fmt.Println("main end,lastRes="+lastRes)
}

同时 当父 Context 被取消时,其派生的所有 Context 都将取消。

<div class="post-text" itemprop="text"> <p>I try to write a kv-raft server code in Go. (refer this <a href="http://otm.github.io/2015/05/raft-a-first-implementation/" rel="nofollow">article</a>).</p> <p>However I found there is problem if I want using <code>context.Context</code> in my RPC call parameter</p> <pre><code>// Init of my RPC server kv := new(KVRaft) rpcs := rpc.NewServer() rpcs.Register(kv) .... //My RPC call function func (kv *KVRaft) Msg(args *context.Context, reply *MsgReply) error { log.Println("[MSG]", *args) return nil } </code></pre> <p>Code could be compiled but when I trying to run </p> <pre><code>c, _ := rpc.Dial("tcp", ":1234") var reply MsgReply{} c.Call("KVRaft.Msg", someContext, &reply) </code></pre> <p>It will pop-up error as <code>gob: type not registered for interface: context.emptyCtx</code> </p> <p>This code works well if I replace <code>*context.Context</code> to <code>*string</code>. I googled it could use <code>gob.Register()</code>, but I got compile failed on <code>gob.Register(context.Context{})</code> </p> <pre><code>invalid type for composite literal: "golang.org/x/net/context".Context </code></pre> <p>Any idea how could I pass <code>context.Context</code> as gob RPC call parameter?</p> <p>[update]</p> <p>I try to use <code>gob.Register(context.Background())</code> but it will get empty context when receive RPC call.</p> <p>[update] Currently my <a href="https://github.com/kkdai/rs" rel="nofollow">repo</a> here, but still ongoing.</p> </div>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页