
前言
上一篇文章分享了單機(jī)模式下,websocket的基本使用方法,但在實(shí)際的業(yè)務(wù)中,通常是不會(huì)這樣使用的,大部項(xiàng)目都是分布式部署的,一個(gè)工程布署了多個(gè)服務(wù)節(jié)點(diǎn),前端并不直接請(qǐng)求具體服務(wù)節(jié)點(diǎn),而是先到nginx或其他代理服務(wù)器,通過nginx的負(fù)載均衡機(jī)制再轉(zhuǎn)發(fā)到具體的服務(wù)節(jié)點(diǎn)。這種場(chǎng)景下,產(chǎn)生了幾個(gè)問題:
第一,前端發(fā)起websocket連接時(shí),是應(yīng)該先連接到nginx,再由nginx轉(zhuǎn)發(fā)到具體的節(jié)點(diǎn)?還是直接連接到具體的服務(wù)節(jié)點(diǎn)?
第二,nginx作為后端服務(wù)節(jié)點(diǎn)的代理者,nginx如何配置才能實(shí)現(xiàn)websocket協(xié)議的連接請(qǐng)求的轉(zhuǎn)發(fā)?
第三,同一瀏覽器端發(fā)起的同一個(gè)websocket請(qǐng)求的session如何在多個(gè)服務(wù)節(jié)點(diǎn)之間實(shí)現(xiàn)共享?
這篇文章來和大家分享一下,實(shí)際業(yè)務(wù)開發(fā)中如何解決上述問題。
分布式場(chǎng)景下的websocket的工作流程
實(shí)際業(yè)務(wù)開發(fā)中,大部分項(xiàng)目都是采用前后端分離的開發(fā)方式和部署方式,對(duì)于http請(qǐng)求來說,通常是瀏覽器端作為客戶端向服務(wù)器端發(fā)起http請(qǐng)求,http請(qǐng)求會(huì)先到nginx,經(jīng)過nginx的負(fù)載均衡機(jī)制處理后,最終會(huì)把http請(qǐng)求轉(zhuǎn)發(fā)到眾多服務(wù)節(jié)點(diǎn)中的一個(gè),完成業(yè)務(wù)邏輯處理后,把結(jié)果響應(yīng)到客戶端,客戶端收到數(shù)據(jù)后渲染在瀏覽器中。
而對(duì)于websocket協(xié)議來說,分布式場(chǎng)景下的工作流程和http也比較類似,只是協(xié)議本身來說各有各的不同,前端瀏覽器在向服務(wù)器端發(fā)起websocket連接時(shí),連接請(qǐng)求也是先到nginx上,由nginx來完轉(zhuǎn)發(fā),最終會(huì)有一個(gè)服務(wù)節(jié)點(diǎn)與瀏覽器端建立websocket連接;
瀏覽器端與服務(wù)器端建立websocket連接后,瀏覽器端和服務(wù)器端就之間操持了一個(gè)真正的長(zhǎng)連接,瀏覽器端可以給服務(wù)器端發(fā)送消息,服務(wù)器端也可以給瀏覽器端發(fā)送消息了;

nginx與websocket
nginx從1.3版本開始是支持websocket協(xié)議的,雖然websocket協(xié)議與http協(xié)議不同,但是websocket建立連接前的握手與http是兼容的,HTTP升級(jí)機(jī)制使用Connection頭Upgrade將連接從http升級(jí)到websocket;

如果nginx要實(shí)現(xiàn)分布式場(chǎng)景下websocket連接的轉(zhuǎn)發(fā),需要額外的配置,實(shí)現(xiàn)瀏覽器端和服務(wù)器端之間建立隧道,額外的配置即Upgrade的相關(guān)配置,nginx.conf的配置如下:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream lezu{
#模擬的服務(wù)節(jié)點(diǎn)1
server 127.0.0.1:8080 weight=1;
#模擬的服務(wù)節(jié)點(diǎn)2
server 127.0.0.1:8081 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://lezu;
# 重點(diǎn)的兩行配置
# 將nginx的請(qǐng)求頭從http升級(jí)到websocket.
proxy_set_header Upgrade $http_upgrade;
#進(jìn)行nginx連接websocket
proxy_set_header Connection "upgrade";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
瀏覽器端在發(fā)起websocket連接請(qǐng)求時(shí),請(qǐng)求的服務(wù)器端地址是nginx的代理地址,連接建立后的消息發(fā)送、接收以及連接關(guān)閉等具體的使用方法和單機(jī)模式下是一模一樣的;
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>websocket測(cè)試</title>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
<input type="button" name="" id="open" value="建立連接"/>
<input type="button" name="" id="close" value="關(guān)閉連接">
<input type="button" name="" id="send" value="發(fā)送信息">
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
$("#open").click(function () {
if (websocket != null) {
if (websocket.readyState == 1) {
throw "連接已建立!"
}
}
//判斷當(dāng)前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
//nginx的代理地址
websocket = new WebSocket("ws://172.18.229.61/websocket/gaox2");
} else {
alert('Not support websocket')
}
//連接發(fā)生錯(cuò)誤的回調(diào)方法
websocket.onerror = function () {
document.getElementById('message').innerHTML += ("發(fā)生錯(cuò)誤") + '<br/>';
};
//連接成功建立的回調(diào)方法
websocket.onopen = function (event) {
document.getElementById('message').innerHTML += ("建立連接") + '<br/>';
console.log("建立連接");
}
//接收到消息的回調(diào)方法
websocket.onmessage = function (event) {
console.log(event.data);
document.getElementById('message').innerHTML += event.data + '<br/>';
}
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function () {
document.getElementById('message').innerHTML += ("關(guān)閉連接") + '<br/>';
console.log("關(guān)閉連接");
}
//監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常。
window.onbeforeunload = function () {
alert("已關(guān)閉連接");
websocket.close();
}
})
var i = 0;
$("#send").click(function () {
for (let j = 0; j < 100; j++) {
i = i + 1;
websocket.send("hello" + i);
}
})
$("#close").click(function () {
websocket.close();
i=0;
})
</script>
</html>
session共享
通過nginx的關(guān)于websocket協(xié)議的相關(guān)配置后,瀏覽器端與服務(wù)器端一個(gè)具體的服務(wù)節(jié)點(diǎn)建立了websocket連接,但這里需要特別注意的是,如果服務(wù)器端主動(dòng)發(fā)消息給瀏覽器端就會(huì)用到session,這個(gè)session是javax.websocket.Session,是不能序列化的,所以不能像http請(qǐng)求中把session放入到緩存中間件中來實(shí)現(xiàn)共享,這樣就會(huì)出現(xiàn)下面的情況:兩個(gè)瀏覽器端用戶A和B可能會(huì)連接到兩個(gè)不同的服務(wù)節(jié)點(diǎn)上,這個(gè)時(shí)候如果用戶A想給用戶B發(fā)一個(gè)消息就有問題了,用戶A的websocket連接的session在服務(wù)節(jié)點(diǎn)1,用戶B的websocket連接的session在服務(wù)節(jié)點(diǎn)2,session又是jvm級(jí)別的,無法實(shí)現(xiàn)共享,那么用戶A和B之間是無法通信的。

解決這個(gè)問題的思路也很簡(jiǎn)單,實(shí)際上是沒有太好的辦法了,具體就是:
1、在上一篇文章里,瀏覽器端與服務(wù)器端成功建立websocket連接后,會(huì)把連接信息包含session緩存起來;
2、用戶A的消息發(fā)送到服務(wù)器端的服務(wù)節(jié)點(diǎn)1時(shí),服務(wù)節(jié)點(diǎn)1利用第三方的消息中間件的發(fā)布訂閱模式,這里以redis為例,對(duì)其他節(jié)點(diǎn)廣播該消息;
3、所有的服務(wù)節(jié)點(diǎn)都訂閱這個(gè)主題的消息,收到訂閱消息的服務(wù)節(jié)點(diǎn),再根據(jù)消息的接收方身份標(biāo)識(shí)查找接收方與服務(wù)器端的session,如果接收方與服務(wù)器端的連接沒有中斷,一定可以找到用戶B與服務(wù)器端的websocke連接的session,找到之后就可以把消息發(fā)送給用戶B了;

WebSocket業(yè)務(wù)場(chǎng)景
基于websocket的通信特點(diǎn),大概的應(yīng)用場(chǎng)景有:
1、在線聊天,實(shí)時(shí)消息發(fā)送;
2、視頻彈幕實(shí)時(shí)更新;
3、重要通知、資訊的實(shí)時(shí)更新;
當(dāng)然,實(shí)際的業(yè)務(wù)場(chǎng)景不止這些,有的場(chǎng)景需要考慮websocket session共享的問題,有的則不需要,但是在設(shè)計(jì)相關(guān)業(yè)務(wù)功能實(shí)現(xiàn)方案的時(shí)候,一定得考慮到websocket session共享的問題;
總結(jié)
至此,websocket的使用方法以及如何應(yīng)用到實(shí)際的業(yè)務(wù)開發(fā)中的坑算是填平了。在這兩篇文章的輸出過程中,除了websocket本身,我get到了兩點(diǎn):
1、學(xué)習(xí)是一個(gè)循序漸進(jìn)的過程,先學(xué)會(huì)基本使用,再深入研究更高級(jí)的用法,會(huì)比較容易且符合自然的思考邏輯;
2、任何一個(gè)相對(duì)比較新的東西,如果沒有正式應(yīng)用到生產(chǎn)的經(jīng)驗(yàn),則一定要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景反復(fù)思考、認(rèn)證、測(cè)試,做出最合理的設(shè)計(jì);文章來源:http://www.zghlxwxcb.cn/news/detail-480571.html
其實(shí)這篇文章還有另外一個(gè)坑未真,那就是只考慮了session共享的問題,還未考慮實(shí)際業(yè)務(wù)場(chǎng)景中高并發(fā)高可用情況下,websocket資源消耗與評(píng)估的問題,這個(gè)坑就靠開發(fā)和測(cè)試的同學(xué)在做壓力測(cè)試的時(shí)候填上了。文章來源地址http://www.zghlxwxcb.cn/news/detail-480571.html
到了這里,關(guān)于Springboot怎么實(shí)現(xiàn)WebSocket通信(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!