JMicro微服務之超時&重試

JMicro是本人開發的基於Java實現的微服務框架,當前正式版本為0.0.3,並已發布到maven中央倉庫。項目源碼github:
//github.com/mynewworldyyl/jmicro,Demo源碼:
//github.com/mynewworldyyl/jmicro_demos/tree/0.0.3-release
Maven地址:
//mvnrepository.com/search?q=cn.jmicro
Demo://jmicro.cn 帳號:test00 密碼:1

超時&重試

客戶端調用服務時,由於網路抖動,服務故障等原因,不能及時響應給客戶端,往往需要重試一兩次才能成功,而不應該在發生超時時就直接給調用者失敗結果,這樣不友好,往往讓用戶懷疑我們服務的穩定性和可用性。

比如平時訪問某個網站,頁面出現長時間讀條載入的情況,我們往往按一下刷新按鈕就能很快成功打開頁面。在微服務調用中,往往由服務調用者在收到失敗響應後,根據業務場景發起合理的重試請求。JMicro在框架級別提供這種配置並且由平台自動做重試操作,提高服務調用的成功率。

JMicro超時重試配置

在前面JMicro微服務Helloworld基基礎上,通過配置SMethod註解屬性達到超時重試的目的。程式碼如下:

@Component
@Service(version="0.0.1")
public class TxShopServiceImpl implements ITxShopService {

	private final static Logger logger = LoggerFactory.getLogger(TxShopServiceImpl.class);
	
	private Random ran = new Random(System.currentTimeMillis());
	
	@Override
	@SMethod(timeout=1000,retryCnt=3,retryInterval=100)
	public Resp<Boolean> buy(int goodId,int num) {
		Resp<Boolean> r = new Resp<>(Resp.CODE_SUCCESS,true);
		int st = ran.nextInt(6);
		if(st > 0) {
			logger.info("Sleep time: " + st+" Seconds idx: " +num);
			try {
				TimeUnit.SECONDS.sleep(30);//睡30秒,讓客戶端超時並重試,只要為了驗證超時重試邏輯
				TimeUnit.SECONDS.sleep(ran.nextInt(5));//睡30秒,隨機睡0到5秒,測試並發表求時部份請求超時
			} catch (InterruptedException e) {
				logger.error("",e);
			}
		}
		logger.info("Success return idx:" +num);
		return r;
	}

}

  

在buy服務方法配置SMethod註解,並設置timeout,retryInterval,retryCnt三個屬性即可。

timeout:表示超時時間,單位毫秒,小於或等於0表示不啟用超時,大於0表示啟用超時;

retryInterval:表示超時後,重試間隔,單位毫秒,如果服務超時後,立即發起重試請求,很可能還是失敗,所以要稍微等一會,讓服務方快取一下,再發起重試,往往成功率會更高。

retryCnt:表示重試次數,不包括第一次,如設置為3,如果第一次超時,第2次成功,則總共請求了2次,同理,全部超時時,總共請求了4次,4次超時後,RPC失敗,拋出TimeoutException異常。

配置啟動參數及入口類

除了可以按照上一節用的命令方式運行測試 ,還可以將項目導入Eclipse或Idea中運行Debug測試。

比如Eclipse下,將源碼以Maven項目導入Eclipse中,在運行測試配置如下圖所示:

Eclipse入口類

參數:

參數及Agent配置

VM參數為Maven倉庫下的Agent類,需要改為你自己機器的路徑,此Jar包用於在我們啟動java虛擬機時,對我們的實體類(SO註解的類)做序列化增強,這樣我們RPC請求就可以飛起來。

-DsysLogLevel=4 日誌級別,用於日誌監控,需要日誌服務支援,後面專門章節說明;

-DclientId=25500 以什麼帳號啟動我們的服務實例,涉及帳號許可權及多租戶隔離;

-DadminClientId=0 超級帳號,用於申請超級帳號許可權

-Dlog4j.configuration=../../log4j.xml Log4j日誌配置文件

-Dpwd=1 clientId帳號對應的密碼

以上參數除了log4j.configuration外,其他都可以先不用管,後面用專門章節說明

通過以上配置後,就可以像平時啟動Java程式那樣啟動服務了。

配置並啟動客戶端

同樣方式配置並啟動客戶端,客戶端以固定頻率調用服務端buy方法,程式碼如下:

public void afterInit(IObjectFactory of) {
		AtomicInteger ai = new AtomicInteger(0);
		//為了不Block主執行緒,我們在此啟動一個執行緒每間隔3秒調用一次商店提供的購買方法
		new Thread(()->{
				for(;true;) {
						try {
						//調用商店服務
						int cnt = ai.getAndIncrement();
						Resp<Boolean> rst = shop.buy(1, cnt);
						if(rst == null || rst.getCode() != Resp.CODE_SUCCESS) {
							if(rst != null) {
								//系統組錯誤
								logger.info(rst.getMsg()+"," + rst.getCode()+",idx:"+cnt);
							} else {
								logger.info("Timeout:" + cnt);
							}
						}else if(rst.getData()) {
							//業務購買失敗
							logger.info("Success idx: " + cnt);
						}else {
							//成功
							logger.info("Fulure"+rst.getMsg()+"," + rst.getCode()+",idx:"+cnt);
						}
						try {
							TimeUnit.SECONDS.sleep(1);
						} catch (InterruptedException e) {
							logger.error("",e);
						}
						}catch(Throwable e) {
						logger.error(e.getMessage());
					}
				}
		}).start();
		}

  

查看客戶端輸出日誌,重試3次後,最終報超時失敗,因為服務端buy方法睡了30秒,所以每個請求都肯定會超時!試著調整服務端的超時時間,讓一部份請求成功或重試成功。

重試3次,最終超時失敗

除了可以在終端看日誌外,還可以通過日誌文件看,日誌文件的位置配置在log4j配置文件中,默認為當前工作目錄下的logs/work.log文件。如下圖:

日誌文件 位置

超時重試存問題

必須要說明的是並非全部服務都需要重試或都能接受重試,只有冪等服務才能接受重試。所謂冪等是:以同一參數調用多次得到的效果相同,則稱服務是冪等服務。比如x++這個操作,每次調用的結果都不相同,則不是冪等,x=y+1則是冪等操作。結合業務場景,在技術實現中,往往通過去重的方式讓服務達到冪等的要求,這樣就可以讓服務接受重試,提高服務的可用性和一致性要求。

另一方面,在超時重試失敗率達到一定量後,比如30%的重試都失敗,則說明我們的服務很有可能出現了問題,此時如再做正常的重試,很有可能會壓垮整個服務系統(雪崩),這個又需要服務熔斷和降級來避免,下回分享JMicro熔斷和降級等內容。

如果你覺得JMicro對你有幫助,麻煩移步到Github上給個星!