一、需求背景
墨斗开发中,同一个设计器页面,如果已有用户在编辑(或使用中)状态,其他人则不能进行编辑,只有当前这个人离开页面后,其他人才可以进入该页面进行操作,目的是为了避免数据被不同的人修改后页面混乱。
二、实现方式
1、轮循
用户隔一段时间去发送http请求查询是否有人在使用,这种方法不足的地方在于,可能在等待的过程中,编辑权限已经放开了,有时间上的延迟。
2、websocket
当编辑权限放开时,主动推送消息,没有时间上的延迟。
综上所述,websocket是最佳实现方式。
大致思路:
三、遇到问题
生产环境下,我们的域名(model.58corp.com)只对应唯一端口8001,而这个端口是用于http
服务的,如何在一个域名下开放两个访问端口,用来同时支持http和websocket服务就成了我们面临的最大问题。
踩坑过程
🤔在运维测,让同一个域名不通的path来转发到不通的端口上
❌但是,经过不断尝试,发现在云平台一个域名只能转发一个端口
❓部署两个集群?
❌ 只能作为兜底方案,不够实用
💡websocket也能通过http服务监听到,区别在于ws的header中含有upgrade这样我们就可以通过监听upgrade来判断是ws还是http请求。于是我们找到了最终的解决方案
四、解决办法
既然域名只转发8001端口,让websocket和http共用8001端口。
新建集群,要申请,要重新建立项目,ws里的一些http操作的东西要复制出来,造成代码片段重复,在维护上要维护两个项目,为了维护和部署方便起见,尝试共享端口是最佳方式。
1、端口共享原理
websocket是http的升级版(upgrade),我们只需要定制http服务器的upgrade函数即可。让upgrade函数充当websocket的路由器(多个websocket共用同一端口可以通过路由器来转发)。
具体逻辑:在客户端发起websocket连接时,客户端会向浏览器发送一个http请求,该请求的请求头包含Connection:Upgrade和Upgrade:websocket,向服务器请求修改协议为websocket,如果服务器同意修改协议,则会响应一个响应码为101的HTTP报文,至此HTTP的职责已经完成,下面的通讯则会采用websocket协议。(注意:只有在客户端使用实例化的websocket实例传输数据才会采用websocket协议,在此期间客户端仍可以发起其他的http请求,两者并不冲突)
由此可得,只需让http服务处理所有的请求,在遇到Connection:Upgrade和Upgrade:websocket时换用websocket服务即可。(wsServer是websocket实例)
export class WsAdapter implements WebSocketAdapter {
constructor(private app: INestApplicationContext) {}
create(port: number, options: any = {}): any {
console.log('ws create');
const serve = new WebSocket.Server({ port, ...options });
// websocket服务
(global as any).wsServer = serve;
return serve;
}
//...
//...
}
// http服务
const server = await app.listen(8001);
// 监听upgrade
server.on('upgrade', (request, socket, head) => {
(global as any).wsServer.handleUpgrade(request, socket, head, (ws) => {
(global as any).wsServer.emit('connection', ws, request);
});
});
2、环境部署
问题1:由于我们部署的正式环境是有负载均衡的(即集群规模>1),这就导致websocket连接的所有会话不能全部拿到,如何解决?
思路就是websocket连接只走一个固定的集群,且不能影响http的负载均衡,我们能做到的就是加一个流量分组ws,且里面只有一个集群,只要是websocket连接只走这个流量池。
问题2:如何做到websokcet只走ws流量分组?
通过路由区分,增加路由/ws。即【域名+/ws】走ws的流量池。
参考[1]:nodejs中,实现HTTP协议和WS协议复用同一端口
参考[2]:websocket和http共用端口的原理