Netty(四):实现通道的多路复用

来源:互联网 发布:部落冲突龙的升级数据 编辑:程序博客网 时间:2024/05/09 07:36

 问题描述:

    

    在进行压测时,一开始很正常,大约在30000次请求之后,错误率达到100%且接口耗时超过3s(人工设定的超时等待时间),查看日志后发现大量的Cannot assign requested address异常

    “Cannot assign requestedaddress”异常是由于Linux分配的客户端连接端口用尽,无法建立socket连接所致,虽然socket正常关闭,但是端口不是立即释放,而是处于TIME_WAIT状态,默认等待60s后才释放。所以也出现了大量的接口超时的现象了。

    因为我们在初始化Netty时使用了保活策略(ChannelOption.SO_KEEPALIVE),当长时间通道没有数据交流时,TCP会发送活动探测数据报文来测试通道的连接状态。在代码逻辑中我们在每一次建立与channel的连接时均新建了一个handler对象(即每次都新建一个通道的连接),所以每当有连接请求时都会分配新的通道,最终导致连接端口用尽。


 解决方案:

    

    使用对象池技术,当有相应服务连接请求时,我们通过将负责具体通讯的对象进行池化,限制处理每个服务的通道的数目,使通道能够复用。


    首先介绍几个概念:

    对象池(ObjectPool): 掌管对象的生命周期,获取,激活,验证,钝化,销毁等

    池对象(PooledObject): 被创建在池中的对象,自己可以有一些附加信息

 池对象工厂(PooledObjectFactory): 池中对象各个生命周期的具体实现,怎么创建,怎么验证,怎么销毁。

    对象池化主要用于减少对象在创建和销毁上面的开销,如果是小对象则不需要池化,如果是大对象可以考虑池化,对于像数据库连接、网络之类的重对象来说是很有必要池化的,我们可以根据需求判断,如果创建某种对象成为了影响程序性能的关键因素则需要池化。

    

    在本项目中,使用到的对象池结构框图如下:



    本服务中新建了ChanneConnectPoolFactory类作为池对象工厂(PooledObjectFactory),此类继承至BasePooledObjectFactory类,基类中有两个方法需要重写:create()方法及wrap()方法。create方法用于对象的创建,wrap方法用于将创建的对象转化为池对象(PooledObject)。然后建立GenericObjectPool的对象池,并通过GenericObjectPoolConfig设置对象池的相关属性:如,池中最多对象总数,最大空闲数,对象的最长等待时间等。当要建立通道连接时,我们通过borrowChannelHandler方法向对象池中“借”一个对象,在将数据发送给下游服务或者发送出现异常时,我们将之前“借”用的对象再“还”给对象池,从而使连接可以复用。

    在发送完数据以后,要判断对象是否有效,将处于有效状态的对象“还”给对象池(如果对象无效时执行return操作,将产生"Object has already beenreturned to this pool or is invalid"异常)。

 

原创粉丝点击