简体   繁体   English

我如何或应该如何微调 Node.js/Socket.io 聊天应用程序,使其不会拖累我的整个网站/服务器?

[英]How Can I or Should I Fine Tune Node.js/Socket.io Chat App So It Doesn't Drag Down My Entire Website/Server?

I built a chatroom w/ NodeJs using, socket.io, mysql2 & express packages.我使用 socket.io、mysql2 和 express 包构建了一个带有 NodeJ 的聊天室。 It's extremely dynamic including public chats, private messaging, and other unique features to make it lively, ie real time updates, notifications, etc.它非常动态,包括公共聊天、私人消息和其他使其生动的独特功能,即实时更新、通知等。

Originally built and tested on a dummy domain and was working perfectly fine.最初在一个虚拟域上构建和测试,并且运行良好。 And then I moved it over to my main website domain when finished.完成后,我将其移至我的主要网站域。 Now that it is, I'm experiencing horrible site load speeds.既然如此,我正在经历可怕的网站加载速度。 However, both domains (the test domain site and main live site were on the same server).但是,两个域(测试域站点和主要实时站点位于同一台服务器上)。

The server;服务器; 2x Intel® Xeon® Processor E5-2697 v4, 64GB RAM, 4x 480GB SSD, HW RAID storage, Network port 1 Gbps, 2 ipv4 addresses, bandwidth 50 TB, Centos 7, cpanel included and have. 2 个英特尔® 至强® 处理器 E5-2697 v4、64GB RAM、4 个 480GB 固态硬盘、硬件 RAID 存储、1 Gbps 网络端口、2 个 ipv4 地址、50 TB 带宽、Centos 7,包括并具有 cpanel。 About 1000 users actively use the website/chat.大约 1000 名用户积极使用该网站/聊天。 The chatroom is negatively impacting the rest of the site and slowing it all down.聊天室正在对该站点的 rest 产生负面影响并减慢速度。 Initial server response is roughly 14 seconds, but in worst case scenarios has been as long as 30+ seconds.初始服务器响应时间大约为 14 秒,但在最坏的情况下长达 30 多秒。 Whereas on the test domain it was only literally 1 second.而在测试域上,它实际上只有 1 秒。 It goes without saying I should expect as good a load times as on the original test domain as a live one with real users, but this bad?不用说,我应该期望在原始测试域上的加载时间与真实用户的实时加载时间一样好,但这很糟糕吗? I digress.我跑题了。

I ran speed tests.我进行了速度测试。 The results of those tests kept reporting back to me the initial load time was too long and literally said nothing in specific to point me to what the problem was so I assumed it was MYSQL, php, among other things.这些测试的结果不断向我报告初始加载时间太长,并且没有具体说明问题所在,所以我认为它是 MYSQL、php 等。 I optimized it all to the best of my ability.我尽我所能对它进行了优化。 But after the site kept dragging, I still kept getting the same results.但是在网站不断拖后,我仍然得到相同的结果。 I finally removed things from the site one by one and when I removed the chat, the site loaded perfectly fine.我终于从网站上一一删除了东西,当我删除聊天时,网站加载得很好。 It was odd, because none of the speed tests pointed to the chat or node in particular being the reasoning.这很奇怪,因为没有任何速度测试表明聊天或节点特别是推理。

So then I optimized Node in the following ways.于是我就通过以下方式优化了Node。 -I tried adding pooling to the sql connection, trying different numbers, from 5-500, -I tried adding a PoolManager to the database connection. -我尝试将池添加到 sql 连接,尝试不同的数字,从 5 到 500,-我尝试将 PoolManager 添加到数据库连接。 -I removed the ability for all visitors to stay connected to our node server. -我删除了所有访问者保持连接到我们的节点服务器的能力。 They're socket connection would be terminated if they didn't mean certain cretia.如果它们并不意味着某些 cretia,它们的套接字连接将被终止。 -I added some parallelize to the code. -我在代码中添加了一些并行化。 To run some functions that called the database.运行一些调用数据库的函数。 -I made sure to add keys and avoided long sql queries, used LIMIT, and avoided * -我确保添加密钥并避免长 sql 查询,使用 LIMIT,并避免 *

Put it back on the site, still the same horrible load times.把它放回网站上,仍然是同样可怕的加载时间。

After some research I am hopeful (but still only guessing) that the following may be cause for concern;经过一些研究,我希望(但仍然只是猜测)以下可能引起关注;

-The server is defaulting to polling. - 服务器默认为轮询。 So maybe that might be causing an issue.所以也许这可能会导致问题。 Since I assume everytime it polls it has to connect and disconnect from the database server which will cause a pretty big delay since I read that a lot of issue are there.因为我假设每次轮询它都必须连接和断开与数据库服务器的连接,这将导致相当大的延迟,因为我读到存在很多问题。 -I tried to fixing the polling but have not been able to get it to work. -我试图修复投票,但无法使其正常工作。 I tried forcing transport:我尝试强制运输:

['websocket'] ['网络套接字']

, with all different types of settings, CORS, secure, and etc. ,具有所有不同类型的设置,CORS,安全等。

I read into possibly using Redis for caching and I think it can be implemented in some areas for sure.我读到可能使用 Redis 进行缓存,我认为它肯定可以在某些领域实现。 But with how lively the chat is.但是聊天是多么的热闹。 Cache might be limited in how often it can be used.缓存的使用频率可能会受到限制。

If there are any problems with my own code I might guess it is here.如果我自己的代码有任何问题,我可能会猜到它在这里。


//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.但在一天结束时,我仍然不知所措,所以我仰望你,让自己受 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. @Gaëtan Boyals - 您要求的有关处理消息的位置的附加代码,如下所示。

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. GET 和 SET 消息。

Your 12-core server is not very large at all, at least when combining multiple applications.您的 12 核服务器根本不是很大,至少在组合多个应用程序时是这样。 Your mileage may vary and greatly in some cases if the database was using all 12-cores and the Node.js app is using 1 core but gets bumped a lot the Node.js app suffers.如果数据库使用所有 12 核并且 Node.js 应用程序使用 1 核但在 Node.js 应用程序遭受的冲击很多,则在某些情况下,您的里程可能会发生很大变化。

Now I don't know how large your site is how many users hit the site, but 12-cores of power is a lot of power...现在我不知道您的网站有多大,有多少用户访问该网站,但是 12 核的电源是很多电源...

1 core of power my HTTP server can handle a million request an hour with "many timeouts" this though would be hitting a single core database on a different server. 1 个核心电源 我的 HTTP 服务器每小时可以处理一百万个请求,但会出现“多次超时”,但这会影响不同服务器上的单个核心数据库。 So if we do the math here, that's quite a lot of requests we got there and you can benchmark to see yourself.因此,如果我们在这里进行数学运算,我们会收到很多请求,您可以进行基准测试以了解自己。


With all that said, you shouldn't have a problem.综上所述,您应该没有问题。 More detail would be required and I can edit this.需要更多细节,我可以编辑它。

As far as I can tell going through that I believe somehow your server is filling to create web socket connections and use polling as a fallback.据我所知,我相信您的服务器正在以某种方式填充以创建 web 套接字连接并使用轮询作为后备。 Which basically means a huge overhead.这基本上意味着巨大的开销。 Can you perhaps separate the chat functionality to a separate node application and possibly containerize it?.您能否将聊天功能分离到单独的节点应用程序并可能将其容器化? Make the initial site load and then load the chat feature until you figure out what exactly is the root cause for this.进行初始站点加载,然后加载聊天功能,直到您弄清楚根本原因是什么。

The user @S1ckhead already partly said what I'm about to explain, but I'll try to go more into details.用户@S1ckhead 已经部分说明了我要解释的内容,但我会尝试更详细地介绍 go。

As I said in the comments, keep in mind that it's what I think is happening, but then again, @S1ckhead seems to have roughly the same idea as me so you might want to read on, even if it won't provide a full-baked solution.正如我在评论中所说,请记住这是我认为正在发生的事情,但话又说回来,@S1ckhead 似乎与我的想法大致相同,所以你可能想继续阅读,即使它不会提供完整的-烘焙的解决方案。

This is not a hardware-related problem不是硬件相关的问题

Of course, we can brag about our personal set-ups and since we're at it, if I can run a self-hosted, bare-metal K8s cluster with three 2006 Dell PCs handling a Redis instance, a MongoDB instance and about 30 micro-services, I think the OP can run a PHP site, a NodeJS service and a MySQL instance with the specs he gave in his question just fine .当然,我们可以吹嘘我们的个人设置,既然我们已经这样做了,如果我可以运行一个自托管的裸机 K8s 集群,其中三台 2006 戴尔 PC 处理一个 Redis 实例、一个 MongoDB 实例和大约 30微服务,我认为 OP 可以运行 PHP 站点、NodeJS 服务和MySQL实例以及他在问题中给出的规范。

He mentioned contacting his host about the issue and asked them if he should upscale, they said no and I think they are the most qualified people to say so.他提到就这个问题联系他的主人并问他们是否应该升级,他们说不,我认为他们是最有资格这么说的人。

Also, imagine running only an instance of anything (Php, MySQL, you name it) per server... We're not all Bill Gates, and there's no need for us to be.另外,想象一下每台服务器只运行一个实例(Php,MySQL,你的名字)......我们并不都是比尔盖茨,我们也没有必要这样做。

Reverse-proxy?反向代理? Routing?路由?

I will assume you have basic or intermediate level when it comes to Sys Admin / Networking, since you know how to set-up domains and link it to your server.我假设您在系统管理员/网络方面具有基本或中级水平,因为您知道如何设置域并将其链接到您的服务器。

A reverse proxy is basically a software that routes the client request to specific applications.反向代理基本上是一种将客户端请求路由到特定应用程序的软件。 For example, let's say you have 3 domains (domainA.com, domainB.com, domainC.com), all pointing to the same IP address (so basically, the same machine).例如,假设您有 3 个域(domainA.com、domainB.com、domainC.com),都指向同一个 IP 地址(所以基本上是同一台机器)。 How does the server know that domainA should hit the PHP site, and domainB should hit a NodeJS service?服务器如何知道 domainA 应该访问 PHP 站点,而 domainB 应该访问 NodeJS 服务? That's where the reverse proxy comes in handy.这就是反向代理派上用场的地方。 That is very roughly explained, but you can find more explanation here and if you are like me and prefer graphic representations, here is one:这是非常粗略的解释,但你可以在这里找到更多解释,如果你像我一样喜欢图形表示,这里有一个:

反向代理图形表示

Now I'm not going to lie, I am not familiar at all with cPanel, PHP and such, I usually go the no-admin-tools route and host NodeJS-based apps, but you can surely set-up a reverse proxy with your set-up.现在我不会撒谎,对 cPanel、PHP 等一无所知,我通常使用 go 无管理员工具路由并托管基于 NodeJS 的应用程序,但您当然可以使用你的设置。

What is the real problem真正的问题是什么

Again, mentioning @S1ckhead, Socket.IO is probably using long-polling as a default, but why?再次提到@S1ckhead,Socket.IO 可能默认使用长轮询,但为什么呢?

Since in your code, you make socketIO listen on your main domain: const chat = io.connect("domain.com", {path: "/node/socket"});由于在您的代码中,您让 socketIO 在您的主域上侦听: const chat = io.connect("domain.com", {path: "/node/socket"}); , the WebSocket requests will probably go to your PHP app. ,WebSocket 请求可能会 go 到您的 PHP 应用程序。 But your PHP app is probably not configured to handle WebSockets, so it defaults to long-polling.但是您的 PHP 应用程序可能未配置为处理 WebSocket,因此它默认为长轮询。 You can read more here , but basically, stating this site:你可以在这里阅读更多内容,但基本上,说明这个网站:

The server holds the request open until new data is available.服务器保持请求打开,直到有新数据可用。 Once available, the server responds and sends the new information.一旦可用,服务器就会响应并发送新信息。

So since it holds the request until something new happens, it could explain why you experience this extremely laggy feeling.因此,由于它会一直保留请求,直到发生新的事情,它可以解释为什么您会体验到这种极其滞后的感觉。

What should you do?你该怎么办?

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.好吧,您可以 go 两条路线,第一种也是最简单的路线是借用新服务器,购买另一个域或子域,将两者链接在一起,在此处托管您的 NodeJS 应用程序并将您的主站点链接到这个新域/子域。

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 mentor/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 的最后说明

Keep in mind that Socket.IO is a wrapper around the Web Socket protocol , and that it will only work with its client/server counterpart (meaning you can't wire up your own WebSockets client implementation with a Socket.IO server).请记住 Socket.IO 是围绕Web Socket 协议的包装器,并且它只能与它的客户端/服务器对应物一起使用(这意味着您不能将您自己的 WebSockets 客户端实现与 Socket.ZCF5BFFZAB9 连接起来)

While I dislike the fact that you can't separate Socket.IO's client from its server, I also like the level of abstraction it provides.虽然我不喜欢无法将 Socket.IO 的客户端与其服务器分开这一事实,但我也喜欢它提供的抽象级别。 Of course, you could go with your own implementation if you're up to the challenge, but it's in my opinion not worth it as it will not improve the performances (and can, in fact, worsen them if the implementation is poorly written) by that much .当然,如果您能应对挑战,您可以使用自己的实现 go,但在我看来这是不值得的,因为它不会提高性能(事实上,如果实现写得不好,可能会使性能恶化)这么多

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在php站点上的专用node.js服务器上使用Socket.io? - How would I use Socket.io on a dedicated node.js server on a php site? 如何在带有Node.js / Socket.IO的PHP中使用popen? - How can I use popen in PHP with Node.js/Socket.IO? Socket.io无法在nginx + node.js + php app中连接 - Socket.io can't connect in nginx + node.js + php app 我们如何在不使用node.js的情况下使用socket.io进行核心php中的实时聊天对话 - How can we use socket.io for real time chat conversation in core php without using node.js 在我的 PHP 网站旁边实现 node.js 和 socket.io - Implementing node.js and socket.io alongside my PHP website 如何在node.js服务器上的PHP Web服务器和Socket.io之间创建握手? - How to create a handshake between PHP web server and Socket.io on node.js server? 如何在不重新加载 socket.io 的情况下在聊天框上进行聊天显示 - how can I make chat show on the chat box without reloading on socket.io Node.js和socket.io用于通知栏:我是否正确行事? - Node.js and socket.io for a notification bar : Am I going the right way? 私人聊天消息使用Node.js,Socket.io,Redis in PHP - Private Chat Messaging using Node.js, Socket.io, Redis in PHP 使用用户身份验证创建Node.js + socket.io服务器/客户端,发送/接收数据 - Creating Node.js + socket.io Server/Client with users authentication, sending/receiving data
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM