確定了微服務(wù)的服務(wù)劃分是關(guān)鍵的一步,接下來(lái)需要考慮選擇合適的接口協(xié)議來(lái)實(shí)現(xiàn)微服務(wù)之間的數(shù)據(jù)通信。目前,主流的接口調(diào)用方式可以分為兩大類:RPC(遠(yuǎn)程過(guò)程調(diào)用)和REST。
?? RPC(Remote Procedure Call): 以本地方法調(diào)用的形式處理遠(yuǎn)程方法調(diào)用的模型。常用的有:SOAP、RMI、Thrift、Avro、gRPC、Dubbo協(xié)議。
?? REST(Representational State Transfer):以資源為中心,描述資源狀態(tài)變更的模型。常見(jiàn)于HTTP協(xié)議。
1. RPC
RPC是Bruce_Jay_Nelson在1984年的《Implementing Remote Procedure Calls》文章中首次提出的,用于構(gòu)建簡(jiǎn)單、高效、通用的通信機(jī)制。以gRPC、Dubbo為代表的RPC方式的優(yōu)點(diǎn)有:
- 可定制協(xié)議/傳輸類型,可實(shí)現(xiàn)高性能通訊
- 可用于強(qiáng)格式約束場(chǎng)景
使用RPC一般而言要先定義IDL(Interface description language,接口描述言),以gRPC為例,我們需要定義類似的proto文件:
// 定義服務(wù)名稱
service Greeter {
// 定義方法
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// 方法入?yún)⑾Ⅲw
message HelloRequest {
string name = 1;
}
// 方法出參消息體
message HelloReply {
string message = 1;
}
對(duì)于Java而言,可以使用protobuf-maven-plugin 這一Maven插件將上述代碼編譯成Java文件,之后在服務(wù)端就可以實(shí)現(xiàn)對(duì)應(yīng)的方法,如
private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
具體示例官方都有,這里不贅述,當(dāng)然我們也可以使用不同的工具生成C++、NodeJS、C#、Go等不同的語(yǔ)言的實(shí)現(xiàn)。
由于Dubbo只支持JVM平臺(tái),所以它的IDL只要定義Java的接口即可。
我們可以看出RPC存在一些不足之處,包括以下方面:
- 多語(yǔ)言約束: 不同調(diào)用方之間存在語(yǔ)言約束。例如,跨語(yǔ)言RPC(如Thrift、Avro、gRPC)需要定義接口描述語(yǔ)言(IDL),而無(wú)需顯式指定IDL的RPC(如Dubbo、RMI)對(duì)協(xié)議的參與方有語(yǔ)言限制。
- 字段變更導(dǎo)致重新部署: 當(dāng)有新的服務(wù)調(diào)用方加入,需要重用某一接口并為其添加新字段時(shí),通常需要修改IDL。大多數(shù)RPC框架要求所有調(diào)用方同步更新,這可能導(dǎo)致頻繁的部署操作。
再看REST時(shí),它主要基于HTTP協(xié)議實(shí)現(xiàn),具有以下優(yōu)點(diǎn):
- 通用性高,無(wú)語(yǔ)言約束: REST具有較高的通用性,不受語(yǔ)言的限制。主流的編程語(yǔ)言都提供了對(duì)REST的良好支持。
- 弱格式約束,字段變更不需要重新部署: REST相對(duì)寬松的格式約束使得在接口字段發(fā)生變更時(shí),不需要所有調(diào)用方都重新部署。這為系統(tǒng)的靈活性和演化提供了一定的便利。Dubbo需要版本控制,可以達(dá)到支持多版本并行的能力也算不錯(cuò)。
- 防火墻友好: 由于REST基于HTTP協(xié)議,而HTTP是廣泛被接受的協(xié)議,因此REST更容易被防火墻接受。這使得在跨越網(wǎng)絡(luò)邊界時(shí)更容易實(shí)現(xiàn)通信。
2.REST
REST一般會(huì)基于Json或XML做為交互格式,兩者均為跨語(yǔ)言、弱約束的格式。我們不需要先定義IDL再生成對(duì)應(yīng)的語(yǔ)言文件,接口的變更除受影響的參與方需要修改外不需要全局重新部署,例如:
GET /user/{id}
{
"name":""
}
這是我們的獲取用戶信息接口,返回用戶姓名,有4個(gè)業(yè)務(wù)方調(diào)用,但后來(lái)有其中一個(gè)業(yè)務(wù)方要求再返回年齡信息,那么我們接口可以修改成:
GET /user/{id}
{
"name":"",
"age":0
}
這里增加了age字段只為某一業(yè)務(wù)方使用,其它業(yè)務(wù)方可以不用同步修改。在實(shí)際生產(chǎn)中我們一般會(huì)為核心接口增加版本號(hào)字段以更好地做多版本兼容。
為REST API添加版本有三種方式:
- 版本信息放在Path或Query中,如
api.example.com/v1
api_v1.example.com
api.example.com/xxx?version=v1
- 使用自定義Header, 如
Accept-Version:v1
Accept-Version:v2
- 使用內(nèi)容協(xié)商
Accept: application/vnd.example.v1+json
Accept: application/vnd.example+json;version=1.0
從REST設(shè)計(jì)的初衷而言,URL對(duì)于表述資源而與版本無(wú)關(guān),因此純學(xué)術(shù)的建議是使用第2、3種方式,但我們也看了大量第1種方式的API,其中不乏有知名的IT廠商,所以筆者覺(jué)得這3種都可以,如何選擇完全看團(tuán)隊(duì)的風(fēng)格。更多討論。
引入API版本控制后,確實(shí)可以在一定程度上提高接口的向下兼容性,允許更自由地進(jìn)行業(yè)務(wù)修改。然而,多版本維護(hù)和兼容性確實(shí)會(huì)對(duì)服務(wù)架構(gòu)提出更高的要求。
在實(shí)踐中,對(duì)于服務(wù)邏輯變更較大的情況,發(fā)布一個(gè)新的服務(wù)版本是一種常見(jiàn)的策略。這種方式可以在保持向下兼容性的同時(shí),為新的業(yè)務(wù)邏輯提供更大的靈活性。而對(duì)于變更相對(duì)有限的情況,可以考慮在服務(wù)中增加兼容/適配層,以平滑地過(guò)渡到新的版本。
對(duì)于REST的局限性,確實(shí)存在一些挑戰(zhàn)。傳輸效率相對(duì)較低,因?yàn)镽EST通常使用文本格式(如JSON或XML),而不像一些RPC框架那樣使用二進(jìn)制協(xié)議,導(dǎo)致傳輸?shù)臄?shù)據(jù)量相對(duì)較大。此外,由于REST缺乏強(qiáng)約定規(guī)范,對(duì)字段和結(jié)構(gòu)的修改可能會(huì)導(dǎo)致已接入調(diào)用方的異常。
總體而言,選擇API版本控制策略和服務(wù)架構(gòu)設(shè)計(jì)都需要根據(jù)具體情況和需求進(jìn)行權(quán)衡。不同的項(xiàng)目可能會(huì)采用不同的方法,取決于團(tuán)隊(duì)的技術(shù)棧、發(fā)展階段和對(duì)靈活性與穩(wěn)定性的重視程度。
那么我們究竟如何選擇呢?
在選擇通信協(xié)議和架構(gòu)模式時(shí),需要綜合考慮多個(gè)因素,包括性能、靈活性、維護(hù)成本等。下面以微服務(wù)架構(gòu)為背景,對(duì)比REST(代表Spring Cloud)和RPC(代表Dubbo)的一些考慮因素:
- 性能: REST相對(duì)于RPC在IO性能上的一些損失。如果對(duì)于實(shí)際的業(yè)務(wù)調(diào)用場(chǎng)景而言,通信性能的差距可以被接受,并且可以通過(guò)緩存、壓縮等手段進(jìn)行優(yōu)化,那么這可能不是一個(gè)決定性的因素。
- 異構(gòu)系統(tǒng)的通訊: REST的無(wú)狀態(tài)和基于標(biāo)準(zhǔn)協(xié)議的特性使其更容易與異構(gòu)系統(tǒng)進(jìn)行通信。如果系統(tǒng)中有多種語(yǔ)言和平臺(tái),REST可能更為方便。
- 接口修改的影響: 對(duì)于REST,確實(shí)需要通過(guò)一定的開(kāi)發(fā)規(guī)范來(lái)防范接口修改可能帶來(lái)的影響。這包括規(guī)范修改的方式,例如不允許刪除或修改字段,而是使用新增字段來(lái)解決。
- 效率與鍥約檢查: 對(duì)于一些業(yè)務(wù)相對(duì)穩(wěn)定、高頻調(diào)用的服務(wù),尤其是核心的公共服務(wù),RPC可能更適合。RPC框架通常提供了強(qiáng)約定和類型檢查,這有助于提高效率并防止一些潛在的錯(cuò)誤。
- 兼容性和集成: 目前沒(méi)有一種通用的框架同時(shí)支持REST和RPC。在微服務(wù)架構(gòu)中,可以根據(jù)具體的業(yè)務(wù)需求和服務(wù)特性選擇不同的通信方式,甚至可以混合使用。
針對(duì)車貸系統(tǒng),考慮到整體并發(fā)要求不高,核心接口的 TPS 不會(huì)超過(guò)1000,選擇REST作為通信協(xié)議是一個(gè)自然而合理的決定。REST在與異構(gòu)系統(tǒng)通信以及保持接口靈活性方面具有優(yōu)勢(shì),適合當(dāng)前的業(yè)務(wù)需求。
但隨著項(xiàng)目的后續(xù)版本迭代,將一些核心服務(wù)改成dubbo,以提升性能和穩(wěn)定性。dubbo作為一種高效的RPC框架,可以在需要更高效通信的情況下發(fā)揮作用。這種逐步的演進(jìn)方式允許在項(xiàng)目的不同階段選擇適合的通信方式,同時(shí)確保了對(duì)未來(lái)需求的靈活響應(yīng)。
這樣的決策考慮到了當(dāng)前的業(yè)務(wù)需求和未來(lái)可能的性能提升需求,為系統(tǒng)架構(gòu)的演進(jìn)提供了一種合理的方法。