RPC是否有超时机制?

huangapple go评论97阅读模式
英文:

Does RPC have a timeout mechanism?

问题

如果RPC没有超时机制,那么如果它试图调用一个已关闭的服务器的RPC方法,我该如何“终止”RPC调用?

英文:

If RPC does not have a timeout mechanism, how do I "kill" an RPC call if it is trying to call an RPC method of a server that is closed?

答案1

得分: 10

你可以使用channels来实现超时模式:

import "time"

c := make(chan error, 1)
go func() { c <- client.Call("Service", args, &result) }()
select {
  case err := <-c:
    // 使用 err 和 result
  case <-time.After(timeoutNanoseconds):
    // 调用超时
}

select语句将会阻塞,直到client.Call返回或者超过了timeoutNanoseconds指定的时间。

英文:

You can use channels to implement a timeout pattern:

import &quot;time&quot;

c := make(chan error, 1)
go func() { c &lt;- client.Call(&quot;Service&quot;, args, &amp;result) } ()
select {
  case err := &lt;-c:
    // use err and result
  case &lt;-time.After(timeoutNanoseconds):
    // call timed out
}

The select will block until either client.Call returns or timeoutNanoseconds elapsed.

答案2

得分: 4

如果你想实现超时功能(以防止调用时间过长),那么你需要将rpc.Dial更改为net.DialTimeout(注意它们是不同的包:rpc vs net)。还要注意返回的类型不再是客户端(如前面的示例中所示),而是一个“连接”。

conn, err := net.DialTimeout("tcp", "localhost:8080", time.Minute)
if err != nil {
  log.Fatal("dialing:", err)
}

client := rpc.NewClient(conn)
英文:

if you want to implement a timeout (to prevent a call from taking too long), then you'll want to change rpc.Dial for net.DialTimeout (notice they're separate packages: rpc vs net). Also be aware that the returned type isn't a client any more (as it is in the previous example); instead it is a 'connection'.

  conn, err := net.DialTimeout(&quot;tcp&quot;, &quot;localhost:8080&quot;, time.Minute)
  if err != nil {
    log.Fatal(&quot;dialing:&quot;, err)
  }

  client := rpc.NewClient(conn)

答案3

得分: 1

似乎 net/rpc 的唯一解决方案是在发现请求卡住时关闭底层连接。然后客户端应该使用“连接中断”错误完成挂起的请求。

另一种方法是使用 https://github.com/valyala/gorpc,它支持开箱即用的超时 RPC 调用。

英文:

It seems the only solution for net/rpc is to close the underlying connection when you notice stuck requests. Then the client should finish pending requests with "connection broken" errors.

An alternative way is to use https://github.com/valyala/gorpc , which supports timeout RPC calls out of the box.

答案4

得分: 1

func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error

调用方法可能会永远阻塞goroutine

改用Go方法:

func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call

客户端示例:

call := rpcClient.Go(method, args, reply, make(chan *rpc.Call, 1))
	select {
	case <-time.After(timeout):
		log.Printf("[WARN] rpc调用超时(%v) %v => %v", timeout, rpcClient, s.RpcServer)
		rpcClient.Close()
		return errors.New("timeout")
	case resp := <-call.Done:
		if resp != nil && resp.Error != nil {
			rpcClient.Close()
			return resp.Error
		}
英文:
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error

Call method may block goroutine forever

Change use Go method:

func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call

Client example:

call := rpcClient.Go(method, args, reply, make(chan *rpc.Call, 1))
	select {
	case &lt;-time.After(timeout):
		log.Printf(&quot;[WARN] rpc call timeout(%v) %v =&gt; %v&quot;, timeout, rpcClient, s.RpcServer)
		rpcClient.Close()
		return errors.New(&quot;timeout&quot;)
	case resp := &lt;-call.Done:
		if resp != nil &amp;&amp; resp.Error != nil {
			rpcClient.Close()
			return resp.Error
		}

答案5

得分: 0

或者,现在有人可能更喜欢使用上下文。这也可以处理在超时时返回适当的错误(context.DeadlineExceeded)。

import (
	"context"
    "log"
	"net/rpc"
)

type Client struct {
	*rpc.Client
}

// CallEx是对rpc的Client.Call()的上下文感知包装
func (c *client) CallEx(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
	ec := make(chan error, 1)
	go func() {
		ec <- c.Call(serviceMethod, args, reply)
	}()
	select {
	case err := <-ec:
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}

使用带有截止时间的上下文调用它:

type Args struct {
   A, B int
}

func main(){
    rpc, err := rpc.DialHTTP("tcp", "host")
	if err != nil {
		t.Fatal(err)
	}
    c := client{rpc}

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
    
    var i int
    if err := c.CallEx(ctx, "Calc.Multiply", Args{2, 2}, &i); err != nil {
	    log.Fatal(err)
    }
}
英文:

Or, anno now, someone might prefer to use context instead. This also takes care of returning a proper error when timed out. (context.DeadlineExceeded)

import (
	&quot;context&quot;
    &quot;log&quot;
	&quot;net/rpc&quot;
)

type Client struct {
	*rpc.Client
}

// CallEx is a context aware wrapper around rpc&#39;s Client.Call()
func (c *client) CallEx(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
	ec := make(chan error, 1)
	go func() {
		ec &lt;- c.Call(serviceMethod, args, reply)
	}()
	select {
	case err := &lt;-ec:
		return err
	case &lt;-ctx.Done():
		return ctx.Err()
	}
}

Invoke this with a Deadlined context:

type Args struct {
   A, B int
}

func main(){
    rpc, err := rpc.DialHTTP(&quot;tcp&quot;, &quot;host&quot;)
	if err != nil {
		t.Fatal(err)
	}
    c := client{rpc}

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
    
    var i int
    if err := c.CallEx(ctx, &quot;Calc.Multiply&quot;, Args{2, 2}, &amp;i); err != nil {
	    log.Fatal(err)
    }
}

huangapple
  • 本文由 发表于 2014年4月28日 06:50:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/23330024.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定