在分布式環(huán)境下,由于一個(gè)服務(wù)通常會(huì)有多個(gè)實(shí)例揭斧,這就使得客戶(hù)端在多次請(qǐng)求時(shí)莱革,可能會(huì)作用到不同的實(shí)例,造成 Socket Session 找不到問(wèn)題讹开。那怎么解決呢盅视?本文將通過(guò)使用 Redis 發(fā)布訂閱模式解決這一問(wèn)題。
邏輯圖
管理員發(fā)送消息
@GetMapping("/message/send")
public Boolean send(@RequestParam(defaultValue = "1", required = false) Long id,
@RequestParam(defaultValue = "測(cè)試消息", required = false) String content) {
WebsocketUtil.sendDistributed(WebsocketMessage.builder().id(id).content(content).build());
return true;
}
每個(gè)客戶(hù)端連接的 session 都會(huì)存儲(chǔ)到WebsocketUtils類(lèi)Map<ID, WebSocketServer>變量中旦万,這也是造成分布式下發(fā)送消息可能會(huì)找不到session的原因左冬。
Redis 消息發(fā)布與訂閱,實(shí)現(xiàn)每個(gè)服務(wù)實(shí)例的消息分發(fā)
public static void sendDistributed(WebsocketMessage websocketMessage) {
StringRedisTemplate template = SpringUtils.getBean(StringRedisTemplate.class);
template.convertAndSend(Topics.CHANNEL_MESSAGE, JSON.toJSONString(websocketMessage));
}
監(jiān)聽(tīng)到 Redis 發(fā)送過(guò)來(lái)的消息
@Component
public class WebsocketMessageListener extends AbstractMessageListener<WebsocketMessage> {
@Override
public void onMessage(WebsocketMessage websocketMessage) {
WebsocketUtil.sendStandalone(websocketMessage);
}
}
WebsocketMessage
為整個(gè)過(guò)程傳遞的消息體纸型,包含 客戶(hù)端編號(hào)(對(duì)應(yīng)上述 Map 的 key 值)和消息內(nèi)容拇砰,就是要向什么人發(fā)送什么內(nèi)容的消息梅忌。
Websocket 進(jìn)行單機(jī)消息發(fā)送
public static void sendStandalone(WebsocketMessage websocketMessage) {
if (websocketMap.containsKey(websocketMessage.getId())) {
try {
websocketMap.get(websocketMessage.getId()).getSession().getBasicRemote().sendText(websocketMessage.getContent());
} catch (IOException e) {
log.error("服務(wù)端發(fā)送消息【{}】失敗,原因:【{}】", websocketMessage.getId(), e.getMessage());
}
log.info("服務(wù)端發(fā)送消息給客戶(hù)端【{}】成功除破,內(nèi)容為【{}】", websocketMessage.getId(), websocketMessage.getContent());
} else {
log.error("服務(wù)端發(fā)送消息【{}】失敗牧氮,原因:客戶(hù)端【{}】未連接", websocketMessage.getContent(), websocketMessage.getId());
}
}
找到存儲(chǔ)在本地緩存中的 WebSocketServer(對(duì)應(yīng)上述 Map 的 value 值) 進(jìn)行單機(jī)消息發(fā)送。
測(cè)試結(jié)果
思考與拓展瑰枫?
1踱葛、消息的可靠性,redis 消息發(fā)布光坝,客戶(hù)端沒(méi)有收到問(wèn)題尸诽?持久化,未發(fā)送的消息繼續(xù)發(fā)送或使用更加可靠的消息中間件
2盯另、消息重復(fù)消費(fèi)問(wèn)題性含?重復(fù)處理
3、websocket掉線重連鸳惯? 客戶(hù)端處理商蕴,在每次掉線的時(shí)候延遲重連
由于作者學(xué)識(shí)有限,文中如有不足之處或有需要改進(jìn)和優(yōu)化的地方芝发,不吝賜教绪商。
參考文章
如何Redis解決WebSocket分布式場(chǎng)景下的Session共享問(wèn)題
WEBSOCKET 在線測(cè)試工具