关于java:netty系列之在netty中实现线程和CPU绑定

4次阅读

共计 4792 个字符,预计需要花费 12 分钟才能阅读完成。

简介

之前咱们介绍了一个十分优良的细粒度管制 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0