前言
上篇文章簡單地介紹了 SOFA-Boot 的功能特性,對 Readiness 健康檢查的配置舉例說明。重點介紹瞭如何在 SOFA-Boot 中引入 SOFA-RPC 中介軟體,給出了基於 bolt、rest 和 dubbo 等不同協議通道的服務釋出與消費的全流程。
本文將進一步介紹 SOFA-RPC 中介軟體提供的豐富而強大的功能,包括單向呼叫、同步呼叫、Future呼叫、回撥,泛化呼叫,過濾器配置等。
其他文章
正文
1. 呼叫方式
SOFA-RPC 提供單向呼叫、同步呼叫、非同步呼叫和回撥四種呼叫機制。為了區分四者的不同之處,這裡給出 SOFA 官方提供的原理圖。
下面給出詳細闡述和配置說明:
1.1. 單向方式
當前執行緒發起呼叫後,不關心呼叫結果,不做超時控制,只要請求已經發出,就完成本次呼叫。目前支援 bolt 協議。
配置說明
使用單向方式需要在服務引用的時候通過 sofa:global-attrs
元素的 type
屬性宣告呼叫方式為 oneway
,這樣使用該服務引用發起呼叫時就是使用的單向方式了。
<sofa:reference id="helloOneWayServiceReference" interface="com.ostenant.sofa.rpc.example.invoke.HelloOneWayService">
<sofa:binding.bolt>
<sofa:global-attrs type="oneway"/>
</sofa:binding.bolt>
</sofa:reference>
複製程式碼
適用場景
單向呼叫不保證成功,而且發起方無法知道呼叫結果。因此通常用於可以重試,或者定時通知類的場景,呼叫過程是有可能因為網路問題,機器故障等原因,導致請求失敗。業務場景需要能接受這樣的異常場景,才可以使用。
1.2. 同步方式
當前執行緒發起呼叫後,需要在指定的超時時間內,等到響應結果,才能完成本次呼叫。如果超時時間內沒有得到結果,那麼會丟擲超時異常。
配置說明
服務介面與實現類
SOFA-RPC 預設採用的就是同步呼叫,可以省略 sofa:global-attrs
配置項。
服務端釋出配置
<bean id="helloSyncServiceImpl" class="com.ostenant.sofa.rpc.example.invoke.HelloSyncServiceImpl"/>
<sofa:service ref="helloSyncServiceImpl" interface="com.ostenant.sofa.rpc.example.invoke.HelloSyncService">
<sofa:binding.bolt/>
</sofa:service>
複製程式碼
客戶端引用配置
<sofa:reference id="helloSyncServiceReference" interface="com.ostenant.sofa.rpc.example.invoke.HelloSyncService">
<sofa:binding.bolt/>
</sofa:reference>
複製程式碼
服務端啟動入口
SpringApplication springApplication = new SpringApplication(SyncServerApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端啟動入口
SpringApplication springApplication = new SpringApplication(SyncClientApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端呼叫
HelloSyncService helloSyncServiceReference = (HelloSyncService) applicationContext.getBean("helloSyncServiceReference");
System.out.println(helloSyncServiceReference.saySync("sync"));
複製程式碼
適用場景
同步呼叫是最常用的方式。注意要根據對端的處理能力,合理設定超時時間。
1.3. Future方式
Future 方式下,客戶端發起呼叫後不會等待服務端的結果,繼續執行後面的業務邏輯。服務端返回的結果會被 SOFA-RPC 快取,當客戶端需要結果的時候,需要主動獲取。目前支援 bolt 協議。
配置說明
服務介面和實現類
HelloFutureService.java
public interface HelloFutureService {
String sayFuture(String future);
}
複製程式碼
HelloFutureServiceImpl.java
public class HelloFutureServiceImpl implements HelloFutureService {
@Override
public String sayFuture(String future) {
return future;
}
}
複製程式碼
服務端釋出配置
<bean id="helloFutureServiceImpl" class="com.ostenant.sofa.rpc.example.invoke.HelloFutureServiceImpl"/>
<sofa:service ref="helloFutureServiceImpl" interface="com.ostenant.sofa.rpc.example.invoke.HelloFutureService">
<sofa:binding.bolt/>
</sofa:service>
複製程式碼
客戶端引用配置
使用 Future 方式需要在服務引用的時候通過 sofa:global-attrs
元素的 type
屬性宣告呼叫方式為 future
。
<sofa:reference id="helloFutureServiceReference" interface="com.ostenant.sofa.rpc.example.invoke.HelloFutureService">
<sofa:binding.bolt>
<sofa:global-attrs type="future"/>
</sofa:binding.bolt>
</sofa:reference>
複製程式碼
這樣使用該服務引用發起呼叫時就是使用的 Future
方式了。
服務端啟動入口
SpringApplication springApplication = new SpringApplication(FutureServerApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端啟動入口
SpringApplication springApplication = new SpringApplication(FutureClientApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端獲取返回結果有兩種方式:
- 其一,通過
SofaResponseFuture
直接獲取結果。第一個引數是獲取結果的超時時間,第二個參數列示是否清除執行緒上下文中的結果。
HelloFutureService helloFutureServiceReference = (HelloFutureService) applicationContext
.getBean("helloFutureServiceReference");
helloFutureServiceReference.sayFuture("future");
try {
String result = (String)SofaResponseFuture.getResponse(1000, true);
System.out.println("Future result: " + result)
} catch (InterruptedException e) {
e.printStackTrace();
}
複製程式碼
- 其二,獲取原生 Future。該種方式會獲取 JDK 原生的 Future ,參數列示是否清除執行緒上下文中的結果。獲取結果的方式就是 JDK Future 的獲取方式。
HelloFutureService helloFutureServiceReference = (HelloFutureService) applicationContext
.getBean("helloFutureServiceReference");
helloFutureServiceReference.sayFuture("future");
try {
Future future = SofaResponseFuture.getFuture(true);
String result = (String)future.get(1000, TimeUnit.MILLISECONDS);
System.out.println("Future result: " + result)
} catch (InterruptedException e) {
e.printStackTrace();
}
複製程式碼
適用場景
Future 方式適用於非阻塞程式設計模式。對於客戶端程式處理後,不需要立即獲取返回結果,可以先完成後續程式程式碼執行,在後續業務中,主動從當前執行緒上下文獲取呼叫返回結果。減少了網路 IO 等待造成的程式碼執行阻塞和延遲。
1.4. 回撥方式
當前執行緒發起呼叫,則本次呼叫馬上結束,可以馬上執行下一次呼叫。發起呼叫時需要註冊一個回撥,該回撥需要分配一個非同步執行緒池。待響應返回後,會在回撥的非同步執行緒池,來執行回撥邏輯。
配置說明
服務介面和實現類
HelloCallbackService.java
public interface HelloCallbackService {
String sayCallback(String callback);
}
複製程式碼
HelloCallbackServiceImpl.java
public class HelloCallbackServiceImpl implements HelloCallbackService {
@Override
public String sayCallback(String string) {
return string;
}
}
複製程式碼
業務回撥類
客戶端回撥類需要實現 com.alipay.sofa.rpc.core.invoke.SofaResponseCallback
介面。
CallbackImpl.java
public class CallbackImpl implements SofaResponseCallback {
@Override
public void onAppResponse(Object appResponse, String methodName, RequestBase request) {
System.out.println("callback client process:" + appResponse);
}
@Override
public void onAppException(Throwable throwable, String methodName, RequestBase request) {
}
@Override
public void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) {
}
}
複製程式碼
SofaResponseCallback 介面提供了 3 個方法:
- onAppResponse: 程式正常執行,則進入該回撥方法。
- onAppException: 服務端程式丟擲異常,則進入該回撥方法。
- onSofaException: 框架內部出現錯誤,則進入該回撥方法。
服務端釋出配置
<bean id="helloCallbackServiceImpl" class="helloFutureServiceReference" interface="com.ostenant.sofa.rpc.example.invoke.HelloCallbackServiceImpl"/>
<sofa:service ref="helloCallbackServiceImpl" interface="helloFutureServiceReference" interface="com.ostenant.sofa.rpc.example.invoke.HelloCallbackService">
<sofa:binding.bolt/>
</sofa:service>
複製程式碼
客戶端引用配置
在服務引用的時候通過 sofa:global-attrs
元素的 type
屬性宣告呼叫方式為 callback
,再通過 callback-ref
宣告回撥的實現類。
<bean id="callbackImpl" class="com.ostenant.sofa.rpc.example.invoke.CallbackImpl"/>
<sofa:reference id="helloCallbackServiceReference"
interface="com.ostenant.sofa.rpc.example.invoke.HelloCallbackService">
<sofa:binding.bolt>
<sofa:global-attrs type="callback" callback-ref="callbackImpl"/>
</sofa:binding.bolt>
</sofa:reference>
複製程式碼
這樣使用該服務引用發起呼叫時,就是使用的回撥方式了。在結果返回時,由 SOFA-RPC 自動呼叫該回撥類的相應方法。
服務端啟動入口
SpringApplication springApplication = new SpringApplication(CallbackServerApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端啟動入口
SpringApplication springApplication = new SpringApplication(CallbackClientApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端發起呼叫
HelloCallbackService helloCallbackServiceReference = (HelloCallbackService) applicationContext
.getBean("helloCallbackServiceReference");
helloCallbackServiceReference.sayCallback("callback");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
複製程式碼
sayCallback() 的返回值不應該直接獲取。在客戶端註冊的回撥類中,返回值會以引數的形式傳入正確的方法,以回撥的形式完成後續邏輯處理。
適用場景
Callback 方式適用於非同步非阻塞程式設計模式。客戶端程式所線上程發起呼叫後,繼續執行後續操作,不需要主動去獲取返回值。服務端程式處理完成,將返回值傳回一個非同步執行緒池,由子執行緒通過回撥函式進行返回值處理。很大情況的減少了網路 IO 阻塞,解決了單執行緒的瓶頸,實現了非同步程式設計。
2. 泛化呼叫
泛化呼叫方式能夠在客戶端不依賴服務端的介面情況下發起呼叫,目前支援 bolt 協議。由於不知道服務端的介面,因此需要通過字串的方式將服務端的介面,呼叫的方法,引數及結果類進行描述。
配置說明
泛化引數類
SampleGenericParamModel.java
public class SampleGenericParamModel {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
複製程式碼
泛化返回類
SampleGenericResultModel.java
public class SampleGenericResultModel {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
複製程式碼
服務介面和實現類
SampleGenericService.java
public interface SampleGenericService {
SampleGenericResultModel sayGeneric(SampleGenericParamModel sampleGenericParamModel);
}
複製程式碼
-
SampleGenericParamModel:作為 sayGeneric() 的輸入引數型別,有一個
name
成員變數,作為真正的方法入參。 -
SampleGenericResultModel:作為 sayGeneric() 的返回結果型別,宣告瞭
name
和value
兩個成員變數,作為真實的返回值。
SampleGenericServiceImpl.java
public class SampleGenericServiceImpl implements SampleGenericService {
@Override
public SampleGenericResultModel sayGeneric(SampleGenericParamModel sampleGenericParamModel) {
String name = sampleGenericParamModel.getName();
SampleGenericResultModel resultModel = new SampleGenericResultModel();
resultModel.setName(name);
resultModel.setValue("sample generic value");
return resultModel;
}
}
複製程式碼
服務端釋出配置
<bean id="sampleGenericServiceImpl" class="com.ostenant.sofa.rpc.example.generic.SampleGenericServiceImpl"/>
<sofa:service ref="sampleGenericServiceImpl" interface="com.ostenant.sofa.rpc.example.generic.SampleGenericService">
<sofa:binding.bolt/>
</sofa:service>
複製程式碼
客戶端引用配置
<sofa:reference id="sampleGenericServiceReference" interface="com.alipay.sofa.rpc.api.GenericService">
<sofa:binding.bolt>
<sofa:global-attrs generic-interface="com.ostenant.sofa.rpc.example.generic.SampleGenericService"/>
</sofa:binding.bolt>
</sofa:reference>
複製程式碼
在泛化呼叫過程中,客戶端配置有兩點需要注意:
sofa:reference
指向的服務介面需要宣告為 SOFA-RPC 提供的泛化介面com.alipay.sofa.rpc.api.GenericService
。sofa:global-attrs
需要宣告屬性generic-interface
,value 為真實的服務介面名稱。
服務端啟動入口
SpringApplication springApplication = new SpringApplication(SampleGenericServerApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端啟動入口
SpringApplication springApplication = new SpringApplication(SampleGenericClientApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端發起呼叫
- 獲取服務的泛化引用
GenericService sampleGenericServiceReference = (GenericService) applicationContext
.getBean("sampleGenericServiceReference");
複製程式碼
- 準備方法引數
由於客戶端沒有呼叫服務的引數類,因此通過 com.alipay.hessian.generic.model.GenericObjectGenericObject
進行描述。
// 準備方法引數
GenericObject genericParam = new GenericObject(
"com.ostenant.sofa.rpc.example.generic.SampleGenericParamModel");
genericParam.putField("name", "Harrison");
複製程式碼
GenericObject
持有一個 Map<String, Object>
型別的變數,你能夠通過 GenericObject
提供的 putField()
方法,將引數類的屬性和值放到這個 Map
中,以此來描述引數類。
- 發起泛化呼叫
通過 GenericService
的 $genericInvoke(arg1, agr2, arg3)
方法可以發起服務的泛化呼叫,各個引數含義如下:
引數 | 含義 | 引數可選 |
---|---|---|
arg1 | 目標方法名稱 | 必填 |
arg2 | 引數型別的陣列,要求嚴格遵循先後次序 | 必填 |
arg3 | 引數值的陣列,要求與引數型別陣列保持一致 | 必填 |
arg4 | 返回值的Class型別 | 可選 |
方式一:
GenericObject genericResult = (GenericObject) sampleGenericServiceReference.$genericInvoke(
// 目標方法名稱
"sayGeneric",
// 引數型別名稱
new String[] { "com.ostenant.sofa.rpc.example.generic.SampleGenericParamModel" },
// 引數的值
new Object[] { genericParam });
// 驗證返回結果
System.out.println("Type: " + genericResult.getType());
System.out.println("Name: " + genericResult.getField("name"));
System.out.println("Value: " + genericResult.getField("value"));
複製程式碼
方式二:
SampleGenericResultModel sampleGenericResult = sampleGenericServiceReference.$genericInvoke(
// 目標方法名稱
"sayGeneric",
// 引數型別名稱
new String[] { "com.ostenant.sofa.rpc.example.generic.SampleGenericParamModel" },
// 引數的值
new Object[] { genericParam },
// 返回值的Class型別
SampleGenericResultModel.class);
// 驗證返回結果
System.out.println("Type: " + sampleGenericResult.getClass().getName());
System.out.println("Name: " + sampleGenericResult.getName());
System.out.println("Value: " + sampleGenericResult.getValue());
複製程式碼
檢視控制檯輸出
兩種方式輸出如下:
Type: com.ostenant.sofa.rpc.example.generic.SampleGenericResultModel
Name: Harrison
Value: sample generic value
複製程式碼
3. 過濾器配置
SOFA-RPC 通過過濾器 Filter 來實現對請求和響應的攔截處理。使用者可以自定義 Filter 實現攔截擴充套件,目前支援 bolt 協議。開發人員通過繼承 com.alipay.sofa.rpc.filter.Filter
實現過濾器的自定義。
配置說明
服務介面與實現類
FilterService.java
public interface FilterService {
String sayFilter(String filter);
}
複製程式碼
FilterServiceImpl.java
public class FilterServiceImpl implements FilterService {
@Override
public String sayFilter(String filter) {
return filters;
}
}
複製程式碼
服務端過濾器
在 Filter 實現類中,invoke()
方法實現具體的攔截邏輯,通過 FilterInvoker.invoke(SofaRequest)
觸發服務的呼叫,在該方法前後可以實現具體的攔截處理。
public class SampleServerFilter extends Filter {
@Override
public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
System.out.println("SampleFilter before server process");
try {
return invoker.invoke(request);
} finally {
System.out.println("SampleFilter after server process");
}
}
}
複製程式碼
服務端釋出配置
服務端需要配置服務實現類、過濾器,然後在 sofa:service
的 sofa:global-attrs
標籤配置 filter
屬性,實現兩者的繫結。
<bean id="sampleFilter" class="com.ostenant.sofa.rpc.example.filter.SampleServerFilter"/>
<bean id="filterService" class="com.ostenant.sofa.rpc.example.filter.FilterServiceImpl"/>
<sofa:service ref="filterService" interface="com.ostenant.sofa.rpc.example.filter.FilterService">
<sofa:binding.bolt>
<sofa:global-attrs filter="sampleFilter"/>
</sofa:binding.bolt>
</sofa:service>
複製程式碼
客戶端過濾器
public class SampleClientFilter extends Filter {
@Override
public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
System.out.println("SampleFilter before client invoke");
try {
return invoker.invoke(request);
} finally {
System.out.println("SampleFilter after client invoke");
}
}
}
複製程式碼
客戶端引用配置
同樣的,客戶端過濾器需要在 sofa:reference
的 sofa:global-attrs
標籤中配置 filter
屬性,實現客戶端引用類的呼叫攔截。
<bean id="sampleFilter" class="com.alipay.sofa.rpc.samples.filter.SampleClientFilter"/>
<sofa:reference id="filterServiceReference" interface="com.ostenant.sofa.rpc.example.filter.FilterService">
<sofa:binding.bolt>
<sofa:global-attrs filter="sampleFilter"/>
</sofa:binding.bolt>
</sofa:reference>
複製程式碼
服務端啟動類
SpringApplication springApplication = new SpringApplication(FilterServerApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端啟動類
SpringApplication springApplication = new SpringApplication(FilterClientApplication.class);
ApplicationContext applicationContext = springApplication.run(args);
複製程式碼
客戶端呼叫
FilterService filterServiceReference = (FilterService) applicationContext.getBean("filterServiceReference");
try {
// sleep 5s, 便於觀察過濾器效果
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = filterServiceReference.sayFilter("filter");
System.out.println("Invoke result: " + result);
複製程式碼
檢視攔截輸出
- 服務端列印輸出
SampleFilter before server process
SampleFilter after server process
複製程式碼
- 客戶端列印輸出
SampleFilter before client invoke
SampleFilter after client invoke
Invoke result: filter
複製程式碼
過濾器配置生效,總結過濾器攔截先後次序如下:
- 客戶端發起呼叫 -> 客戶端前置攔截 -> 服務端前置攔截
- 服務端方法執行
- 服務端後置攔截 -> 客戶端後置攔截 -> 客戶端接收返回值
小結
本文介紹了 SOFA-RPC 的集中呼叫方式,包括單向呼叫、同步呼叫、Future呼叫、回撥,引入了 SOFA-RPC 獨有的泛化呼叫機制,同時對過濾器的配置進行了簡單介紹。
歡迎關注技術公眾號: 零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。