使用JSONRPC 2.0规范解决多语言RPC交互的方案

转载注明出处

动机

最近做的一个项目比较大,分了许多模块,但是由于不同的开源技术使用的语言不同,不同模块使用的语言可能不同,但基本上是使用Java和Python实现的。当各模块需要进行交互的时候,问题就出现了,模块不能像Jar包或者Python模块那样引入,Java有它的JVM,Python有它的解释器,单机调用只能是用native方案。但native明显与os有关,换个环境又不知道会有什么兼容问题出现。

具体问题还是要具体分析,考虑的因素有很多。在该实际项目中,关于效率需要考虑的因素有:

  1. 单输入文本的处理时间
  2. 网络传输时间
  3. 机器的配置

经过测试,单输入文本处理时间>>网络传输时间,而且没有很高配置的机器,如果用native方法,需要跑几个模块,机器可能负荷不了,所以考虑构建成分布式,以提高效率。

设计分布式系统需要考虑机器间如何交互。最简单的又通用的就是使用互联网协议,如http协议,由于它是个应用层协议,所以效率肯定不会高,但为了简单起见,而且在网络传输效率基本可以忽略不计的情况下,我们还是选用了这个应用层协议。

http协议只是作为通信协议,但是数据传输的格式还是要规范的,传统的有xml和一些序列化方案,近年流行更加轻量级和通用的json格式,它被广泛用于web数据传输。本文选用的就是json格式。

于是朝着这个方向去调研,找到了json-rpc 2.0规范。

JSON-RPC 2.0简介

JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. Primarily this specification defines several data structures and the rules around their processing. It is transport agnostic in that the concepts can be used within the same process, over sockets, over http, or in many various message passing environments. It uses JSON (RFC 4627) as data format.

JSON-RPC是一个无状态的、轻量级的远程过程调用(RPC)协议。本规范主要围绕它的处理方式定义了几个数据结构和规则。这个概念可用于在同一进程中、套接字或HTTP之间、或其他很多消息传递的环境中传输数据。它使用JSON (RFC 4627)作为数据格式。

好了,此处关键词:JSON、HTTP。
既然是规范,应当被很多人应用,而且有详尽文档,就不需要自己傻乎乎地去写详细的交互文档。

下面介绍一下规范中定义的对象必须有的成员:

jsonrpc
    A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
    
method
    A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used for anything else.
    
params
    A Structured value that holds the parameter values to be used during the invocation of the method. This member MAY be omitted.
    
id
    An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. The value SHOULD normally not be Null [1] and Numbers SHOULD NOT contain fractional parts [2] 
    The Server MUST reply with the same value in the Response object if included. This member is used to correlate the context between the two objects.

当发起rpc调用时,服务器必须回复一个响应,通知除外。响应被表示成一个单一的对象,包含下列的成员:
jsonrpc
指定JSON-RPC版本的字符串,它必须是“2.0”。
result
当调用成功时,该成员是必须的。
如果调用方法出现错误时,必须不包含该成员。
该成员的值由服务器上调用的方法决定。
error
当调用发生错误时,该成员是必须的。
在调用期间如果没有错误产生,必须不包含该成员。
该成员的值必须是一个5.1节定义的对象。
id
该成员是必须的。
它的值必须与请求对象中的id成员的值相同。
如果检查请求对象中的id时发生错误(如:转换错误或无效的请求),它必须为Null。
必须包含result或error成员,但是两个成员都必须不能同时包含。

Java Server

既然是使用http协议,那就需要一个web容器是装载。(不装载也可以,自己去实现一个http容器咯,或者去找开源的,其实也挺大的)

Java有一段时间没用了,以前用Java是做web开发,用经典的Spring框架做对象管理,SpringMVC管理整个web框架,数据层框架用Hibernate或者JPA等。听说最近有个框架很火,叫Spring Boot,它能够快速地构建web应用,而且配置纯Java化,通过一个函数即可启动,像Python的Flask那样的方便,实质它默认使用的底层容器还是Tomcat,简化了我们的操作而已。

用传统的web应用构建方式的成本跟学习Spring Boot框架的成本之间衡量了一下,选择了后者,因为前者再用也是没有什么收益,后者却能体验到新框架,而且现在的框架网站上的Quick Start都很容易实现。但是提高效率的方法不应是单个单个地学习,而是联系起来学习,所以我直接去github上找java jsonrpc的项目。于是定位到了一个叫jsonrpc4j的开源项目,Star299,肯定没找错了。

直接看它的Wiki,还真有Spring Boot的Quick Start。

这里省点力,直接贴:
Server
Configuration
To get the entire system working, you need to define the AutoJsonRpcServiceImplExporter bean in your @Configuration class:

package example.jsonrpc4j.springboot;

import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfig {

    @Bean
    public static AutoJsonRpcServiceImplExporter autoJsonRpcServiceImplExporter() {
        AutoJsonRpcServiceImplExporter exp = new AutoJsonRpcServiceImplExporter();
        //in here you can provide custom HTTP status code providers etc. eg:
        //exp.setHttpStatusCodeProvider();
        //exp.setErrorResolver();
        return exp;
    }
}

Service
Then create your service interface. My example is a simple calculator endpoint:

package example.jsonrpc4j.springboot.api;

import com.googlecode.jsonrpc4j.JsonRpcParam;
import com.googlecode.jsonrpc4j.JsonRpcService;

@JsonRpcService("/calculator")
public interface ExampleServerAPI {
    int multiplier(@JsonRpcParam(value = "a") int a, @JsonRpcParam(value = "b") int b);
}

And implement your interface like this:

package example.jsonrpc4j.springboot.api;

import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImpl;
import org.springframework.stereotype.Service;

@Service
@AutoJsonRpcServiceImpl
public class ExampleServerAPIImpl implements ExampleServerAPI {
    @Override
    public int multiplier(int a, int b) {
        return a * b;
    }
}

测试:

curl -H "Content-Type:application/json" -d '{"id":"1","jsonrpc":"2.0","method":"multiplier","params":{"a":5,"b":6}}' http://localhost:8080/calculator

Python Server

这里我使用Flask-jsonrpc模块,直接pip安装。
有点尿急,也直接贴吧:

Create your application and initialize the Flask-JSONRPC.

from flask import Flask
from flask_jsonrpc import JSONRPC

app = Flask(__name__)
jsonrpc = JSONRPC(app, '/api')

Write JSON-RPC methods.

@jsonrpc.method('App.index')
def index():
    return u'Welcome to Flask JSON-RPC'

All code of example run.py.

$ python run.py
 * Running on http://0.0.0.0:5000/

Test:

$ curl -i -X POST \
   -H "Content-Type: application/json; indent=4" \
   -d '{
    "jsonrpc": "2.0",
    "method": "App.index",
    "params": {},
    "id": "1"
}' http://localhost:5000/api
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 77
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Fri, 14 Dec 2012 19:26:56 GMT

{
  "jsonrpc": "2.0",
  "id": "1",
  "result": "Welcome to Flask JSON-RPC"
}

如果是Python调用它,可以

>>> from flask_jsonrpc.proxy import ServiceProxy
>>> server = ServiceProxy('http://localhost:5000/api')
>>>
>>> server.App.index()
{'jsonrpc': '2.0', 'id': '91bce374-462f-11e2-af55-f0bf97588c3b', 'result': 'Welcome to Flask JSON-RPC'}

Flask-jsonrpc有一个优点就是它有一个api管理页面:


Flask-jsonrpc API管理界面

交互方法

以上我们可以看到我们已经可以通过http协议,加上一些json字符串就可以实现调用了。

在实际项目中,我们只需要实现客户端使用语言的jsonrpc 2.0规范的http调用方法即可,如上一节中的Python使用ServiceProxy对象调用。

还有一定值得注意的是,交互的对象必须要能转为json格式,否则需要自己写转json字符串的方法。

总结

总感觉这是个笨笨的方法,是因为http协议笨重吗?
反正我觉得比native好,少侵入代码,又能形成分布式,而且有个好处就是,前端应用可以直接用js调用。
其实RPC调用就是一个将模块服务化的过程,一个模块能够向多个模块提供服务,例如可以想一些公共功能服务化,就不需要重复代码。
由于分布式知识有限,不能进行更多方法的对比,日后如果有所学习,一定会更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容