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模塊什么的,直接引用 。
1
2
|
class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass
|
然后rpc初始化的方法換成。
1
2
|
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()
|
1
2
3
4
5
6
7
8
9
|
# 創建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,太弱爆了。
1
2
|
注釋下,原文地址,
<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。
1
2
3
|
import socket
socket.setdefaulttimeout()
|
再說下rpc安全的問題。
至於安全方面,有興趣就開個ssl,或者是在程序里面判斷下client ip,反正配置都是統一下發的,你重載daemon的時候,也就知道該判斷什么ip了。
我個人對於rpc的應用,更加的傾向於基本資源的獲取和調用,畢竟單純的用socket或者是mq,你在程序里面還要做一個解析過來的數據,然后根據過來的數據在做調用。 (alert: 我想觸發 add() ,如果是rpc的話,我不用管,只是傳過去就行了,到那時mq和socket就需要eval調用函數了),一些復雜的應用還是喜歡用面向資源的rest,也推薦大家用這個,靠譜的。