简介

之前我们介绍了一个非常优秀的细粒度控制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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

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

netty系列之:在netty中实现线程和CPU绑定的更多相关文章

  1. netty系列之:在netty中使用protobuf协议

    目录 简介 定义protobuf 定义handler 设置ChannelPipeline 构建client和server端并运行 总结 简介 netty中有很多适配不同协议的编码工具,对于流行的goo ...

  2. netty系列之:在netty中处理CORS

    目录 简介 服务端的CORS配置 CorsConfigBuilder CorsHandler netty对cors的支持 总结 简介 CORS的全称是跨域资源共享,他是一个基于HTTP-header检 ...

  3. netty系列之:在netty中使用native传输协议

    目录 简介 native传输协议的依赖 netty本地传输协议的使用 总结 简介 对于IO来说,除了传统的block IO,使用最多的就是NIO了,通常我们在netty程序中最常用到的就是NIO,比如 ...

  4. netty系列之: 在netty中使用 tls 协议请求 DNS 服务器

    目录 简介 支持DoT的DNS服务器 搭建支持DoT的netty客户端 TLS的客户端请求 总结 简介 在前面的文章中我们讲过了如何在netty中构造客户端分别使用tcp和udp协议向DNS服务器请求 ...

  5. netty系列之:在netty中使用proxy protocol

    目录 简介 netty对proxy protocol协议的支持 HAProxyMessage的编码解码器 netty中proxy protocol的代码示例 总结 简介 我们知道proxy proto ...

  6. netty系列之:使用netty搭建websocket服务器

    目录 简介 netty中的websocket websocket的版本 FrameDecoder和FrameEncoder WebSocketServerHandshaker WebSocketFra ...

  7. netty系列之:使用netty搭建websocket客户端

    目录 简介 浏览器客户端 netty对websocket客户端的支持 WebSocketClientHandshaker WebSocketClientCompressionHandler netty ...

  8. netty系列之:使用netty实现支持http2的服务器

    目录 简介 基本流程 CleartextHttp2ServerUpgradeHandler Http2ConnectionHandler 总结 简介 上一篇文章中,我们提到了如何在netty中配置TL ...

  9. netty系列之:请netty再爱UDT一次

    目录 简介 netty对UDT的支持 搭建一个支持UDT的netty服务 异常来袭 TypeUDT和KindUDT 构建ChannelFactory SelectorProviderUDT 使用UDT ...

  10. Silverlight实用窍门系列:47.Silverlight中元素到元素的绑定,以及ObservableCollection和List的使用区别

    问题一:在某一些情况下,我们使用MVVM模式的时候,对于某一个字段(AgeField)需要在前台的很多个控件(A.B.C.D.E)进行绑定,但是如何能够让我们后台字段名改变的时候能够非常方便的改变所有 ...

随机推荐

  1. 反射,装饰器,类当中的方法,property---day24

    1.反射 # ### 反射(针对于类对象 模块) '''概念:通过字符串去操作类对象或者模块当中的成员(属性方法)''' class Man(): pass class Woman(): pass c ...

  2. 记录一个错误:Unable to find a match: python-dev

    今天尝试在Linux下运行一个Python项目,在安装requirements.txt时报错 执行命令如下: [root@VM-16-8-centos cve-search]# pip3 instal ...

  3. [Rust] Workspace,Package, Crate 和 Module

    package(包) 一个 package 对应一个项目,package 的信息在 Cargo.toml 里面定义. crate(木箱.箱子) crate 指的是 package 编译后的输出文件.以 ...

  4. 【Azure 应用服务】应用服务连接 Azure MySQL 一直失败,报错 Create connection error

    问题描述 App Service上部署的Java应用,连接 Azure Database for MySQL 失败.错误信息:Create connection error, url: jdbc:my ...

  5. 【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?

    问题描述 如何查看PHP应用部署到App Service后,Azure上面使用的应用服务器是什么呢?因为App Service支持两种操作系统,Windows 和 Linux.在Windows中,使用 ...

  6. nftables语法及例子

    先上我自己实际测试通过的例子,用例子便于在实践中学习: # 0 --- 说明 ---下面例子中的单引号目的是为了避免nftable参数中的星号.花括号.分号等符号被shell展开解释掉了,导致nft命 ...

  7. C/C++、C#、JAVA(三):字符串操作

    C/C++.C#.JAVA(三):字符串操作 目录 C/C++.C#.JAVA(三):字符串操作 定义字符串 C C++ C# JAVA 捕捉输入和输出 等值比较 C/C++ C# JAVA 字符串操 ...

  8. Java 一悟结束异常处理 Biu丶

  9. Java面经知识点图谱总结

    未完待续~~~

  10. Linux 网络编程从入门到进阶 学习指南

    前言 大家好,我是小康.在上一篇文章中,我们探讨了 Linux 系统编程的诸多基础构件,包括文件操作.进程管理和线程同步等,接下来,我们将视野扩展到网络世界.在这个新篇章里,我们要让应用跳出单机限制, ...