RPC的入門應用


一、什么是RPC

RPC 的全稱是 Remote Procedure Call ,是一種進程間通信方式。它允許程序調用另一個地址空間(通常是共享網絡的另一台機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節。即無論是調用本地接口/服務的還是遠程的接口/服務,本質上編寫的調用代碼基本相同。

說起RPC,就不能不提到分布式,這個促使RPC誕生的領域。

假設你有一個計算器接口,Calculator模塊,以及它的實現類CalculatorImpl,那么在系統還是單體應用時,你要調用Calculator的add方法來執行一個加運算,直接實例一個CalculatorImpl對象,然后調用add方法就行了,這其實就是非常普通的本地函數調用,因為在同一個地址空間,或者說在同一塊內存,所以可以直接實現。

 

 現在,基於高性能和高可靠等因素的考慮,你決定將系統改造為分布式應用,將很多可以共享的功能都單獨拎出來,比如上面說到的計算器,你單獨把它放到一個服務里頭,讓別的服務去調用它。

這下問題來了,服務A里頭並沒有CalculatorImpl這個類,那它要怎樣調用服務B的CalculatorImpl的add方法呢?

有同學會說,可以模仿B/S架構的調用方式呀,在B服務暴露一個Restful接口,然后A服務通過調用這個Restful接口來間接調用CalculatorImpl的add方法。

很好,這已經很接近RPC了,不過如果是這樣,那每次調用時,是不是都需要寫一串發起http請求的代碼呢?比如
res=requests.get("URL") 
 
但是,兩個問題:
1、http協議較為復雜,效率低,相對笨重
2、調用方式不像本地調用簡單方便,讓調用者感知不到遠程調用的邏輯。

二 如何實現RPC

2.1 RPC實現原理

實際情況下,RPC很少用到http協議來進行數據傳輸,畢竟我只是想傳輸一下數據而已,何必動用到一個文本傳輸的應用層協議呢,所以一般會選擇直接傳輸二進制數據

不管你用何種協議進行數據傳輸,一個完整的RPC過程,都可以用下面這張圖來描述:

 
 

以左邊的Client端為例,Application就是rpc的調用方,Client Stub就是我們上面說到的代理對象,也就是那個看起來像是Calculator的實現類,其實內部是通過rpc方式來進行遠程調用的代理對象,至於Client Run-time Library,則是實現遠程調用的工具包,比如python的socket模塊,最后通過底層網絡實現實現數據的傳輸。

這個過程中最重要的就是 序列化反序列化了,因為數據傳輸的數據包必須是二進制的,你直接丟一個python對象過去,人家可不認識,你必須把python對象序列化為二進制格式,傳給Server端,Server端接收到之后,再反序列化為python對象。

2.2 python實現RPC

# 客戶端

import rpyc

# 參數主要是host, port
conn = rpyc.connect('localhost', 9999)
# test是服務端的那個以"exposed_"開頭的方法
print('start')
for i in range(100):
    cResult = conn.root.cal(i)
    print(cResult)
print('end')

conn.close()

# 服務端
from rpyc import Service
from rpyc.utils.server import ThreadedServer


class TestService(Service):

    # 對於服務端來說, 只有以"exposed_"打頭的方法才能被客戶端調用,所以要提供給客戶端的方法都得加"exposed_"
    def exposed_cal(self, num):
        return  num*2

sr = ThreadedServer(TestService, port=9999, auto_register=False)
sr.start()

2.3 GRPC框架

目前流行的開源 RPC 框架還是比較多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。

gRPC:是 Google 公布的開源軟件,基於最新的 HTTP 2.0 協議,並支持常見的眾多編程語言。RPC 框架是基於 HTTP 協議實現的,底層使用到了 Netty 框架的支持。
Thrift:是 Facebook 的開源 RPC 框架,主要是一個跨語言的服務開發框架。用戶只要在其之上進行二次開發就行,應用對於底層的 RPC 通訊等都是透明的。不過這個對於用戶來說需要學習特定領域語言這個特性,還是有一定成本的。
Dubbo:是阿里集團開源的一個極為出名的 RPC 框架,在很多互聯網公司和企業應用中廣泛使用。協議和序列化框架都可以插拔是極其鮮明的特色。

以使用較為廣泛的gRPC為例學習下RPC框架的使用

gRPC 是 Google 開放的一款 RPC (Remote Procedure Call) 框架,建立在 HTTP2 之上,使用 Protocol Buffers。

2.3.1 Protocol Buffers 簡介

protocol buffers 是 Google 公司開發的一種數據描述語言,采用簡單的二進制格式,比 XML、JSON 格式體積更小,編解碼效率更高。用於數據存儲、通信協議等方面。

通過一個 .proto 文件,你可以定義你的數據的結構,並生成基於各種語言的代碼。目前支持的語言很多,有 Python、golang、js、java 等等。

2.3.2 gRPC 簡介

有了 protocol buffers 之后,Google 進一步推出了 gRPC。通過 gRPC,我們可以在 .proto 文件中也一並定義好 service,讓遠端使用的 client 可以如同調用本地的 library 一樣使用。

 
 

可以看到 gRPC Server 是由 C++ 寫的,Client 則分別是 Java 以及 Ruby,Server 跟 Client 端則是通過 protocol buffers 來信息傳遞。

1. 定義功能函數

calculate.py

# -*- coding: utf-8 -*-
import math


# 求平方
def square(x):
    return math.sqrt(x)

2. 創建 .proto 文件

在這里描述我們要使用的 message 以及 service

syntax = "proto3";

message Number {
    float value = 1;
}

service Calculate {
    rpc Square(Number) returns (Number) {}
}

3. 生成 gRPC 類

這部分可能是整個過程中最“黑盒子”的部分。我們將使用特殊工具自動生成類。

$ pip install grpcio grpcio-tools
$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. calculate.proto

你會看到生成來兩個文件:

  • calculate_pb2.py  —  包含 message(calculate_pb2.Number)
  • calculate_pb2_grpc.py  —  包含 server(calculate_pb2_grpc.CalculatorServicer) and client(calculate_pb2_grpc.CalculatorStub)

4. 創建 gRPC 服務端

server.py

# -*- coding: utf-8 -*-
import grpc
import calculate_pb2
import calculate_pb2_grpc
import calculate
from concurrent import futures
import time


# 創建一個 CalculateServicer 繼承自 calculate_pb2_grpc.CalculateServicer
class CalculateServicer(calculate_pb2_grpc.CalculateServicer):
    def Square(self, request, context):
        response = calculate_pb2.Number()
        response.value = calculate.square(request.value)
        return response


# 創建一個 gRPC server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
# 利用 add_CalculateServicer_to_server 這個方法把上面定義的 CalculateServicer 加到 server 中
calculate_pb2_grpc.add_CalculateServicer_to_server(CalculateServicer(), server)
# 讓 server 跑在 port 50051 中
print 'Starting server. Listening on port 50051.'
server.add_insecure_port('[::]:50051')
server.start()

# 因為 server.start() 不會阻塞,添加睡眠循環以持續服務
try:
    while True:
        time.sleep(24 * 60 * 60)
except KeyboardInterrupt:
    server.stop(0)

啟動 gRPC server:

$ python server.py
Starting server. Listening on port 50051.

5. 創建 gRPC 客戶端

client.py

 
# -*- coding: utf-8 -*-
import grpc
import calculate_pb2
import calculate_pb2_grpc

# 打開 gRPC channel,連接到 localhost:50051
channel = grpc.insecure_channel('localhost:50051')
# 創建一個 stub (gRPC client)
stub = calculate_pb2_grpc.CalculateStub(channel)
# 創建一個有效的請求消息 Number
number = calculate_pb2.Number(value=16)
# 帶着 Number 去調用 Square
response = stub.Square(number)
print response.value

啟動 gRPC client:

$ python client.py
4.0

最終的文件結構:

 

三 總結

RPC 主要用於公司內部的服務調用,性能消耗低,傳輸效率高,實現復雜。

HTTP 主要用於對外的異構環境,瀏覽器接口調用,App 接口調用,第三方接口調用等。

RPC 使用場景(大型的網站,內部子系統較多、接口非常多的情況下適合使用 RPC):

  • 長鏈接。不必每次通信都要像 HTTP 一樣去 3 次握手,減少了網絡開銷。
  • 注冊發布機制。RPC 框架一般都有注冊中心,有豐富的監控管理;發布、下線接口、動態擴展等,對調用方來說是無感知、統一化的操作。
  • 安全性,沒有暴露資源操作。
  • 微服務支持。就是最近流行的服務化架構、服務化治理,RPC 框架是一個強力的支撐。

四 RPC沒那么簡單

要實現一個RPC不算難,難的是實現一個高性能高可靠的RPC框架。

比如,既然是分布式了,那么一個服務可能有多個實例,你在調用時,要如何獲取這些實例的地址呢?

這時候就需要一個服務注冊中心,比如在Dubbo里頭,就可以使用Zookeeper作為注冊中心,在調用時,從Zookeeper獲取服務的實例列表,再從中選擇一個進行調用。

那么選哪個調用好呢?這時候就需要負載均衡了,於是你又得考慮如何實現復雜均衡,比如Dubbo就提供了好幾種負載均衡策略。

這還沒完,總不能每次調用時都去注冊中心查詢實例列表吧,這樣效率多低呀,於是又有了緩存,有了緩存,就要考慮緩存的更新問題,blablabla......

你以為就這樣結束了,沒呢,還有這些:

  • 客戶端總不能每次調用完都干等着服務端返回數據吧,於是就要支持異步調用;
  • 服務端的接口修改了,老的接口還有人在用,怎么辦?總不能讓他們都改了吧?這就需要版本控制了;
  • 服務端總不能每次接到請求都馬上啟動一個線程去處理吧?於是就需要線程池;
  • 服務端關閉時,還沒處理完的請求怎么辦?是直接結束呢,還是等全部請求處理完再關閉呢?
  • ......

如此種種,都是一個優秀的RPC框架需要考慮的問題。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM