golang RPC通信读写超时设置

golang RPC通信中,有时候就怕读写hang住。

那是否可以设置读写超时呢?

1.方案一: 设置连接的读写超时

1.1 client

RPC通信基于底层网络通信,可以通过设置connection的读写超时时间,达到RPC读写超时的目的。更多细节可参考golang网络通信超时设置.

下面以client端的读超时为例,介绍设置方法。

server端和client端代码如下。
server
一个简单的json RPC server。

package main

import (
    "fmt"
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "time"
)

type Counter struct {
    Sum int
}

func (this *Counter) Add(i int, r *int) error {
    //sleep
    time.Sleep(3*time.Second)

    if i < 0 {
        return fmt.Errorf("data format incorrect")
    }

    this.Sum += i
    *r = this.Sum
    log.Printf("i: %v\n", i)
    return nil
}

func NewJsonRpcSocketServer() {

    rpc.Register(new(Counter))

    l, err := net.Listen("tcp", ":3333")
    if err != nil {
        log.Printf("Listener tcp err: %s", err)
        return
    }

    for {
        log.Println("waiting...")
        conn, err := l.Accept()
        if err != nil {
            log.Printf("accept connection err: %s\n", conn)
            continue
        }

        go jsonrpc.ServeConn(conn)
    }

}

func main() {
    NewJsonRpcSocketServer()
}

client

json RPC client。

连接建立后,会设置connection的读超时时间。

package main

import (
    "log"
    "net"
    "net/rpc/jsonrpc"
    "time"
)


func main() {

     NewJsonRpcSocketClient()
}

func NewJsonRpcSocketClient() {
    timeout := time.Second*30
    conn, err := net.DialTimeout("tcp", "127.0.0.1:3333", timeout)
    if err != nil {
        log.Printf("create client err:%s\n", err)
        return
    }
    defer conn.Close()

    // timeout
    readAndWriteTimeout := 2*time.Second
    err = conn.SetDeadline(time.Now().Add(readAndWriteTimeout))
    if err != nil {
        log.Println("SetDeadline failed:", err)
    }

    client := jsonrpc.NewClient(conn)

    var reply int

    err = client.Call("Counter.Add", 2, &reply)
    if err != nil {
        log.Println("error:", err, "reply:", reply)
        return
    }

    log.Printf("reply: %d, err: %v\n", reply, err)

}

client输出:

2019/05/12 22:52:57 error: read tcp 127.0.0.1:55226->127.0.0.1:3333: i/o timeout reply: 0

1.2 server

通常情况下,RPC server端的代码如下:

    server := rpc.NewServer()
    ... ....
	for {
		conn, err := l.Accept()
		if err != nil {
			log.Println("listener accept fail:", err)
			time.Sleep(time.Duration(100) * time.Millisecond)
			continue
		}
		
		// timeout
		timeout := 10*time.Second
		conn.SetDeadline(time.Now().Add(timeout))
		
		go server.ServeCodec(jsonrpc.NewServerCodec(conn))
	}

这样,如果设置超时,只有效的影响一次。

对于server来说,都是多次读写。所以,暂时没有方法设置。

2.方案二:定时器

通过定时器的方式,如果RPC调用在指定时间内没有完成,触发定时器,返回超时错误,关闭连接。

2.1 client端

func  RpcCall(method string, args interface{}, reply interface{}) error {
    ... ...

	timeout := time.Duration(10 * time.Second)
	done := make(chan error, 1)

	go func() {
		err := rpcClient.Call(method, args, reply)
		done <- err
	}()

	select {
	case <-time.After(timeout):
		log.Printf("[WARN] rpc call timeout %v => %v", rpcClient, RpcServer)
		rpcClient.Close()
        return fmt.Errorf("timeout")
	case err := <-done:
		if err != nil {
		    rpcClient.Close()
			return err
		}
	}

	return nil
}

2.2 server端

无论是gobServerCodec 或者 jsonrpc ,都是实现了ServerCodec接口。

gobServerCodec文件路径:/usr/local/go/src/net/rpc/server.go

jsonrpc文件路径:/usr/local/go/src/net/rpc/server.go

要给server端RPC读写加上超时机制,需要重新定义ServerCodec接口,加上超时的控制。

// A ServerCodec implements reading of RPC requests and writing of
// RPC responses for the server side of an RPC session.
// The server calls ReadRequestHeader and ReadRequestBody in pairs
// to read requests from the connection, and it calls WriteResponse to
// write a response back. The server calls Close when finished with the
// connection. ReadRequestBody may be called with a nil
// argument to force the body of the request to be read and discarded.
type ServerCodec interface {
	ReadRequestHeader(*Request) error
	ReadRequestBody(interface{}) error
	// WriteResponse must be safe for concurrent use by multiple goroutines.
	WriteResponse(*Response, interface{}) error

	Close() error
}

目前,已经有现成的实现方式,可参考Golang标准库RPC实践及改进

3.参考

Does RPC have a timeout mechanism?

net/rpc: expected Timeout based alternatives to functions for rpc.Dial, rpc.DialHTTP, rpc.DialHTTPPath [proposal]. #15236

### Golang RPC 教程概述 Go语言中的RPC(远程过程调用)机制允许程序像调用本地函数一样调用另一个地址空间内的子程序。这极大地简化了分布式系统的开发工作。 #### 示例实现 下面是一个使用`net/rpc/jsonrpc`包创建的服务端和客户端的例子,它展示了如何利用JSON-RPC协议执行简单的远程方法调用[^1]: 服务端代码如下所示: ```go package main import ( "log" "net" "net/rpc" "net/rpc/jsonrpc" ) // HelloService defines a simple hello world service. type HelloService struct{} func (s *HelloService) Hello(request string, reply *string) error { *reply = "Hello! " + request return nil } func main() { helloService := new(HelloService) rpc.Register(helloService) listener, err := net.Listen("tcp", ":1235") if err != nil { log.Fatalf("listen tcp :1235: %v", err) } defer listener.Close() for { conn, err := listener.Accept() if err != nil { log.Printf("accept error: %v", err) continue } go jsonrpc.ServeConn(conn) } } ``` 对应的客户端可以这样编写[^4]: ```go package main import ( "fmt" "log" "net/rpc" ) func main() { client, err := rpc.Dial("tcp", "localhost:1235") if err != nil { log.Fatal("dialing:", err) } var reply string err = client.Call("HelloService.Hello", "world!", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) } ``` 这段代码实现了两个功能:一是定义了一个名为`HelloService`的服务;二是提供了一种方式来启动监听指定端口上的TCP连接,并接受来自客户端的请求。当接收到请求时,会自动解析成相应的结构化数据并传递给注册的方法处理逻辑。 #### 工作原理 在上述例子中,每当有一个新的网络连接到来时,都会开启一个新的goroutine去处理这个连接上的所有消息直到关闭为止。而每一个这样的goroutine内部则运行着一个标准库提供的`jsonrpc.ServerCodec`实例负责序列化/反序列化通信双方交换的数据流。一旦完成一次完整的读取操作之后就会尝试匹配已知的服务接口名称和服务方法名组合(`ServiceMethod`)找到对应的实际处理器对象及其成员函数指针,最后再把解码后的输入参数传进去执行真正的业务逻辑[^3]. #### 调试技巧 调试过程中常见的问题是无法建立正确的连接或者是由于编码错误导致的消息格式不兼容等问题。为了更好地定位问题所在,在遇到困难的时候可以通过打印日志的方式查看具体的交互细节,比如每次收发的具体字节内容是什么样的形式等。另外也可以考虑借助一些第三方工具如Wireshark抓包分析实际传输的内容是否符合预期。 #### 常见问题解答 - **Q:** 如果我的服务需要支持并发访问怎么办? 可以为每个新来的连接分配独立的工作协程(goroutine),从而使得多个客户端能够同时发起请求而不互相干扰。 - **Q:** 如何提高性能? 减少不必要的锁竞争,优化内存管理策略,尽可能减少GC压力;还可以采用更高效的编解码器代替默认的选择以加快速度。 - **Q:** 支持哪些类型的返回值? `net/rpc`只支持导出那些满足特定条件的方法——即接收者必须是指针类型且第一个参数应为`context.Context`(可选),第二个才是用户自定义入参类型,第三个则是用于存储结果的对象引用。注意这里的出入参都应该是公开可见(struct field首字母大写)才能被正确识别出来参与序列化进程之中.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值