rpc项目中的长连接与短连接的思考

这篇具有很好参考价值的文章主要介绍了rpc项目中的长连接与短连接的思考。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

对于rpc项目,在接受大佬指导的时候曾问过对于长连接和短连接是处理处理的,在面试的时候也被问起socket是长连接还是短连接,发现自己没有好好思考过这个问题,因此好好总结一下。

前置知识点:rpc基础,tcp基础

rpc项目中的长连接与短连接的思考

什么是rpc项目中的长连接和短连接

类似于http的长连接和短连接的概念,rpc项目中的短连接是指处理完一次rpc请求后就断开连接,长连接是指处理完一次rpc请求后不断开连接,复用连接。

http中长连接是指处理完一次http请求和响应之后不断开tcp连接,http短连接是指处理完一次http请求和响应之后断开tcp连接(一般是服务器断开,至于为什么是服务器断开,则又是一篇小文章了hhh)。

与tcp和http的长连接短连接的异同

对于rpchttp的长短连接上文已经分析过了(毕竟有的rpc框架底层的传输协议甚至都是http),这里主要要区别的就是** http****rpc**的长短连接本质上就是控制的**tcp**的断开时机。

这里又涉及一个平时很容易弄错的地方了,比如tcp的包活机制(keep-aliving)与http协议的长连接(Connection: Keep-Alive)英文很相似,但是本质上不是一个东西。

客户端与服务器有哪几种连接模式与利弊分析

rpc连接的三种方式:

常规 RPC 的连接模型主要有三种:

  • 短连接:每次请求都创建新连接,得到返回后立即关闭连接
  • 长连接池:单个连接同时只能处理一次完整请求与返回
  • 连接多路复用:单个连接可以同时异步处理多个请求与返回

每类连接模型没有绝对好坏,取决于实际使用场景,连接多路复用虽然一般来说性能相对最好。

这些应该是一些比较成熟的rpc框架实现的,中间配有负载均衡器才能实现连接池的操作。
如果是客户端与服务端直连,本质上就是两种:长连接和短连接。

长连接不是银弹

本节主要说明长连接虽然相对于短连接一般情况下性能好,但是不是十全十美,必须有所考量。

1. client 和 server 的数量

rpc长连接模式下相比于rpc短连接,在相同client数量的情况下,需要维系的连接数更多(连接一般不会断开,或者是需要超时或者是其他情况才会断开),因此当client数量相比于server数量过多的时候,使用长连接会有以下几个问题:

  • server需要维护数量众多的连接,压力很大。
  • 端口很容易耗尽

因此在client数量特别多的情况下就不适合用长连接了,用短连接反而合适一些。
使用长连接的时候也需要考虑超时断开等机制。
所幸rpc服务器一般来说client的数量相比于网页服务器等会少很多,因此使用长连接应该就可以了。

可以考虑一篇新文章,服务器端口用尽怎么办:

未开始:服务器端口用尽

2. 负载均衡机制

现代后端服务端架构中, 为了实现高可用和可伸缩, 一般都会引入单独的模块来提供负载均衡的功能, 称为负载均衡器。根据工作在 OSI 不同的层级, 不同的负载均衡器会提供不同的转发功能。不同的均衡器是根据工作在的 层进行区分的,接下来就最常见的 L4 (工作在 TCP 层)和** L7** (工作在应用层, 如 HTTP) 两种负载均衡器来分析。

我的rpc项目没有实现单独模块的负载均衡,而是直接在客户端实现负载均衡的,因此下面的这些只是一些设计上的学习。

L4 负载均衡器

L4工作在TCP层,就是对TCP的流量进行负载均衡的转发,由于TCP的特性,因此L4的负载均衡器并不能知道某次rpc请求是否处理完毕,只是在发起请求的时候进行负载均衡处理(选择要转发到哪个服务器上)。
rpc项目中的长连接与短连接的思考
那么这样和RPC长短连接有什么关系呢?

  • 长连接:长连接情况下client会一直保持和某个server的连接,这样的话在某种意义上来说负载均衡就失效了。之所以说是某种意义上失效,是因为**client**的请求一直在某一台服务器上,并没有均衡到其他服务器,但是新的**client**连接进来的时候还是会负载均衡的。这样在client数量很少的时候会导致流量分发不平均:rpc项目中的长连接与短连接的思考
  • 短连接:每次请求都会重新连接,因此每次都会负载均衡。

L7 负载均衡器

L7均衡器无论是长连接还是短连接都不会有L4在长连接情况下的负载均衡的问题,原因是因为L7可以进行HTTP协议的解析。

L4负载均衡在长连接情况下导致负载均衡在某种意义下失效的本质原因是负载均衡器在第一次连接的时候负载均衡后,后续不会再负载均衡了。
相比 L4 只能基于连接进行负载均衡, L7 可以进行 **HTTP** 协议的解析.。当 client 发送请求时, client 会先和 L7 握手, L7 再和后端的一个或几个 server 握手,并根据不同的策略将请求分发给这些 server,实现基于请求的负载均衡。
rpc项目中的长连接与短连接的思考

可以看到L7的负载均衡无论是长连接还是短连接都没有负载均衡“失效”的问题,因为L7的负载均衡器是可以读取到请求的具体内容的。

因此使用长连接还是短连接必须要根据实际情况来确定,不能无脑的选择长连接。

如何实现长连接

rpc服务提供方和调用方只要在处理完之后不要断开连接就可以了。在检测到对方断开后也要断开。


// 新的socket连接回调
void RpcProvider::OnConnection(const muduo::net::TcpConnectionPtr &conn)
{
    // 如果是新连接就什么都不干,即正常的接收连接即可
    if (!conn->connected())
    {
        // 和rpc client的连接断开了
        conn->shutdown();
    }
}

/*
在框架内部,RpcProvider和RpcConsumer协商好之间通信用的protobuf数据类型
service_name method_name args    定义proto的message类型,进行数据头的序列化和反序列化
                                 service_name method_name args_size
16UserServiceLoginzhang san123456

header_size(4个字节) + header_str + args_str
10 "10"
10000 "1000000"
std::string   insert和copy方法
*/
// 已建立连接用户的读写事件回调 如果远程有一个rpc服务的调用请求,那么OnMessage方法就会响应
// 这里来的肯定是一个远程调用请求
// 因此本函数需要:解析请求,根据服务名,方法名,参数,来调用service的来callmethod来调用本地的业务
void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr &conn,
                            muduo::net::Buffer *buffer,
                            muduo::Timestamp)
{
    // 网络上接收的远程rpc调用请求的字符流    Login args
    std::string recv_buf = buffer->retrieveAllAsString();

    // 从字符流中读取前4个字节的内容
    uint32_t header_size = 0;
    recv_buf.copy((char *)&header_size, 4, 0);

    // 根据header_size读取数据头的原始字符流,反序列化数据,得到rpc请求的详细信息
    std::string rpc_header_str = recv_buf.substr(4, header_size);
    mprpc::RpcHeader rpcHeader;
    std::string service_name;
    std::string method_name;
    uint32_t args_size;
    if (rpcHeader.ParseFromString(rpc_header_str))
    {
        // 数据头反序列化成功
        service_name = rpcHeader.service_name();
        method_name = rpcHeader.method_name();
        args_size = rpcHeader.args_size();
    }
    else
    {
        // 数据头反序列化失败
        std::cout << "rpc_header_str:" << rpc_header_str << " parse error!" << std::endl;
        return;
    }

    // 获取rpc方法参数的字符流数据
    std::string args_str = recv_buf.substr(4 + header_size, args_size);

    // 打印调试信息
    std::cout << "============================================" << std::endl;
    std::cout << "header_size: " << header_size << std::endl;
    std::cout << "rpc_header_str: " << rpc_header_str << std::endl;
    std::cout << "service_name: " << service_name << std::endl;
    std::cout << "method_name: " << method_name << std::endl;
    std::cout << "args_str: " << args_str << std::endl;
    std::cout << "============================================" << std::endl;

    // 获取service对象和method对象
    auto it = m_serviceMap.find(service_name);
    if (it == m_serviceMap.end())
    {
        std::cout << service_name << " is not exist!" << std::endl;
        return;
    }

    auto mit = it->second.m_methodMap.find(method_name);
    if (mit == it->second.m_methodMap.end())
    {
        std::cout << service_name << ":" << method_name << " is not exist!" << std::endl;
        return;
    }

    google::protobuf::Service *service = it->second.m_service;      // 获取service对象  new UserService
    const google::protobuf::MethodDescriptor *method = mit->second; // 获取method对象  Login

    // 生成rpc方法调用的请求request和响应response参数
    google::protobuf::Message *request = service->GetRequestPrototype(method).New();
    if (!request->ParseFromString(args_str))
    {
        std::cout << "request parse error, content:" << args_str << std::endl;
        return;
    }
    google::protobuf::Message *response = service->GetResponsePrototype(method).New();

    // 给下面的method方法的调用,绑定一个Closure的回调函数
    // closure是执行完本地方法之后会发生的回调,因此需要完成序列化和反向发送请求的操作
    google::protobuf::Closure *done = google::protobuf::NewCallback<RpcProvider,
                                                                    const muduo::net::TcpConnectionPtr &,
                                                                    google::protobuf::Message *>(this,
                                                                                                 &RpcProvider::SendRpcResponse,
                                                                                                 conn, response);

    // 在框架上根据远端rpc请求,调用当前rpc节点上发布的方法
    // new UserService().Login(controller, request, response, done)

    /*
    为什么下面这个service->CallMethod 要这么写?或者说为什么这么写就可以直接调用远程业务方法了
    这个service在运行的时候会是注册的service
    // 用户注册的service类 继承 .protoc生成的serviceRpc类 继承 google::protobuf::Service
    // 用户注册的service类里面没有重写CallMethod方法,是 .protoc生成的serviceRpc类 里面重写了google::protobuf::Service中
    的纯虚函数CallMethod,而 .protoc生成的serviceRpc类 会根据传入参数自动调取 生成的xx方法(如Login方法),
    由于xx方法被 用户注册的service类 重写了,因此这个方法运行的时候会调用 用户注册的service类 的xx方法
    真的是妙呀
    */
    service->CallMethod(method, nullptr, request, response, done);
}

rpc服务提供方

如上,不要断开连接即可,然后要记得保存socket对应的fd(文件描述符),因为下次要复用。

rpc请求方

while (m_clientFd == -1)
    { // 没有连接或者连接已经断开,那么就要重新连接呢,会一直不断地重试
      xxx
    }

当不能正常发送、接受、第一次连接的时候,总而言之就是异常的情况下:m_clientFd==-1,此时会从哈希环中分配尝试一个节点。
写成while是因为可能由于服务下线zookeeper没有及时检测到(zk检测是用心跳,默认是30s),那么连接肯定会失败,此时就会又从哈希环分配其他节点上面,以保证正确的连接。

一些其他层面的优化

其他层面的优化即对socket tcp编程的优化

  1. TCP_NODELAY:禁用 Nagle 算法,使小数据包能够及时发送。
  2. TCP_QUICKACK:启用 quickack 模式,减少应答延迟。

面试相关

这部分没有知识点,只是假设面试场景。

问:你这边对rpc的长连接和短连接是怎么思考的?
答:我的项目中采用的是长连接。
具体来说,rpc连接类似于HTTP连接,按照对每次响应处理后TCP是否断开分成了长连接和短连接,更具体的还可以分成:短连接,长连接池等等,但是本质上都是长连接和短连接之争。
长连接相比于短连接,在每次处理请求的时候不用重新握手了,对于数量多、体积小、处理快的请求很适用(速度快),但是使用的时候也必须有一些考量,不能无脑的选择长连接,具体来说有两点:

  1. 服务器和客户端的数量(连接数量):由于使用长连接,端口资源不会马上释放,因此client数量特别多的时候容易导致端口耗尽,考虑到一台服务器大概率不会维护那么多的client,因此使用长连接也是可行的。
  2. 负载均衡策略:如果是L4的负载均衡器的话有可能导致负载均衡策略在某种意义下失效。但我在项目中是直接在客户端部分实现的一致性hash算法用于负载均衡,并没有外置负载均衡器,是在客户端实现的负载均衡压根没有负载均衡器,因此自然不会收到负载均衡策略的影响。

综上使用的就是长连接了,此外:在长连接中加入了定时器,类似于web 服务器的设计,用来断开长时间没有请求的连接数量,用来缓解端口资源;进行了TCP的一些参数设置,NO_DELAY和QUICK_ACK来加快响应。

本文由博客一文多发平台 OpenWrite 发布!文章来源地址https://www.toymoban.com/news/detail-710567.html

到了这里,关于rpc项目中的长连接与短连接的思考的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • http的长连接、短连接、长轮询、短轮询

    我们都知道,HTTP1.1是可以支持长连接的,并且默认就是长连接。只需要在请求头设置 Connection:keep-alive 。长连接要想起作用,客户端也需要设置 Connection:keep-alive 。 长连接,短连接是针对TCP来说的 ,因为TCP才是负责数据传输的协议,长连接保证的是可以复用TCP连接。 这里就

    2023年04月09日
    浏览(28)
  • TCP协议的长连接和短连接详解

    TCP在真正开始进行数据传输之前,Server 和 Client 之间必须建立一个连接。当数据传输完成后,双方不再需要这个连接时,就可以释放这个连接。 TCP连接的建立是通过三次握手,而连接的释放是通过四次挥手。所以说,每个TCP连接的建立和释放都是需要消耗资源和时间成本的。

    2023年04月08日
    浏览(25)
  • 前端项目构建中的低代码思考

      🍎 个人博客: 个人主页 🏆 个人专栏: 日常聊聊 ⛳️   功不唐捐,玉汝于成 目录 前言 正文 低代码平台的崛起 低代码在前端项目中的应用 低代码的优势与挑战 如何合理应用低代码 结语  我的其他博客         随着信息技术的快速发展,软件开发行业也在不断地

    2024年03月23日
    浏览(31)
  • 深度思考rpc框架面经之五:rpc限流:rpc事务:tps测试

    是的,我可以为你提供关于RPC注册中心及其监控的相关信息。RPC注册中心是用于管理微服务之间调用关系的中心化服务,它可以帮助微服务发现和调用其他服务。而监控是确保微服务健康、稳定运行的重要手段,可以实时检测和报警系统中的异常情况。 对于RPC注册中心的监控

    2024年02月07日
    浏览(37)
  • SegNetr: 重新思考 U 形网络中的局部-全局交互和跳过连接

    论文出处: arXiv预印版 除了没有代码,其余没毛病🚀 近年来,U 形网络因其简单且易于调整的结构而在医学图像分割领域占据主导地位。然而,现有的U型分割网络:1)大多侧重于设计复杂的自注意力模块来弥补基于卷积运算的长期依赖性的不足,这增加了网络的总体参数数

    2024年02月09日
    浏览(29)
  • 对于现有的分布式id发号器的思考 id生成器 雪花算法 uuid

    目录 雪花id tinyid uuid 分布式id特点 业务编号 数据中心编号 当前时间 ip地址 当前序号 对于时钟回拨问题 发号器机器当期时间小于redis的时间 解决步骤 发号器机器当期时间等于redis时间 发号器机器当期时间大于redis最大的时间(相关的key不存在) 分布式id的单次获取和批次获

    2024年02月13日
    浏览(40)
  • “远程计算机或设备不接受连接”解决方法

    问题现象:笔记本显示已经连接上了网络,但是所有浏览器都上不了网,QQ也打不开。在浏览器界面点击诊断网络,出现提示“远程计算机或设备不接受连接”。 产生原因:使用了网络代理软件,在关闭软件之前没有关闭代理,比如使用了VPN,在关闭软件之前没有关闭网络代

    2024年02月16日
    浏览(52)
  • web代理未设置接受端口7890上的连接

    今天能上qq但不能上网,然后网络诊断出web代理未设置接受端口7890上的连接 原因:未正常关闭clash等代理工具 总之就是待机前忘记关了0.0 解决方案: 1.找到Internet 选项 2.找到”连接“,点击”局域网设置“ 3.关闭”为LAN使用代理服务器“,再点击确定就好了 然后我们就可以

    2024年02月11日
    浏览(27)
  • Deepin设置接受从Windows的远程桌面连接

       信息化时代,网络越来越普及,电脑越来越多,经常会有使用远程桌面操作多台电脑(PC/服务器/虚拟机/云服务器等等)的情形。例如用远程桌面连接Windows服务器/台式机,Xshell连接Linux服务器/台式机;还有更多的工具,包括QQ远程协助、TeamView(越来越不友好,基本弃用

    2024年02月06日
    浏览(31)
  • 电脑本地连接受限制或无连接怎么办?

      电脑本地连接受限制或无连接怎么办?这是个一个非常常见的问题,就是哦在任务栏上的“本地连接”图标有一个黄色叹号。查看后状态:“受限制或无连接”,点“修复”也修复不了,有时候显示无法获取IP地址,获得私网地址!但ADSL又可以登录,可以访问网站。下面下

    2024年02月07日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包