日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

目錄
  • 一、Reactor模式和Netty線程模型
    • 1. BIO模型
    • 2. NIO模型
    • 3. Reacor模型
      • ①. 單Reacor單線程模型
      • ②. 單Reactor多線程模型
      • ③. 主從Reactor多線程模型
      • ④. 部分源碼分析
  • 二、select/poll和epoll
    • 1.概念
      • 2.jdk提供selector
        • 3.Netty提供的Epoll封裝
          • 4.Netty相關類圖
            • 5.配置Netty為EpollEventLoop
            • 三、Netty相關參數
              • 1.SO_KEEPALIVE
                • 2.SO_REUSEADDR
                  • 3.TCP_NODELAY
                    • 4.SO_BACKLOG
                      • 5.ALLOCATOR和RCVBUF_ALLOCATOR
                      • 四、Netty關閉

                        一、Reactor模式和Netty線程模型

                        最近因為工作需要,學習了一段時間Netty的源碼,并做了一個簡單的分享,研究還不是特別深入,繼續努力。因為分享也不涉及公司業務,所以這里也把這次對源碼的研究成果分享出來 以下都是在游戲服務器開發中針對Netty使用需要了解知識點以及相關優化

                        這次分享主要設計以下內容

                        • Netty線程模型
                        • Netty對TCP相關參數的配置和具體含義
                        • Netty對Epoll的封裝
                        • Netty的優雅關閉

                        客戶端連接數的限制

                        • 內存資源
                        • CPU資源

                        端口號資源

                        cat /proc/sys/net/ipv4/ip_local_port_range
                        

                        文件描述符資源

                        • 系統級:當前系統可打開的最大數量,通過 cat /proc/sys/fs/file-max 查看
                        • 用戶級:指定用戶可打開的最大數量,通過 cat /etc/security/limits.conf 查看
                        • 進程級:單個進程可打開的最大數量,通過 cat /proc/sys/fs/nr_open 查看
                        • 線程資源 BIO/NIO

                        1. BIO模型

                        • 所有操作都是同步阻塞(accept,read)
                        • 客戶端連接數與服務器線程數比例是1:1

                        游戲服務器中的Netty應用以及源碼剖析

                        2. NIO模型

                        • 非阻塞IO
                        • 通過selector實現可以一個線程管理多個連接
                        • 通過selector的事件注冊(OP_READ/OP_WRITE/OP_CONNECT/OP_ACCEPT),處理自己感興趣的事件

                        客戶端連接數與服務器線程數比例是n:1

                        游戲服務器中的Netty應用以及源碼剖析

                        3. Reacor模型

                        ①. 單Reacor單線程模型

                            所有IO在同一個NIO線程完成(處理連接,分派請求,編碼,解碼,邏輯運算,發送)

                        優點

                        • 編碼簡單
                        • 不存在共享資源競爭
                        • 并發安全

                        缺點

                        • 單線程處理大量鏈路時,性能無法支撐,不能合理利用多核處理
                        • 線程過載后,處理速度變慢,會導致消息積壓
                        • 一旦線程掛掉,整個通信層不可用 redis使用的就是reactor單進程模型,redis由于都是內存級操作,所以使用此模式沒什么問題

                        reactor單線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        netty reactor單線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        Netty對應實現方式

                        // Netty對應實現方式:創建io線程組是,boss和worker,使用同一個線程組,并且線程數為1
                        EventLoopGroup ioGroup = new NioEventLoopGroup(1);
                        b.group(ioGroup, ioGroup)
                                .channel(NioServerSocketChannel.class)
                                .childHandler(initializer);
                        ChannelFuture f = b.bind(portNumner);
                        cf = f.sync();
                        f.get();
                        

                        ②. 單Reactor多線程模型

                        根據單線程模型,io處理中最耗時的編碼,解碼,邏輯運算等cpu消耗較多的部分,可提取出來使用多線程實現,并充分利用多核cpu的優勢

                        優點

                        多線程處理邏輯運算,提高多核CPU利用率

                        缺點

                        對于單Reactor來說,大量鏈接的IO事件處理依然是性能瓶頸

                        reactor多線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        netty reactor多線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        Netty對應實現方式

                        // Netty對應實現方式:創建io線程組是,boss和worker,使用同一個線程組,并且線程數為1,把邏輯運算部分投遞到用戶自定義線程處理
                        EventLoopGroup ioGroup = new NioEventLoopGroup(1);
                        b.group(ioGroup, ioGroup)
                                .channel(NioServerSocketChannel.class)
                                .childHandler(initializer);
                        ChannelFuture f = b.bind(portNumner);
                        cf = f.sync();
                        f.get();
                        

                        ③. 主從Reactor多線程模型

                        根據多線程模型,可把它的性能瓶頸做進一步優化,即把reactor由單個改為reactor線程池,把原來的reactor分為mainReactor和subReactor

                        優點

                        • 解決單Reactor的性能瓶頸問題(Netty/Nginx采用這種設計)

                        reactor主從多線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        netty reactor主從多線程模型圖

                        游戲服務器中的Netty應用以及源碼剖析

                        Netty對應實現方式

                        // Netty對應實現方式:創建io線程組boss和worker,boss線程數為1,work線程數為cpu*2(一般IO密集可設置為2倍cpu核數)
                        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
                        EventLoopGroup workerGroup = new NioEventLoopGroup();
                        b.group(bossGroup, workerGroup)
                                .channel(NioServerSocketChannel.class)
                                .childHandler(initializer);
                        ChannelFuture f = b.bind(portNumner);
                        cf = f.sync();
                        f.get();
                        

                        ④. 部分源碼分析

                        • 創建group實例
                        // 1.構造參數不傳或傳0,默認取系統參數配置,沒有參數配置,取CPU核數*2
                        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
                        private static final int DEFAULT_EVENT_LOOP_THREADS;
                        static {
                            DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                                    "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
                        }
                        // 2.不同版本的JDK會有不同版本的SelectorProvider實現,Windows下的是WindowsSelectorProvider
                        public NioEventLoopGroup(int nThreads, Executor executor) {
                            //默認selector,最終實現類似:https://github.com/frohoff/jdk8u-jdk/blob/master/src/macosx/classes/sun/nio/ch/DefaultSelectorProvider.java
                            //basic flow: 1 java.nio.channels.spi.SelectorProvider 2 META-INF/services 3 default
                            this(nThreads, executor, SelectorProvider.provider());
                        }
                        // 3.創建nThread個EventExecutor,并封裝到選擇器chooser,chooser會根據線程數分別有兩種實現(GenericEventExecutorChooser和PowerOfTwoEventExecutorChooser,算法不同,但實現邏輯一樣,就是均勻的分配線程處理)
                        EventExecutorChooserFactory.EventExecutorChooser chooser;
                        children = new EventExecutor[nThreads];
                        for (int i = 0; i < nThreads; i ++) {
                            // ...
                            children[i] = newChild(executor, args);
                            // ...
                        }
                        chooser = chooserFactory.newChooser(children);
                        
                        • 設置group
                        // 兩種方式設置group
                        // parent和child使用同一個group,調用仍然是分別設置parent和child
                        @Override
                        public ServerBootstrap group(EventLoopGroup group) {
                            return group(group, group);
                        }
                        ServerBootstrap.group(EventLoopGroup parentGroup, EventLoopGroup childGroup){
                            // 具體代碼略,可直接參考源碼
                            // 里面實現內容是把parentGroup綁定到this.group,把childGroup綁定到this.childGroup
                        }
                        
                        • Netty啟動
                        // 調用順序
                        ServerBootstrap:bind() -> doBind() -> initAndRegister()
                        private ChannelFuture doBind(final SocketAddress localAddress) {
                            final ChannelFuture regFuture = initAndRegister();
                            // ...
                            doBind0(regFuture, channel, localAddress, promise);
                            // ...
                        }
                        final ChannelFuture initAndRegister() {
                            // 創建ServerSocketChannel
                            Channel channel = channelFactory.newChannel();
                            // ...
                            // 開始register
                            ChannelFuture regFuture = config().group().register(channel);
                            // register調用順序
                            // next().register(channel) -> (EventLoop) super.next() -> chooser.next()
                            // ...
                        }
                        

                        由以上源碼可得知,bind只在起服調用一次,因此bossGroup僅調用一次regist,也就是僅調用一次next,因此只有一根線程是有用的,其余線程都是廢棄的,所以bossGroup線程數設置為1即可

                        // 啟動BossGroup線程并綁定本地SocketAddress
                        private static void doBind0(
                                final ChannelFuture regFuture, final Channel channel,
                                final SocketAddress localAddress, final ChannelPromise promise) {
                            channel.eventLoop().execute(new Runnable() {
                                @Override
                                public void run() {
                                    if (regFuture.isSuccess()) {
                                        channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                                    } else {
                                        promise.setFailure(regFuture.cause());
                                    }
                                }
                            });
                        }
                        
                        • 客戶端連接
                        // 消息事件讀取
                        NioEventLoop.run() -> processSelectedKeys() -> ... -> ServerBootstrapAcceptor.channelRead
                        // ServerBootstrapAcceptor.channelRead處理客戶端連接事件
                        // 最后一行的childGroup.register的邏輯和上面的代碼調用處一樣
                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                            child.pipeline().addLast(childHandler);
                            setChannelOptions(child, childOptions, logger);
                            setAttributes(child, childAttrs);
                            childGroup.register(child)
                        }
                        

                        二、select/poll和epoll

                        1.概念

                        • select(時間復雜度O(n)):用一個fd數組保存所有的socket,然后通過死循環遍歷調用操作系統的select方法找到就緒的fd
                        while(1) {
                          nready = select(list);
                          // 用戶層依然要遍歷,只不過少了很多無效的系統調用
                          for(fd <-- fdlist) {
                            if(fd != -1) {
                              // 只讀已就緒的文件描述符
                              read(fd, buf);
                              // 總共只有 nready 個已就緒描述符,不用過多遍歷
                              if(--nready == 0) break;
                            }
                          }
                        }
                        

                        poll(時間復雜度O(n)):同select,不過把fd數組換成了fd鏈表,去掉了fd最大連接數(1024個)的數量限制

                        epoll(時間復雜度O(1)):解決了select/poll的幾個缺陷

                        • 調用需傳入整個fd數組或fd鏈表,需要拷貝數據到內核
                        • 內核層需要遍歷檢查文件描述符的就緒狀態
                        • 內核僅返回可讀文件描述符個數,用戶仍需自己遍歷所有fd

                        epoll是操作系統基于事件關聯fd,做了以下優化:

                        • 內核中保存一份文件描述符集合,無需用戶每次都重新傳入,只需告訴內核修改的部分即可。(epoll_ctl)
                        • 內核不再通過輪詢的方式找到就緒的文件描述符,而是通過異步 IO 事件喚醒。(epoll_wait)
                        • 內核僅會將有 IO 事件的文件描述符返回給用戶,用戶也無需遍歷整個文件描述符集合。

                        epoll僅在Linux系統上支持

                        2.jdk提供selector

                        // DefaultSelectorProvider.create方法在不同版本的jdk下有不同實現,創建不同Selector
                        // Windows版本的jdk,其實現中調用的是native的poll方法
                        public static SelectorProvider create() {
                            return new WindowsSelectorProvider();
                        }
                        // Linux版本的jdk
                        public static SelectorProvider create() {
                            String str = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
                            if (str.equals("SunOS")) {
                                return createProvider("sun.nio.ch.DevPollSelectorProvider");
                            }
                            if (str.equals("Linux")) {
                                return createProvider("sun.nio.ch.EPollSelectorProvider");
                            }
                            return new PollSelectorProvider();
                        }
                        

                        3.Netty提供的Epoll封裝

                        netty依然基于epoll做了一層封裝,主要做了以下事情:

                        (1)java的nio默認使用水平觸發,Netty的Epoll默認使用邊緣觸發,且可配置

                        • 邊緣觸發:當狀態變化時才會發生io事件。
                        • 水平觸發:只要滿足條件,就觸發一個事件(只要有數據沒有被獲取,內核就不斷通知你)

                        (2)Netty的Epoll提供更多的nio的可配參數。

                        (3)調用c代碼,更少gc,更少synchronized 具體可以參考源碼NioEventLoop.run和EpollEventLoop.run進行對比

                        4.Netty相關類圖

                        線程組類圖

                        游戲服務器中的Netty應用以及源碼剖析

                        channel類圖

                        游戲服務器中的Netty應用以及源碼剖析

                        5.配置Netty為EpollEventLoop

                        // 創建指定的EventLoopGroup
                        bossGroup = new EpollEventLoopGroup(1, new DefaultThreadFactory("BOSS_LOOP"));
                        workerGroup = new EpollEventLoopGroup(32, new DefaultThreadFactory("IO_LOOP"));
                        b.group(bossGroup, workerGroup)
                                // 指定channel的class
                                .channel(EpollServerSocketChannel.class)
                                .childHandler(initializer);
                        // 其中channel(clz)方法是通過class來new一個反射ServerSocketChannel創建工廠類
                        public B channel(Class<? extends C> channelClass) {
                            if (channelClass == null) {
                                throw new NullPointerException("channelClass");
                            }
                            return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
                        }
                        final ChannelFuture initAndRegister() {
                            // ...
                            Channel channel = channelFactory.newChannel();
                            // ...
                        }
                        

                        三、Netty相關參數

                        1.SO_KEEPALIVE

                        childOption(ChannelOption.SO_KEEPALIVE, true)
                        

                        TCP鏈路探活

                        2.SO_REUSEADDR

                        option(ChannelOption.SO_REUSEADDR, true)
                        

                        重用處于TIME_WAIT但是未完全關閉的socket地址,讓端口釋放后可立即被重用。默認關閉,需要手動開啟

                        3.TCP_NODELAY

                        childOption(ChannelOption.TCP_NODELAY, true)
                        

                        IP報文格式

                        游戲服務器中的Netty應用以及源碼剖析

                        TCP報文格式

                        游戲服務器中的Netty應用以及源碼剖析

                        開啟則禁用TCP Negal算法,優點低延時,缺點在大量小數據包的情況下,網絡利用率低

                        關閉則開啟TCP Negal算法,優點提高網絡利用率(數據緩存到一定量才發送),缺點延時高

                        Negal算法

                        • 如果包長度達到MSS(maximum segment size最大分段長度),則允許發送;
                        • 如果該包含有FIN,則允許發送;
                        • 設置了TCP_NODELAY選項,則允許發送;
                        • 未設置TCP_CORK選項(是否阻塞不完整報文)時,若所有發出去的小數據包(包長度小于MSS)均被確認,則允許發送;
                        • 上述條件都未滿足,但發生了超時(一般為200ms),則立即發送。

                        MSS計算規則 MSS的值是在TCP三次握手建立連接的過程中,經通信雙方協商確定的 802.3標準里,規定了一個以太幀的數據部分(Payload)的最大長度是1500個字節(MTU)

                        MSS = MTU – IP首部 – TCP首部
                        以太網環境下:
                          MTU = 1500字節
                        IP首部 = 32*5/4 = 160bit = 20字節
                        TCP首部 = 32*5/4 = 160bit = 20字節
                        最終得出MSS = 1460字節

                        結論:因為游戲服務器的實時性要求,在網絡帶寬足夠的情況下,建議開啟TCP_NODELAY,關閉Negal算法,帶寬可以浪費,響應必須及時

                        注意:需要客戶端服務器均關閉Negal算法,否則仍然會有延遲發送,影響傳輸速度

                        4.SO_BACKLOG

                        option(ChannelOption.SO_BACKLOG, 100)
                        

                        操作系統內核中維護的兩個隊列

                        • syns queue:保存syn到達,但沒完成三次握手的半連接
                        cat /proc/sys/net/ipv4/tcp_max_syn_backlog
                        
                        • accpet queue:保存完成三次握手,內核等待accept調用的連接
                        cat /proc/sys/net/core/somaxconn
                        

                        netty對于backlog的默認值設置在NetUtil類253行

                        SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
                            @Override
                            public Integer run() {
                                // 1.設置默認值
                                int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
                                File file = new File("/proc/sys/net/core/somaxconn");
                                if (file.exists()) {
                                    // 2.文件存在,讀取操作系統配置
                                    in = new BufferedReader(new FileReader(file));
                                    somaxconn = Integer.parseInt(in.readLine());
                                } else {
                                    // 3.文件不存在,從各個參數中讀取
                                    if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) {
                                        tmp = sysctlGetInt("kern.ipc.somaxconn");
                                        if (tmp == null) {
                                            tmp = sysctlGetInt("kern.ipc.soacceptqueue");
                                            if (tmp != null) {
                                                somaxconn = tmp;
                                            }
                                        } else {
                                            somaxconn = tmp;
                                        }
                                    }
                                }
                            }
                        }
                        

                        結論:

                        Linux下/proc/sys/net/core/somaxconn一定存在,所以backlog一定取得它的值,我參考prod機器的參數配置的65535,也就是不設置backlog的情況下,服務器運行緩存65535個全連接

                        5.ALLOCATOR和RCVBUF_ALLOCATOR

                        游戲服務器中的Netty應用以及源碼剖析

                        默認分配ByteBuffAllocator賦值如下: ByteBufUtil.java

                        static {
                            //以io.netty.allocator.type為準,沒有的話,安卓平臺用非池化實現,其他用池化實現
                            String allocType = SystemPropertyUtil.get(
                                    "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
                            allocType = allocType.toLowerCase(Locale.US).trim();
                            ByteBufAllocator alloc;
                            if ("unpooled".equals(allocType)) {
                                alloc = UnpooledByteBufAllocator.DEFAULT;
                                logger.debug("-Dio.netty.allocator.type: {}", allocType);
                            } else if ("pooled".equals(allocType)) {
                                alloc = PooledByteBufAllocator.DEFAULT;
                                logger.debug("-Dio.netty.allocator.type: {}", allocType);
                            } else {
                                //io.netty.allocator.type設置的不是"unpooled"或者"pooled",就用池化實現。
                                alloc = PooledByteBufAllocator.DEFAULT;
                                logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
                            }
                            DEFAULT_ALLOCATOR = alloc;
                        }
                        

                        RCVBUF_ALLOCATOR默認AdaptiveRecvByteBufAllocator

                        public class DefaultChannelConfig implements ChannelConfig {
                            // ...
                            public DefaultChannelConfig(Channel channel) {
                                this(channel, new AdaptiveRecvByteBufAllocator());
                            }
                            // ...
                        }
                        

                        四、Netty關閉

                        /**
                         * Shortcut method for {@link #shutdownGracefully(long, long, TimeUnit)} with sensible default values.
                         *
                         * @return the {@link #terminationFuture()}
                         */
                        Future<?> shutdownGracefully();
                        /**
                         * Signals this executor that the caller wants the executor to be shut down.  Once this method is called,
                         * {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down.
                         * Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
                         * (usually a couple seconds) before it shuts itself down.  If a task is submitted during the quiet period,
                         * it is guaranteed to be accepted and the quiet period will start over.
                         *
                         * @param quietPeriod the quiet period as described in the documentation
                                             靜默期:在此期間,仍然可以提交任務
                         * @param timeout     the maximum amount of time to wait until the executor is {@linkplain #shutdown()}
                         *                    regardless if a task was submitted during the quiet period
                                             超時時間:等待所有任務執行完的最大時間
                         * @param unit        the unit of {@code quietPeriod} and {@code timeout}
                         *
                         * @return the {@link #terminationFuture()}
                         */
                        Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
                        // 抽象類中的實現
                        static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;
                        static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;
                        @Override
                        public Future<?> shutdownGracefully() {
                            return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
                        }
                        
                        • 把NIO線程的狀態位設置成ST_SHUTTING_DOWN狀態,不再處理新的消息(不允許再對外發送消息);
                        • 退出前的預處理操作:把發送隊列中尚未發送或者正在發送的消息發送完、把已經到期或者在退出超時之前到期的定時任務執行完成、把用戶注冊到NIO線程的退出Hook任務執行完成;
                        • 資源的釋放操作:所有Channel的釋放、多路復用器的去注冊和關閉、所有隊列和定時任務的清空取消,最后是NIO線程的退出。

                        以上就是游戲服務器中的Netty應用以及源碼剖析的詳細內容,更多關于Netty游戲服務器的資料請關注其它相關文章!

                        分享到:
                        標簽:剖析 器中 服務 服務器 源碼
                        用戶無頭像

                        網友整理

                        注冊時間:

                        網站:5 個   小程序:0 個  文章:12 篇

                        • 51998

                          網站

                        • 12

                          小程序

                        • 1030137

                          文章

                        • 747

                          會員

                        趕快注冊賬號,推廣您的網站吧!
                        最新入駐小程序

                        數獨大挑戰2018-06-03

                        數獨一種數學游戲,玩家需要根據9

                        答題星2018-06-03

                        您可以通過答題星輕松地創建試卷

                        全階人生考試2018-06-03

                        各種考試題,題庫,初中,高中,大學四六

                        運動步數有氧達人2018-06-03

                        記錄運動步數,積累氧氣值。還可偷

                        每日養生app2018-06-03

                        每日養生,天天健康

                        體育訓練成績評定2018-06-03

                        通用課目體育訓練成績評定