简介
之前咱们介绍了一个十分优良的细粒度管制 JAVA 线程的库:java thread affinity。应用这个库你能够将线程绑定到特定的 CPU 或者 CPU 核上,通过缩小线程在 CPU 之间的切换,从而晋升线程执行的效率。
尽管 netty 曾经够优良了,然而谁不想更加优良一点呢?于是一个想法产生了,那就是能不能把 affinity 库用在 netty 中呢?
答案是必定的,一起来看看吧。
引入 affinity
affinity 是以 jar 包的模式提供进来的,目前最新的正式版本是 3.20.0,所以咱们须要这样引入:
<!-- https://mvnrepository.com/artifact/net.openhft/affinity -->
<dependency>
<groupId>net.openhft</groupId>
<artifactId>affinity</artifactId>
<version>3.20.0</version>
</dependency>
引入 affinity 之后,会在我的项目的依赖库中增加一个 affinity 的 lib 包,这样咱们就能够在 netty 中欢快的应用 affinity 了。
AffinityThreadFactory
有了 affinity,怎么把 affinity 引入到 netty 中呢?
咱们晓得 affinity 是用来控制线程的,也就是说 affinity 是跟线程无关的。而 netty 中跟线程无关的就是 EventLoopGroup, 先看一下 netty 中 EventLoopGroup 的根本用法,这里以 NioEventLoopGroup 为例,NioEventLoopGroup 有很多构造函数的参数,其中一种是传入一个 ThreadFactory:
public NioEventLoopGroup(ThreadFactory threadFactory) {this(0, threadFactory, SelectorProvider.provider());
}
这个构造函数示意 NioEventLoopGroup 中应用的线程都是由 threadFactory 创立而来的。这样以来咱们就找到了 netty 和 affinity 的对应关系。只须要结构 affinity 的 ThreadFactory 即可。
刚好 affinity 中有一个 AffinityThreadFactory 类,专门用来创立 affinity 对应的线程。
接下来咱们来具体理解一下 AffinityThreadFactory。
AffinityThreadFactory 能够依据提供的不同 AffinityStrategy 来创立对应的线程。
AffinityStrategy 示意的是线程之间的关系。在 affinity 中,有 5 种线程关系,别离是:
SAME_CORE - 线程会运行在同一个 CPU core 中。SAME_SOCKET - 线程会运行在同一个 CPU socket 中,然而不在同一个 core 上。DIFFERENT_SOCKET - 线程会运行在不同的 socket 中。DIFFERENT_CORE - 线程会运行在不同的 core 上。ANY - 只有是可用的 CPU 资源都能够。
这些关系是通过 AffinityStrategy 中的 matches 办法来实现的:
boolean matches(int cpuId, int cpuId2);
matches 传入两个参数,别离是传入的两个 cpuId。咱们以 SAME_CORE 为例来看看这个 mathes 办法到底是怎么工作的:
SAME_CORE {
@Override
public boolean matches(int cpuId, int cpuId2) {CpuLayout cpuLayout = AffinityLock.cpuLayout();
return cpuLayout.socketId(cpuId) == cpuLayout.socketId(cpuId2) &&
cpuLayout.coreId(cpuId) == cpuLayout.coreId(cpuId2);
}
}
能够看到它的逻辑是先获取以后 CPU 的 layout,CpuLayout 中蕴含了 cpu 个数,sockets 个数,每个 sockets 的 cpu 核数等根本信息。并且提供了三个办法依据给定的 cpuId 返回对应的 socket、core 和 thread 信息:
int socketId(int cpuId);
int coreId(int cpuId);
int threadId(int cpuId);
matches 办法就是依据传入的 cpuId 找到对应的 socket,core 信息进行比拟,从而生成了 5 中不同的策略。
先看一下 AffinityThreadFactory 的构造函数:
public AffinityThreadFactory(String name, boolean daemon, @NotNull AffinityStrategy... strategies) {
this.name = name;
this.daemon = daemon;
this.strategies = strategies.length == 0 ? new AffinityStrategy[]{AffinityStrategies.ANY} : strategies;
}
能够传入 thread 的 name 前缀,和是否是守护线程,最初如果 strategies 不传的话,默认应用的是 AffinityStrategies.ANY 策略,也就是说为线程调配任何能够绑定的 CPU。
接下来看下这个 ThreadFactory 是怎么创立新线程的:
public synchronized Thread newThread(@NotNull final Runnable r) {String name2 = id <= 1 ? name : (name + '-' + id);
id++;
Thread t = new Thread(new Runnable() {
@Override
public void run() {try (AffinityLock ignored = acquireLockBasedOnLast()) {r.run();
}
}
}, name2);
t.setDaemon(daemon);
return t;
}
private synchronized AffinityLock acquireLockBasedOnLast() {AffinityLock al = lastAffinityLock == null ? AffinityLock.acquireLock() : lastAffinityLock.acquireLock(strategies);
if (al.cpuId() >= 0)
lastAffinityLock = al;
return al;
}
从下面的代码能够看出,创立的新线程会以传入的 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:
// 建设两个 EventloopGroup 用来解决连贯和音讯
EventLoopGroup acceptorGroup = new NioEventLoopGroup(acceptorThreads);
// 创立 AffinityThreadFactory
ThreadFactory threadFactory = new AffinityThreadFactory("affinityWorker", AffinityStrategies.DIFFERENT_CORE,AffinityStrategies.DIFFERENT_SOCKET,AffinityStrategies.ANY);
// 将 AffinityThreadFactory 退出 workerGroup
EventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads,threadFactory);
try {ServerBootstrap b = new ServerBootstrap();
b.group(acceptorGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new AffinityServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口并开始接管连贯
ChannelFuture f = b.bind(port).sync();
// 期待 server socket 敞开
f.channel().closeFuture().sync();} finally {
// 敞开 group
workerGroup.shutdownGracefully();
acceptorGroup.shutdownGracefully();}
为了获取更好的性能,Affinity 还能够对 CPU 进行隔离,被隔离的 CPU 只容许执行本利用的线程,从而取得更好的性能。
要应用这个个性须要用到 linux 的 isolcpus。这个性能次要是将一个或多个 CPU 独立进去,用来执行特定的 Affinity 工作。
isolcpus 命令前面能够接 CPU 的 ID, 或者能够批改 /boot/grub/grub.conf 文件,增加要隔离的 CPU 信息如下:
isolcpus=3,4,5
总结
affinity 能够对线程进行极致管控,对性能要求严格的敌人能够试试,然而在应用过程中须要抉择适合的 AffinityStrategies,否则可能会得不到想要的后果。
本文的例子能够参考:learn-netty4
更多内容请参考 http://www.flydean.com/51-netty-thread-affinity/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!