項(xiàng)目背景
Server端使用netty 4,Client端使用websocket與server交互袁辈。
client與server有兩種交互方式(如下圖):
從圖中可以看出菜谣,對(duì)于 server -> client B(標(biāo)紅的那段),簡單來說分兩種:
1晚缩、client B 主動(dòng)請(qǐng)求后的response
2尾膊、server的主動(dòng)push
亂序問題
無論是response還是push,在服務(wù)端都是使用netty的ctx.channel().writeAndFlush()荞彼。但是在并發(fā)情況下冈敛,發(fā)現(xiàn)某些消息到達(dá)客戶端是亂序的,如下圖鸣皂。
經(jīng)過一段追查后終于找到原因抓谴,是netty線程模型搞的鬼。netty線程模型的介紹可以參見Netty版本升級(jí)血淚史之線程篇的第6點(diǎn)寞缝。
簡單來說就是癌压,netty 4中,使用ctx.channel().writeAndFlush()時(shí)荆陆,會(huì)去檢查?當(dāng)前線程 是否為 netty為channel分配的eventLoop所對(duì)應(yīng)的線程滩届,如果是的話就直接調(diào)用,不是的話就封裝成一個(gè)task提交到eventLoop的任務(wù)隊(duì)列中被啼。
對(duì)應(yīng)到本case中帜消,因?yàn)閷?duì)于request - response方式棠枉,我是直接在netty的work線程(netty為channel分配的eventLoop所對(duì)應(yīng)的線程)中直接處理request,并回response泡挺。所以對(duì)于給Client B的response辈讶,調(diào)用Client B的ctx.channel().writeAndFlush()是直接寫出,只要在業(yè)務(wù)層面上保證調(diào)用ctx.channel().writeAndFlush()是有序的娄猫,那么寫出到Client B就是有序的贱除。
但是對(duì)于push方式,是在其他線程中調(diào)用Client B的ctx.channel().writeAndFlush()稚新,這時(shí)候會(huì)把消息封裝成task提交到Client B的eventLoop任務(wù)隊(duì)列中,與response消息混在一起跪腹。在并發(fā)情況下褂删,即便在業(yè)務(wù)層上做了有序性的保證,但無法去保證netty是先執(zhí)行task的消息冲茸,還是先執(zhí)行那種直接調(diào)用ctx.channel().writeAndFlush()的消息屯阀。
解決方案
業(yè)務(wù)層面上,對(duì)消息增加時(shí)序性保證的id(我這里采用的是遞增id)轴术,端上接收到消息后难衰,按照id去做消息重排序,這樣就能保證消息按序到達(dá)逗栽。