我使用、socket.io、mysql2 和 express 包建立了一个带有 NodeJs 的聊天室。它非常动态,包括公共聊天、私人讯息和其他使其生动的独特功能,即实时更新、通知等。
最初在一个虚拟域上构建和测验,并且运行良好。完成后,我将其移至我的主要网站域。既然如此,我正在经历可怕的网站加载速度。但是,两个域(测验域站点和主要实时站点位于同一台服务器上)。
服务器; 2 个英特尔? 至强? 处理器 E5-2697 v4、64GB RAM、4 个 480GB SSD、硬件 RAID 存盘、1 Gbps 网络埠、2 个 ipv4 地址、50 TB 带宽、Centos 7、cpanel 包括并拥有。大约 1000 名用户积极使用该网站/聊天。聊天室对网站的其他部分产生了负面影响并减慢了速度。初始服务器回应时间大约为 14 秒,但在最坏的情况下长达 30 多秒。而在测验域上,它实际上只有 1 秒。不用说,我应该期望在原始测验域上的加载时间与真实用户的实时加载时间一样好,但这很糟糕吗?我跑题了。
我进行了速度测验。这些测验的结果不断向我报告初始加载时间太长,并且没有具体说明问题是什么,所以我认为它是 MYSQL、php 等等。我尽我所能对它进行了优化。但是在网站不断拖后,我仍然得到相同的结果。我终于从网站上一一洗掉了东西,当我洗掉聊天时,网站加载得很好。这很奇怪,因为没有任何速度测验表明聊天或节点特别是推理。
于是我就通过以下方式优化了Node。-我尝试将池添加到 sql 连接,尝试不同的数字,从 5 到 500,-我尝试将 PoolManager 添加到数据库连接。-我洗掉了所有访问者保持连接到我们的节点服务器的能力。如果它们并不意味着某些 cretia,它们的套接字连接将被终止。-我在代码中添加了一些并行化。运行一些呼叫数据库的函式。-我确保添加键并避免长 sql 查询,使用 LIMIT,并避免 *
把它放回网站上,仍然是同样可怕的加载时间。
经过一些研究,我希望(但仍然只是猜测)以下可能引起关注;
- 服务器默认为轮询。所以也许这可能会导致问题。因为我假设每次轮询它都必须连接和断开与数据库服务器的连接,这将导致相当大的延迟,因为我读到存在很多问题。-我试图修复投票,但无法使其正常作业。我尝试强制运输:
['网络套接字']
,具有所有不同型别的设定、CORS、安全等。
我读到可能使用 Redis 进行快取,我认为它肯定可以在某些领域实作。但是聊天是多么的热闹。快取的使用频率可能会受到限制。
如果我自己的代码有任何问题,我可能会猜到它在这里。
//server
const f = require('./functions.js');
const db = require('./db.js');
const cookie = require('cookie');
const http = require("http");
const express = require("express");
const { Server } = require("socket.io");
const port = 3000;
const appBaseUrl = "/node";
const socketBaseUrl = "/node/socket";
global.last_message_time = new Date('1995-12-17T03:24:00');
// Create the server and socket
const expressApp = express();
const nodeServer = http.createServer(expressApp);
const socketIo = new Server(nodeServer, {
path: socketBaseUrl,
});
//client
const chat = io.connect("domain.com", {path: "/node/socket"});
But at the end of the day I am still at a loss and so I look to you and throw myself at the mercy of StackOverflow. Opinions, feedback and ideas as to why the chat is not playing nicely with my website would be more appreciated than you know. Thank you in advance and I look forward to hearing from you.
------------------------------Update
@Ga?tan Boyals - the additional code you asked for concerning where messages are handled, below.
let chats = []
await db.getMessages(data.id, user.user_id, user.isAdmin, 'user')
.then(response=>{
chats = response.reverse();
}).catch((err)=>{
console.log(err);
})
let chats_html = [];
for(let chat of chats){
chat.message_type = chat.type;
chat.images = [];
chat.small = [];
if(chat.type=='photos'){
let encoded = JSON.parse(chat.message);
for(let small of encoded){
let j = f.createToken(small);
chat.small.push(j "?height=150");
chat.images.push(j)
;
}
}
//might cause issues later so see what's up here.
chat.emojis = await db.getEmojiReactions(1, chat.id);
chat.avatar = f.createToken(chat.avatar);
chats_html.push(f.createMessage(chat, user.user_id, user.user_type));
}
let chat_info = await db.getPrivateRoom(data.id, user.user_id);
socket.join(chat_info);
user.current_room = data.id;
user.current_type = 'user';
user.room = chat_info;
let send = {chats:chats_html};
socketIo.to(socket.id).emit('get-chat', send);
let best_badge = await db.bestBadge(data.id);
let user_1 = await db.getUserFromId(data.id);
user_1.badge = best_badge ? best_badge.badge_id:false;
user_1.avatar = f.createToken(user_1.avatar);
let group_user_html = [];
group_user_html.push(f.rightSideUsers(user_1));
group_user_html.push(f.rightSideUsers(user));
socketIo.to(socket.id).emit('right-users', group_user_html);
if(user.current_room != 0){
let isMuted = false;
if(data.type=='group'){
isMuted = await db.isMuted(user.user_id, data.id);
}
if(!isMuted){
data.user_id = user.user_id;
data.to = data.id;
if(data.message_type == 'photos'){
data.message = JSON.stringify(data.message);
}
if(user.user_id==0){
data.guest = user.username;
}
db.newMessage(data)
.then((response)=>{
if(response){
if(data.type=='group'){
db.setChatMeta(response, 'group', data.id,user.user_id);
}else{
db.setChatMeta(response, 'user', data.id,user.room);
}
data.id = response;
data.chat_id = response;
let html = f.createMessage(data, user.user_id, user.user_type);
let new_data = f.createRightData(data);
new_data.message = new_data.message.replace('<br />', '');
let uni = user.room;
let send = {html:html, id:uni,type:data.type}
if(data.message_type == 'video'){
setTimeout(()=>{
socketIo.in(user.room).emit("new-message",send);
socketIo.emit('refresh');
}, 5000)
}else{
console.log(user.room);
socketIo.in(user.room).emit("new-message",send);
socketIo.emit('refresh');
}
}
}).catch((err)=>{
console.log(err)
})
}else{
socketIo.to(socket.id).emit('muted')
}
}
GET and SET messages.
uj5u.com热心网友回复:
据我所知,我相信您的服务器正在以某种方式填充以创建 Web 套接字连接并使用轮询作为后备。这基本上意味着巨大的开销。您能否将聊天功能分离到单独的节点应用程序并可能将其容器化?进行初始站点加载,然后加载聊天功能,直到您弄清楚根本原因是什么。
uj5u.com热心网友回复:
您的 12 核服务器根本不是很大,至少在组合多个应用程序时是这样。如果数据库使用所有 12 核并且 Node.js 应用程序使用 1 核,但在某些情况下,您的里程可能会发生很大变化,但 Node.js 应用程序会受到很大影响。
现在我不知道您的网站有多大,有多少用户访问该网站,但是 12 核的电源是很多电源...
1 个核心电源我的 HTTP 服务器可以每小时处理一百万个请求,但会出现“多次超时”,但这会影响不同服务器上的单个核心数据库。因此,如果我们在这里进行数学运算,我们会收到很多请求,您可以进行基准测验以了解自己。
OP 的问题是他们没有正确使用 Node.JS。他们正在运行使用单核的应用程序的单个实体,并且 HTTPS 与 websockets 共享,这严重限制了服务到 HTTPS/WSS 的单核。
该解决方案正在变得无状态,并将单核 Node.JS 应用程序转变为多核应用程序。这需要一些关于垂直缩放和水平缩放应用程序的知识。因此,有了这些数字,OP 作为回应发布了一个单一的核心,这是直接踢。
祝你好运,由于 12 核并且您没有托管媒体服务器,我现在将这归咎于 NodeJS。干杯。
uj5u.com热心网友回复:
用户@S1ckhead 已经部分说明了我将要解释的内容,但我会尝试更详细地介绍。
正如我在评论中所说,请记住这是我认为正在发生的事情,但话又说回来,@S1ckhead 似乎和我有大致相同的想法,所以你可能想继续阅读,即使它不会提供完整的-烘焙的解决方案。
这不是硬件相关的问题
当然,我们可以吹嘘我们的个人设定,既然我们已经这样做了,如果我可以运行一个自托管的裸机 K8s 集群,其中三台 2006 戴尔 PC 处理一个 Redis 实体、一个 MongoDB 实体和大约 30 个微服务,我觉得OP可以运行PHP的网站,一个服务的NodeJS和MySQL实体与他在他的问题给出了规范就好了。见鬼,当我还是某家公司的员工时,我什至可以用他整个规格的 1/3 的预算来运行 ElasticSearch。
他提到就这个问题联系他的主人并问他们是否应该升级,他们说不,我认为他们是最有资格这幺说的人。
另外,想象一下每台服务器只运行一个实体(Php、MySQL,你的名字)......我们并不都是比尔盖茨,我们也没有必要这样做。
反向代理?路由?
我假设您在系统管理员/网络方面具有基本或中级水平,因为您知道如何设定域并将其链接到您的服务器。
反向代理基本上是一种将客户端请求路由到特定应用程序的软件。例如,假设您有 3 个域(domainA.com、domainB.com、domainC.com),它们都指向同一个 IP 地址(基本上是同一台机器)。服务器如何知道 domainA 应该访问 PHP 站点,而 domainB 应该访问 NodeJS 服务?这就是反向代理派上用场的地方。这是非常粗略的解释,但你可以在这里找到更多解释,如果你像我一样喜欢图形表示,这里有一个:
现在,我不会说谎,我不熟悉,在所有与的cPanel,PHP和这样的,我经常去的基于无的NodeJS -管理工具路线和主机应用程序,但你一定能够建立一个反向代理你的设定。
真正的问题是什么
再次提到@S1ckhead,Socket.IO 可能默认使用长轮询,但为什么呢?
由于在您的代码中,您让 socketIO 监听您的主域:const chat = io.connect("domain.com", {path: "/node/socket"});
,WebSocket 请求可能会转到您的 PHP 应用程序。但是您的 PHP 应用程序可能未配置为处理 WebSockets 请求,只有 HTTP 请求,因此它默认为长轮询。你可以在这里阅读更多内容,但基本上,说明这个网站:
服务器保持请求打开,直到有新资料可用。一旦可用,服务器就会回应并发送新信息。
因此,由于它会一直保留请求,直到发生新的事情,它可以解释为什么您会体验到这种极其滞后的感觉。
你该怎么办?
Well, you could go two routes, the first one and the easiest would be to loan a new server, buy another domain or subdomain, link the two together, host your NodeJS app here and link your main site to this new domain/subdomain.
The other, greatly reducing costs, would be to setup a reverse proxy as previously mentioned. It could prove to be a difficult challenge (I did it with no previous experience myself, but I also had no work obligations/pressure to do it) however. It is up to you and your constraints/requirements to chose which one you want to follow.
Of course, other problems will arise when your pool of user grow larger, and the question of scaling will make sense, but that's not the concern right now.
A final note on Socket.IO
请记住,Socket.IO 是Web Socket 协议的包装器,并且它只能与它的客户端/服务器对应物一起使用(这意味着您不能将自己的 WebSockets 客户端实作与 Socket.IO 服务器连接起来,反之亦然)反之亦然)。这可能意味着您必须撰写自己的 WebSockets 实作,因为我似乎无法在 PHP 中找到 Socket.IO 的最新实作。(我什至不知道这是否可能,再说一次,我不是 PHP 专家)
0 评论