当前位置: 首页 > 后端技术 > Java

Netty系列:在Netty中实现线程与CPU绑定

时间:2023-04-02 10:26:32 Java

介绍之前我们介绍过一个非常好的JAVA线程细粒度控制的库:java线程亲和性。使用这个库,可以将线程绑定到特定的CPU或CPU核上,通过减少线程在CPU之间的切换来提高线程的执行效率。虽然netty已经够好了,但谁不想更好呢?于是就有了一个想法,就是亲和库能不能在netty中使用呢?答案是肯定的,一起来看看吧。affinity的介绍以jar包的形式提供。最新的官方版本是3.20.0,所以我们需要这样导入:net.openhftaffinity3.20.0引入affinity后,项目lib包的依赖库会增加一个affinity,方便我们愉快的使用netty中的亲和力。AffinityThreadFactory有affinity,netty怎么引入affinity?我们知道亲和力是用来控制线程的,也就是说亲和力是和线程相关的。netty中线程相关的事件是EventLoopGroup。下面我们看一下netty中EventLoopGroup的基本用法。这里我们以NioEventLoopGroup为例。NioEventLoopGroup有很多构造函数参数,其中一个就是传入一个ThreadFactory:publicNioEventLoopGroup(ThreadFactorythreadFactory){this(0,threadFactory,SelectorProvider.provider());}这个构造函数表示NioEventLoopGroup中使用的线程都是threadFactory创建的。这样,我们就找到了netty和affinity的对应关系。只需要构造亲和性的ThreadFactory即可。正好affinity中有一个AffinityThreadFactory类,专门用来创建affinity对应的线程。接下来,让我们仔细看看AffinityThreadFactory。AffinityThreadFactory可以根据提供的不同的AffinityStrategy创建相应的线程。AffinityStrategy表示线程之间的关系。在affinity中,有5种线程关系,分别是:SAME_CORE——线程会运行在同一个CPU核心。SAME_SOCKET-线程将在同一个CPU插槽上运行,但不在同一个内核上。DIFFERENT_SOCKET-线程将在不同的套接字上运行。DIFFERENT_CORE-线程将在不同的内核上运行。ANY-只要CPU资源可用。这些关系是通过AffinityStrategy中的matches方法实现的:booleanmatches(intcpuId,intcpuId2);matches传入两个参数,就是传入的两个cpuId。让我们以SAME_CORE为例,看看这个数学方法是如何工作的:SAME_CORE{@Overridepublicbooleanmatches(intcpuId,intcpuId2){返回cpuLayout.socketId(cpuId)==cpuLayout.socketId(cpuId2)&&cpuLayout.coreId(cpuId)==cpuLayout.coreId(cpuId2);}}可以看出它的逻辑是先获取当前CPU的布局。CpuLayout包含cpu和套接字的数量。每个插槽的CPU核心数等基本信息。并提供了三个方法根据给定的cpuId返回对应的socket、core和thread信息:intsocketId(intcpuId);intcoreId(intcpuId);intthreadId(intcpuId);matches方法根据传入的cpuId找到对应的socket和core信息进行比对,从而生成5种不同的策略。先看AffinityThreadFactory的构造函数:publicAffinityThreadFactory(Stringname,booleandaemon,@NotNullAffinityStrategy...strategies){this.name=name;this.daemon=守护进程;this.strategies=strategies.length==0?newAffinityStrategy[]{AffinityStrategies.ANY}:策略;}可以传入线程的名字前缀,是否是守护线程。最后,如果不传strategies,则默认使用AffinityStrategies.ANY策略,也就是说线程被分配了any绑定的CPU。接下来,让我们看看这个ThreadFactory是如何创建一个新线程的:publicsynchronizedThreadnewThread(@NotNullfinalRunnabler){Stringname2=id<=1?名称:(名称+'-'+ID);编号++;Threadt=newThread(newRunnable(){@Overridepublicvoidrun(){try(AffinityLockignored=acquireLockBasedOnLast()){r.run();}}},name2);吨。设置守护进程(守护进程);返回吨;}privatesynchronizedAffinityLockacquireLockBasedOnLast(){AffinityLockal=lastAffinityLock==null?AffinityLock.acquireLock():lastAffinityLock.acquireLock(策略);如果(al.cpuId()>=0)lastAffinityLock=al;从上面返回al;}从代码可以看出,新创建的线程会以传入的名字为前缀,后面加上1、2、3、4等后缀。并且根据传入的标签是否为守护线程,setDaemon相应线程的方法将被调用。重点是运行在Thread内部的Runnable内容。在run方法内部,首先调用acquireLockBasedOnLast方法获取锁,在获取到锁的前提下运行对应的线程方法,这样当前运行的Thread就会绑定CPU。从acquireLockBasedOnLast方法可以看出AffinityLock其实是一个链式结构。每次请求都会调用lastAffinityLock的acquireLock方法。如果获得了锁,则将lastAffinityLock替换为下一个锁。获得。有了AffinityThreadFactory,我们只需要在netty的使用中传入AffinityThreadFactory即可。在netty中使用AffinityThreadFactory上面提到在netty中使用affinity,可以将AffinityThreadFactory传入EventLoopGroup。对于nettyserver来说,可以有两个EventLoopGroup,分别是acceptorGroup和workerGroup。在下面的例子中,我们将AffinityThreadFactory传入workerGroup,这样后续工作中分配的线程就会按照AffinityThreadFactory中配置的AffinityStrategies策略去获取对应的CPU。://创建两个EventloopGroups来处理连接和消息EventLoopGroupacceptorGroup=newNioEventLoopGroup(acceptorThreads);//创建AffinityThreadFactoryThreadFactorythreadFactory=newAffinityThreadFactory("affinityWorker",AffinityStrategies.DIFFERENT_CORE,AffinityStrategies.DIFFERENT_SOCKET)/添加AffinityThreadFactory到workerGroupEventLoopGroupworkerGroup=newNioEventLoopGroup(workerThreads,threadFactory);尝试{ServerBootstrapb=newServerBootstrap();b.group(acceptorGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(newChannel(ockChannelInitializer<){@OverridepublicvoidinitChannel(SocketChannelch)抛出异常{ch.pipeline().addLast(newAffinityServerHandler());}}).option(ChannelOption.SO_BACKLOG,128).childOption(ChannelOption.SO_KEEPALIVE,true);//绑定端口并开始接收连接ChannelFuturef=b.bind(port).sync();//等待服务器套接字关闭f.channel().closeFuture().sync();}finally{//关闭群组workerGroup.shutdownGracefully();acceptorGroup.shutdownGracefully();为了获得更好的性能,Affinity还可以隔离CPU。孤立的CPU只允许执行本应用的线程,以获得更好的性能。要使用此功能,您需要使用linuxisolcpus。该功能主要是隔离一个或多个CPU来执行特定的Affinity任务。isolcpus命令后面可以跟CPUID,也可以修改/boot/grub/grub.conf文件,添加需要隔离的CPU信息,如下:isolcpus=3,4,5总结亲和性可以控制线程到极致,对性能要求严格的可以尝试,但是在使用过程中需要选择合适的AffinityStrategies,否则可能得不到想要的结果。本文示例可参考:learn-netty4更多内容请参考http://www.flydean.com/51-netty-thread-affinity/最通俗的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!