Golang标准库RPC实践及改进

2023-10-28

转载自:http://daizuozhuo.github.io/golang-rpc-practice/
一直用Golang标准库里的的RPC package来进行远程调用,简单好用. 但是随着任务数量的增大, 发现简单的像包里面的示例那样的代码出现了各种各样的问题,下面就把我踩过的一些坑记录一下吧. 首先是最初使用的文档里的版本,使用HTTP来发送请求.

server.go

func ListenRPC() {
    rpc.Register(NewWorker())
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", ":4200")
    if e != nil {
        log.Fatal("Error: listen 4200 error:", e)
    }
    go http.Serve(l, nil)
}

client.go

func call(srv string, rpcname string, args interface{}, reply interface{}) error {
    c, errx := rpc.DialHTTP("tcp", srv+":4200")
    if errx != nil {
        return fmt.Errorf("ConnectError: %s", errx.Error())
    }
    defer c.Close()
    return c.Call(rpcname, args, reply)
}

这样四五台机器的情况是够用了, 但是后来集群的机器增加到了十二台, 当请求大了之后发现总有很多任务卡住,通过call函数发送任务之后总会有没有返回的情况. 于是转而直接用tcp,效率有很大提升.

server.go

func ListenRPC() {
    rpc.Register(NewWorker())
    l, e := net.Listen("tcp", ":4200")
    if e != nil {
        log.Fatal("Error: listen 4200 error:", e)
    }
    go func() {
        for {
            conn, err := l.Accept()
            if err != nil {
                log.Print("Error: accept rpc connection", err.Error())
                continue
            }
            go rpc.ServeConn(conn)
        }
    }()
}

client.go

func call(srv string, rpcname string, args interface{}, reply interface{}) error {
    c, errx := rpc.Dial("tcp", srv+":4200")
    if errx != nil {
        return fmt.Errorf("ConnectError: %s", errx.Error())
    }
    defer c.Close()
    return c.Call(rpcname, args, reply)
}

这样局面有所改观,但是还是有任务卡住,概率大概是0.01%, 也就是一万个call里会有一个没有响应. 仔细研究后发现这个rpc package有两大坑:

rpc包里的rpc.Dial函数没有timeout, 系统默认是没有timeout的,所以在这里可能卡住.所以我们可以采用net包里的 net.DialTimeout函数.

rpc包里默认使用gobCodec来编码解码, 这里io可能会卡住而不返回错误,所以我们要自己编写加入timeout的codec. 注意server这边读写都有timeout,但是client这边只有写有timeout,因为读的话并不能预知任务完成的时间. 于是就有了接下来这个版本的rpc,几十万个任务下来没有任何问题.

完整的代码可以在在github rpc-example上下载.

server.go

func TimeoutCoder(f func(interface{}) error, e interface{}, msg string) error {
    echan := make(chan error, 1)
    go func() { echan <- f(e) }()
    select {
    case e := <-echan:
        return e
    case <-time.After(time.Minute):
        return fmt.Errorf("Timeout %s", msg)
    }
}

type gobServerCodec struct {
    rwc    io.ReadWriteCloser
    dec    *gob.Decoder
    enc    *gob.Encoder
    encBuf *bufio.Writer
    closed bool
}

func (c *gobServerCodec) ReadRequestHeader(r *rpc.Request) error {
    return TimeoutCoder(c.dec.Decode, r, "server read request header")
}

func (c *gobServerCodec) ReadRequestBody(body interface{}) error {
    return TimeoutCoder(c.dec.Decode, body, "server read request body")
}

func (c *gobServerCodec) WriteResponse(r *rpc.Response, body interface{}) (err error) {
    if err = TimeoutCoder(c.enc.Encode, r, "server write response"); err != nil {
        if c.encBuf.Flush() == nil {
            log.Println("rpc: gob error encoding response:", err)
            c.Close()
        }
        return
    }
    if err = TimeoutCoder(c.enc.Encode, body, "server write response body"); err != nil {
        if c.encBuf.Flush() == nil {
            log.Println("rpc: gob error encoding body:", err)
            c.Close()
        }
        return
    }
    return c.encBuf.Flush()
}

func (c *gobServerCodec) Close() error {
    if c.closed {
        // Only call c.rwc.Close once; otherwise the semantics are undefined.
        return nil
    }
    c.closed = true
    return c.rwc.Close()
}

func ListenRPC() {
    rpc.Register(NewWorker())
    l, e := net.Listen("tcp", ":4200")
    if e != nil {
        log.Fatal("Error: listen 4200 error:", e)
    }
    go func() {
        for {
            conn, err := l.Accept()
            if err != nil {
                log.Print("Error: accept rpc connection", err.Error())
                continue
            }
            go func(conn net.Conn) {
                buf := bufio.NewWriter(conn)
                srv := &gobServerCodec{
                    rwc:    conn,
                    dec:    gob.NewDecoder(conn),
                    enc:    gob.NewEncoder(buf),
                    encBuf: buf,
                }
                err = rpc.ServeRequest(srv)
                if err != nil {
                    log.Print("Error: server rpc request", err.Error())
                }
                srv.Close()
            }(conn)
        }
    }()
}

client.go

type gobClientCodec struct {
    rwc    io.ReadWriteCloser
    dec    *gob.Decoder
    enc    *gob.Encoder
    encBuf *bufio.Writer
}

func (c *gobClientCodec) WriteRequest(r *rpc.Request, body interface{}) (err error) {
    if err = TimeoutCoder(c.enc.Encode, r, "client write request"); err != nil {
        return
    }
    if err = TimeoutCoder(c.enc.Encode, body, "client write request body"); err != nil {
        return
    }
    return c.encBuf.Flush()
}

func (c *gobClientCodec) ReadResponseHeader(r *rpc.Response) error {
    return c.dec.Decode(r)
}

func (c *gobClientCodec) ReadResponseBody(body interface{}) error {
    return c.dec.Decode(body)
}

func (c *gobClientCodec) Close() error {
    return c.rwc.Close()
}

func call(srv string, rpcname string, args interface{}, reply interface{}) error {
    conn, err := net.DialTimeout("tcp", srv+":4200", time.Second*10)
    if err != nil {
        return fmt.Errorf("ConnectError: %s", err.Error())
    }
    encBuf := bufio.NewWriter(conn)
    codec := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
    c := rpc.NewClientWithCodec(codec)
    err = c.Call(rpcname, args, reply)
    errc := c.Close()
    if err != nil && errc != nil {
        return fmt.Errorf("%s %s", err, errc)
    }
    if err != nil {
        return err
    } else {
        return errc
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang标准库RPC实践及改进 的相关文章

  • Go 无法推断赋值中的类型:“non-name on left side of :=”

    该片段按预期工作play golang org p VuCl OKMav http play golang org p VuCl OKMav i 10 next 11 prev i i next 然而这个几乎相同的片段给出了non name
  • 错误“binary.Write:无效类型”是什么意思?

    下面显示的代码 我创建了一个结构类型并希望将其编码为二进制 但它显示binary Write invalid type main Stu错误 我读过一些类似的代码 但我找不到为什么我的代码不起作用 type Stu struct Name
  • 为什么 Go 中只有 int 而没有 float?

    在 Go 中 有这样的类型int这可能相当于int32 or int64取决于系统架构 我可以声明一个整数变量而不用担心它的大小 var x int 为什么没有这个类型float 这相当于float32 or float64取决于我的系统架
  • 我应该避免在 golang 中使用单例包吗?

    现在我有一个包裹store包含以下内容 package store var db Database func Open url string error open db connection func FindAll model inter
  • 如何在 Ubuntu 中将 Go 程序作为守护进程启动?

    在 Ubuntu 中将 Go 程序作为守护进程启动的正确方法是什么 然后我将使用 Monit 对其进行监控 我应该做这样的事情 go run myapp go 我应该考虑 Go 特有的事情吗 您应该为您的程序构建一个可执行文件 go bui
  • GORM中的一对多递归关系

    我需要有一个Organization与父级有关系 像这样的事情 type Organization struct gorm Model Parent Organization gorm ForeignKey ParentId Name st
  • bazel go_embed_data“无法嵌入”

    我将以下 bazel BUILD 配置为 gazelle name gazelle go embed data name static files srcs glob static package main var staticFS go
  • 为什么 Go 中的函数不能命名为“init”?

    所以 今天在编码时我发现创建一个名为init产生错误method init not found 但是当我将其重命名为startup一切都很好 init 这个词是为 Go 中的某些内部操作而保留的 还是我在这里遗漏了一些东西 是的 该功能in
  • 在 Go 中调用外部命令

    如何在GO中调用外部命令 我需要调用外部程序并等待它完成执行 在执行下一条语句之前 您需要使用执行包 http golang org pkg os exec 使用启动命令Command http golang org pkg os exec
  • Go 算术中处理浮点数精度?

    我对 Go 中精确减去 2 个浮点数的方法感兴趣 我尝试过使用math big图书馆 但我无法得到准确的结果 我用过big js https github com MikeMcl big jsJavascript 库解决了这个问题 Go 算
  • 非本地包中的本地导入

    我知道应该避免本地进口 但在这种情况下有特殊情况需要 这是一个私人仓库 heroku buildpack 在go get 由于服务器上缺少私钥 与绝对 URL 一起使用时会出现此阶段 现在我得到这个错误local import in non
  • 如何退出执行延迟调用的 go 程序?

    我需要使用defer释放手动创建的分配C图书馆 但我还需要os Exit在某个时刻具有非 0 状态 棘手的部分是os Exit跳过任何延迟指令 package main import fmt import os func main defe
  • Go中通过反射给struct成员赋值

    我有一个结构 v 其中包含成员 A B C 字符串 使用反射 我可以获得字段的名称及其值 typ v Type for i 0 i lt v NumField i gets us a StructField fi typ Field i f
  • 如何从 gorm 中的模型获取表名?

    是否可以获得模型的表名 我发现可以从模型结构但我不知道如何正确地做到这一点 我没有找到该结构的任何初始化 user User tableName db 对于 Gorm v2 根据https github com go gorm gorm i
  • Go gin框架CORS

    我正在使用 Go gin 框架gin https github com gin gonic gin func CORSMiddleware gin HandlerFunc return func c gin Context c Writer
  • Go 中的 Map 与 Switch 性能对比

    考虑这个基准 我们比较地图访问与切换 var code int32 0 10 100 100 0 10 0 10 100 14 1000 100 1000 0 0 10 100 1000 10 0 1000 12 var mapCode m
  • 如何在 GAE Standard Go 中缩小到 0 个实例

    我已将 Golang 应用程序部署到 Google Cloud Platform 的 App Engine 标准环境 我的 app yaml 包含以下几行 automatic scaling min idle instances 0 max
  • 在 docker 中为 docker golang api 的容器设置端口

    我期待使用 docker golang api 做下面这样的事情 cmd docker run t i p 8989 8080 image name bin bash 我也在使用golang sdkhttps github com moby
  • 指向切片和数组的指针

    我正在查看 Go 的堆包 https golang org pkg container heap https golang org pkg container heap 优先队列示例并遇到了这个 type PriorityQueue Ite
  • 如何实现链表

    我正在尝试在 Go 中实现排序链表 我很难想出一种通用的方法来使链表适用于任何可以与其自身进行比较的类型 由于它是一个排序列表 我希望 go 编译器 确保可以比较插入到链接列表中的值 例如 import linkedlist type Pe

随机推荐

  • 代码黑科技

    快读 include
  • @Valid 使用

    Valid 使用 作用 Valid注解用于校验 所属的包 javax validation Valid 而在Springboot启动器的web包已经含有该包 所以无需添加多余的依赖 你可以定义实体 在实体的属性上添加校验规则 在API接收数
  • maven命令

    目录 常用命令 命令集锦 命令介绍 常用命令 常用命令及其作用 命令 描述 mvn clean 对项目进行清理 删除target目录下编译的内容 mvn compile 编译项目源代码 mvn test 对项目进行运行测试 mvn pack
  • React hooks 对比class优势

    1 hook可以直接从组件中提取状态逻辑 无需修改组件结构复用 不用形成render props 高阶组件嵌套地狱 2 将组件关联部分设置为更小函数 订阅发布或请求数据 可根据reducer管理组件内部状态 不用像class一样根据生命周期
  • 浅谈卷积神经网络及matlab实现

    1卷积神经网络的优点 卷积神经网络进行图像分类是深度学习关于图像处理的一个应用 卷积神经网络的优点是能够直接与图像像素进行卷积 从图像像素中提取图像特征 这种处理方式更加接近人类大脑视觉系统的处理方式 另外 卷积神经网络的权值共享属性和po
  • Android译文

    http blog csdn net vanpersie 9987 article list 4 http blog csdn net qinxiandiqi article category 2394347 Android Gradle
  • c语言中的头文件string.h的作用,C语言常用头文件及库函数——string.h

    string h 函数与形参类型 函数功能 返回值 例子 1 memcmp 函数与形参类型 int memcmp buf1 buf2 count void buf1 void buf2 int count 函数功能 按字典顺序比较由buf1
  • 十个值得一试的开源深度学习框架

    十个值得一试的开源深度学习框架 oschina 发布于 2015年11月16日 28评 分享到 收藏 359 12月12日北京OSC源创会 开源技术的年终盛典 本周早些时候Google开源了TensorFlow GitHub 此举在深度学习
  • 图像数字识别、数字分割(OCR识别,毕业设计)

    基本图像处理流程 这是我在测试图像处理中使用的原始图像 它有一些眩光点 但是图像相当干净 让我们逐步完成获取此源图像的过程 并尝试将其分解为单个数字 影像准备 在开始图像处理流程之前 我们决定先调整一些图像属性 然后再继续 这有点试验和错误
  • 架构、框架、设计模式三者的区别

    架构 框架 设计模式 对于搞IT的人来说 再熟悉不过了 那么它们三者有什么联系 又有什么区别呢 架构 架构是几个动词 是一个方法论 包含一系列活动过程和步骤 可以联想一下架构师 大数据架构师 软件架构师 首席架构师等等 他们的工作职责 就是
  • 小程序腾讯位置服务路线规划插件申请方法

    如果用以下常规的方法个人小程序大概率是通过不了的 在腾讯微信公众平台中 微信小程序官方后台 设置 第三方服务 插件管理 里点击 添加插件 搜索 腾讯位置服务路线规划 申请 申请后小程序开发者可在小程序内使用该插件 可以试试以下方法 点击右上
  • 橘子学ES18之聚合分析

    本文来说一个ES中极其重要的一个概念 就是聚合 聚合功能是一个十分方便的功能 一 ES的聚合分析 1 什么是聚合 Aggregation 1 ES除了文本搜索之外 提供了针对ES数据进行统计分析的功能 实时性高 Hadoop有时候是T 1的
  • TRC20地址监听php,USDT-TRC20 PHP开发包

    1 开发包概述 开发包适用于为PHP应用快速增加对Tron USDT TRC20数字资产的支持能力 即支持使用自有Tron区块链节点的应用场景 也支持基于Tron官方公共API服务的 轻量级部署场景 支持Tron区块链原生Trx交易 支持T
  • C++面向对象

    文章目录 一 内存四区 一些小问题 二 再谈引用 三 再谈函数 四 面向对象 封装 五 面向对象 继承 六 面向对象 多态 七 文件输入输出 八 泛型与模板 九 STL容器 string vector deque stack queue l
  • 【华为OD机试 】 在字符串中找出连续最长的数字串(含“+-”号)(C++ Java JavaScript Python)

    华为od机试题库 华为OD机试2022 2023 C Java JS Py https blog csdn net banxia frontend category 12225173 html 华为OD机试2023最新题库 更新中 C Ja
  • alter system与alter database的区别

    alter system与alter database的区别alter database 改变数据库的属性 是物理的改变 可以看得见的alter system 改变实例的属性 是逻辑性的改变 看不见的 alter database moun
  • 微信小程序 --自定义堆叠式Swiper

    原生小程序写堆叠式swiper 首先看下最终的效果 三张卡片堆叠式swiper 居中的为展示 左右两边为前一个和后一个 如果是第一长 或者最后一张 对应的前后无阴影堆叠 实现思路 一共渲染出4个卡片 然后根据显示位置设置zIndex sca
  • c语言 查看磁盘信息,获取磁盘列表以及磁盘信息的一些WIN32 API

    1 获取所有的驱动器 利用函数 GetLogicalDriveStrings The GetLogicalDriveStrings function fills a buffer with strings that specify vali
  • postfixadmin连mysql出现乱码问题解决

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 最近搭建一个邮件服务器 使用的是postfix maildrop mysql sasl extmail postfixadmin这么软件 主要参考 http sery bl
  • Golang标准库RPC实践及改进

    转载自 http daizuozhuo github io golang rpc practice 一直用Golang标准库里的的RPC package来进行远程调用 简单好用 但是随着任务数量的增大 发现简单的像包里面的示例那样的代码出现