??xml version="1.0" encoding="utf-8" standalone="yes"?>成人国产二区,国产一区二区三区观看,国产一区 二区http://www.aygfsteel.com/freeman1984/category/55069.htmlSTANDING ON THE SHOULDERS OF GIANTS zh-cnWed, 13 Jul 2016 02:05:17 GMTWed, 13 Jul 2016 02:05:17 GMT60Netty百万U推送服务(转)(j)http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431178.html疯狂疯狂Wed, 13 Jul 2016 02:03:00 GMThttp://www.aygfsteel.com/freeman1984/archive/2016/07/13/431178.htmlhttp://www.aygfsteel.com/freeman1984/comments/431178.htmlhttp://www.aygfsteel.com/freeman1984/archive/2016/07/13/431178.html#Feedback0http://www.aygfsteel.com/freeman1984/comments/commentRss/431178.htmlhttp://www.aygfsteel.com/freeman1984/services/trackbacks/431178.html转蝲自:(x)http://www.open-open.com/lib/view/open1420623195375.html

1. 背景

1.1. 话题来源

最q很多从事移动互联网和物联网开发的同学l我发邮件或者微博私信我Q咨询推送服务相关的问题。问题五花八门,在帮助大家答疑解惑的q程中,我也寚w题进行了(jin)ȝQ大概可以归Uؓ(f)如下几类Q?/p>

  1. Netty是否可以做推送服务器Q?/li>
  2. 如果使用Netty开发推送服务,一个服务器最多可以支撑多个客户端?
  3. 使用Netty开发推送服务遇到的各种技术问题?/li>

׃咨询者众多,x点也比较集中Q我希望通过本文的案例分析和Ҏ(gu)送服务设计要点的ȝQ帮助大家在实际工作中少走弯路?/p>

1.2. 推送服?/h3>

Ud互联|时代,推?Push)服务成ؓ(f)App应用不可或缺的重要组成部分,推送服务可以提升用L(fng)z跃度和留存率。我们的手机每天接收到各U各L(fng)q告和提C消息等大多数都是通过推送服务实现的?/p>

随着物联|的发展Q大多数的智能家居都支持Ud推送服务,未来所有接入物联网的智能设备都是推送服务的客户端,q就意味着推送服务未来会(x)面(f)量的设备和l端接入?/p>

1.3. 推送服务的特点

Ud推送服务的主要特点如下Q?/p>

  1. 使用的网l主要是q营商的无线Ud|络Q网l质量不E_Q例如在地铁上信号就很差Q容易发生网l闪断;
  2. 量的客L(fng)接入Q而且通常使用长连接,无论是客L(fng)q是服务端,资源消耗都非常大;
  3. ׃h的推送框架无法在国内使用QAndroid的长q接是由每个应用各自l护的,q就意味着每台安卓讑֤上会(x)存在多个长连接。即便没有消息需要推送,长连接本w的?j)蟩消息量也是非常巨大的Q这׃(x)D量和耗电(sh)量的增加Q?/li>
  4. 不稳定:(x)消息丢失、重复推送、gq送达、过期推送时有发生;
  5. 垃圾消息满天飞,~Zl一的服务治理能力?/li>

Z(jin)解决上述弊端Q一些企业也l出?jin)自q解决Ҏ(gu)Q例如京东云推出的推送服务,可以实现多应用单服务单连接模式,使用AlarmManager定时?j)蟩节省电(sh)量和流量?/p>

2. 家居领域的一个真实案?/h2>

2.1. 问题描述

家居MQTT消息服务中间Ӟ保持10万用户在UKq接Q?万用户ƈ发做消息h。程序运行一D|间之后,发现内存泄露Q怀疑是Netty的Bug。其它相关信息如下:(x)

  1. MQTT消息服务中间件服务器内存16GQ?个核?j)CPUQ?/li>
  2. Netty中bossU程池大ؓ(f)1QworkerU程池大ؓ(f)6Q其余线E分配给业务使用。该分配方式后来调整为workerU程池大ؓ(f)11Q问题依旧;
  3. Netty版本?.0.8.Final?/li>

2.2. 问题定位

首先需要dump内存堆栈Q对疑似内存泄露的对象和引用关系q行分析Q如下所C:(x)

Netty百万U推送服? src=

我们发现Netty的ScheduledFutureTask增加?076%Q达?10W个左右的实例Q通过对业务代码的分析发现用户使用IdleStateHandler用于在链路空闲时q行业务逻辑处理Q但是空闲时间设|的比较大,?5分钟?/p>

Netty 的IdleStateHandler?x)根据用L(fng)使用场景Q启动三cd时Q务,分别是:(x)ReaderIdleTimeoutTask?WriterIdleTimeoutTask和AllIdleTimeoutTaskQ它们都?x)被加入到NioEventLoop的Task队列中被调度 和执行?/p>

?于超时时间过长,10W个长链接链\?x)创?0W个ScheduledFutureTask对象Q每个对象还保存有业务的成员变量Q非常消耗内存。用L(fng) 持久代设|的比较大,一些定时Q务被老化到持久代中,没有被JVM垃圾回收掉,内存一直在增长Q用戯认ؓ(f)存在内存泄露?/p>

事实上,我们q一步分析发玎ͼ用户的超时时间设|的非常不合理,15分钟的超时达不到设计目标Q重新设计之后将时旉讄?5U,内存可以正常回收Q问题解冟?/p>

2.3. 问题ȝ

如果?00个长q接Q即便是长周期的定时dQ也不存在内存泄露问题,在新生代通过minor GC可以实现内存回收。正是因为十万的长q接Q导致小问题被放大,引出?jin)后l的各种问题?/p>

事实上,如果用户实有长周期q行的定时Q务,该如何处理?对于量长连接的推送服务,代码处理E有不慎Q就满盘皆输Q下面我们针对Netty的架构特点,介绍下如何用Netty实现百万U客L(fng)的推送服务?/p>

3. Netty量推送服务设计要?/h2>

作ؓ(f)高性能的NIO框架Q利用Netty开发高效的推送服务技术上是可行的Q但是由于推送服务自w的复杂性,惌开发出E_、高性能的推送服务ƈ非易事,需要在设计阶段针对推送服务的特点q行合理设计?/p>

3.1. 最大句柄数修改

百万长连接接入,首先需要优化的是Linux内核参数Q其中Linux最大文件句柄数是最重要的调优参C一Q默认单q程打开的最大句柄数?024Q通过ulimit -a可以查看相关参数Q示例如下:(x)

[root@lilinfeng ~]# ulimit -a core file size          (blocks, -c) 0 data seg size           (kbytes, -d) unlimited scheduling priority             (-e) 0 file size               (blocks, -f) unlimited pending signals                 (-i) 256324 max locked memory       (kbytes, -l) 64 max memory size         (kbytes, -m) unlimited open files                      (-n) 1024  ......后箋输出省略

当单个推送服务接收到的链接超q上限后Q就?x)?#8220;too many open files”Q所有新的客L(fng)接入失败?/p>

通过vi /etc/security/limits.conf d如下配置参数Q修改之后保存,注销当前用户Q重新登录,通过ulimit -a 查看修改的状态是否生效?/p>

*  soft        nofile        1000000 *  hard        nofile        1000000

需要指出的是,管我们可以单个进E打开的最大句柄数修改的非常大Q但是当句柄数达C定数量之后Q处理效率将出现明显下降Q因此,需要根据服务器的硬仉|和处理能力q行合理讄。如果单个服务器性能不行也可以通过集群的方式实现?/p>

3.2. 当心(j)CLOSE_WAIT

从事Ud推送服务开发的同学可能都有体会(x)Q移动无U网l可靠性非常差Q经常存在客L(fng)重置q接Q网l闪断等?/p>

在百万长q接的推送系l中Q服务端需要能够正处理这些网l异常,设计要点如下Q?/p>

  1. 客户端的重连间隔需要合理设|,防止q接q于频繁D的连接失败(例如端口q没有被释放Q;
  2. 客户端重复登陆拒l机Ӟ
  3. 服务端正处理I/O异常和解码异常等Q防止句柄泄霌Ӏ?/li>

最 后特别需要注意的一点就是close_wait q多问题Q由于网l不E_l常?x)导致客L(fng)断连Q如果服务端没有能够?qing)时关闭socketQ就?x)导致处于close_wait状态的链\q多?close_wait状态的链\q不释放句柄和内存等资源Q如果积压过多可能会(x)Dpȝ句柄耗尽Q发?#8220;Too many open files”异常Q新的客L(fng)无法接入Q涉?qing)创建或者打开句柄的操作都失败?/p>

下面对close_wait状态进行下单介l,被动关闭TCPq接状态迁Ud如下所C:(x)

Netty百万U推送服? src=

?-1 被动关闭TCPq接状态迁Ud

close_wait 是被动关闭连接是形成的,Ҏ(gu)TCP状态机Q服务器端收到客L(fng)发送的FINQTCP协议栈会(x)自动发送ACKQ链接进入close_wait状态。但如果 服务器端不执行socket的close()操作Q状态就不能由close_waitq移到l(f)ast_ackQ则pȝ中会(x)存在很多close_wait?态的q接。通常来说Q一个close_wait?x)维持至?个小时的旉Q系l默认超时时间的?200U,也就?时Q。如果服务端E序因某个原因导 致系l造成一堆close_wait消耗资源,那么通常是等不到释放那一刻,pȝ已崩溃?/p>

Dclose_waitq多的可能原因如下:(x)

  1. E序处理BugQ导致接收到Ҏ(gu)的fin之后没有?qing)时关闭socketQ这可能是Netty的BugQ也可能是业务层BugQ需要具体问题具体分析;
  2. 关闭socket不及(qing)Ӟ(x)例如I/OU程被意外阻塞,或者I/OU程执行的用戯定义Task比例q高Q导致I/O操作处理不及(qing)Ӟ链\不能被及(qing)旉放?/li>

下面我们l合Netty的原理,Ҏ(gu)在的故障点进行分析?/p>

?计要?Q不要在Netty的I/OU程上处理业务(?j)蟩发送和(g)除外)(j)。Why? 对于Javaq程Q线E不能无限增长,q就意味着Netty的ReactorU程数必L敛。Netty的默认值是CPU核数 * 2Q通常情况下,I/O密集型应用徏议线E数量讄大些Q但q主要是针对传统同步I/O而言Q对于非dI/OQ线E数q不讄太大Q尽没有最?|但是I/OU程数经验值是[CPU核数 + 1QCPU核数*2 ]之间?/p>

?如单个服务器支撑100万个长连接,服务器内核数?2Q则单个I/OU程处理的链接数L = 100/(32 * 2) = 15625?假如?S有一ơ消息交互(新消息推送、心(j)x息和其它理消息Q,则^均CAPS = 15625 / 5 = 3125?U。这个数值相比于Netty的处理性能而言压力q不大,但是在实际业务处理中Q经怼(x)有一些额外的复杂逻辑处理Q例如性能l计、记录接口日 志等Q这些业务操作性能开销也比较大Q如果在I/OU程上直接做业务逻辑处理Q可能会(x)dI/OU程Q媄(jing)响对其它链\的读写操作,q就?x)导致被动关闭的?路不能及(qing)时关闭,造成close_wait堆积?/p>

设计要点2Q在I/OU程上执行自定义Task要当?j)。Netty的I/O处理U程NioEventLoop支持两种自定义Task的执行:(x)

  1. 普通的Runnable: 通过调用NioEventLoop的execute(Runnable task)Ҏ(gu)执行Q?/li>
  2. 定时dScheduledFutureTask:通过调用NioEventLoop的schedule(Runnable command, long delay, TimeUnit unit)pd接口执行?/li>

Z么NioEventLoop要支持用戯定义Runnable和ScheduledFutureTask的执行,q不是本文要讨论的重点,后箋?x)有专题文章q行介绍。本文重点对它们的媄(jing)响进行分析?/p>

?NioEventLoop中执行Runnable和ScheduledFutureTaskQ意味着允许用户在NioEventLoop中执行非I/O?作类的业务逻辑Q这些业务逻辑通常用消息报文的处理和协议管理相兟뀂它们的执行?x)抢占NioEventLoop I/Od的CPU旉Q如果用戯定义Taskq多Q或者单个Task执行周期q长Q会(x)DI/Od操作被阻塞,q样也间接导致close_wait 堆积?/p>

所 以,如果用户在代码中使用C(jin)Runnable和ScheduledFutureTaskQ请合理讄ioRatio的比例,通过 NioEventLoop的setIoRatio(int ioRatio)Ҏ(gu)可以讄该|默认gؓ(f)50Q即I/O操作和用戯定义d的执行时间比?Q??/p>

我的是当服务端处理v量客L(fng)长连接的时候,不要在NioEventLoop中执行自定义TaskQ或者非?j)蟩cȝ定时d?/p>

?计要?QIdleStateHandler使用要当?j)。很多用户会(x)使用IdleStateHandler做心(j)跛_送和(g),q种用法值得提倡。相比于?己启定时d发送心(j)跻Iq种方式更高效。但是在实际开发中需要注意的是,在心(j)跳的业务逻辑处理中,无论是正常还是异常场景,处理时g要可控,防止时g不可 控导致的NioEventLoop被意外阻塞。例如,?j)蟩时或者发生I/O异常Ӟ业务调用Email发送接口告警,׃Email服务端处理超Ӟ?致邮件发送客L(fng)被阻塞,U联引vIdleStateHandler的AllIdleTimeoutTaskd被阻塞,最lNioEventLoop?路复用器上其它的链\d被阻塞?/p>

对于ReadTimeoutHandler和W(xu)riteTimeoutHandlerQ约束同样存在?/p>

3.3. 合理的心(j)跛_?/h3>

百万U的推送服务,意味着?x)存在百万个长连接,每个长连接都需要靠和App之间的心(j)xl持链\。合理设|心(j)跛_期是非常重要的工作,推送服务的?j)蟩周期讄需要考虑Ud无线|络的特炏V?/p>

?一台智能手上移动网l时Q其实ƈ没有真正q接上InternetQ运营商分配l手机的IP其实是运营商的内|IPQ手机终端要q接上Internet q必通过q营商的|关q行IP地址的{换,q个|关UCؓ(f)NAT(NetWork Address Translation)Q简单来说就是手机终端连接Internet 其实是Ud内网IPQ端口,外网IP之间怺映射?/p>

GGSN(GateWay GPRS Support Note)模块实C(jin)NAT功能Q由于大部分的移动无U网l运营商Z(jin)减少|关NAT映射表的负荷Q如果一个链路有一D|间没有通信时就?x)删除其对?表,造成链\中断Q正是这U刻意羃短空闲连接的释放时Q原本是惌省信道资源的作用Q没惛_让互联网的应用不得以q高于正帔R率发送心(j)xl护推送的?q接。以中移动的2.5G|络ZQ大U?分钟左右的基带空Ԍq接׃(x)被释放?/p>

?于移动无U网l的特点Q推送服务的?j)蟩周期q不能设|的太长Q否则长q接?x)被释放Q造成频繁的客L(fng)重连Q但是也不能讄太短Q否则在当前~Zl一?j)蟩?架的机制下很Ҏ(gu)D信o(h)风暴Q例如微信心(j)跳信令风暴问题)(j)。具体的?j)蟩周期q没有统一的标准,180S也许是个不错的选择Q微信ؓ(f)300S?/p>

在Netty中,可以通过在ChannelPipeline中增加I(yng)dleStateHandler的方式实现心(j)x,在构造函C指定链\I闲旉Q然后实现空闲回调接口,实现?j)蟩的发送和(g),代码如下Q?/p>

public void initChannel({@link Channel} channel) {  channel.pipeline().addLast("idleStateHandler", new {@link   IdleStateHandler}(0, 0, 180));  channel.pipeline().addLast("myHandler", new MyHandler()); } 拦截链\I闲事gq处理心(j)跻I(x)  public class MyHandler extends {@link ChannelHandlerAdapter} {      {@code @Override}       public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {           if (evt instanceof {@link IdleStateEvent}} {               //?j)蟩处?          }       }   }

3.4. 合理讄接收和发送缓冲区定w

对于镉K接,每个链\都需要维护自q消息接收和发送缓冲区QJDK原生的NIOcd使用的是java.nio.ByteBuffer,它实际是一个长度固定的Byte数组Q我们都知道数组无法动态扩容,ByteBuffer也有q个限制Q相关代码如下:(x)

public abstract class ByteBuffer     extends Buffer     implements Comparable {     final byte[] hb; // Non-null only for heap buffers     final int offset;     boolean isReadOnly;

?量无法动态扩展会(x)l用户带来一些麻?ch),例如׃无法预测每条消息报文的长度,可能需要预分配一个比较大的ByteBufferQ这通常也没有问题。但是在 量推送服务系l中Q这?x)给服务端带来沉重的内存负担。假讑֍条推送消息最大上限ؓ(f)10KQ消息^均大ؓ(f)5KQؓ(f)?jin)满?0K消息的处 理,ByteBuffer的容量被讄?0KQ这h条链路实际上多消耗了(jin)5K内存Q如果长链接链\Cؓ(f)100万,每个链\都独立持?ByteBuffer接收~冲区,则额外损耗的d?Total(M) = 1000000 * 5K = 4882M。内存消耗过大,不仅仅增加了(jin)g成本Q而且大内存容易导致长旉的Full GCQ对pȝE_性会(x)造成比较大的冲击?/p>

实际上,最灉|的处理方式就是能够动态调整内存,x收缓冲区可以Ҏ(gu)以往接收的消息进行计,动态调整内存,利用CPU资源来换内存资源Q具体的{略如下Q?/p>

  1. ByteBuffer支持定w的扩展和收羃Q可以按需灉|调整Q以节约内存Q?/li>
  2. 接收消息的时候,可以按照指定的算法对之前接收的消息大进行分析,q未来的消息大小Q按照预值灵z调整缓冲区定wQ以做到最的资源损耗满程序正常功能?/li>

q运的是QNetty提供的ByteBuf支持定w动态调_(d)对于接收~冲区的内存分配器,Netty提供?jin)两U:(x)

  1. FixedRecvByteBufAllocatorQ固定长度的接收~冲区分配器Q由它分配的ByteBuf长度都是固定大小的,q不?x)根据实际数据?的大动态收~。但是,如果定w不Q支持动态扩展。动态扩展是Netty ByteBuf的一基本功能,与ByteBuf分配器的实现没有关系Q?/li>
  2. AdaptiveRecvByteBufAllocatorQ容量动态调整的接收~冲区分配器Q它?x)根据之前Channel接收到的数据报大进行计, 如果q箋填充满接收缓冲区的可写空_(d)则动态扩展容量。如果连l?ơ接收到的数据报都小于指定|则收~当前的定wQ以节约内存?/li>

相对于FixedRecvByteBufAllocatorQ用AdaptiveRecvByteBufAllocator更ؓ(f)合理Q可以在创徏客户端或者服务端的时候指定RecvByteBufAllocatorQ代码如下:(x)

 Bootstrap b = new Bootstrap();             b.group(group)              .channel(NioSocketChannel.class)              .option(ChannelOption.TCP_NODELAY, true)              .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)

如果默认没有讄Q则使用AdaptiveRecvByteBufAllocator?/p>

另外值得注意的是Q无论是接收~冲是发送缓冲区Q缓冲区的大徏议设|ؓ(f)消息的^均大,不要讄成最大消息的上限Q这?x)导致额外的内存(gu)费。通过如下方式可以讄接收~冲区的初始大小Q?/p>

/** 	 * Creates a new predictor with the specified parameters. 	 *  	 * @param minimum 	 *            the inclusive lower bound of the expected buffer size 	 * @param initial 	 *            the initial buffer size when no feed back was received 	 * @param maximum 	 *            the inclusive upper bound of the expected buffer size 	 */ 	public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) 

对于消息发送,通常需要用戯己构造ByteBufq编码,例如通过如下工具cd建消息发送缓冲区Q?/p>

Netty百万U推送服? src=

?-2 构造指定容量的~冲?/p>

3.5. 内存?/h3>

推送服务器承蝲?jin)v量的镉K接,每个镉K接实际就是一个会(x)话。如果每个会(x)话都持有?j)蟩数据、接收缓冲区、指令集{数据结构,而且q些实例随着消息的处理朝生夕灭,q就?x)给服务器带来沉重的GC压力Q同时消耗大量的内存?/p>

最 有效的解决策略就是用内存池Q每个NioEventLoopU程处理N个链路,在线E内部,链\的处理时串行的。假如A链\首先被处理,它会(x)创徏接收~?冲区{对象,待解码完成之后,构造的POJO对象被封装成Task后投递到后台的线E池中执行,然后接收~冲Z(x)被释放,每条消息的接收和处理都会(x)重复?收缓冲区的创建和释放。如果用内存池Q则当A链\接收到新的数据报之后Q从NioEventLoop的内存池中申L(fng)闲的ByteBufQ解码完成之 后,调用releaseByteBuf释放到内存池中,供后lB链\l箋使用?/p>

使用内存池优化之后,单个NioEventLoop的ByteBuf甌和GCơ数从原来的N = 1000000/64 = 15625 ơ减ؓ(f)最?ơ(假设每次甌都有可用的内存)(j)?/p>

下面我们以推特用Netty4的PooledByteBufAllocatorq行GC优化作ؓ(f)案例Q对内存池的效果q行评估Q结果如下:(x)

垃圾生成速度是原来的1/5Q而垃圾清理速度快了(jin)5倍。用新的内存池机制Q几乎可以把|络带宽压满?/p>

Netty4 之前的版本问题如下:(x)每当收到C息或者用户发送信息到q程端,Netty 3均会(x)创徏一个新的堆~冲区。这意味着Q对应每一个新的缓冲区Q都?x)有一个new byte[capacity]。这些缓冲区?x)导致GC压力Qƈ消耗内存带宽。ؓ(f)?jin)安全v见,新的字节数组分配时会(x)用零填充Q这?x)消耗内存带宽。然而,用零 填充的数l很可能?x)再ơ用实际的数据填充,q又?x)消耗同L(fng)内存带宽。如果Java虚拟机(JVMQ提供了(jin)创徏新字节数l而又无需用零填充的方式,那么?们本来就可以内存带宽消耗减?0%Q但是目前没有那样一U方式?/p>

在Netty 4中实C(jin)一个新的ByteBuf内存池,它是一个纯Java版本?nbsp;jemalloc QF(tun)acebook也在用)(j)。现在,Netty不会(x)再因为用零填充缓冲区而浪费内存带宽了(jin)。不q,׃它不依赖于GCQ开发h员需要小?j)内存泄漏。如果忘记在处理E序中释攄冲区Q那么内存用率?x)无限地增长?/p>

Netty默认不用内存池Q需要在创徏客户端或者服务端的时候进行指定,代码如下Q?/p>

Bootstrap b = new Bootstrap();             b.group(group)              .channel(NioSocketChannel.class)              .option(ChannelOption.TCP_NODELAY, true)              .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)

使用内存池之后,内存的申请和释放必须成对出现Q即retain()和release()要成对出玎ͼ否则?x)导致内存泄霌Ӏ?/p>

值得注意的是Q如果用内存池Q完成ByteBuf的解码工作之后必L式的调用ReferenceCountUtil.release(msg)Ҏ(gu)收缓冲区ByteBufq行内存释放Q否则它?x)被认?f)仍然在用中Q这样会(x)D内存泄露?/p>

3.6. 当心(j)“日志隐Ş杀?#8221;

通常情况下,大家都知道不能在Netty的I/OU程上做执行旉不可控的操作Q例如访问数据库、发送Email{。但是有个常用但是非常危险的操作却容易被忽略Q那便是记录日志?/p>

?常,在生产环境中Q需要实时打印接口日志,其它日志处于ERRORU别Q当推送服务发生I/O异常之后Q会(x)记录异常日志。如果当前磁盘的WIO比较高,?能会(x)发生写日志文件操作被同步dQ阻塞时间无法预。这׃(x)DNetty的NioEventLoopU程被阻塞,Socket链\无法被及(qing)时关闭、其 它的链\也无法进行读写操作等?/p>

以最常用的log4jZQ尽它支持异步写日志(AsyncAppenderQ,但是当日志队列满之后Q它?x)同步阻塞业务线E,直到日志队列有空闲位|可用,相关代码如下Q?/p>

 synchronized (this.buffer) {       while (true) {         int previousSize = this.buffer.size();         if (previousSize < this.bufferSize) {           this.buffer.add(event);           if (previousSize != 0) break;           this.buffer.notifyAll(); break;         }         boolean discard = true;         if ((this.blocking) && (!Thread.interrupted()) && (Thread.currentThread() != this.dispatcher)) //判断是业务线E?        {           try           {             this.buffer.wait();//d业务U程             discard = false;           }           catch (InterruptedException e)           {             Thread.currentThread().interrupt();           }          }

cMq类BUGh极强的隐蔽性,往往WIO高的旉持箋非常短,或者是偶现的,在测试环境中很难模拟此类故障Q问题定位难度非常大。这p求读者在qx写代码的时候一定要当心(j)Q注意那些隐性地雗?/p>

3.7. TCP参数优化

常用的TCP参数Q例如TCP层面的接收和发送缓冲区大小讄Q在Netty中分别对应ChannelOption的SO_SNDBUF和SO_RCVBUFQ需要根据推送消息的大小Q合理设|,对于量长连接,通常32K是个不错的选择?/p>

另外一个比较常用的优化手段是软中断,如图所C:(x)如果所有的软中断都q行在CPU0相应|卡的硬件中断上Q那么始l都是cpu0在处理Y中断Q而此时其它CPU资源p费?jin),因?f)无法q行的执行多个Y中断?/p>

Netty百万U推送服? src=

?-3 中断信息

?于等?.6.35版本的Linux kernel内核Q开启RPSQ网l通信性能提升20%之上。RPS的基本原理:(x)Ҏ(gu)数据包的源地址Q目的地址以及(qing)目的和源端口Q计出一个hash| 然后Ҏ(gu)q个hash值来选择软中断运行的cpu。从上层来看Q也是说将每个q接和cpul定Qƈ通过q个hash|来均衡Y中断q行在多个cpu 上,从而提升通信性能?/p>

3.8. JVM参数

最重要的参数调整有两个Q?/p>

  • -Xmx:JVM最大内存需要根据内存模型进行计ƈ得出相对合理的|
  • GC相关的参? 例如新生代和老生代、永久代的比例,GC的策略,新生代各区的比例{,需要根据具体的场景q行讄和测试,q不断的优化Q尽量将Full GC的频率降到最低?/li>

4. 作者简?/h2>

李林锋,2007q毕业于东北大学Q?008q进入华为公总事高性能通信软g的设计和开发工作,?qNIO设计和开发经验,_NNetty、Mina{NIO框架。Netty中国C֌创始人,《Netty权威指南》作者?/p>

疯狂 2016-07-13 10:03 发表评论
]]>Netty 概述Q{Q?/title><link>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431176.html</link><dc:creator>疯狂</dc:creator><author>疯狂</author><pubDate>Wed, 13 Jul 2016 01:49:00 GMT</pubDate><guid>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431176.html</guid><wfw:comment>http://www.aygfsteel.com/freeman1984/comments/431176.html</wfw:comment><comments>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431176.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/freeman1984/comments/commentRss/431176.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/freeman1984/services/trackbacks/431176.html</trackback:ping><description><![CDATA[<div>转蝲自http://blog.csdn.net/zxhoo/article/details/17264263<br /><h2>Netty4</h2><p style="margin: 0px; padding: 0px; font-family: Arial; line-height: 26px; background-color: #ffffff;"><a target="_blank" style="color: #ca0000; text-decoration: none;">Netty</a>是一个和<a target="_blank" style="color: #ca0000; text-decoration: none;">MINA</a>cM的Java NIO框架Q目前的最新版本是<a target="_blank" style="color: #ca0000; text-decoration: none;">4.0.13</a>Q这两个框架的主要作者好像都?a target="_blank" style="color: #ca0000; text-decoration: none;">同一个韩国h</a>?/p><h2 id="articleHeader0" style="box-sizing: border-box; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-weight: 500; line-height: 1.1; color: #404040; margin-top: 20px; margin-bottom: 10px; font-size: 1.75em; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: #eeeeee; padding: 0px 0px 5px; background-color: #ffffff;"><span style="box-sizing: border-box; line-height: 43.2px; font-size: 24px; font-weight: 600;">Channel</span></h2><p style="margin: 0px; padding: 0px; font-family: Arial; line-height: 26px; background-color: #ffffff;"><br /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">Channel是Netty最核心(j)的接口,一个Channel是一个联lSocket的通道Q通过ChannelQ你可以对Socketq行各种操作?/p><h2>ChannelHandler</h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">用Netty~写|络E序的时候,你很直接操UChannelQ而是通过ChannelHandler来间接操UChannel?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">Netty中的所有handler都实现自<span style="box-sizing: border-box; line-height: 28.8px; text-decoration: underline;"><span style="box-sizing: border-box; font-weight: 700;">ChannelHandler接口</span></span>。按照输?gu)出来分,分?f)<span style="box-sizing: border-box; line-height: 28.8px; text-decoration: underline;"><span style="box-sizing: border-box; font-weight: 700;">ChannelInboundHandler、ChannelOutboundHandler两大c?/span></span>?/p><blockquote style="box-sizing: border-box; padding-top: 10px; padding-right: 0px; padding-bottom: 1px; margin: 20px 0px 20px 20px; border-left-width: 5px; border-left-color: #eeeeee; max-width: 650px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; line-height: 28.8px; font-size: 12px !important; background-color: #f5f5f5;"><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 21.6px; color: #555555;">ChannelInboundHandler对从客户端发往服务器的报文q行处理Q一般用来执行解码、读取客L(fng)数据、进行业务处理等Q?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 21.6px; color: #555555;">ChannelOutboundHandler对从服务器发往客户端的报文q行处理Q一般用来进行编码、发送报文到客户端?/p></blockquote><h2>ChannelPipeline</h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">ChannelPipeline实际上应该叫做ChannelHandlerPipelineQ可以把ChannelPipeline看成是一个ChandlerHandler的链表,当需要对Channelq行某种处理的时候,Pipeline负责依次调用每一个Handlerq行处理。每个Channel都有一个属于自qPipelineQ调用Channel#pipeline()Ҏ(gu)可以获得Channel的PipelineQ调用Pipeline#channel()Ҏ(gu)可以获得Pipeline的Channel?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094120_258.jpg" width="676" height="166" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">ChannelPipeline的方法有很多Q其中一部分是用来管理ChannelHandler的,如下面这些:(x)</p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">ChannelPipeline addFirst(String name, ChannelHandler handler); ChannelPipeline addLast(String name, ChannelHandler handler); ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler); ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler); ChannelPipeline remove(ChannelHandler handler); ChannelHandler remove(String name); ChannelHandler removeFirst(); ChannelHandler removeLast(); ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler); ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler); ChannelHandler first(); ChannelHandler last(); ChannelHandler get(String name);</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><h2>ChannelHandlerContext</h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">ChannelPipelineq不是直接管理ChannelHandlerQ而是通过ChannelHandlerContext来间接管理,q一炚w过ChannelPipeline的默认实现DefaultChannelPipeline可以看出来?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_162.jpg" width="692" height="220" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">调用<span style="box-sizing: border-box; font-weight: 700;">ChannelHandlerContext#channel()</span>Ҏ(gu)可以得到和Contextl定的ChannelQ调?span style="box-sizing: border-box; font-weight: 700;">ChannelHandlerContext#handler()</span>Ҏ(gu)可以得到和Contextl定的Handler?/p><h2>ChannelPipeline和ChannelHandlerContext默认实现</h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">DefaultChannelHandlerContext和DefaultChannelPipeline是ChannelHandlerContext和ChannelPipeline的默认实玎ͼ下面是它们的部分代码Q?/p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {     volatile DefaultChannelHandlerContext next;     volatile DefaultChannelHandlerContext prev;     private final boolean inbound;     private final boolean outbound;     private final AbstractChannel channel;     private final DefaultChannelPipeline pipeline;     private final String name;     private final ChannelHandler handler;     private boolean removed;     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">final class DefaultChannelPipeline implements ChannelPipeline {     // ...     final DefaultChannelHandlerContext head;     final DefaultChannelHandlerContext tail;     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><span style="box-sizing: border-box; line-height: 23px;">从上面的代码可以看出Q在DefaultPipeline内部QDefaultChannelHandlerContextl成?jin)一个双向链表:(x)</span></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_321.jpg" width="798" height="217" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 738px; height: 190px;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">再来看看DefaultChannelPipeline的构造函敎ͼ(x)</p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">    public DefaultChannelPipeline(AbstractChannel channel) {         if (channel == null) {             throw new NullPointerException("channel");         }         this.channel = channel;         TailHandler tailHandler = new TailHandler();         tail = new DefaultChannelHandlerContext(this, null, generateName(tailHandler), tailHandler);         HeadHandler headHandler = new HeadHandler(channel.unsafe());         head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);         head.next = tail;         tail.prev = head;     }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">可以看到QDefaultChinnelPipeline内部使用?jin)两个特D的Handler来表CHandler铄头和:(x)</p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_487.jpg" width="991" height="262" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 837px; height: 188px;" /></p><h2>ChannelHandler的种c?/h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">Netty中的所有handler都实现自ChannelHandler接口。按照输?gu)出来分,分?f)ChannelInboundHandler、ChannelOutboundHandler两大cRChannelInboundHandler对从客户端发往服务器的报文q行处理Q一般用来执行解码、读取客L(fng)数据、进行业务处理等QChannelOutboundHandler对从服务器发往客户端的报文q行处理Q一般用来进行编码、发送报文到客户端?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">从上面DefaultChannelHandlerContext代码可以知道QHandler实际上分ZU,Inbound和OutboundQ这一点也可以从ChannelHandler接口的子接口得到证明Q?/p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">public interface ChannelInboundHandler extends ChannelHandler {   // ... } public interface ChannelOutboundHandler extends ChannelHandler {   // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><h2><br style="box-sizing: border-box; line-height: 10px;" /></h2><h2>事g的传?/h2><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">Z(jin)搞清楚事件如何在Pipeline里传播,让我们从Channel的抽象子cAbstractChannel开始,下面是AbstractChannel#write()Ҏ(gu)的实玎ͼ(x)</p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {     // ...     @Override     public Channel write(Object msg) {         return pipeline.write(msg);     }     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">AbstractChannel直接调用?jin)Pipeline的write()Ҏ(gu)Q?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_93.jpg" width="1002" height="259" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 867px; height: 178px;" /></p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">final class DefaultChannelPipeline implements ChannelPipeline {     // ...     @Override     public ChannelFuture write(Object msg) {         return tail.write(msg);     }     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">因ؓ(f)write是个outbound事gQ所以DefaultChannelPipeline直接扑ֈtail部分的contextQ调用其write()Ҏ(gu)Q?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_417.jpg" width="990" height="296" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 846px; height: 213px;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><span style="box-sizing: border-box; line-height: 26px; font-family: Arial; font-size: 14px;">接着看DefaultChannelHandlerContext的write()Ҏ(gu)Q?/span></p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">final class DefaultChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {     // ...     @Override     public ChannelFuture write(Object msg) {         return write(msg, newPromise());     }     @Override     public ChannelFuture write(final Object msg, final ChannelPromise promise) {         if (msg == null) {             throw new NullPointerException("msg");         }         validatePromise(promise, true);         write(msg, false, promise);         return promise;     }     private void write(Object msg, boolean flush, ChannelPromise promise) {         DefaultChannelHandlerContext next = findContextOutbound();         next.invokeWrite(msg, promise);         if (flush) {             next.invokeFlush();         }     }     private DefaultChannelHandlerContext findContextOutbound() {         DefaultChannelHandlerContext ctx = this;         do {             ctx = ctx.prev;         } while (!ctx.outbound);         return ctx;     }     private void invokeWrite(Object msg, ChannelPromise promise) {         try {             ((ChannelOutboundHandler) handler).write(this, msg, promise);         } catch (Throwable t) {             notifyOutboundHandlerException(t, promise);         }     }     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">context的write()Ҏ(gu)沿着context铑־前找Q直xC个outboundcd的context为止Q然后调用其invokeWrite()Ҏ(gu)Q?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_595.jpg" width="979" height="409" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 846px; height: 352px;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">invokeWrite()接着调用handler的write()Ҏ(gu)Q?/p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094121_617.jpg" width="985" height="427" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 795px; height: 348px;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">最后看看ChannelOutboundHandlerAdapter的write()Ҏ(gu)实现Q?/p><pre true;="" auto-links:="" false;"="" style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13.6px; padding: 6px; margin-top: 0px; margin-bottom: 10px; line-height: 1.45; color: #333333; word-break: break-all; word-wrap: break-word; border: none; border-radius: 3px; background-color: #f6f6f6;">public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {     // ...     @Override     public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {         ctx.write(msg, promise);     }     // ... }</pre><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">默认的实现调用了(jin)context的write()Ҏ(gu)而不做Q何处理,q样write事g沿着outbound铄l传播:(x)</p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;"><img alt="netty概述" src="http://static.open-open.com/lib/uploadImg/20140528/20140528094122_946.jpg" width="989" height="430" style="box-sizing: border-box; border: 0px; vertical-align: middle; max-width: 100%; width: 717px; height: 295px;" /></p><p style="box-sizing: border-box; margin: 0px 0px 25px; line-height: 28.8px; color: #404040; font-family: "Microsoft YaHei", Verdana, sans-serif, SimSun; font-size: 16px; background-color: #ffffff;">可见QPipeline的事件传播,是靠PipelineQContext和Handler共同协作完成的?/p></div><img src ="http://www.aygfsteel.com/freeman1984/aggbug/431176.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/freeman1984/" target="_blank">疯狂</a> 2016-07-13 09:49 <a href="http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431176.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Netty优雅退出机制和原理Q{Q?/title><link>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431175.html</link><dc:creator>疯狂</dc:creator><author>疯狂</author><pubDate>Wed, 13 Jul 2016 01:39:00 GMT</pubDate><guid>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431175.html</guid><wfw:comment>http://www.aygfsteel.com/freeman1984/comments/431175.html</wfw:comment><comments>http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431175.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.aygfsteel.com/freeman1984/comments/commentRss/431175.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/freeman1984/services/trackbacks/431175.html</trackback:ping><description><![CDATA[     摘要: 转蝲自:(x)http://www.infoq.com/cn/articles/netty-elegant-exit-mechanism-and-principles1.q程的优雅退?.1.Kill -9 PID带来的问题在Linux上通常?x)通过kill -9 pid的方式强制将某个q程杀掉,q种方式单高效,因此很多E序的停止脚本经怼(x)选择使用kill -9 pid的方式。无论是Linux的Kill...  <a href='http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431175.html'>阅读全文</a><img src ="http://www.aygfsteel.com/freeman1984/aggbug/431175.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/freeman1984/" target="_blank">疯狂</a> 2016-07-13 09:39 <a href="http://www.aygfsteel.com/freeman1984/archive/2016/07/13/431175.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <a href="http://www.aygfsteel.com/" title="狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频">狠狠久久亚洲欧美专区_中文字幕亚洲综合久久202_国产精品亚洲第五区在线_日本免费网站视频</a> </div> </footer> վ֩ģ壺 <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ݰ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ƺ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">౱</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ƽ</a>| <a href="http://" target="_blank">ӱʡ</a>| <a href="http://" target="_blank">ͨ</a>| <a href="http://" target="_blank">ɽ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ī񹤿</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ʯ</a>| <a href="http://" target="_blank">ʡ</a>| <a href="http://" target="_blank">ij</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ʹ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">˲</a>| <a href="http://" target="_blank">ȷ</a>| <a href="http://" target="_blank">ͨ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">˳</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ӥ̶</a>| <a href="http://" target="_blank">غ</a>| <a href="http://" target="_blank">ˮ</a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank"></a>| <a href="http://" target="_blank">ƺ</a>| <a href="http://" target="_blank">Ɽ</a>| <a href="http://" target="_blank"></a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>