目的
爲了保證客戶端的請求是順序發送到服務端的。
實現方法(初版)
ctx.writeAndFlush被包裝在用synchronized修飾的send方法中,客戶端統一調用sendRequest方法。
有多個線程使用sendRequest方法
1.eventLoop處理channelRead的結果,並調用sendRequest發送到服務端。
2.業務線程(如心跳線程,http請求對應的線程)去調用ctx.writeAndFlush
造成的後果
從客戶端看日誌是順序的,但是服務端總是收到非連續的請求。
代碼
服務端
檢測客戶端的請求cmdCnt,如果小於當前服務端的值,那麼就認爲是過期的命令
客戶端
同步的sendRequest方法,保證不同線程都是順序執行,cmdCnt順序增長。
除了EventLoop線程,業務線程也會調用到sendRequest
EventLoop線程執行sendRequest
EventLoop處理任務流程
原因
這是EventLoop的一個特性:【非EventLoop線程A】調用ctx.writeAndFlush時,不會直接執行,而是放入taskQueue隊列中(等待之後eventLoop線程去執行)。
當【非EventLoop線程A】執行同步的sendRequest並調用ctx.writeAndFlush的同時。 EventLoop線程此時如果正“忙”,也調用到sendRequest,但被synchronized阻塞了。 一旦 A線程將本次發送的內容打包爲taskB到taskQueue(EventLoop->SingleThreadEventExecutor裏面的taskQueue)。那EventLoop會在A線程offerTask完成,並釋放synchronized對應的對象鎖後。先執行本次sendRequest從taskQueue取出
代碼請求流程
由於EventLoop即處理了channelRead(讀) 以及後面的sendRequest(業務操作),
又處理writeAndFlush(寫)。
解決方法
全部改爲非業務線程去執行同步的sendRequest方法,去執行ctx.writeAndFlush,最終都提交給隊列。那麼順序提交到隊列,就順序取出發送到服務端。避免了EventLoop線程直接執行writeAndFlush。
解決問題的代碼
注意,內部的sendRequest纔是同步方法。