netty 系列之: 在 netty 中实现线程和 CPU 绑定
简介
之前我们介绍了一个非常优秀的细粒度控制 JAVA 线程的库:java thread affinity。使用这个库你可以将线程绑定到特定的 CPU 或者 CPU 核上,通过减少线程在 CPU 之间的切换,从而提升线程执行的效率。
虽然 netty 已经够优秀了,但是谁不想更加优秀一点呢?于是一个想法产生了,那就是能不能把 affinity 库用在 netty 中呢?
答案是肯定的,一起来看看吧。
引入 affinity
affinity 是以 jar 包的形式提供出去的,目前最新的正式版本是 3.20.0,所以我们需要这样引入:
引入 affinity 之后,会在项目的依赖库中添加一个 affinity 的 lib 包,这样我们就可以在 netty 中愉快的使用 affinity 了。
AffinityThreadFactory
有了 affinity,怎么把 affinity 引入到 netty 中呢?
我们知道 affinity 是用来控制线程的,也就是说 affinity 是跟线程有关的。而 netty 中跟线程有关的就是 EventLoopGroup,先看一下 netty 中 EventLoopGroup 的基本用法,这里以 NioEventLoopGroup 为例,NioEventLoopGroup 有很多构造函数的参数,其中一种是传入一个 ThreadFactory:
这个构造函数表示 NioEventLoopGroup 中使用的线程都是由 threadFactory 创建而来的。这样以来我们就找到了 netty 和 affinity 的对应关系。只需要构造 affinity 的 ThreadFactory 即可。
刚好 affinity 中有一个 AffinityThreadFactory 类,专门用来创建 affinity 对应的线程。
接下来我们来详细了解一下 AffinityThreadFactory。
AffinityThreadFactory 可以根据提供的不同 AffinityStrategy 来创建对应的线程。
AffinityStrategy 表示的是线程之间的关系。在 affinity 中,有 5 种线程关系,分别是:
这些关系是通过 AffinityStrategy 中的 matches 方法来实现的:
matches 传入两个参数,分别是传入的两个 cpuId。我们以 SAME_CORE 为例来看看这个 mathes 方法到底是怎么工作的:
可以看到它的逻辑是先获取当前 CPU 的 layout,CpuLayout 中包含了 cpu 个数,sockets 个数,每个 sockets 的 cpu 核数等基本信息。并且提供了三个方法根据给定的 cpuId 返回对应的 socket、core 和 thread 信息:
matches 方法就是根据传入的 cpuId 找到对应的 socket,core 信息进行比较,从而生成了 5 中不同的策略。
先看一下 AffinityThreadFactory 的构造函数:
可以传入 thread 的 name 前缀,和是否是守护线程,最后如果 strategies 不传的话,默认使用的是 AffinityStrategies.ANY 策略,也就是说为线程分配任何可以绑定的 CPU。
接下来看下这个 ThreadFactory 是怎么创建新线程的:
从上面的代码可以看出,创建的新线程会以传入的 name 为前缀,后面添加 1,2,3,4 这种后缀。并且根据传入的是否是守护线程的标记,将调用对应线程的 setDaemon 方法。
重点是 Thread 内部运行的 Runnable 内容,在 run 方法内部,首先调用 acquireLockBasedOnLast 方法获取 lock,在获得 lock 的前提下运行对应的线程方法,这样就会将当前运行的 Thread 和 CPU 进行绑定。
从 acquireLockBasedOnLast 方法中,我们可以看出 AffinityLock 实际上是一个链式结构,每次请求的时候都调用的是 lastAffinityLock 的 acquireLock 方法,如果获取到 lock,则将 lastAffinityLock 进行替换,用来进行下一个 lock 的获取。
有了 AffinityThreadFactory,我们只需要在 netty 的使用中传入 AffinityThreadFactory 即可。
在 netty 中使用 AffinityThreadFactory
上面讲到了要在 netty 中使用 affinity,可以将 AffinityThreadFactory 传入 EventLoopGroup 中。对于 netty server 来说可以有两个 EventLoopGroup,分别是 acceptorGroup 和 workerGroup,在下面的例子中我们将 AffinityThreadFactory 传入 workerGroup,这样后续 work 中分配的线程都会遵循 AffinityThreadFactory 中配置的 AffinityStrategies 策略,来获得对应的 CPU:
为了获取更好的性能,Affinity 还可以对 CPU 进行隔离,被隔离的 CPU 只允许执行本应用的线程,从而获得更好的性能。
要使用这个特性需要用到 linux 的 isolcpus。这个功能主要是将一个或多个 CPU 独立出来,用来执行特定的 Affinity 任务。
isolcpus 命令后面可以接 CPU 的 ID,或者可以修改/boot/grub/grub.conf 文件,添加要隔离的 CPU 信息如下:
总结
affinity 可以对线程进行极致管控,对性能要求严格的朋友可以试试,但是在使用过程中需要选择合适的 AffinityStrategies,否则可能会得不到想要的结果。
本文的例子可以参考:learn-netty4
更多内容请参考 http://www.flydean.com/51-netty-thread-affinity/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
版权声明: 本文为 InfoQ 作者【程序那些事】的原创文章。
原文链接:【http://xie.infoq.cn/article/8d3db2b235c8ca0179aa787c2】。文章转载请联系作者。
评论