数据实时可视化——实时交互(websocket、SSE)
在许多场景下,数据分析需要做实时可视化,以满足实时监测的需要。实时可视化的前端可视化库有不少选择,如echarts,但不管前端用什么可视化库,前端与后端通信总是一个需要处理的问题,一般的处理方式有轮询,长连接,websocket,sse等。这几种处理方式有各自不同特点。
轮询
与后端做实时交互首先想到的方案是轮询。轮询是最直接的,就是设定一个间隔时间,每隔一段时间向后端服务器发送请求,刷新前端数据。轮询的缺点是很明显的,因为不知道服务器什么时候有新数据,需要不断发请求,很多请求都是无用请求,而且前端在每次请求都在重新刷新渲染。这样不光会浪费后端资源,前端的不断无用刷新可能影响用户体验,另外间隔时间的设定也是一个问题。但是,轮询有一个优势就是适应性好,iframe或者ajax一般浏览器都支持。
var realTime = {
url: 'realTimeData',
frush: function(){
$.get(this.url, function(data, status){
if(status===200){
console.log(data)
}else{
console.log("Have an err")
}
})
}
}
setInterval(realtime.frush(),1000);
websocket
websocket可能是实时交互最受欢迎的方案。websocket作为一种全双工、双向持久链接通信方案,它有很多优点,相比于轮询,减少了客户端与服务器之间的数据发送量,这样服务器与客户端都可避免不必要的资源消耗。websocket也有不好的地方,首先后端需要支持websocket的框架,前端上有部分浏览器还不支持。
一个心跳检测的websocket例子
function createConnect(url){
try{
if('WebSocket' in window){
var ws = new WebSocket(url)
}else if('MozWebSocket' in window){
var ws = new MozWebSocket(url)
}else{
console.log('浏览器不支持websock')
}
initWs(url);
}catch(e){
reconnect(url)
}
}
function initWs(url){
ws.onopen = function(){
console.log('ws连接成功')
};
ws.onclose = function(){
console.log('ws连接关闭');
reconnect(url)
};
ws.onerror = function(){
console.log('ws连接出错')
reconnect(url)
};
ws.onmessage = function(){
heartCheck.init().start();
if(event.data !== 'pong'){
console.log('接收到信息:'+event.data)
}
}
}
function reconnect(url){
if(lockReconnect) return;
lockReconnect = true;
setTimeout(function(){
creatWsConnect(url);
lockReconnect=false;
},2000);
}
var heartCheck = {
timeout: 60*1000,
timeoutObj:null,
serverTimeoutObj: null,
init: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var that = this;
this.timeoutObj = setTimeout(function(){
ws.send('ping');
that.serverTimeoutObj = setTimeout(function(){
ws.close();
},that.timeout)
},this.timeout)
}
}
var lockReconnect = false;
var ws = null;
var wsUrl = 'ws://';
creatWsConnect(wsUrl);
server端--Python
import asyncio, random
import websockets
async def index(websocket, path):
text = await websocket.recv()
if(text == 'ping'):
await websocket.send('pong')
else:
print(text)
await websocket.send("收到")
start_server = websockets.serve(index, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
sse
sse(server-sentevents, 服务器发送事件)是服务器到客户端的单向连接。sse的优势是,一般浏览器都支持,相对于websocket实现简单,另外特别适合实时可视化的需求,服务器向客户端的单向推送。
用flask-sse的一个例子,是基于Redis发布/订阅,需要安装Redis。
server端
from flask import Flask, render_template
from flask_sse import sse
app = Flask(__name__)
app.config['REDIS_URL'] = "redis://localhost"
app.register_blueprint(sse, url_prefix='/stream')
@app.route('/')
def index():
return render_template('index.html')
HTML
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
</head>
<body>
<h1>flask-sse</h1>
<script type="text/javascript">
var source = new EventSource("{{ url_for('sse.stream') }}")
source.onmessage=function(event){
var data = JSON.parse(event.data);
console.log("收到信息"+data.message);
};
</script>
</body>
</html>
Redis
# flask-sse默认发布订阅频道是sse
# redis-cli
publish sse "{\"data\":{\"message\":\"测试\"}}"
封面来源:Pexels 上的 Kelvin Valerio 拍摄的图片