使用Springboot + netty 打造聊天服务之Nacos集群问题记录

news2025/4/12 23:53:08

目录

  • 1、前言
    • 1.1、方法一
    • 1.2、方法二
  • 2、方案二实战
    • 2.1、在netty服务里加上ws连接、中断事件
    • 2.2、在netty服务里加上消息服务
  • 4、总结


使用Springboot + netty 打造聊天服务系列文章
第一章 初始搭建工程
第二章 Nacos集群问题记录


1、前言

在使用Springboot + Nacos + Netty(WebSocket) 集群后,发现了一个问题。
在集群环境下, X用户已经连接上了集群中的A服务器,这时Y用户发送给X用户的消息在B服务器,那么此时的消息应该如何处理呢?
在这里插入图片描述

1.1、方法一

通过广播的模式,把消息发送到MQ(且带有netty的channelId),netty集群的服务都订阅这个MQ。
通过对比channelId,不存在channelId的丢弃消息不处理。存在channelId的服务,处理此消息,并通过channel把消息推送给X用户。
在这里插入图片描述

1.2、方法二

发消息时,去寻找对应用户X的channel。
1、从缓存里获取用户X对应的channelId等信息,首先判断是否在缓存里,如果没有即用户X不在线,用户X下次连接netty服务时,再去推送消息;
2、如果缓存里有,判断此channel是否在当前服务中,
首先判断当前服务里是否有用户X对应的channelId,如果有,直接通过channel发送消息给用户X;
3、如果没有,则去组装IP、端口,去调用此服务的消息服务去发送消息。
在这里插入图片描述

2、方案二实战

方案一非常简单,订阅MQ即可实现,网上案例大多基于此。
我们今天重点讲解方案二,在netty服务里,加入消息服务,后续通过匹配用户X的channel去发送消息;

2.1、在netty服务里加上ws连接、中断事件

在ws连接时,本地服务器加入channelId、channel的缓存;
同时把channelId、本机IP、本机端口放入redis缓存(供远程消息服务调用)。
在这里插入图片描述

2.2、在netty服务里加上消息服务

1、通过传递来的channelId,从缓存里找到对应的服务(IP、端口);
2、调用对应的消息服务(IP、端口加上消息服务的地址)
3、消息服务里,铜鼓
在这里插入图片描述
在这里插入图片描述


import cn.hutool.core.bean.BeanUtil;
import com.qhkj.nettychatserver.bean.domain.Message;
import com.qhkj.nettychatserver.bean.request.MessageRequest;
import com.qhkj.nettychatserver.config.NettyConfig;
import com.qhkj.nettychatserver.config.http.HttpResult;
import com.qhkj.nettychatserver.config.http.HttpResultGenerator;
import com.qhkj.nettychatserver.config.http.HttpStatusEnum;
import com.qhkj.nettychatserver.constant.Common;
import com.qhkj.nettychatserver.constant.NettyCommon;
import com.qhkj.nettychatserver.netty.NettyHandler;
import com.qhkj.nettychatserver.service.MessageService;
import com.qhkj.nettychatserver.util.RedisUtil;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import com.qhkj.nettychatserver.bean.request.NettyMesaage;
import com.qhkj.nettychatserver.service.NettyService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;

@Slf4j
@Service("chat")
public class ChatNettyServiceImpl implements NettyService {

    @Resource
    private MessageService messageService;

    @Resource
    private NettyConfig nettyConfig;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RestTemplate restTemplate;

    // 确定channel之后,发送消息
    private void nettyHandler(NettyMesaage message, Channel channel) {
        log.info("message-> channelId:{}  , nettyName: {}", channel.id(), nettyConfig.getNettyServerName());
        Date now = new Date();
        Message dbmsg = Message.builder()
                .messageId(NettyCommon.getIdWorker().nextId())
                .createTime(now)
                .modifyTime(now)
                .build();
        BeanUtil.copyProperties(message, dbmsg, Common.options);
        boolean flag = messageService.insertOne(dbmsg);
        if (flag) {
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(HttpResultGenerator.success(nettyConfig.getNettyServerName()))));
        } else {
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(HttpResultGenerator.fail(HttpStatusEnum.INTERNAM_SERVER_ERROR.getCode(), nettyConfig.getNettyServerName() + ""))));
        }
    }

    @Override
    public HttpResult nettyHandler(MessageRequest request) {
        NettyMesaage nettyMesaage = new MessageRequest();
        BeanUtil.copyProperties(request, nettyMesaage);

        String serverInfo = (String) redisUtil.get(request.getChannelId());
        if(StringUtils.isEmpty(serverInfo)) {
            log.info("用户不在线!");
            return HttpResultGenerator.success("用户不在线!");
        }

        Channel channel = NettyHandler.channelMap.get(request.getChannelId());
        // 本机与用户有连接
        if(null != channel) {
            this.nettyHandler(nettyMesaage, channel);
        } else {
            String url = "http://" + serverInfo + "/msg/send";
            HashMap jsonObject =  restTemplate.postForObject(url, request, HashMap.class);
            if( !jsonObject.get("code").equals(200) ) {
                log.info("消息发送失败!");
                return HttpResultGenerator.fail(HttpStatusEnum.SERVER_BUSY.getCode(),"消息发送失败");
            }
        }
        return HttpResultGenerator.success("消息发送成功!");
    }
}

4、总结

文章写完之后,发现第二种方法问题特别多,需要在用户上下线(ws连接、掉线、netty服务销毁等)时,使用缓存记录用户与服务器的关系。在消息发送给接收方时,从缓存里取出接收方服务器信息,通过接收方服务器通知接收方有新消息。

最后给出2张消息服务架构简图:
1、集群版
在这里插入图片描述

2、单机版
分析单机版,客户端和netty之间的压力是相当小,万人同时在线,人均每秒2条消息,所需带宽也仅仅接近2MB,对应的内存消耗也是非常之小,几乎也是毫无压力。理论上来说,netty单机,10MB带宽、100MB内存就可以支撑5万用户(当然还得维护在线用户channel池、写数据到消息队列等等消耗,支撑2万在线用户肯定是没问题)。

而与之配套的数据服务系统(获取数据、解析消息并存库等),就可以做成集群,分配更大内存、更多的机器,去支撑快速增长的用户。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1978183.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAPUI5基础知识23 - 模型的种类(小结)

1. 背景 在前序的学习中,我们学习了SAPUI5的MVC架构中的各个知识点,包括视图的设计,控制器的设计,以及模型的使用。 在企业级应用程序中,对于数据的处理的需求是很大的,在学习更复杂的数据绑定方式之前&a…

Pr2024苹果(mac)版剪辑软件安装下载(附下载链接)

Adobe Premiere Pro 2024(简称PR 2024)是一款由Adobe公司开发的专业视频编辑软件,被广泛应用于电影、电视、广告和社交媒体视频的制作。以下是对PR 2024的详细简介: 链接:https://pan.baidu.com/s/1rN-3kB3KQgn0JswDa…

《学会 SpringMVC 系列 · 剖析初始化》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思?

win10系统资源监视器磁盘蓝黄线、活动时间代表什么意思?在win10系统中磁盘在运行工程中会有不同的数据信息显示,如下图所示,10兆字节/秒、磁盘中蓝线和黄线、活动时间都达标什么意思呢?本文中winwin7小编给大家分享介绍下win10系统…

Python 爬虫入门(六):urllib库的使用方法

Python 爬虫入门(六):urllib库的使用方法 前言1. urllib 概述2. urllib.request 模块2.1 发送GET请求2.2 发送POST请求2.3 添加headers2.4 处理异常 3. urllib.error 模块4. urllib.parse 模块4.1 URL解析4.2 URL编码和解码4.3 拼接URL 5. ur…

MySQL主从复制原理及实现教程

MySQL主从复制是一种数据复制技术,通过建立主服务器(Master)与从服务器(Slave)之间的数据同步,实现数据的备份、负载均衡和高可用性。 主从复制原理 MySQL binlog(binary log 即二进制日志文件) 主要记录…

Python 如何创建和操作矩阵?

在Python中,矩阵是二维数组的一种常见表示方式,特别是在数学和科学计算领域。矩阵用于表示和操作多维数据,包括数据分析、图像处理、机器学习、以及物理模拟等诸多领域。Python的NumPy库是处理矩阵的主要工具之一。NumPy提供了高效的多维数组…

Deformable Detr

参考: https://search.bilibili.com/all?vt68804228&keywordco-detr&from_sourcewebtop_search&spm_id_from333.1007&search_source5 详解可以看李宏毅的transformer视频。

SAP MM维护采购信息记录只到采购组织层级时候,税码输入报错 MESSAGE 06388

原因:税确认应该是在工厂层级 解决:对06388 消息号进行更改类型改成W

Java-文件操作和IO

文件介绍 文件本身有多重含义,狭义的文件,特指硬盘上的文件(以及保存文件的目录),广义的文件:计算机上的很多硬件设备,软件资源,在操作系统中,都会被视为是"文件" 文件除了有数据内容之外,还有一部分信息,例如文件名,文件类型,文件大小,这些信息可以称作文件的元信…

Redis-管道

面试题 如何优化频繁命令往返造成的性能瓶颈 Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤: 1 客户端向服务端发送命令分四步(发送命令-命令排队一命令执行-返回结果),并监听Socket返回,通常以阻塞模式等待服…

看完这八本AI产品经理书籍,offer收的手软,收藏我这一篇就够了

对于想要深入了解人工智能领域并成为一名优秀的人工智能产品经理的专业人士来说,选择合适的书籍至关重要。下面是我为您精心挑选的八本关于AI产品经理领域的书籍,它们涵盖了从基础知识到高级实践的各个方面,旨在帮助读者构建全面的知识体系。…

情人节变情人劫?三好夫人:为何好男人怕过节

真正的爱,是两颗心灵的相互靠近,是理解、尊重与支持的结晶。———三好夫人 七夕节来源 2024年8月10是中国传统的情人节--七夕。七夕又称七巧节、七姐节、女儿节。妇女们在这一天相约,穿针乞巧,投针验巧,祈求得到智慧…

三十种未授权访问漏洞复现 合集( 一 )

未授权访问漏洞介绍 未授权访问可以理解为需要安全配置或权限认证的地址、授权页面存在缺陷,导致其他用户可以直接访问,从而引发重要权限可被操作、数据库、网站目录等敏感信息泄露。---->目录遍历 目前主要存在未授权访问漏洞的有:NFS服务&a…

全网最清晰Endnote教程笔记,轻松搞定文献管理以及文献引用

提示:从安装到使用,全网最清晰EndNote教程,轻松搞定文献管理!_哔哩哔哩_bilibili 目录 前言 1 导入文献 1.1 文献数据库建立 1.2 正式导入文献 1.3 Endnote导入PDF文献 2 管理文献 3. 引用文献 3.1 Endnote预览参考文献样…

搭建 Rancher 服务,配置k8s集群

1. 前提条件 前提条件: 安装docker,要求版本各节点版本一致。网上还有额外的要求:关闭swap、禁用selinux等等。 2. 搭建 Rancher 服务 直接通过docker命令实现即可,很方便。 docker run -d \--name rancher \--restart unles…

数学建模-数据预处理(数据清洗、标准化)

在数学建模的比赛中,数据分析类的题目中往往我们要面对大量的数据,在给出的数据中,如果数据缺失会影响到判断和后序的操作 这里我们给出一个数据预处理的办法:数据清洗(Data Clearning):涉及到…

良心推荐——揭秘10款文件加密软件!(小编真心推荐!好用!)

文件加密不仅能够有效防止敏感数据被非法获取或篡改,还能在数据丢失或被盗时降低损失。 因此,选择一款高效、可靠的文件加密软件成为了企业保障信息安全不可或缺的一环。 今天,小编就为大家揭秘10款良心推荐的文件加密软件,其中首…

BurpSuite教程——渗透测试第一关:BP工具使用

想要学好网络渗透,了解并熟练使用渗透工具是重要的一关。 Burp Suite 是一款广泛使用的网络安全工具,主要用于测试Web应用程序的安全性。它提供了一系列的功能,帮助安全专家和渗透测试人员发现和利用Web应用程序中的安全漏洞。 目录 一、前…

JetBrains GoLand单元测试不支持单个单元测试case执行

譬如函数代码 func AddInt(a, b int32) int32 {return a b } 单元测试代码: func TestAddInt(t *testing.T) {type args struct {a int32b int32}tests : []struct {name stringargs argswant int32}{{name: "add",args: args{a: 1, b: 2},want: 3},{n…