rpc 一般俗称,远程过程调用,把本地的函数,放到远端去调用。
通常我们调用一个方法,譬如: sumadd(10, 20),sumadd方法的具体实现要么是用户自己定义,要么存在于该语言的库函数中,也就说在sumadd方法的代码实现在本地,它是一个本地调用!
“远程调用”意思就是:被调用方法的具体实现不在程序运行本地,而是在别的某个地方(分布到各个服务器),但是用起来像是在本地。
rpc远程调用原理 :
比如 A调用B提供的remoteAdd方法:
首先A与B之间建立一个TCP连接;
然后A把需要调用的方法名(这里是remoteAdd)以及方法参数(10, 20)序列化成字节流发送出去;
B接受A发送过来的字节流,然后反序列化得到目标方法名,方法参数,接着执行相应的方法调用(可能是localAdd)并把结果30返回;
A接受远程调用结果,然后do()。
RPC框架也就是把上线说的具体的细节封装起来,给用户好用的API使用(提示:有些远程调用选择比较底层的socket协议,有些远程调用选择比较上层的HTTP协议);
一般rpc配合http协议的多点,也就是走http的多。 当然还是看应用,我曾经一共的rpc框架是基于zeromq的zerorpc。速度是挺快,server和client都有python的gevent支持,速度没道理慢。(有兴趣的,可以看看有关zerorpc的文章 http://rfyiamcool.blog.51cto.com/1030776/1254000 )最少要比python本身的xml-rpc要快。 rpc over http(基于http的rpc)有两种协议,一种是xml-rpc ,还有一个是 json-rpc。
XML-RPC:XML Remote Procedure Call,即XML远程方法调用,利用http+xml封装进行RPC调用。基于http协议传输、XML作为信息编码格式。一个xml-rpc消息就是一个请求体为xml的http-post请求,服务端执行后也以xml格式编码返回。这个标准面前已经演变为下面的SOAP协议。可以理解SOAP是XML-RPC的高级版本。
注释下,原文地址,blog.xiaorui.cc
JSON-RPC:JSON Remote Procedure Call,即JSON远程方法调用 。类似于XML-RPC,不同之处是使用JSON作为信息交换格式
下面是一个例子,很简单。我们是用python的rpc库SimpleXMLRPCServer 做的测试,创建rpc server,然后注册一些函数,供应别的客户端去调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
from
SimpleXMLRPCServer
import
SimpleXMLRPCServer
原文:
xiaorui
.
cc
def
add
(
x
,
y
)
:
return
x
+
y
def
subtract
(
x
,
y
)
:
return
x
-
y
def
multiply
(
x
,
y
)
:
return
x
*
y
def
divide
(
x
,
y
)
:
return
x
/
y
server
=
SimpleXMLRPCServer
(
(
"localhost"
,
8000
)
)
print
"Listening on port 8000..."
server
.
register_multicall_functions
(
)
server
.
register_function
(
add
,
'add'
)
server
.
register_function
(
subtract
,
'subtract'
)
server
.
register_function
(
multiply
,
'multiply'
)
server
.
register_function
(
divide
,
'divide'
)
server
.
serve_forever
(
)
|
这个是连接的Client,python下的xmlrpclib 本身就提供了server和client类,当然咱们也可以自己写,看源码就知道他只是把func,args做了xml格式化而已。
1
2
3
4
5
6
7
8
9
10
11
12
|
import
xmlrpclib
proxy
=
xmlrpclib
.
ServerProxy
(
"http://localhost:8000/"
)
multicall
=
xmlrpclib
.
MultiCall
(
proxy
)
multicall
.
add
(
7
,
3
)
multicall
.
subtract
(
7
,
3
)
multicall
.
multiply
(
7
,
3
)
multicall
.
divide
(
7
,
3
)
result
=
multicall
(
)
print
"7+3=%d, 7-3=%d, 7*3=%d, 7/3=%d"
%
tuple
(
result
)
|
RPC本来是单任务的,如果任务相对频繁,可以设置成多线程的默认,你不用在调用threading模块什么的,直接引用 。
|
class
AsyncXMLRPCServer
(
SocketServer
.
ThreadingMixIn
,
SimpleXMLRPCServer
)
:
pass
|
然后rpc初始化的方法换成。
|
server
=
AsyncXMLRPCServer
(
(
''
,
1111
)
,
SimpleXMLRPCRequestHandler
)
|
这里再说下,和xmlrpc相似的jsonrpc,貌似现在用xmlrpc的,要比jsonrpc的多点。 有时候到国外的it论坛看帖子,xmlrpc用的交多点。其实现在较大的公司,一般干脆直接自己实现了rpc框架,像淘宝Dubbo(朋友有搞过,搞了半天,没有对接成接口,说是有难度,不明觉厉!),百度的xxx(忘名字了)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import
jsonrpc
server
=
jsonrpc
.
Server
(
jsonrpc
.
JsonRpc20
(
)
,
jsonrpc
.
TransportTcpIp
(
addr
=
(
"127.0.0.1"
,
31415
)
,
logfunc
=
jsonrpc
.
log_file
(
"myrpc.log"
)
)
)
#原文:xiaorui.cc
# 注册一个函数方法
def
echo
(
s
)
:
return
s
def
search
(
number
=
None
,
last_name
=
None
,
first_name
=
None
)
:
sql_where
=
[
]
sql_vars
=
[
]
if
number
is
not
None
:
sql_where
.
append
(
"number=%s"
)
sql_vars
.
append
(
number
)
if
last_name
is
not
None
:
sql_where
.
append
(
"last_name=%s"
)
sql_vars
.
append
(
last_name
)
if
first_name
is
not
None
:
sql_where
.
append
(
"first_name=%s"
)
sql_vars
.
append
(
first_name
)
sql_query
=
"SELECT id, last_name, first_name, number FROM mytable"
if
sql_where
:
sql_query
+=
" WHERE"
+
" AND "
.
join
(
sql_where
)
cursor
=
.
.
.
cursor
.
execute
(
sql_query
,
*
sql_vars
)
return
cursor
.
fetchall
(
)
server
.
register_function
(
echo
)
server
.
register_function
(
search
)
# start server
server
.
serve
(
)
|
|
# 创建jsonrpc客户端
import
jsonrpc
server
=
jsonrpc
.
ServerProxy
(
jsonrpc
.
JsonRpc20
(
)
,
jsonrpc
.
TransportTcpIp
(
addr
=
(
"127.0.0.1"
,
31415
)
)
)
#调用远端的一个函数
result
=
server
.
echo
(
"hello world"
)
found
=
server
.
search
(
last_name
=
'Python'
)
|
我做过一些个压力的测试,XMLRPCSERVER的开了async之后,每个连接特意堵塞5秒,他的并发在40个左右 。也就是每秒成功40个左右,剩下的还是在堵塞等待中。 其实他的瓶颈不是在于rpc的本身,是承载rpc的那个basehttpserver,太弱爆了。
|
注释下,原文地址,
<
a
href
=
"http://blog.xiaorui.cc/"
target
=
"_blank"
>
blog
.
xiaorui
.
cc
<
/
a
>
<
span
style
=
"line-height:1.5;font-family:微软雅黑, 'Microsoft YaHei';font-size:20px;"
>
<
/
span
>
|
接收请求,调用方法 !
现在开源社区这么发达,有不少人都根据rpc的协议,重写了承载rpc的web服务。 比如用flask,tornado,配合uwsgi,你猜咋招了。。。。如果不堵塞连接,那还可以,如果堵塞连接,uwsgi的废材特色就显出来了,以前有文章说过,uwsgi是prework,他会预先启动进程,官方都推荐要根据你的cpu核数或者超线程来开启进程,如果开的太多,你会发现,uwsgi他是驾驭不了那么多进程的。还是看我大tornado,用了@gen.engine之后。轻易飙到500的并发连接。
(以上是我的吃饱又蛋疼测试,没听过谁会重复调用那么多的堵塞方法,自评 sx行为)
不多说了,看flask实现xmlrpc服务端的代码,看了下flask xmlrpc的源码,实现的不难。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from
flask
import
Flask
from
flaskext
.
xmlrpc
import
XMLRPCHandler
,
Fault
app
=
Flask
(
__name__
)
handler
=
XMLRPCHandler
(
'api'
)
handler
.
connect
(
app
,
'/api'
)
@
handler
.
register
def
woca
(
name
=
"world"
)
:
if
not
name
:
raise
Fault
(
"fuck...fuck"
,
"fuck shencan!"
)
return
"Hello, %s!"
%
name
原文:
xiaorui
.
cc
app
.
run
(
)
|
对于每个连接的超时,有多种的方法,如果你用的是flask,tornado做web server,那就写个装饰器single起来,只是性能不好。 或者是前面挂一个nginx,然后做个client_header_timeout,client_body_timeout,proxy_connect_timeout(你懂的。),如果用的python自带的xml-rpc的话,需要引入socket。
|
import
socket
socket
.
setdefaulttimeout
(
)
|
再说下rpc安全的问题。
至于安全方面,有兴趣就开个ssl,或者是在程序里面判断下client ip,反正配置都是统一下发的,你重载daemon的时候,也就知道该判断什么ip了。
我个人对于rpc的应用,更加的倾向于基本资源的获取和调用,毕竟单纯的用socket或者是mq,你在程序里面还要做一个解析过来的数据,然后根据过来的数据在做调用。 (alert: 我想触发 add() ,如果是rpc的话,我不用管,只是传过去就行了,到那时mq和socket就需要eval调用函数了),一些复杂的应用还是喜欢用面向资源的rest,也推荐大家用这个,靠谱的。