go jsonrpc

在实际项目中,我们经常会碰到服务之间交互的情况,如何方便的与远端服务进行交互,就是一个需要我们考虑的问题。

通常,我们可以采用restful的编程方式,各个服务提供相应的web接口,相互之间通过http方式进行调用。或者采用rpc方式,约定json格式进行数据交互。

在我们的项目中,服务端对用户客户端提供的是restful的接口方式,而在服务器内部,我们则采用rpc方式进行服务之间的交互。

go语言本来就提供了jsonrpc的支持,所以自然开始我们就直接使用jsonrpc。jsonrpc的使用非常简单,对于调用端来说,就如同一个函数调用,如下:

args := &Args{7, 8}
reply := new(Reply)
err := client.Call("Arith.Add", args, reply)

上面是go jsonrpc自带的一个例子,可以看到,虽然我们通过call(rpcName, inParams, outParams)这样的形式可以很方便的进行rpc的调用,但是跟go实际的函数调用还是稍微有一点区别,对我来说,这么使用总觉得很别扭。

我觉得方便的rpc使用方式

对于go jsonrpc来说,它的调用格式是这样的

err := call(name, in, out)

但是对我来说,我希望采用这样的调用方式:

out, err := RpcFunc(in)

假设server端有如下的一个RPC函数,注册的rpc name为testrpc。

func Test(id int) (int, error)

对于client端来说,我希望的使用方式是这样:

var rpcTest func(id int) (int, error)
MakeRpc("testrpc", &rpcTest) id, err := rpcTest(10)

在client端,我们首先声明了跟server Test类型完全一致的一个函数变量。然后通过MakeRpc接口将其命名为testrpc,并且将rpcTest绑定到实际的rpc函数上面,最后rpcTest就跟普通函数的调用一样。

可以看到,这种使用方式跟jsonrpc最大的不同在于rpc的返回值直接就是函数自身的返回值。而这个可能更符合我使用go函数的习惯。

实现

实现一套rpc框架需要考虑server,client以及包协议的问题。

包协议

我使用了最简单的包头 + 实际数据的做法,包头使用一个4字节的int表示后续数据的长度。而对于实际的rpc数据,我采用的是gob进行打包解包。

为什么选用gob而不是json?主要在于我不想自己做数据类型的转换,在json中,int类型的encode,decode会变成float类型的,如果函数需要的参数是int,json decode之后还需要我们自己根据参数实际的类型进行转换。增加了复杂度。而gob则在encode时候会加上实际的数据类型,这样decode之后我就能直接使用。

而且gob还支持注册自定义的类型,但是为了简单,建议只支持基本的数据类型,因为对于rpc来说,传递复杂的数据类型进行函数调用,我总觉得有点复杂,这在设计上面已经有问题了。

server

在server需要解决的问题就是rpc函数注册并通过名字能进行该rpc函数调用。而这个通过reflect就能非常方便的实现,一个通过函数名字进行函数调用的例子:

func Test(id int) (string, error) {
return "abc", nil
} funcmap = map[string]reflect.Value{} v := reflect.ValueOf(Test) funcmap["test_rpc"] = v args := []reflect.Value{reflect.ValueOf(10)} funcmap["test_rpc"](args)

client

在client层,我们需要关注在声明一个rpc原型的函数变量之后,如何将其替换成另一个函数进行rpc调用。我们可以通过reflect的MakeFunc函数方便的做到,go自身的例子:

swap := func(in []reflect.Value) []reflect.Value {
return []reflect.Value{in[1], in[0]}
} makeSwap := func(fptr interface{}) {
fn := reflect.ValueOf(fptr).Elem()
v := reflect.MakeFunc(fn.Type(), swap)
fn.Set(v)
} var intSwap func(int, int) (int, int)
makeSwap(&intSwap)
fmt.Println(intSwap(0, 1))

MakeFunc的原理在于,根据传入的函数变量的类型,创建一个新的函数,该函数调用的是我们指定的另一个函数。

同时,我们得到传入变量的指针,并用新的函数重新给该变量赋值。

error处理

因为rpc调用可能会出现其他错误,譬如网络断线,gob encode错误等,client在调用的时候必须得处理这些错误,暴力的作法就是如果是这种内部错误,我们直接panic,但是我觉得太不友好,所以我们约定,所有的rpc函数在最后一个返回值必须是error。这样就是是rpc内部的错误,我们也能够通过error返回。

在注册rpc的时候,我们可以通过判断最后一个返回值是否是interface,同时是否具有Error函数来强制要求必须为error。如下

v := reflect.ValueOf(rpcFunc)

nOut := v.Type().NumOut()

if nOut == 0 || v.Type().Out(nOut-1).Kind() != reflect.Interface {
err = fmt.Errorf("%s return final output param must be error interface", name)
return
} _, b := v.Type().Out(nOut - 1).MethodByName("Error")
if !b {
err = fmt.Errorf("%s return final output param must be error interface", name)
return
}

但是,如果在MakeFunc里面直接返回error,会出现“reflect: function created by MakeFunc using closure returned wrong type: have *errors.errorString for error”这样的问题,主要在于reflect.Value需要知道我们error的接口类型,参考这里

所以,我们通过如下方式对error进行处理,转成相应的reflect.Value

v := reflect.ValueOf(&e).Elem()

nil处理

在实际rpc中,我们可能还会面临参数为nil的问题,如果直接对nil进行reflect.ValueOf,是得不到我们期望的类型的,这时候的Kind是0,reflect压根不能将其正确的转换成函数实际的类型。

当碰到nil的情况,我们只需要根据当前函数参数实际的类型,生成一个Zero Value,就可以很方便的解决这个问题:

假设函数第一个返回值为nil,那么我们这样

v := reflect.Zero(fn.Type().Out(0))

代码

最开始写了一个代码片段验证自己的想法,在这里

一个rpc frame的完整实现

使用go reflect实现一套简易的rpc框架的更多相关文章

  1. 一个最最简易的RPC框架雏形---转载自梁飞的博客

    查阅RPC与HTTP区别的时候, 无意间发现一篇博客,内容是一个简易的RPC服务框架, 仔细一看, 不得了,博主竟然就是阿里dubbo的作者. 原文链接在此: http://javatar.iteye ...

  2. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

  3. 程序员修神之路--设计一套RPC框架并非易事

    菜菜哥,我最近终于把Socket通信调通了 这么底层的东西你现在都会了,恭喜你离涨薪又进一步呀 http协议不也是利用的Socket吗 可以这么说,http协议是基于TCP协议的,底层的数据传输可以说 ...

  4. RPC框架简易实现

           RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些 ...

  5. 自行实现一个简易RPC框架

    10分钟写一个RPC框架 1.RpcFramework package com.alibaba.study.rpc.framework; import java.io.ObjectInputStrea ...

  6. 简易RPC框架-学习使用

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  7. 简易RPC框架-心跳与重连机制

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  8. 简易RPC框架-客户端限流配置

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  9. 简易RPC框架-SPI

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

随机推荐

  1. Go 语言基础语法

    Go 标记 Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号.如以下 GO 语句由 6 个标记组成: fmt.Println("Hello, World!") ...

  2. Docker如何获取镜像

    可以使用 docker pull 命令来从仓库获取所需要的镜像. 下面的例子将从 Docker Hub 仓库下载一个 Ubuntu 12.04 操作系统的镜像. $ sudo docker pull ...

  3. Django Views(视图函数)

    http请求中产生两个核心对象: http请求:HttpRequest对象 http响应:HttpResponse对象 所在位置:django.http 之前我们用到的参数request就是HttpR ...

  4. Linux(十七)动态监控进程

    17.1 介绍 top与ps命令很相似.它们都用来显示正在执行的进程.top与ps最大的不同之处,在于top在执行一段时间可以更新正在运行的进程 17.2 语法 top    [选项] 常用选项   ...

  5. 使用kprobes查看内核内部信息

    前言:使用printk打印变量等方法,是调试内核的有效方法之一,但是这种方法必须重新构建并用新内核启动,调试效率比较低.以内核模块的方式使用kprobes.jprobes,就可以在任意地址插入侦测器, ...

  6. 前端CSS技术全解(一)

    一.概述 1)用HTML完成样式工作 哪个标签有哪个属性难以记忆 需求变更影响较大(例如像修改成功法则以下的文字颜色需要修改4个地方) <h1 align="center"& ...

  7. 浅析深度学习mini_batch的BP反传算法

    在深度学习中,如果我们已经定义了网络,输入,以及输出,那么接下来就是损失函数,优化策略,以及一般由框架完成的BP反传.这篇博文我们主要探讨一下深度的BP反传算法(以梯度下降为例),尤其是mini_ba ...

  8. ELK平台的搭建

    ELK是指Elasticsearch + Logstash + Kibaba三个组件的组合.本文讲解一个基于日志文件的ELK平台的搭建过程,有关ELK的原理以及更多其他信息,会在接下来的文章中继续研究 ...

  9. CoreAnimation动画结构变量

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...

  10. Protobuf-net判断字段是否有值

    Protobuf-net判断字段是否有值Unity3d使用Protobuf-net序列化数据与服务器通信,但是发现默认情况下,Protobuf-net生成的cs文件中没有接口判断可选参数是否有值.需有 ...