RPC中Callback Function與CountDownLatch的用法

目錄

 

●What & Why

●回調函數Callback Function與倒數計數器CountDownLatch


●What & Why

RPC(Remote Procedure Call),通俗地說,就是在一臺計算機上調用另一臺計算機提供的服務。這裏的服務對應RPC中的P(Procedure),表現形式通常是API接口,或者說好比一個本地代碼工程中的一個函數。那爲什麼要用RPC呢?最主要的原因有兩點:1、符合低耦合、職責分離、可複用的開發原則,將不同的服務(模塊、功能……什麼名字都好,理解其本質即可)放在不同的代碼工程,甚至不同的計算機(服務器)上,避免所有代碼雜糅在一個工程中,難以開發與維護;2、緩解負載壓力,不同計算機(服務器)提供不同的服務,各司其職,不用做所有事,降低資源耗盡的風險。

 

RPC其實沒有那麼高深,如果不考慮底層實現原理,則對於程序員來說幾乎完全透明,就是一套固定步驟的開發流程,和調用本地工程代碼中的函數沒有差別。以筆者目前所做的項目爲例,步驟大體爲:準備對應的stub(筆者將其理解爲對方所能提供函數,在我方的一個說明)、準備遠程調用的通道、控制器對象;開啓子線程,利用回調函數Callback Function,等待處理響應,並用線程倒數控制器CountDownLatch讓主線程阻塞;開啓遠程調用。

●回調函數Callback Function與倒數計數器CountDownLatch

筆者之前看過一些關於回調函數的文章,概念上已經理解,即A類的函數a1調用B類的函數b1,b1中需要調用A類的函數a2,這個a2就是回調函數。但是對於其存在的意義,或者說使用回調函數的場景到底是什麼,還不太想得到。直到項目中接觸到了RPC,才真的體會到回調函數的作用。

 

我們做如下安排,服務器B是一臺提供許多實用功能/服務的機器,它對外提供的接口包括字符串處理(strDeal)等,我們在服務器A的代碼中,去調用服務器B所提供的函數。

服務器A——

public class Client{
	public boolean checkBackMessage(BackMessage backMessage){
		return backMessage.equals("Done") ? true : false;
	}
	
	public BackMessage dealStr(String str) {
		//接收RPC響應消息的對象
		final BackMessage backMessage = new BackMessage();
		//構造一個RPC通道,具體代碼未貼出,根據不同框架、項目而異
		RpcChannelImpl channel = RpcChannelImpl.builderChannel();
		if (channel != null) {
			//構造一個RPC控制器
			RpcController controller = channel.newRpcController();
			//對方所能提供的服務/函數都通過stub進行描述
			Service.Stub stub = Service.newStub(channel);
			//構造RPC的請求
			DealStrRequest.Builder request = DealStrRequest.newBuilder();
			//設置請求參數
			request.setCmd(CmdUtil.newRequestCmd(Hpp.CmdId.SERVER_RESTART_REQ));
			request.setStr(str);
			//倒數計數器
			final CountDownLatch latch = new CountDownLatch(1);
			//回調函數處理響應
			RpcCallback<DealStrResponse> done = new RpcCallback<DealStrResponse>() {	
				@Override
				public void run(DealStrResponse response) {
					try {
						//響應處理
						backMessage.setBackCode(response.getCmd().getResultCode());
						backMessage.setBackMessage(response.getCmd().getResultString());
						System.out.println("完成RPC調用,接收到響應,已進行設置")
					} catch (Exception e) {
						latch.countDown();
					}
					latch.countDown();
				}
			};
			//執行RPC調用
			stub.dealStr(controller, request.build(), done);
			//主線程阻塞等待響應處理完成
			try {
				latch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			backMessage.setBackCode(-1);
		}
		return backMessage;
	}
}

在服務器A中,我們需要準備一些對象,他們包括接收響應對象backMessage、RPC通道channel、PRC控制器controller、對方服務存根stub、RPC請求request。通過調用存根裏的函數接口就和調用本地的函數一樣。

我們對於響應的處理採用了比較精妙的方式:開啓一個子線程,定義回調函數run(),並且用CountDownLatch去阻塞主線程,讓其等待子線程中回調函數完成處理再繼續進行。

我們繼續看看服務器B所提供的RPC的服務——

public class ServiceImpl extends Service{
	@HppMethod(commonId = 10001)
	public void dealStr(RpcController controller, DealStrRequest request, RpcCallback<DealStrResponse> done) {
		//對請求中的字符串進行大寫轉換並存入數據庫
		request.getStr.toUpperCase().save();
		int ret=ErrorCode.CMS_SUCCESSED;
		DealStrResponse.Builder response = DealStrResponse.newBuilder().setCmd(CmdUtil.newResponseCmd(request.getCmd(), ResultUtil.getResultCode(ret)));
		//回調函數!!
		done.run(response.build());
	}
	
	//提供的其他RPC服務/函數
	@HppMethod(commonId = 10002)
	public void method1(RpcController controller, method1tRequest request, RpcCallback<method1tResponse> done) {
		……
	}
	
	@HppMethod(commonId = 10003)
	public void method2(RpcController controller, method2tRequest request, RpcCallback<method1tResponse> done) {
		……
	}
	
	……
}

在其處理完字符串後,回調了服務器A的函數,即done.run(response.build()),利用回調函數,把響應作爲參數從服務器B傳給服務器A中,並在服務器A的回調函數中對響應進行處理(設置響應碼、響應消息、打印日誌等操作)。

整個過程的數據流向就非常明晰了:服務器A的字符串通過請求發給服務器B(走RPC調用),服務器B處理完請求,生成響應對象,利用回調函數將其交給服務器A處理。怎麼樣,是不是似曾相識,好像在哪兒聽過?Bingo!沒錯!Ajax裏面也用到了回調函數!頁面前端發送請求(例如Post)給後端處理,後端生成響應回傳給頁面,Ajax異步處理響應生成頁面內容。

function testAjax(){
	$.ajax({
		dataType:"text",
		type:"POST",
		cache:false,
		url: path+"/testAjax.action",
		data:{
		 	"textDetail" : str
		},
		success:function(response){			
			if(response != null){
				consolo.log(response);
		  	}						
		},
		error:function(response){
			tLayer.warning("Error!");
		}
	 });		
}

回到正題,我們再看看服務器A中的回調函數裏面用到的CountDownLatch,這也是一個非常精妙的設計,它的作用是通過倒數來控制線程的阻塞。我們設置倒數計數爲1,當回調函數所在線程完成了響應處理,則將其減1,而主線程則在latch.await()的地方等待CountDownLatch倒數變爲0才繼續進行。可見,CountDownLatch適用於主線程中等待多個線程均執行完成後才繼續進行的場景。

筆者目前接觸的項目自己搭建的RPC架構,實際開發中,大家也可以選擇Thrift、Dubbo等第三方的框架進行使用。目前很火的微服務架構,就用到了RPC的相關思想,這一部分的知識還是值得掌握的。最後,給大家推薦兩篇文章:

https://blog.csdn.net/mindfloating/article/details/39473807

https://blog.csdn.net/mindfloating/article/details/39474123

今天,你學會了嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章