热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

kubernetes下实现socket.io的集群模式

2019独角兽企业重金招聘Python工程师标准socket.io单节点模式是很容易部署的,但是往往在生产环境一个节点不能满足业务需求,况且还要保

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

socket.io 单节点模式是很容易部署的,但是往往在生产环境一个节点不能满足业务需求,况且还要保证节点挂掉的情况仍能正常提供服务,所以多节点模式就成为了生成环境的一种必须的部署模式。

本文将介绍如何在kubernetes 集群上部署多节点的socket.io服务。

文章中涉及到的代码可以前往https://github.com/cnych/k8s-socketio-cluster-demo查看。

问题

现在正在准备将线上环境一步步迁移到kubernetes 集群上,这样我们可以根据实际情况部署多个POD 来提供服务,但是socket.io服务并不是单纯的无状态应用,只需要将POD 部署成多个就可以正常提供服务了,因为其底层需要建立很多连接来保持长连接,但是这样的话上一个请求可能会被路由到一个POD,下一个请求则很有可能会被路由到另外一个POD 中去了,这样就会出现错误了,如下图:

socket-io errors

socket-io errors

从上面的错误中我们可以看出是有的请求找不到对应的Session ID,也证明了上面提到的引起错误的原因。

解决方法

我们从socket.io 官方文档中可以看到对于多节点的介绍,其中通过Nginxip_hash 配置用得比较多,同一个ip 访问的请求通过hash 计算过后会被路由到相同的后端程序去,这样就不会出现上面的问题了。我们这里是部署在kubernetes集群上面的,通过traefik ingress来连接外部和集群内部间的请求的,所以这里中间就省略了Nginx这一层,当然你也可以多加上这一层,但是这样显然从架构上就冗余了,而且还有更好的解决方案的:sessionAffinity(也称会话亲和力)

什么是sessionAffinity? sessionAffinity是一个功能,将来自同一个客户端的请求总是被路由回服务器集群中的同一台服务器的能力。

kubernetes中启用sessionAffinity很简单,只需要简单的Service中配置即可:

service.spec.sessionAffinity = "ClientIP"

默认情况下sessionAffinity=None,会随机选择一个后端进行路由转发的,设置成ClientIP后就和上面的ip_hash功能一样了,由于我们使用的是traefik ingress,这里还需要在Service中添加一个traefikannotation:

apiVersion: v1
kind: Service
metadata:name: socket-demonamespace: kube-appsannotations:traefik.backend.loadbalancer.stickiness: "true"traefik.backend.loadbalancer.stickiness.COOKIEName: "socket"labels:k8s-app: socket-demo
spec:sessionAffinity: "ClientIP"ports:- name: socketioport: 80protocol: TCPtargetPort: 3000selector:k8s-app: socket-demo

注意上面的annotationssessionAffinity两项配置,然后我们再来看看我们的socket.io服务吧

socket.io已经正常了吧,注意看上面打印出来的hostname都是一样的,因为我们这里去访问的都是来自同一个IP,多刷新几次是不是还是这样,证明上面的sessionAffinity配置生效了。

如果是另外的地方去访问,会路由到不一样的后端去吗?我们这里启用一个代理来测试下:socket.io从上图中打印出来的hostname可以看出两个请求被路由到了不同的POD 中,但是现在又有一个新的问题了:绘制的图形并没有被广播出去,这是为什么呢?其实在上面提到的socket.io 官方文档中已经提到过了:

Now that you have multiple Socket.IO nodes accepting connections, if you want to broadcast events to everyone (or even everyone in a certain room) you’ll need some way of passing messages between processes or computers.

The interface in charge of routing messages is what we call the Adapter. You can implement your own on top of the socket.io-adapter (by inheriting from it) or you can use the one we provide on top of Redis: socket.io-redis:

var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));

总结起来就是你如果想在进程间或者节点之前发送信息,那么就需要实现自己实现一个socket.io-adapter或者利用官方提供的socket.io-redis

我们这里利用socket.io-redis 这个adapter 来实现消息的广播,最终的服务端代码如下:

const express = require('express');
const socketRedis = require('socket.io-redis');
const os = require('os');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;app.use(express.static(__dirname + '/static'));app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');app.get('/', function (req, res) {res.render('index', {'os': os.hostname()});
});function onConnection(socket){socket.on('drawing', (data) => socket.broadcast.emit('drawing', data));
}io.adapter(socketRedis({host: 'redis', port: 6379}));
io.on('connection', onConnection);http.listen(port, () => console.log('listening on port ' + port));

部署在kubernetes集群上的yaml文件如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:name: socket-demonamespace: kube-appslabels:k8s-app: socket-demo
spec:replicas: 3template:metadata:labels:k8s-app: socket-demospec:containers:- image: cnych/socketdemo:k8simagePullPolicy: Alwaysname: socketdemoports:- containerPort: 3000protocol: TCPresources:limits:cpu: 100mmemory: 100Mirequests:cpu: 50mmemory: 50Mi---
apiVersion: v1
kind: Service
metadata:name: socket-demonamespace: kube-appsannotations:traefik.backend.loadbalancer.stickiness: "true"traefik.backend.loadbalancer.stickiness.COOKIEName: "socket"labels:k8s-app: socket-demo
spec:sessionAffinity: Noneports:- name: socketioport: 80protocol: TCPtargetPort: 3000selector:k8s-app: socket-demo

现在看看最终的效果吧:socket.io cluster

不同节点间也可以传递数据了,到这里我们就实现了在kubernetes集群下部署socket.io多节点。

上面的根据traefik.backend.loadbalancer.stickiness.COOKIEName来进行路由的规则在测试环境生效了,在线上没生效,可能这个地方有什么问题?

上面没有生效是因为客户端连接socket.io的协议的时候没有使用polling造成的,客户端连接socket.io要按照标准的方式指定trasports=[‘polling’, ‘websocket’]

sessionAffinity 与 traefik设置COOKIEName的方式貌似不能同时存在,如果遇到不生效的,将sessionAffinity设置为None ,只保留traefik的annotaions。

在使用socket.io-redis的时候一定要注意,在joinleave房间的时候一定要使用adapter提供的remoteJoinremoteLeave方法,不然多个节点间的数据同步有问题,这个被坑了好久……


转:https://my.oschina.net/xueyi28/blog/3047171



推荐阅读
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 掌握远程执行Linux脚本和命令的技巧
    本文将详细介绍如何利用Python的Paramiko库实现远程执行Linux脚本和命令,帮助读者快速掌握这一实用技能。通过具体的示例和详尽的解释,让初学者也能轻松上手。 ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
  • 使用Python在SAE上开发新浪微博应用的初步探索
    最近重新审视了新浪云平台(SAE)提供的服务,发现其已支持Python开发。本文将详细介绍如何利用Django框架构建一个简单的新浪微博应用,并分享开发过程中的关键步骤。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 本文详细探讨了 JMS (Java Message Service) 中创建会话(session)时使用的 createSession 方法的两个参数 paramA 和 paramB 的不同组合方式及其具体含义,特别是它们如何影响事务处理和消息确认模式。 ... [详细]
  • 本文将介绍如何利用Python爬虫技术抓取国内主流在线学习平台的数据,并以51CTO学院为例,进行详细的技术解析和实践操作。 ... [详细]
  • 深入探讨配置文件的管理与优化
    尽管配置文件的重要性不言而喻,但其管理和安全性问题却常被忽视。本文将详细讨论配置文件的不同管理策略及其优缺点。 ... [详细]
  • Kubernetes 实践指南:初次体验
    本文介绍了如何通过官方提供的简易示例,快速上手 Kubernetes (K8S),并深入理解其核心概念和操作流程。 ... [详细]
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • 本文详细探讨了在微服务架构中,使用Feign进行远程调用时出现的请求头丢失问题,并提供了具体的解决方案。重点讨论了单线程和异步调用两种场景下的处理方法。 ... [详细]
  • 本文详细介绍了JSP(Java Server Pages)的九大内置对象及其功能,探讨了JSP与Servlet之间的关系及差异,并提供了实际编码示例。此外,还讨论了网页开发中常见的编码转换问题以及JSP的两种页面跳转方式。 ... [详细]
  • 本文总结了一次针对大厂Java研发岗位的面试经历,探讨了面试中常见的问题及其背后的原因,并分享了一些实用的面试准备资料。 ... [详细]
author-avatar
mobiledu2502920033
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有