小白入門微服務(1) - RPC 初體驗,python、nodejs互調
概述
- 前言
- 什麼是 RPC
- RPC 原理
- 常用 RPC 框架對比
- thrift 基礎
- python、nodejs 互調
- 後記
前言
上一篇文章中,我們初步瞭解了什麼是微服務,那麼我們這次來體驗一下微服務中是怎麼通訊的。如果我的文章對你有幫助,歡迎關注、點贊、轉發,這樣我會更有動力做原創分享。
什麼是 RPC
Remote Procedure Call,即為 -- 遠端過程呼叫。通俗地解釋一下:你有 A、B 兩臺電腦,A 電腦用 python 實現了一個加法運算,此時此刻 B 電腦有一個用 Java 實現的程式,想呼叫 A 電腦的加法運算程式。然而,記憶體空間不在同一臺電腦,且程式語言也不相同,如何呼叫呢?此時此刻就用網路來表達呼叫的語義與呼叫引數。當然,現在我們不用自己去實現這些東西,當下有很多成熟的 RPC 框架供我們選擇。
RPC 原理
什麼都別說,先上圖。
RPC原理
在往下看之前,我們先來了解一下:stub
stub 規定了 server 能夠提供什麼服務,這在 server 和 client 上是一致的。
RPC 呼叫鏈文字描述:
(1)client 以本地呼叫方式呼叫服務;
(2)client stub 接收到呼叫後負責將方法、引數等組裝成能夠進行網路傳輸的訊息體;
(3)client stub 找到服務地址,並將訊息傳送到服務端;
(4)server stub 收到訊息後進行解碼;
(5)server stub 根據解碼結果呼叫本地的服務;
(6)server 執行方法並將結果返回給 server stub;
(7)server stub 將返回結果打包成訊息併發送至 client;
(8)client stub 接收到訊息,並進行解碼;
(9)client 得到最終結果。
RPC 呼叫鏈:
(1)client 發起請求:rpc call --> send --> network
(2)server 接受請求:network --> receive --> local call
(3)server 返回結果:local return --> send --> network
(4)client 接收結果:network --> receive --> rpc return
以上就是 RPC 的原理,需要說明的是,它是同步呼叫的。
常用 RPC 框架對比
RPC 種類 | dubbon | rpcx | grpc | thrift |
---|---|---|---|---|
開發語言 | Java | go | 跨語言 | 跨語言 |
服務治理 | √ | √ | × | × |
多序列化框架支援 | √ | √ | × | × |
多種註冊中心 | √ | √ | × | × |
管理中心 | √ | √ | × | × |
跨語言 | × | × | √ | √ |
一下文字為引用(https://colobu.com/2016/09/05/benchmarks-of-popular-rpc-frameworks/)文章的描述:
Dubbo 是阿里巴巴公司開源的一個 Java 高效能優秀的服務框架,使得應用可通過高效能的 RPC 實現服務的輸出和輸入功能,可以和 Spring框架無縫整合。不過,略有遺憾的是,據說在淘寶內部,dubbo 由於跟淘寶另一個類似的框架 HSF(非開源)有競爭關係,導致 dubbo 團隊已經解散(參見http://www.oschina.net/news/55059/druid-1-0-9 中的評論),反到是噹噹網的擴充套件版本仍在持續發展,牆內開花牆外香。其它的一些知名電商如噹噹、京東、國美維護了自己的分支或者在 dubbo 的基礎開發,但是官方的庫缺乏維護,相關的依賴類比如 Spring,Netty 還是很老的版本(Spring 3.2.16.RELEASE, netty 3.2.5.Final),倒是有些網友寫了升級 Spring 和 Netty 的外掛。
rpcx 是Go語言生態圈的 Dubbo, 比 Dubbo 更輕量,實現了 Dubbo 的許多特性,藉助於Go語言優秀的併發特性和簡潔語法,可以使用較少的程式碼實現分散式的 RPC 服務。
gRPC 是 Google 開發的高效能、通用的開源 RPC 框架,其由 Google 主要面向移動應用開發並基於 HTTP/2 協議標準而設計,基於 ProtoBuf(Protocol Buffers) 序列化協議開發,且支援眾多開發語言。本身它不是分散式的,所以要實現上面的框架的功能需要進一步的開發。
thrift 是 Apache 的一個跨語言的高效能的服務框架,基於 thrift 進行序列化。也得到了廣泛的應用。
其中比較受關注的是:grpc 與 thrift 。
grpc 支援的語言:C++,C#,Dart,Go,Java,Node.js,Objective-C,PHP,Python,Ruby,
thrift 支援的語言:C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi 等
thrift 基礎
實現兩門語言的相互呼叫,這裡選用 thrift 框架,接下來會簡單介紹一下 thrift 的用法,並編碼實現一個 python 與 nodejs 互相呼叫的程式。下面簡單介紹下 thrift 語法。
基本資料型別:
bool: 布林型別(true / false)
byte: 8位帶符號整數
i16: 16位帶符號整數
i32: 32位帶符號整數
i64: 64位帶符號整數
double: 64位浮點數
string: 採用UTF-8編碼的字串
map<t1,t2> 鍵值對
list<t1> 列表
set<t1> 集合
結構:
struct User {
1: i32 uid,
2: string name,
3: string age,
4: string sex
}
service,對外擴充套件的介面:
service UserStorage {
void addUser(1: User user),
User getUser(1: i32 uid)
}
最後,使用 thrift 命令生成相應的介面檔案:
thrift -out ../python --gen py test.thrift
thrift -out 儲存路徑 --gen 介面語言 thrift 檔名
python、nodejs 互調
OK,語法差不多都熟悉了,那麼我們來實踐一下:
專案結構圖
其中綠色框框為我們自己新建的程式碼,紅色框框為 thrift 生成的程式碼,我們呼叫就行。
我們先來看互相呼叫的結果:
先看看 python 為服務端,nodejs 為客戶端的呼叫情況:
python 服務端
nodejs 客戶端
在看看,nodejs 為服務端,python 為客戶端的情況:
nodejs 服務端
python 客戶端
createThrift.sh
#!/bin/bash
cd thrift
thrift -out ../nodejs --gen js:node test.thrift
thrift -out ../python --gen py test.thrift
test.thrift
生成紅色框框的 thrift 介面程式碼檔案。
struct Student{
1: string name,
2: string age
}
service UserService{
void addStu(1: Student stu),
Student getStu(1: string name)
}
python server
from python.test import UserService
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
stus = {}
class TestHandler:
def addStu(self, stu):
print("我是 python 伺服器,我的 addStu() 方法被呼叫了.")
stus[stu.name] = stu
# print("add new student : " + stu.name)
def getStu(self, name):
print("我是 python 伺服器,我的 getStu() 方法被呼叫了.")
print("get student : " + name)
return stus[name]
# 建立服務端
handler = TestHandler()
processor = UserService.Processor(handler)
# 監聽埠
transport = TSocket.TServerSocket("127.0.0.1", 3000)
# 選擇傳輸層
tfactory = TTransport.TBufferedTransportFactory()
# 選擇傳輸協議
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
# 建立服務端
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print("Starting thrift server in python...")
server.serve()
python client
from python.test import UserService
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
__HOST = '127.0.0.1'
__PORT = 3000
tsocket = TSocket.TSocket(__HOST, __PORT)
transport = TTransport.TBufferedTransport(tsocket)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
# 穿件客戶端
client = UserService.Client(protocol)
# thrift 生成的的 Student 結構
stu = UserService.Student("zone", "18")
transport.open()
# 呼叫服務端 addStu() 方法
print("我是 python 客戶端,我呼叫了 addStu() 方法.")
client.addStu(stu)
# 呼叫服務端 getStu() 方法
print("我是 python 客戶端,我呼叫了 getStu() 方法.")
print("返回的結果為:" + client.getStu("zone"))
transport.close()
nodejs server
var thrift = require("thrift");
var UserService = require('../UserService.js');
var ttypes = require('../test_types');
var stus = {}
var server = thrift.createServer(UserService,
{
addStu: function (stu, callback) {
console.log("我是 nodejs 伺服器,我的 addStu() 方法被呼叫了.");
stus[stu.name] = stu
console.log(stu);
callback();
},
getStu: function (name, callback) {
console.log("我是 nodejs 伺服器,我的 getStu() 方法被呼叫了.");
callback(null, stus[name])
}
}
);
// 啟動服務
server.listen(3000);
console.log("nodejs server start");
server.on("error", function (e) {
console.log(e);
});
nodejs client
var thrift = require('thrift');
var UserService = require('../UserService.js');
var ttypes = require('../test_types');
var connection = thrift.createConnection('127.0.0.1', 3000);
var client = thrift.createClient(UserService, connection);
connection.on("error", function (e) {
console.log(e);
});
var stu = new ttypes.Student({name: "zone-nodejs", age: "23"});
// 呼叫服務端 addStu() 方法
client.addStu(stu, function (err, res) {
if (err) {
console.log(err);
return
}
console.log("我是 nodejs 客戶端,我呼叫了 addStu() 方法.")
})
// 呼叫服務端 getStu() 方法
client.getStu("zone-nodejs", function (err, res) {
if (err) {
console.log(err);
return
}
console.log("我是 nodejs 客戶端,我呼叫了 getStu() 方法.")
console.log("返回的結果為:" + res)
})
後記
微服務中,RPC 框架的效能是很重要的,因為一旦要做微服務,就是成百上千個微服務的,這涉及到各個微服務之間的通訊問題。通訊慢了,那麼整體的響應速度也就相對慢很多了。下一篇文章講一下訊息佇列,敬請期待!
作者:zone7_
連結:https://www.jianshu.com/p/6e5f6f1280b9
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。