pollset 方法:AIX 解决 poll () /select () 可伸缩性问题的方法

来源:互联网 发布:中国基础设施投资数据 编辑:程序博客网 时间:2024/06/06 01:30

传统的 poll 方法有可伸缩性问题;它不太适合处理大量文件描述符。根本问题是,随着文件描述符数量的增加,每个 poll 操作所需的工作量会线性增加。为了提高可伸缩性,已经提出了许多新的 API,比如 /dev/poll、实时信号、I/O 完成端口、/dev/epoll 和内核队列。对于哪个 API 是最好的长期解决方案,有许多争论(见 [POLLCMP])。

poll() 的哪些方面影响可伸缩性?

  • 每个 poll() 调用都提供要 poll 的文件描述符列表。对于每个调用,都要把这个列表复制到内核空间中。图 1 中红色的事件表示这些重复的复制。
  • poll 一个对象需要两步:首先在文件描述符上建立一个持有计数,然后调用与这个文件描述符相关联的操作。
  • 异步和同步 poll 之间的主要路径长度差异是,分配和最终清除控制块。
  • 作为 poll 操作的最后一步,要清除所有控制块。必须从与块相关联的对象中删除每个控制块。这要求 poll 方法锁住对象。

在监视大量文件描述符时,如果在循环中调用 poll(),poll 所涉及的这些高成本的系统调用会严重影响总体性能。

为了让 poll() 能够处理大量文件描述符,AIX pollset 接口提供了两个优化措施。第一个措施减少每个 poll 操作在内核和用户空间之间传输的信息量,见 图 2。pollset 接口在本机(内核)pollset 层中创建并维护一个文件描述符集和感兴趣的事件。应用程序直接向本机 pollset 层注册文件描述符和感兴趣的事件。与 poll() 不同,pollset 接口并不要求选择器在每次调用 select() 时复制整个文件描述符集。相反,它只复制在前一次 select() 调用之后新注册的事件。


图 2. Pollset() 方法
pollset 方法 

第二个优化措施是在内核中使用 poll 缓存机制。它跨系统调用在请求的文件描述符集上维护文件描述符状态。在每个 poll 操作的开头,通过 poll 繁忙的文件描述符跟踪状态。空闲文件描述符的状态是已知的,因为在状态改变时会通知 poll 缓存服务。


图 3. poll 缓存内部结构
poll 缓存内部结构 

图 3 显示 poll 缓存中的组件及其关系。poll 缓存管理一个可能很大的文件描述符集。其中的每个文件描述符由一个 poll 缓存控制块(pccb)表示。每个 pccb 根据文件描述符散列值放在 poll 缓存中。维护一个未处理列表,它标识最近有状态转换的 pccb。支持选择和 poll 的每个子系统都向 poll 缓存注册。当文件描述符的状态发生变化时,子系统通知 poll 缓存,这会在 poll 缓存中触发状态转换。传统的 poll()/select() 需要检查选择的所有文件描述符,为了解决由此产生的可伸缩性问题,poll 缓存通过状态转换只把 ‘繁忙的’ pccb 转移到事件列表中。这样,poll 操作就不需要访问 pollset 中的所有 pccb。只为已经添加到事件列表中的控制块提供服务。如果繁忙的文件描述符数量接近选择的文件描述符总数,而且文件描述符数量非常大,就会出现最糟糕的情况。在这种情况下,pollset 方法与传统的选择 /poll 方法相比并不能显著提高性能。

IBM® JDK 从 6.0 Service Refresh 5 开始支持 pollset 接口。应用程序不需要为了启用 pollset 接口进行修改。如果操作系统支持 pollset 接口,Java.nio.SelectorProvider 方法在默认情况下会打开 pollset 选择器。NIO 的 pollset 选择器使用下面的本机 pollset API(见 清单 6)提高应用程序性能。


清单 6. NIO 库使用的本机 pollset 接口
   pollset_t ps = pollset_create(int maxfd);  int rc = pollset_destroy(pollset_t ps);  int rc = pollset_ctl(pollset_t ps, struct poll_ctl *pollctl_array, int array_length);  int nfound = pollset_poll(pollset_t ps,  struct pollfd *polldata_array,  int array_length, int timeout); 

正如前面提到的,当应用程序打开 poll 选择器时,poll 选择器会创建一个本机 pollset 结构。当应用程序注册通道时,选择器在本机 pollset 结构中注册文件描述符和感兴趣的事件。这意味着,对于每个注册,选择器调用必须执行两个模式切换。第一个切换是从 Java API 层到 Java native Interface (JNI) 层。第二个切换是从 JNI 层到内核空间。如果应用程序注册大量通道,这些切换会影响性能。


图 4. Pollset() - 批量更新
pollset - 批量更新 

为了避免过多的模式切换,选择器方法在内部维护一个数据结构(见 图 4),在其中临时存储注册的文件描述符,直到它们的数量达到 java.nio.pollset.RegistrationPerCall(默认值为 50)。注意,当应用程序在选择器上调用 select() 时,即使注册数量没有达到 java.nio.pollset.RegistrationPerCall,选择器也会在本机 pollset 层中注册所有文件描述符。应用程序可以在启动时通过设置正整数值调整 registrationPerCall 数量,见 清单 7。


清单 7. 如何设置每次注册的文件描述符数量
   java -Djava.nio.pollset.RegistrationPerCall=30 "class name"

实验环境:Pet Store 2.0

我们使用 Java Pet Store 2.0(见 [PETSTORE])评估性能改进。这是 Sun 公司提供的示例应用程序。它支持虚拟宠物商店中的网上购物场景,比如列出出售的宠物、发贴出售自己的宠物、通过 PayPal 购买宠物和搜索某一地理区域的宠物。这个应用程序演示 JEE5 (Java Platform Enterprise Edition 5) 技术,尤其是与 Web 2.0 特性相关的技术,比如 Ajax (Asynchronous JavaScript and XML),提供响应性的用户界面并通过 Web 2.0 mashup 组合来自多个源的信息。mashup 场景之一使用非常流行的 Google Maps 地图服务在浏览器中显示的地图上标出每个宠物的位置。

我们的实验集中于一个 Ajax 场景,这个场景允许客户机浏览器与服务器异步地通信。这种技术通常在浏览器中显示和更新一个小的弹出窗口,这比需要下载和更新整个网页的传统网站快得多,可以提供响应性的用户界面。因此,尽管应用程序的主要代码在服务器上运行,但是 Ajax 技术可以让用户觉得应用程序就像是在客户机浏览器中运行一样。

在 Pet Store 2.0 的这个场景中,当用户把鼠标光标放在浏览器中一个宠物条目上时,在浏览器中运行的 JavaScript 代码向服务器发送一个 Ajax 请求,请求获取关于这个条目的信息。服务器获取这个条目的数据库记录并以 XML (Extensible Markup Language) 格式把记录返回给客户机浏览器。客户机浏览器显示一个小的弹出窗口,在其中显示收到的信息。

因为每个 Ajax 请求都需要在服务器上执行一个非常简单的数据库查询事务,服务器会在操作系统方面花费相当多的 CPU 周期(见[ISPASS])。这个特点促使我们使用 pollset API 减少系统时间,从而增加吞吐量性能。Ajax 请求的吞吐量性能是一个重要的指标,因为在服务器负载重时它会直接影响用户体验。


图 5. Petstore 环境
Petstore - 实验环境 

图 5 说明我们的实验环境,它包含三层:模拟的客户机、应用服务器和后端数据库服务器。对于模拟客户机,我们使用 8 台基于 Linux® 的刀片服务器,它们运行一个基于开放源码 Grinder 工具的客户机模拟程序(见 [GRIDER])。对于我们的 Ajax 实验,模拟总共 1280 个客户机,每个客户机在循环中随机选择一个宠物条目并向应用服务器发送 Ajax 请求,请求获取关于这个宠物的信息。对于应用服务器,使用在 IBM BladeCenter® JS22 服务器上运行的 Glassfish 应用服务器(见 [GLASSFISH]),这台服务器使用 4GHz 的 4 核 POWER6™ 处理器。另外,使用在 IBM BladeCenter HS21 上运行的 MySQL 数据库,这台机器使用 8 核 Intel Xeon E5320 处理器。

实验结果

我们通过在一个 Java 驱动程序中使用 pollset API 来评估性能改进。在实验中,我们主要关注前面介绍的 Ajax 请求,因为这个场景代表 Web 2.0 应用程序中常见的客户机 - 服务器交互模式。图 6 显示对于两个驱动程序测得的吞吐量性能结果:一个使用 poll(),另一个使用 pollset()。结果表明,与使用 poll API 的老驱动程序相比,使用 pollset API 的驱动程序的吞吐量性能提高了 13.3%。


图 6. 两个驱动程序的吞吐量性能,一个使用 poll(),另一个使用 pollset()
Petstore - 性能对比 

我们还使用 curt 命令进一步分析了系统时间。这个命令是 AIX 跟踪工具的一部分(见 [AIX TOOL]),我们通过它了解 pollset API 可以减少多少系统时间。图 7 显示在修改 java.nio.pollset.RegistrationPerCall 的值时,每毫秒两个系统调用(pollset_ctl() 和 pollset_poll())的数量。随着 java.nio.pollset.RegistrationPerCall 值的增加,pollset_ctl() 的 CPU 时间减少,这是因为每个 pollset_ctl() 处理的套接字数量增加了。


图 7. 每毫秒系统调用数量
每毫秒系统调用数量 

图 8 显示 poll()、pollset_ctl() 和 pollset_poll() API 的 CPU 时间比例。原来的驱动程序在调用 poll() 方面花费 5.3% 的 CPU 时间(见 图 8 中最左边的条),新的驱动程序在调用 pollset_ctl() 和 pollset_poll() 方面只花费 3% 的 CPU 时间。因此我们的实验结果表明,pollset API 有助于减少系统时间,而且我们的实现对于 java.nio.pollset.RegistrationPerCall 参数不敏感,修改这个参数对系统时间和吞吐量性能影响不大。


图 8. CPU 时间花费
每个调用的 CPU 时间 

结束语

本文通过一个宠物商店应用程序展示了使用 pollset 接口对性能的益处。另外,pollset 接口只查询繁忙的文件描述符,这减少了内核和用户空间之间传输的数据量。在文件描述符集不需要频繁更新的情况下,最适合使用 pollset 接口。

原创粉丝点击