成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

宜人貸蜂巢API網(wǎng)關技術解密之Netty使用實踐

2021-02-08    分類: 網(wǎng)站建設

宜人貸蜂巢團隊,由Michael創(chuàng)立于2013年,通過使用互聯(lián)網(wǎng)科技手段助力金融生態(tài)和諧健康發(fā)展。自成立起一直致力于多維度數(shù)據(jù)閉環(huán)平臺建設。目前團隊規(guī)模超過百人,涵蓋征信、

圖1 - API網(wǎng)關項目框架

圖中描繪了API網(wǎng)關系統(tǒng)的處理流程,以及與服務注冊發(fā)現(xiàn)、日志分析、報警系統(tǒng)、各類爬蟲的關系。其中API網(wǎng)關系統(tǒng)接收請求,對請求進行編解碼、鑒權、限流、加解密,再基于Eureka服務注冊發(fā)現(xiàn)模塊,將請求發(fā)送到有效的服務節(jié)點上;網(wǎng)關及抓取系統(tǒng)的日志,會被收集到elk平臺中,做業(yè)務分析及報警處理。

二、BIO vs NIO

API網(wǎng)關承載數(shù)倍于爬蟲的流量,提升服務器的并發(fā)處理能力、縮短系統(tǒng)的響應時間,通信模型的選擇是至關重要的,是選擇BIO,還是NIO?

1. Streamvs Buffer & 阻塞 vs 非阻塞

BIO是面向流的,io的讀寫,每次只能處理一個或者多個bytes,如果數(shù)據(jù)沒有讀寫完成,線程將一直等待于此,而不能暫時跳過io或者等待io讀寫完成異步通知,線程滯留在io讀寫上,不能充分利用機器有限的線程資源,造成server的吞吐量較低,見圖2。而NIO與此不同,面向Buffer,線程不需要滯留在io讀寫上,采用操作系統(tǒng)的epoll模式,在io數(shù)據(jù)準備好了,才由線程來處理,見圖3。

NioEvenrLoopGroup的創(chuàng)建,具體執(zhí)行過程是執(zhí)行類MultithreadEventExecutorGroup的構造方法:

  1. /**  
  2.  * Create a new instance.  
  3.  *  
  4.  * @param nThreads          the number of threads that will be used by this instance.  
  5.  * @param executor          the Executor to use, or {@code null} if the default should be used.  
  6.  * @param chooserFactory    the {@link EventExecutorChooserFactory} to use.  
  7.  * @param args              arguments which will passed to each {@link #newChild(Executor, Object...)} call  
  8.  */  
  9. protected MultithreadEventExecutorGroup(int nThreads, Executor executor,  
  10.                                         EventExecutorChooserFactory chooserFactory, Object... args) {  
  11.     if (nThreads <= 0) {  
  12.         throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));  
  13.     }  
  14.     if (executor == null) {  
  15.         executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());  
  16.     }  
  17.     children = new EventExecutor[nThreads];  
  18.     for (int i = 0; i < nThreads; i ++) {  
  19.         boolean success = false;  
  20.         try {  
  21.             children[i] = newChild(executor, args);  
  22.             success = true;  
  23.         } catch (Exception e) {   
  24.             throw new IllegalStateException("failed to create a child event loop", e);  
  25.         } finally {  
  26.             if (!success) {  
  27.                 for (int j = 0; j < i; j ++) {  
  28.                     children[j].shutdownGracefully();  
  29.                 }  
  30.                 for (int j = 0; j < i; j ++) { 
  31.                      EventExecutor e = children[j]; 
  32.                      try { 
  33.                          while (!e.isTerminated()) {  
  34.                             e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);  
  35.                         }  
  36.                     } catch (InterruptedException interrupted) {  
  37.                         // Let the caller handle the interruption.  
  38.                         Thread.currentThread().interrupt();  
  39.                         break;  
  40.                     }  
  41.                 }  
  42.             }  
  43.         }  
  44.     }  
  45.     chooser = chooserFactory.newChooser(children);  
  46.     final FutureListener<Object> terminationListener = new FutureListener<Object>() {  
  47.         @Override  
  48.         public void operationComplete(Future<Object> future) throws Exception {  
  49.             if (terminatedChildren.incrementAndGet() == children.length) {  
  50.                 terminationFuture.setSuccess(null);  
  51.             }  
  52.         }  
  53.     };  
  54.     for (EventExecutor e: children) {  
  55.         e.terminationFuture().addListener(terminationListener);  
  56.     }  
  57.     Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);  
  58.     Collections.addAll(childrenSet, children);  
  59.     readonlyChildren = Collections.unmodifiableSet(childrenSet);  
  60. 其中,創(chuàng)建細節(jié)見下:

    • 線程池中的線程數(shù)nThreads必須大于0;
    • 如果executor為null,創(chuàng)建默認executor,executor用于創(chuàng)建線程(newChild方法使用executor對象);
    • 依次創(chuàng)建線程池中的每一個線程即NioEventLoop,如果其中有一個創(chuàng)建失敗,將關閉之前創(chuàng)建的所有線程;
    • chooser為線程池選擇器,用來選擇下一個EventExecutor,可以理解為,用來選擇一個線程來執(zhí)行task。

    chooser的創(chuàng)建細節(jié),見下:

    DefaultEventExecutorChooserFactory根據(jù)線程數(shù)創(chuàng)建具體的EventExecutorChooser,線程數(shù)如果等于2^n,可使用按位與替代取模運算,節(jié)省cpu的計算資源,見源碼:

    1. @SuppressWarnings("unchecked")  
    2. @Override  
    3. public EventExecutorChooser newChooser(EventExecutor[] executors) {  
    4.     if (isPowerOfTwo(executors.length)) {  
    5.         return new PowerOfTowEventExecutorChooser(executors);  
    6.     } else {  
    7.         return new GenericEventExecutorChooser(executors);  
    8.     }  
    9. }   
    10.     private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {  
    11.         private final AtomicInteger idx = new AtomicInteger();  
    12.         private final EventExecutor[] executors;   
    13.  
    14.         PowerOfTowEventExecutorChooser(EventExecutor[] executors) {  
    15.             this.executors = executors;  
    16.         }   
    17.  
    18.         @Override  
    19.         public EventExecutor next() {  
    20.             return executors[idx.getAndIncrement() & executors.length - 1];  
    21.         }  
    22.     }   
    23.  
    24.     private static final class GenericEventExecutorChooser implements EventExecutorChooser {  
    25.         private final AtomicInteger idx = new AtomicInteger();  
    26.         private final EventExecutor[] executors;   
    27.  
    28.         GenericEventExecutorChooser(EventExecutor[] executors) {  
    29.             this.executors = executors;  
    30.         }   
    31.  
    32.         @Override  
    33.         public EventExecutor next() {  
    34.             return executors[Math.abs(idx.getAndIncrement() % executors.length)];  
    35.         }  
    36.     } 

    newChild(executor, args)的創(chuàng)建細節(jié),見下:

    MultithreadEventExecutorGroup的newChild方法是一個抽象方法,故使用NioEventLoopGroup的newChild方法,即調(diào)用NioEventLoop的構造函數(shù):

    1. @Override  
    2.     protected EventLoop newChild(Executor executor, Object... args) throws Exception {  
    3.         return new NioEventLoop(this, executor, (SelectorProvider) args[0], 
    4.             ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);  
    5.     } 

    在這里先看下NioEventLoop的類層次關系:

    創(chuàng)建任務隊列tailTasks(內(nèi)部為有界的LinkedBlockingQueue):

    創(chuàng)建線程的任務隊列taskQueue(內(nèi)部為有界的LinkedBlockingQueue),以及任務過多防止系統(tǒng)宕機的拒絕策略rejectedHandler。

    其中tailTasks和taskQueue均是任務隊列,而優(yōu)先級不同,taskQueue的優(yōu)先級高于tailTasks,定時任務的優(yōu)先級高于taskQueue。

    五、ServerBootstrap初始化及啟動

    了解了Netty線程池NioEvenrLoopGroup的創(chuàng)建過程后,下面看下API網(wǎng)關服務ServerBootstrap的是如何使用線程池引入服務中,為高并發(fā)訪問服務的。

    API網(wǎng)關ServerBootstrap初始化及啟動代碼,見下:

    1. serverBootstrap = new ServerBootstrap();  
    2. bossGroup = new NioEventLoopGroup(config.getBossGroupThreads());  
    3. workerGroup = new NioEventLoopGroup(config.getWorkerGroupThreads());   
    4.  
    5. serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)  
    6.         .option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay())  
    7.         .option(ChannelOption.SO_BACKLOG, config.getBacklogSize())  
    8.         .option(ChannelOption.SO_KEEPALIVE, config.isSoKeepAlive())  
    9.         // Memory pooled  
    10.         .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)  
    11.         .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)  
    12.         .childHandler(channelInitializer);    
    13.  
    14. ChannelFuture future = serverBootstrap.bind(config.getPort()).sync();  
    15. log.info("API-gateway started on port: {}", config.getPort());  
    16. future.channel().closeFuture().sync(); 

    API網(wǎng)關系統(tǒng)使用netty自帶的線程池,共有三組線程池,分別為bossGroup、workerGroup和executorGroup(使用在channelInitializer中,本文暫不作介紹)。其中,bossGroup用于接收客戶端的TCP連接,workerGroup用于處理I/O、執(zhí)行系統(tǒng)task和定時任務,executorGroup用于處理網(wǎng)關業(yè)務加解密、限流、路由,及將請求轉發(fā)給后端的抓取服務等業(yè)務操作。

    六、Channel與線程池的綁定

    ServerBootstrap初始化后,通過調(diào)用bind(port)方法啟動Server,bind的調(diào)用鏈如下:

    1. AbstractBootstrap.bind ->AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister 

    其中,ChannelFuture regFuture = config().group().register(channel);中的group()方法返回bossGroup,而channel在serverBootstrap的初始化過程指定channel為NioServerSocketChannel.class,至此將NioServerSocketChannel與bossGroup綁定到一起,bossGroup負責客戶端連接的建立。那么NioSocketChannel是如何與workerGroup綁定到一起的?

    調(diào)用鏈AbstractBootstrap.initAndRegister -> AbstractBootstrap. init-> ServerBootstrap.init ->ServerBootstrapAcceptor.ServerBootstrapAcceptor ->ServerBootstrapAcceptor.channelRead:

    1. public void channelRead(ChannelHandlerContext ctx, Object msg) {  
    2.     final Channel child = (Channel) msg;  
    3.     child.pipeline().addLast(childHandler);  
    4.     for (Entry<ChannelOption<?>, Object> e: childOptions) {  
    5.         try {  
    6.             if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {  
    7.                 logger.warn("Unknown channel option: " + e);  
    8.             }  
    9.         } catch (Throwable t) {  
    10.             logger.warn("Failed to set a channel option: " + child, t); 
    11.         }  
    12.     }  
    13.     for (Entry<AttributeKey<?>, Object> e: childAttrs) {  
    14.         child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());  
    15.     } 
    16.  
    17.     try {  
    18.         childGroup.register(child).addListener(new ChannelFutureListener() {  
    19.             @Override  
    20.             public void operationComplete(ChannelFuture future) throws Exception {  
    21.                 if (!future.isSuccess()) { 
    22.                      forceClose(child, future.cause());  
    23.                 }  
    24.             }  
    25.         });  
    26.     } catch (Throwable t) {  
    27.         forceClose(child, t);  
    28.     }  

    其中,childGroup.register(child)就是將NioSocketChannel與workderGroup綁定到一起,那又是什么觸發(fā)了ServerBootstrapAcceptor的channelRead方法?

    其實當一個 client 連接到 server 時,Java 底層的 NIO ServerSocketChannel 會有一個SelectionKey.OP_ACCEPT 就緒事件,接著就會調(diào)用到 NioServerSocketChannel.doReadMessages方法。

    1. @Override  
    2. protected int doReadMessages(List<Object> buf) throws Exception {  
    3.     SocketChannel ch = javaChannel().accept();  
    4.     try {  
    5.         if (ch != null) {  
    6.             buf.add(new NioSocketChannel(this, ch));  
    7.             return 1;  
    8.         }  
    9.     } catch (Throwable t) {          … 
    10.  
    11.     }  
    12.     return 0;  

    javaChannel().accept() 會獲取到客戶端新連接的SocketChannel,實例化為一個 NioSocketChannel, 并且傳入 NioServerSocketChannel 對象(即 this),由此可知, 我們創(chuàng)建的這個NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 實例 。

    接下來就經(jīng)由 Netty 的 ChannelPipeline 機制,將讀取事件逐級發(fā)送到各個 handler 中,于是就會觸發(fā)前面我們提到的 ServerBootstrapAcceptor.channelRead 方法啦。

    至此,分析了Netty線程池的初始化、ServerBootstrap的啟動及channel與線程池的綁定過程,能夠看出Netty中線程池的優(yōu)雅設計,使用不同的線程池負責連接的建立、IO讀寫等,為API網(wǎng)關項目的高并發(fā)訪問提供了技術基礎。

    七、總結

    網(wǎng)站題目:宜人貸蜂巢API網(wǎng)關技術解密之Netty使用實踐
    文章分享:http://jinyejixie.com/news46/99846.html

    成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供面包屑導航、定制開發(fā)、動態(tài)網(wǎng)站、網(wǎng)站改版、做網(wǎng)站網(wǎng)頁設計公司

    廣告

    聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

    微信小程序開發(fā)
    兴城市| 河源市| 青铜峡市| 元阳县| 禹城市| 犍为县| 安康市| 哈巴河县| 新密市| 兴国县| 新密市| 中方县| 卢氏县| 宝丰县| 普陀区| 道真| 恭城| 韩城市| 犍为县| 保德县| 百色市| 滁州市| 龙州县| 黎城县| 惠州市| 板桥市| 永泰县| 定陶县| 连州市| 三台县| 赞皇县| 二连浩特市| 金沙县| 肇东市| 田林县| 枣庄市| 武陟县| 洛阳市| 潞城市| 永年县| 靖边县|