Netty 实现百万级连接服务的难点和优点分析总结

news2024/11/25 6:54:27

推送服务

还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。

所以之前在 Android 上做推送大部分只能靠轮询。而我们之前在技术调研的时候,搜到了 jPush 的博客,上面介绍了一些他们的技术特点,他们主要做的其实就是移动网络下的长连接服务。单机 50W-100W 的连接的确是吓我一跳!后来我们也采用了他们的免费方案,因为是一个受众面很小的产品,所以他们的免费版够我们用了。一年多下来,运作稳定,非常不错!

时隔两年,换了部门后,竟然接到了一项任务,优化公司自己的长连接服务端。

再次搜索网上技术资料后才发现,相关的很多难点都被攻破,网上也有了很多的总结文章,单机 50W-100W 的连接完全不是梦,其实人人都可以做到。但是光有连接还不够,QPS 也要一起上去。

所以,这篇文章就是汇总一下利用 Netty 实现长连接服务过程中的各种难点和可优化点。

Netty 是什么

Netty: http://netty.io/

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

官方的解释最精准了,其中最吸引人的就是高性能了。但是很多人会有这样的疑问:直接用 NIO 实现的话,一定会更快吧?就像我直接手写 JDBC 虽然代码量大了点,但是一定比 iBatis 快!

但是,如果了解 Netty 后你才会发现,这个还真不一定!

利用 Netty 而不用 NIO 直接写的优势有这些:

  • 高性能高扩展的架构设计,大部分情况下你只需要关注业务而不需要关注架构

  • Zero-Copy 技术尽量减少内存拷贝

  • 为 Linux 实现 Native 版 Socket

  • 写同一份代码,兼容 java 1.7 的 NIO2 和 1.7 之前版本的 NIO

  • Pooled Buffers 大大减轻 Buffer 和释放 Buffer 的压力

特性太多,大家可以去看一下《Netty in Action》这本书了解更多。

另外,Netty 源码是一本很好的教科书!大家在使用的过程中可以多看看它的源码,非常棒!

瓶颈是什么

想要做一个长链服务的话,最终的目标是什么?而它的瓶颈又是什么?

其实目标主要就两个:

  1. 更多的连接

  2. 更高的 QPS

所以,下面就针对这两个目标来说说他们的难点和注意点吧。

更多的连接

非阻塞 IO

其实无论是用 Java NIO 还是用 Netty,达到百万连接都没有任何难度。因为它们都是非阻塞的 IO,不需要为每个连接创建一个线程了。

欲知详情,可以搜索一下BIO,NIO,AIO的相关知识点。

Java NIO 实现百万连接

ServerSocketChannel ssc = ServerSocketChannel.open();  
Selector sel = Selector.open();  
  
ssc.configureBlocking(false);  
ssc.socket().bind(new InetSocketAddress(8080));  
SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT);  
  
while(true) {  
    sel.select();  
    Iterator it = sel.selectedKeys().iterator();  
    while(it.hasNext()) {  
        SelectionKey skey = (SelectionKey)it.next();  
        it.remove();  
        if(skey.isAcceptable()) {  
            ch = ssc.accept();  
        }  
    }  
}  

这段代码只会接受连过来的连接,不做任何操作,仅仅用来测试待机连接数极限。

大家可以看到这段代码是 NIO 的基本写法,没什么特别的。

Netty 实现百万连接

NioEventLoopGroup bossGroup =  new NioEventLoopGroup();  
NioEventLoopGroup workerGroup= new NioEventLoopGroup();  
ServerBootstrap bootstrap = new ServerBootstrap();  
bootstrap.group(bossGroup, workerGroup);  
  
bootstrap.channel( NioServerSocketChannel.class);  
  
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {  
    @Override protected void initChannel(SocketChannel ch) throws Exception {  
        ChannelPipeline pipeline = ch.pipeline();  
        //todo: add handler  
    }});  
bootstrap.bind(8080).sync();  

这段其实也是非常简单的 Netty 初始化代码。同样,为了实现百万连接根本没有什么特殊的地方。

瓶颈到底在哪

上面两种不同的实现都非常简单,没有任何难度,那有人肯定会问了:实现百万连接的瓶颈到底是什么?

其实只要 java 中用的是非阻塞 IO(NIO 和 AIO 都算),那么它们都可以用单线程来实现大量的 Socket 连接。不会像 BIO 那样为每个连接创建一个线程,因为代码层面不会成为瓶颈。

其实真正的瓶颈是在 Linux 内核配置上,默认的配置会限制全局最大打开文件数(Max Open Files)还会限制进程数。所以需要对 Linux 内核配置进行一定的修改才可以。

这个东西现在看似很简单,按照网上的配置改一下就行了,但是大家一定不知道第一个研究这个人有多难。

如何验证

让服务器支持百万连接一点也不难,我们当时很快就搞定了一个测试服务端,但是最大的问题是,我怎么去验证这个服务器可以支撑百万连接呢?

我们用 Netty 写了一个测试客户端,它同样用了非阻塞 IO ,所以不用开大量的线程。但是一台机器上的端口数是有限制的,用root权限的话,最多也就 6W 多个连接了。所以我们这里用 Netty 写一个客户端,用尽单机所有的连接吧。

NioEventLoopGroup workerGroup =  new NioEventLoopGroup();  
Bootstrap b = new Bootstrap();  
b.group(workerGroup);  
b.channel( NioSocketChannel.class);  
  
b.handler(new ChannelInitializer<SocketChannel>() {  
    @Override  
    public void initChannel(SocketChannel ch) throws Exception {  
        ChannelPipeline pipeline = ch.pipeline();  
        //todo:add handler  
    }  
    });  
  
for (int k = 0; k < 60000; k++) {  
    //请自行修改成服务端的IP  
    b.connect(127.0.0.1, 8080);  
}  

代码同样很简单,只要连上就行了,不需要做任何其他的操作。

这样只要找到一台电脑启动这个程序即可。这里需要注意一点,客户端最好和服务端一样,修改一下 Linux 内核参数配置。

怎么去找那么多机器

按照上面的做法,单机最多可以有 6W 的连接,百万连接起码需要17台机器!

如何才能突破这个限制呢?其实这个限制来自于网卡。我们后来通过使用虚拟机,并且把虚拟机的虚拟网卡配置成了桥接模式解决了问题。

根据物理机内存大小,单个物理机起码可以跑4-5个虚拟机,所以最终百万连接只要4台物理机就够了。

讨巧的做法

除了用虚拟机充分压榨机器资源外,还有一个非常讨巧的做法,这个做法也是我在验证过程中偶然发现的。

根据 TCP/IP 协议,任何一方发送FIN后就会启动正常的断开流程。而如果遇到网络瞬断的情况,连接并不会自动断开。

那我们是不是可以这样做?

  1. 启动服务端,千万别设置 Socket 的keep-alive属性,默认是不设置的

  2. 用虚拟机连接服务器

  3. 强制关闭虚拟机

  4. 修改虚拟机网卡的 MAC 地址,重新启动并连接服务器

  5. 服务端接受新的连接,并保持之前的连接不断

我们要验证的是服务端的极限,所以只要一直让服务端认为有那么多连接就行了,不是吗?

经过我们的试验后,这种方法和用真实的机器连接服务端的表现是一样的,因为服务端只是认为对方网络不好罢了,不会将你断开。

另外,禁用keep-alive是因为如果不禁用,Socket 连接会自动探测连接是否可用,如果不可用会强制断开。

更高的 QPS

由于 NIO 和 Netty 都是非阻塞 IO,所以无论有多少连接,都只需要少量的线程即可。而且 QPS 不会因为连接数的增长而降低(在内存足够的前提下)。

而且 Netty 本身设计得足够好了,Netty 不是高 QPS 的瓶颈。那高 QPS 的瓶颈是什么?

是数据结构的设计!

如何优化数据结构

首先要熟悉各种数据结构的特点是必需的,但是在复杂的项目中,不是用了一个集合就可以搞定的,有时候往往是各种集合的组合使用。

既要做到高性能,还要做到一致性,还不能有死锁,这里难度真的不小…

我在这里总结的经验是,不要过早优化。优先考虑一致性,保证数据的准确,然后再去想办法优化性能。

因为一致性比性能重要得多,而且很多性能问题在量小和量大的时候,瓶颈完全会在不同的地方。所以,我觉得最佳的做法是,编写过程中以一致性为主,性能为辅;代码完成后再去找那个 TOP1,然后去解决它!

解决 CPU 瓶颈

在做这个优化前,先在测试环境中去狠狠地压你的服务器,量小量大,天壤之别。

有了压力测试后,就需要用工具来发现性能瓶颈了!

我喜欢用的是 VisualVM,打开工具后看抽样器(Sample),根据自用时间(Self Time (CPU))倒序,排名第一的就是你需要去优化的点了!

备注:Sample 和 Profiler 有什么区别?前者是抽样,数据不是最准但是不影响性能;后者是统计准确,但是非常影响性能。如果你的程序非常耗 CPU,那么尽量用 Sample,否则开启 Profiler 后降低性能,反而会影响准确性。

 

还记得我们项目第一次发现的瓶颈竟然是ConcurrentLinkedQueue这个类中的size()方法。量小的时候没有影响,但是Queue很大的时候,它每次都是从头统计总数的,而这个size()方法我们又是非常频繁地调用的,所以对性能产生了影响。

size()的实现如下:

public int size() {  
    int count = 0;  
    for (Node<E> p = first(); p != null; p = succ(p))  
    if (p.item != null)  
    // Collection.size() spec says to max out  
    if (++count == Integer.MAX_VALUE)  
    break;  
    return count;  
}  

后来我们通过额外使用一个AtomicInteger来计数,解决了问题。但是分离后岂不是做不到高一致性呢?没关系,我们的这部分代码关心最终一致性,所以只要保证最终一致就可以了。

总之,具体案例要具体分析,不同的业务要用不同的实现。

解决 GC 瓶颈

GC 瓶颈也是 CPU 瓶颈的一部分,因为不合理的 GC 会大大影响 CPU 性能。

这里还是在用 VisualVM,但是你需要装一个插件:VisualGC

 

有了这个插件后,你就可以直观的看到 GC 活动情况了。

按照我们的理解,在压测的时候,有大量的 New GC 是很正常的,因为有大量的对象在创建和销毁。

但是一开始有很多 Old GC 就有点说不过去了!

后来发现,在我们压测环境中,因为 Netty 的 QPS 和连接数关联不大,所以我们只连接了少量的连接。内存分配得也不是很多。

而 JVM 中,默认的新生代和老生代的比例是1:2,所以大量的老生代被浪费了,新生代不够用。

通过调整 -XX:NewRatio 后,Old GC 有了显著的降低。

但是,生产环境又不一样了,生产环境不会有那么大的 QPS,但是连接会很多,连接相关的对象存活时间非常长,所以生产环境更应该分配更多的老生代。

总之,GC 优化和 CPU 优化一样,也需要不断调整,不断优化,不是一蹴而就的。

其他优化

如果你已经完成了自己的程序,那么一定要看看《Netty in Action》作者的这个网站:Netty Best Practices a.k.a Faster == Better。

相信你会受益匪浅,经过里面提到的一些小小的优化后,我们的整体 QPS 提升了很多。

最后一点就是,java 1.7 比 java 1.6 性能高很多!因为 Netty 的编写风格是事件机制的,看似是 AIO。可 java 1.6 是没有 AIO 的,java 1.7 是支持 AIO 的,所以如果用 java 1.7 的话,性能也会有显著提升。

最后成果

经过几周的不断压测和不断优化了,我们在一台16核、120G内存(JVM只分配8G)的机器上,用 java 1.6 达到了60万的连接和20万的QPS。

其实这还不是极限,JVM 只分配了8G内存,内存配置再大一点连接数还可以上去;

QPS 看似很高,System Load Average 很低,也就是说明瓶颈不在 CPU 也不在内存,那么应该是在 IO 了!上面的 Linux 配置是为了达到百万连接而配置的,并没有针对我们自己的业务场景去做优化。

因为目前性能完全够用,线上单机 QPS 最多才 1W,所以我们先把精力放在了其他地方。相信后面我们还会去继续优化这块的性能,期待 QPS 能有更大的突破!

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

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

相关文章

Lua学习笔记:C/C++和Lua的相互调用

前言 本篇在讲什么 C/C和Lua的相互调用 本篇适合什么 适合初学Lua的小白 适合需要C/C和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&…

云南智慧档案库综合管理系统建设解决方案

一、智慧档案管理系统建设背景 档案作为一种特殊的文献&#xff0c;是人类社会活动的产物&#xff0c;具有特殊的价值&#xff0c;其价值可以概括为现实价值和历史价值。档案是人类留给国家和社会的宝贵财富&#xff0c;它在经济与社会建设中起着重要的作用。档案是反映一个单…

多重共线性的处理方法

回归分析需要考虑多重共线性问题。多重共线性是指自变量之间存在高度相关性&#xff0c;导致回归模型的系数估计不稳定和假设检验不可靠。在实际应用中&#xff0c;许多自变量之间都可能存在一定程度的相关性&#xff0c;如果没有进行控制&#xff0c;就会导致多重共线性问题的…

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

面向对象的三个环节&#xff1a;面向对象分析&#xff08;OOA&#xff09;、面向对象设计&#xff08;OOD&#xff09;、面向对象编程&#xff08;OOP&#xff09;。只知道OOA、OOD、OOP只能说有一个宏观了解&#xff0c;我们更重要的还是要知道“如何做”&#xff0c;也就是&a…

【快应用】多语言适配案例

【关键词】 多语言&#xff0c;$t 【问题背景】 快应用平台的能力会覆盖多个国家地区&#xff0c;平台支持多语言的能力后&#xff0c;可以让一个快应同时支持多个语言版本的切换&#xff0c;开发者无需开发多个不同语言的源码项目&#xff0c;避免给项目维护带来困难。使用系…

子串分值--子串分值和 模拟,找规律

子串分值和 n有十万&#xff0c;需要找规律&#xff0c;O(n^2)不满足要求 分析样例&#xff1a; Ababc 01234 长度是n5 索引下标-对应字符 0A贡献 112 a;ab;---22*1 next a 2&#xff1b; pre a -1 1b贡献 112 b;ba;---42*2 next b 3&#xff1b; pre b -1 2a贡献 1113…

2023年测试前景?测试开发工程师养成记,开发企业级测试平台...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试开发&#xff…

IDS 和 IPS 日志监控

什么是IDS/IPS 入侵检测系统 &#xff08;IDS&#xff09; 和入侵防御系统 &#xff08;IPS&#xff09; 是监视组织网络中的流量以检测和防止恶意活动和策略违规的网络组件。 入侵检测系统&#xff08;IDS&#xff09;和入侵防御系统&#xff08;IPS&#xff09;可以说是企业…

C语言学习分享(第八次)------数据的存储

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C语言学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C语言知识   &#x1f51d;&#x1f51d; 数据的存储 1. 前言&#x1f6a9;2…

现代化智慧档案室建设图文推介

1、防火。建立档案库房防火制度&#xff0c;档案库房附近严禁存放易燃、易爆物品&#xff0c;库房内严禁吸烟&#xff0c;并备有灭火器&#xff0c;经常进行检查更换。 主要设备为&#xff1a;烟雾探测器和感温探测器和七氟丙烷灭火系统。 2、防潮。库房内备有温湿度计&#x…

GB28181 对接海康平台,解决音视频卡顿问题

GB28181 对接海康平台,解决音视频卡顿问题 一、概述二、问题分析1、设备对比分析2、抓包对比分析3、验证分析结果三、总结四、讨论一、概述 设备使用GB28181协议对接海康平台时,发现音频和视频存在卡顿现象,不是一直卡顿,有时候卡有时候不卡,但是卡顿的时候音视频一起卡顿…

炫技操作--递归实现翻转链表(java)

递归实现链表的逆序 leetcode 206题。 翻转链表递归解法普通方式实现链表翻转链表专题 leetcode 206题。 翻转链表 leetcode链接用于测试 题目&#xff1a;描述 将一个链表翻转&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 递归解法 解题思路…

chatgpt赋能python:Python中删除的SEO

Python中删除的SEO Python是一个强大的编程语言&#xff0c;它广泛应用于各种领域&#xff0c;包括SEO。在SEO领域中&#xff0c;Python可以用来处理各种数据&#xff0c;包括删除不必要的数据。本文将介绍如何在Python中删除SEO数据。 什么是SEO数据&#xff1f; SEO是搜索…

代码创造的欢乐世界-通用人工智能让儿童熟练应用编程

想要复杂的参考这一篇&#xff0c;使用云平台即可完成&#xff1a; 美美的圣诞树画出来-CoCube- 把圣诞树换成六一儿童节主题的就可以啦。 这一篇是使用chatgpt类应用&#xff0c;给出关键提示词&#xff0c;代码自动生成哦。 神十六发射成功&#xff0c;科技工作者博士学位…

python接口自动化使用requests库发送http请求

目录 前言一、requests库二、HTTP 请求方法三、发送GET请求四、发送POST请求五、获取响应数据六、高级操作 6.1文件下载6.2文件上传6.3SSL证书验证6.4保持会话6.5requests封装总结 前言 今天笔者想和大家来聊聊python接口自动化如何使用requests库发送http请求&#xff0c;废…

【JavaSE】Java基础语法(三十七):Java 中的 String 类(源码级别)

文章目录 1. 构造方法1.1 String()1.2 String(String original)1.3 String(char[] chars)1.4 String(char数组,起始下标,长度)1.5 String(byte数组)1.6 String(byte数组,起始下标,长度)1.7 String(StringBuffer buffer)1.8 String(StringBuilder builder) 2. 普通方法2.1 char …

【Python教学】Python兼职有哪些?给你们分享一下最适合学生党/工作党的Python兼职攻略以及接私活经验

文章目录 前言一、做兼职的优势二、兼职种类三、基本技能要求四、平台和渠道五、案例分析六、做兼职注意事项总结 前言 Python是一种高级编程语言&#xff0c;它具有简单易学、代码可读性高、功能强大等特点&#xff0c;被广泛应用于数据分析、人工智能、Web开发等领域。Pytho…

修改mysql密码与mac中mysql的启动与终止

目录 修改mysql密码 1.进入你的mysql文件下的bin目录下&#xff1a; 2.修改mysql密码 mysql的启动与终止&#xff08;mac&#xff09; 修改mysql密码 1.进入你的mysql文件下的bin目录下&#xff1a; 如果不知道自己电脑上的mysql在哪里的话&#xff0c;输入&#xff1a; …

Redis7实战加面试题-高阶篇(布隆过滤器BloomFilter,缓存预热+缓存雪崩+缓存击穿+缓存穿透)

布隆过滤器BloomFilter 先看看大厂真实需求面试题反馈 1.现有50亿个电话号码&#xff0c;现有10万个电话号码&#xff0c;如何要快速准确的判断这些电话号码是否已经存在? 2.判断是否存在&#xff0c;布隆过滤器了解过吗? 3.安全连接网址&#xff0c;全球数10亿的网址判断 …

Java 多线程共享数据引发的问题

一、多线程并发情况下&#xff0c;线程不安全​​ 1、使用多线程实现银行取钱​​ package theads;/*** ClassName: TestBank* Description: TODO* Author: HLX* date: 2023/5/29 14:53* Version: V1.0*//*** 线程不安全&#xff1a; 取钱* <p>* 逻辑&#xff1a;* 连取…