专栏首页赵KK日常技术记录为什么Netty变慢了?

为什么Netty变慢了?

2860元腾讯云代金券免费领取,付款直接抵现金,立即领取>>>

腾讯云海外服务器1折限时抢购,2核4G云主机768元/1年,立即抢购>>>

腾讯云服务器1折限时抢购,2核4G云主机899元/3年,立即抢购>>>

体验过众多Netty的demo以后,在跟朋友分享后实现了页面昵称输入,消息可回车发送,页面保持输入框在底部,消息页面支持滚动,但是为什么当多客户端接入,消息会延迟,并注册变慢呢?

传统JAVA BIO 阻塞+同步

特点:

1.客户端启动一个socket 每个客户建立一个链接

2.判断服务器是否有线程响应,没有会等待或被拒绝

3.有线程,等待请求响应结束

体验代码

package com.chat.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.LocalDate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zhaokkstart
 * @create 2020-01-12 21:00
 */
public class Bio {

    public static void main(String[] args) throws IOException {

        ExecutorService executorService = Executors.newCachedThreadPool();

        ServerSocket serverSocket = new ServerSocket(8088);
        System.out.println("服务器启动....");
        while (true) {
            final Socket accept = serverSocket.accept();
            System.out.println("客户端接入....."+ LocalDate.now());
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    handler(accept);
                }
            });

        }
    }

    public static void handler(Socket socket) {
        try {
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream();
            while (true) {
                int read = inputStream.read(bytes);
                if (read != -1) {
                    System.out.println("接受到数据" + new String(bytes, 0, read));
                } else {
                    System.out.println("读取完毕");
                    break;
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("关闭客户端连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

启动:

cmd  telnet 127.0.0.1 8088
ctrl ]
send hello netty
Connected to the target VM, address: '127.0.0.1:51231', transport: 'socket'
服务器启动....
客户端接入.....2020-01-12
接受到数据hello netty

同样打开一个窗口 发送hello netty2

客户端接入.....2020-01-12
接受到数据hello netty 2

而Nio是如何做的呢?

NIO 非阻塞IO java1.4

channel buffer Selector

线程Thread

Selector 根据不同的事件在各个channel切换

Channel(read/write) 多个连接 Event决定切换到那个channel

↑↓

Buffer buffer底层有一个数组 数据的读/写 双向的flip切换

Client(socket)

目前没有数据可用 就什么都不做,不会阻塞

Http2.0使用多路IO复用技术同一个技术处理多个请求

public class BasicBuffer {

    public static void main(String[] args) {
        IntBuffer intBuffer =IntBuffer.allocate(5);
        intBuffer.put(10);
        intBuffer.put(11);
        intBuffer.put(12);
        intBuffer.put(13);
        intBuffer.put(14);
        //读取数据  读写切换
        intBuffer.flip();
        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
    }
}

Buffer源码

public abstract class Buffer {

    /**
     * The characteristics of Spliterators that traverse and split elements
     * maintained in Buffers.
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    // Invariants: mark <= position <= limit <= capacity
    mark标记
    private int mark = -1;
     position下一次执行位置
    private int position = 0;
    放入长度限制
    private int limit;
    容量
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;
}

Buffer子类

ByteBuffer,存储字节数据到缓冲区
ShortBuffer,存储字符串数据到缓冲区
CharBuffer,存储字符数据到缓冲区
IntBuffer,存储整数数据到缓冲区
LongBuffer,存储长整型数据到缓冲区
DoubleBuffer,存储小数到缓冲区
FloatBuffer,存储小数到缓冲区

以上子类中每一个源码中都有一个数组

    final int[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;

对比StringBuffer

StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

如何做到线程安全的?

@Override
    public synchronized int length() {
        return count;
    }

    @Override
    public synchronized int capacity() {
        return value.length;
    }


    @Override
    public synchronized void ensureCapacity(int minimumCapacity) {
        super.ensureCapacity(minimumCapacity);
    }

    /**
     * @since      1.5
     */
    @Override
    public synchronized void trimToSize() {
        super.trimToSize();
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @see        #length()
     */
    @Override
    public synchronized void setLength(int newLength) {
        toStringCache = null;
        super.setLength(newLength);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @see        #length()
     */
    @Override
    public synchronized char charAt(int index) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        return value[index];
    }

    /**
     * @since      1.5
     */
    @Override
    public synchronized int codePointAt(int index) {
        return super.codePointAt(index);
    }

    /**
     * @since     1.5
     */
    @Override
    public synchronized int codePointBefore(int index) {
        return super.codePointBefore(index);
    }

    /**
     * @since     1.5
     */
    @Override
    public synchronized int codePointCount(int beginIndex, int endIndex) {
        return super.codePointCount(beginIndex, endIndex);
    }

    /**
     * @since     1.5
     */
    @Override
    public synchronized int offsetByCodePoints(int index, int codePointOffset) {
        return super.offsetByCodePoints(index, codePointOffset);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    @Override
    public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
                                      int dstBegin)
{
        super.getChars(srcBegin, srcEnd, dst, dstBegin);
    }

    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @see        #length()
     */
    @Override
    public synchronized void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        toStringCache = null;
        value[index] = ch;
    }

    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

大部分的方法都是synchronized

Netty特点是一个线程对应多个channel,不需要启动多个线程连接的啊?为什么会变慢呢?

public class HappyChatMain {
    private static final Logger logger = LoggerFactory.getLogger(HappyChatMain.class);

    public static void main(String[] args) {
        final HappyChatServer server = new HappyChatServer(Constants.DEFAULT_PORT);
        server.init();
        System.out.println("服务器等待连接.............");
        System.out.println("当前服务器CPU核数"+Runtime.getRuntime().availableProcessors());
        server.start();
        // 注册进程钩子,在JVM进程关闭前释放资源
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run(){
                server.shutdown();
                logger.warn(">>>>>>>>>> jvm shutdown");
                System.exit(0);
            }
        });
    }
}

消息处理

@Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame)
            throws Exception {
        UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel());
        String content = frame.text();
        System.out.println("服务器在------" + LocalDate.now() + "收到消息" + content +
                "当前线程的id----" + Thread.currentThread().getId() + "线程名称" + Thread.currentThread().getName());
        Channel incoming = ctx.channel();
        clients.forEach(channel -> {
            if (channel != incoming) {
                channel.writeAndFlush(new TextWebSocketFrame("[" + incoming.remoteAddress() + "]" + frame.text()));
            } else {
                channel.writeAndFlush(new TextWebSocketFrame("[you]" + frame.text()));
            }
        });
        if (userInfo != null && userInfo.isAuth()) {
            JSONObject json = JSONObject.parseObject(frame.text());
            // 广播返回用户发送的消息文本
            UserInfoManager.broadcastMess(userInfo.getUserId(), userInfo.getNick(), json.getString("mess"));
        }
    }

启动:

服务器等待连接.............
当前服务器CPU核数4//本地电脑
userCount:
服务器在------2020-01-12收到消息{'code':10086,'mess':'2020年1月12日21:35:35  登录用户'}
当前线程的id----19线程名称DEFAULTEVENTLOOPGROUP_1

再启动一个客户端连接

服务器在------2020-01-12收到消息{'code':10086,'mess':'祖儿  登录2020年1月12日21:37:23'}
当前线程的id----22线程名称DEFAULTEVENTLOOPGROUP_2

UserInfoManager.java:124, broadCastPing userCount: 2个用户

但是当服务器接收多个客户端消息会出现延迟

服务器在------2020-01-12收到消息{'code':10086,'mess':'迪丽热巴 2020年1月12日21:39:47'}
当前线程的id----24线程名称DEFAULTEVENTLOOPGROUP_3

?????????

不是说好的Nio异步执行,不需要每次启动线程保持连接的吗?

现在为何每个客户端都进行了线程启动?

接受到的数据:[服务器在]2020-01-10T10:16:14.943接受到消息, 消息为:1002-------KK-------{'code':10086,'mess':'测试'}
2020-01-10 10:16:30.409  INFO 5514 --- [pool-1-thread-1] com.wolfbe.chat.handler.UserInfoManager  : broadCastPing userCount: 2
2020-01-10 10:16:30.443  INFO 5514 --- [VENTLOOPGROUP_1] com.wolfbe.chat.handler.UserAuthHandler  : receive pong message, address: 39.155.237.42:13007
2020-01-10 10:16:30.621  INFO 5514 --- [VENTLOOPGROUP_2] com.wolfbe.chat.handler.UserAuthHandler  : receive pong message, address: 223.104.3.131:25731
接受到的数据:[服务器在]2020-01-10T10:16:37.634接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'缺点不能去重'}
接受到的数据:[服务器在]2020-01-10T10:16:42.171接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:42.307接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:42.567接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:42.764接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'11'}
接受到的数据:[服务器在]2020-01-10T10:16:42.890接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.118接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.242接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.327接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.450接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.902接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:43.987接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:44.101接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:44.222接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:44.359接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
接受到的数据:[服务器在]2020-01-10T10:16:44.459接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'1'}
2020-01-10 10:16:50.389  INFO 5514 --- [pool-1-thread-1] com.wolfbe.chat.HappyChatServer          : scanNotActiveChannel --------
接受到的数据:[服务器在]2020-01-10T10:17:09.481接受到消息, 消息为:1002-------KK-------{'code':10086,'mess':'有没有延迟'}
接受到的数据:[服务器在]2020-01-10T10:17:18.279接受到消息, 消息为:1001-------kk-------{'code':10086,'mess':'有延迟'}
2020-01-10 10:17:20.409  INFO 5514 --- [pool-1-thread-1] com.wolfbe.chat.handler.UserInfoManager  : broadCastPing userCount: 2
2020-01-10 10:17:20.434  INFO 5514 --- [VENTLOOPGROUP_1] com.wolfbe.chat.handler.UserAuthHandler  : receive pong message, address: 39.155.237.42:13007
2020-01-10 10:17:20.484  INFO 5514 --- [VENTLOOPGROUP_2] com.wolfbe.chat.handler.UserAuthHandler  : receive pong message, address: 223.104.3.131:25731
2020-01-10 10:17:50.389  INFO 5514 --- [pool-1-thread-1] com.wolfbe.chat.HappyChatServer          : scanNotActiveChannel --------
2020-01-10 10:17:51.332  WARN 5514 --- [VENTLOOPGROUP_2] com.wolfbe.chat.handler.UserInfoManager  : channel will be remove, address is :223.104.3.131:25731
2020-01-10 10:17:51.333  WARN 5514 --- [VENTLOOPGROUP_2] com.wolfbe.chat.handler.UserInfoManager  : channel will be remove, address is :223.104.3.131:25731
2020-01-10 10:18:10.409  INFO 5514 --- [pool-1-thread-1] com.wolfbe.chat.handler.UserInfoManager  : broadCastPing userCount: 1
2020-01-10 10:18:10.437  INFO 5514 --- [VENTLOOPGROUP_1] com.wolfbe.chat.handler.UserAuthHandler  : receive pong message, address: 39.155.237.42:13007
2020-01-10 10:18:24.418  WARN 5514 --- [VENTLOOPGROUP_1] com.wolfbe.chat.handler.UserInfoManager  : channel will be remove, address is :39.155.237.42:13007

第一有延迟

而我可怜的阿里云私人服务器1 vCPU 2 GiB (I/O优化)!!!

1核啊,不管是cpu密集型,还是io密集型4个线程就够服务器响应的了,而且每个客户端为了保持长连接,还不会断开,所以当客户端多了,消息多了以后就会变慢,这严重违背了Nio的思想。

修改:

mainGroup = new NioEventLoopGroup();
subGroup = new NioEventLoopGroup();

启用Nio事件驱动组即可解决!!!

Connected to the target VM, address: '127.0.0.1:52594', transport: 'socket'
当前服务器CPU核数4
服务器等待连接.............
接收到的数据是:     hello  netty
TextWebSocketFrame(data: UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 78, cap: 156))
当前执行线程名字nioEventLoopGroup-5-1

永远不会延迟加载。

Netty的0拷贝不是不拷贝 是指没有CPU拷贝

只要你想开始,随时可以启程

本文分享自微信公众号 - 赵KK日常技术记录(gh_cc4c9f1a9521),作者:赵kk

原文出处及转载信息见文内详细说明,如有侵权,请联系 [email protected] 删除。

原始发表时间:2020-01-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 彻底解决分布式系统一致性问题整理(下)

    其实个人理解的时候,更希望能够得到代码层面的实现,单纯的理论知识还是不够落地,总结容易,真正实现起来还是需要项目的积累。

    疯狂的KK
  • JAVA8 Lambda表达式到底优不优雅?

    今天看到一个关注很久的公众号,作者是一个10年+的程序员,现在自主创业,他原文中是这么说的:多去看看外面的世界,不要把自己封闭在技术的圈子内,这...

    疯狂的KK
  • Jmeter系统入门教程(安装、组件使用、Demo展示、连接数据库、压测报告)

    压测工具实际项目中接触过ab,ab算一个常用而又直接的工具,jmeter以前自己测试过,但如此系统,细致的测试还是第一次,这个博主很多文章都很细致,问题解答及时

    疯狂的KK
  • eclipse maven 报错Could not get the value for parameter encoding for plugin execution default

    问题描述:更改默认的maven仓库路径完成后、即存maven项目或者新建maven项目的时候出现如下错误 Could not get the value for...

    庞小明
  • SpringMVC拦截器实现登录认证

    aopalliance.jar:这个包是AOP联盟的API包,里面包含了针对面向切面的接口。通常spring等其它具备动态织入功能的框架依赖这个jar

    用户1208223
  • “平行宇宙”要证实?科学家打造出预测多个“未来”的量子计算机

    该计算机在亚原子尺度上运行,最多能够同时模拟16条光子位置的时间线,也就是说该设备目前能够模拟16种未来。

    镁客网
  • Spring 详解(二)------- AOP关键概念以及两种实现方式

    当我们为系统做参数验证,登录权限验证或者日志操作等,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法,这样的话模...

    海向
  • 回车、换行、空格、空

    2009-06-14 很久很久以前写的文章了,最早发布于百度空间,一个已经被下线的博客平台。 CR 回车:\r ASCII:13、0d LF 换行:\n AS...

    雷大亨
  • 简约的JAVA版本MapReduce和日常No.25

    昨天做了一个小调查,说看看想看些啥。大概的分布是这样的,一个1代表一个投票。看来还是2、3比较多。 11111 希望看到"算法"回复1。 111...

    大蕉
  • 小程序音视频一触即现,助力企业新服务模式探索创新

    近日,微盛基于腾讯云小程序音视频解决方案开发出专属小程序应用,帮助客户从零开始一天搭建拥有互动音视频能力的小程序,助力企业新服务模式探索创新。

    江苏微盛网络科技有限公司

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动
http://www.vxiaotou.com