|
GlassFish 是一款完全符合 Java EE 5 的应用服务器,它具有“企业就绪”功能并且获得了两项 OSI 批准。除了许多其他企业级功能之外,GlassFish 还提供了非常优秀的自我管理功能,并可通过 Java Management eXtension (JMX) 标准进行扩展。
GlassFish 应用服务器提供了良好的群集管理和负载均衡功能。但有时我们仍然需要对如何使用请求加载群集节点提供细粒度的控制。在共享的环境中,当我们使用处理能力来托管多个应用程序(包括数据库、批作业处理,以及为来自不同客户的请求提供服务的应用服务器)时可能出现这种情况。这种状况会引导我们更改不同时间段内路由到每个 GlassFish 实例的负载。我们可以手动更改路由的负载,但是有时候拥有根据已定义的规则自动对其进行更改的能力是非常有用的。
本文将介绍如何使用 GlassFish 自我管理功能、JMX 以及 Application Server Management eXtension (AMX) API 来自动更改负载均衡器配置。本文提供了一些示例代码以描述这些过程,这些代码也可以从 参考资料 部分获得。
除了重温一些背景信息以外,我们将需要执行三个步骤来完成该示例,以更改路由到每个应用服务器实例的负载:
- 开发一个可以更改负载均衡器配置的 Mbean。
- 创建并部署管理规则。
- 使用新功能测试系统。
JMX 和 AMX API 基础
JMX 指一些允许软件提供商和开发人员拥有公共的技术,以便展示管理和监视其基于 Java 的应用程序的功能的JSR。
通常当我们说 JMX 时,我们指的是至少有两个 JSR 的集合:Java 管理扩展(JSR 3)和 JMX 远程 API(JSR 160)。尽管有其他补充 JMX 的规范,比如J2EE管理规范(JSR 77),最常用的 JS R还是 JSR 3 和 JSR 160。
JMX 拥有三个重要组件:
- MBean:MBean 是 JMX 规范中探测器级别的对象。有四种类型的 MBean 用于不同的使用情况;最常见的 MBean 类型是 Dynamic。
MBeanServer:这是 MBean 的访问点。所有的 MBean 都应该注册 MBeanServer。该访问点允许各种操作,比如调用 MBeans 的运行、更改 MBean 的属性、事件监听器注册等等。
- 连接器和适配器:它们允许各种类型的管理控制台创建从主机 JVM 的外面到
MBeanServer 的连接。常见的连接器为 RMI 和 JMX 消息协议(JMXMP;由 JSR 160 定义)。常见的适配器有在 Java 平台性能分析架构(JSR 163)中定义的 SNMP 适配器,以及 Web(HTML/HTTP)适配器。
使用 JMX,我们可以保证任何其他可识别 JMX 的软件能够与 MBean 交互,以便监视特定的属性或更改某些一由 MBean 显示的其他属性,或者侦听由 MBean 发布的各种 JMX 事件。
如前所述,只能使用 MBeanServer 访问 MBean,对于很多用户来说这并不是一种方便的方式,因为这使他们要处理 JMX,而 JMX 并不是一种易于处理的 API。调用 MBean 将要经历反射,所以我们需要知道我们想要使用的 MBean、方法参数和签名等等。通过使用 ObjectName(我们使用该名称注册我们的 MBean),我们可以找到所需的 MBean。ObjectName core 架构后面的一个字符串,以用可查询的方式表示已注册的 MBean。根据由 MBeanServer 实例构建的树状表示,MBeanServer 提供查询注册的方法。
AMX 用于和 GlassFish 应用服务器 MBean 交互,不会面临以上讨论的困难。AMX 是一组将管理 MBean 公开为实现 AMX 接口的客户端代理服务器的 API。这些 AMX 接口大多数包含在 com.sun.appserv.management.config 以及它的一些子包中。我们可以在客户端使用 AMX API,以便管理几乎所有 JSR 77 批准的 Java EE 对象,比如服务器、应用程序、资源等等,而无需知道有关 JMX 的任何信息。但是,所有这些EE对象也可以通过普通 JMX 进行管理。
使用 AMX,我们可以:
- 更改应用服务器配置——创建资源、删除资源、启用或禁用等等。
- 管理服务器、节点代理、群集等等。
- 接收几乎所有应用服务器中发生的事件的通知,并相应地进行响应。
- 监视很多托管于应用服务器内的对象的状态。这些包括 EJB、Web 应用程序、企业应用程序、连接池等等。
当我们使用群集或企业配置安装 GlassFish 时,我们会创建一个 GlassFish 应用服务器的 域,该域初始是没有成员的。第一个成员是域管理服务器(DAS),并且它是我们添加到该域中的各个群集或实例的单点管理。各个域对象(比如实例、群集、连接池等等)的所有相关配置都存储在 DAS 中,而且可以直接使用 DAS 来更改。AMX 允许我们连接到 DAS 并在每个应用服务器 NBean 上执行每个所需的操作,以便更改提及的域对象的配置。在 com.sun.appserv.management.helper 中包含了一些 helper 类,可以方便使用 AMX API。以下实例展示了如何使用这些API更改群集的服务器实例的权重。权重 是一个用于指示负载平衡器发送到实例的请求的百分比的数字。
AppserverConnectionSource ASConnection = Connect.connectNoTLS("127.0.0.1", 8686, "admin", "adminadmin"); DomainRoot dRoot = ASConnection.getDomainRoot(); Map<String, ClusterConfig> clusters = dRoot.getContaineeMap(XTypes.CLUSTER_CONFIG); ClusterConfig clusterConf= clusters.get("Cluster-1"); Map <String, ClusteredServerConfig< servers = clusterConf.getClusteredServerConfigMap(); ClusteredServerConfig instance1 = servers.get("instance-01"); instance1.setLBWeight("25");
正如您所看到的,我们只是简单得获得群集的服务器实例并更改其配置——无需知道任何有关JMX的信息。这就是 AMX API 给我们带来的好处。在 AMX 设计中,所有的对象都是域根的分支,而域根无缝地映射到 DAS。在下一节中,我们将讨论什么是管理规则,以及我们如何使用它们来实现已定义的要求。
GlassFish 管理规则基础知识
管理规则是以下内容的集合:
- 事件:事件使用 JMX 通知机制来出发操作。事件包括 MBean 属性更改一直到特定的日志消息。
- 操作:操作与事件相关联,当相关的事件发生时则被触发。操作可以是实现
NotificationListener 实例的 MBean。
重要的事件如下所示:
- 监视器事件:这种类型的事件触发基于 MBean 属性更改的操作。
- 通知事件:MBean 可以实现
NotificationBroadcaster,以便发送通知到所有将兴趣注册到其事件通知上的侦听器。
- 系统事件:这是一组预定义的事件,来自 GlassFish 应用服务器的内部基础结构。这些事件包括:生命周期、日志、计时器、跟踪以及群集事件。
本文的关注点在于群集事件,它是系统事件家族的成员。有三种群集事件,当群集启动或停止或者出现故障时会激发。我们的示例代码会监听群集启动事件,并且一旦接收到此事件,它就会计划并启动一个计时器。该计时器会调用方法以便执行用于确定是否要求更新实例权重的逻辑。该决定将根据一个配置文件的内容来做出,该文件具有以下元素:
- 我们将要更新其实例的群集的名称。
- 在输入一个时间片和更新实例权重之间可接受的延迟。该值是先前提到的计时器将用来执行任务(即执行我们的逻辑来确定是否更新实例权重)的时间间隔。
- 一些时间片,其中每个时间片是一组开始和结束时间以及实例名称(连同它们在该时间片期间的权重)。
以下片段是本文示例代码使用的示例配置文件。
<config interval="10000" cluster-name="Cluster-1"> <slice start-time="06:40:00 AM" end-time="10:40:00 AM"> <instance name="instance-01" weight="55"/> <instance name="instance-02" weight="55"/> </slice> <slice start-time="03:00:00 PM" end-time="06:00:00 PM"> <instance name="instance-01" weight="38"/> <instance name="instance-02" weight="38"/> </config>
该配置文件显示有一个名为 Cluster-1 的群集,并且要求重新配置其在两个时间片中的实例权重。在输入时间片和更新实例权重之间可接受的延迟为 1000 毫秒;例如,一秒钟。
我们看到每个管理规则都有一个将会触发操作的事件。操作是实现 NotificationListener 接口的 MBean,它有一个将要实现的方法 handleNotification。当管理规则触发操作时,它将会调用此方法。
通过查阅 JMX 教程和参考材料,我们可以发现最基本的 MBean 类型是标准 MBean。这些 MBean 包含一个必须遵循一个特定命名架构的接口和实现该接口的类。该接口必须以词 MBean 结尾。在本文中,操作 MBean 是一个自定义 MBean,它除了有自己的 MBean 接口 ClusterWeightManagerMBean 外,还实现 NotificationListener 接口,以便接收群集的 启动 通知。所以最终的操作 MBean 类实现以下两个接口:
ClusterWeightManagerMBean:该接口包括我们的 MBean 使 JMX 代理和类似 JConsole 的工具可用的公共方法。
NotificationListener:实现此接口允许我们的 MBean 在我们通过定义管理规则为该类型事件注册其兴趣后监听群集启动事件。
以上讨论的操作 MBean 需要一个配置文件,以从中读取时间片并在其运行期间使用它们,而且该配置文件的地址为需要保留的 MBean 的属性,以供将来使用。这里,GlassFish 帮助我们提供一些工具,用于存储和初始化 JavaBean 类型的 MBean 属性。GlassFish 在 domain.xml 文件中存储该属性值,并且在每次 MBean 的对象进行构建时对其进行初始化。
为了让 GlassFish 读取和写入该属性值,我们应该提供可以从管理代理访问的 setter 和 getter 方法;正如我们已经看到的,任何需要能够被管理代理访问的方法应该包含在 MBean 接口中。以下代码片段显示了具有一对 setter 和 getter 方法的 ClusterWeightManagerMBean,以读取和写入 configurationFilePath 属性。
public interface ClusterWeightManagerMBean { public String getConfigurationFilePath(); public void setConfigurationFilePath(String configurationFilePath); }
ClusterWeightManager 类实现此接口,以构建一个可接受的 MBean。它也应该实现 NotificationListener 接口,要求使用该接口从管理规则接收通知。在详细讨论 ClusterWeightManager 的实现之前,让我们看一看名为 TimeSlice 的 helper 类,它保存了每个事件片的信息。以下列出的内容显示了 TimeSlice 类。
public class TimeSlice { private Map<String, String> instanceWeights; private Date startDate; private Date endDate; public TimeSlice(Map<String, String> instanceWeights, Date startDate, Date endDate) { this.instanceWeights = instanceWeights; this.startDate = startDate; this.endDate = endDate; } public Map<String, String> getInstanceWeights() { return instanceWeights; } public void setInstanceWeights(Map<String, String> instanceWeights) { this.instanceWeights = instanceWeights; }
public int compareTime(Date curDate) { int result = 0; if (curDate.compareTo(endDate) == 1) { result = 1; } if (curDate.compareTo(startDate) == -1) { result = -1; } return result; } }
最后我们需要实现位于 ClusterWeightManager 中的核心功能,它是 MBean 实现类。完整的代码太长,无法包含在此处;请参阅 资源 部分的示例代码 ZIP。现在,让我们把注意力集中到最重要的部分上。
在讨论任何其他方法之前,我们应该讨论 applyInstanceWeights,它应用每个基于 timeSlice 配置的实例的权重,该配置由我们传递给实例。这里是 applyInstanceWeights 方法的实现。
private void applyInstanceWeights(TimeSlice currectTimeslice) { String instanceNamePrefix = "amx:j2eeType=X-ClusteredServerConfig,name="; Map<String, String> instanceWeights = currectTimeslice.getInstanceWeights(); try { Set instanceNames = instanceWeights.keySet(); Iterator It = instanceNames.iterator(); while (It.hasNext()) { String instanceName = (String) (It.next()); String instanceWeight = instanceWeights.get(instanceName); ObjectName name = new ObjectName(instanceNamePrefix + instanceName); Attribute attrib = new Attribute("LBWeight", new Integer(instanceWeight)); mBeanServer.setAttribute(name, attrib); } } catch (Exception ex) { ex.printStackTrace(); } }
仔细查看以上代码片段,您会发现我们将一个包含当前时间片的对象发送给该方法,它将会使用其 instanceWeights 属性(实例名称和实例权重的映射)来更新实例权重。正如您可能注意到的一样,instanceNamePrefix 值是不完整的,在以后几步中,我们会附加实例名称以使其成为一个完整的 ObjectName,以便让 MBeanServer 找到其相应的对象(一个群集的对象)。然后我们使用该 ObjectName 和 MBeanServer 来更改找到的实例的 LBWeight 属性的值。
另一个重要的方法为 handleNotification,它是从 NotificationListener 继承而来的。当它接收到群集启动通知时,它就会启动实例权重更新进程。
public void handleNotification(Notification notification, Object handback) { initialize(); this.startManager(); }
接收通知后,它会调用 initialize 方法,该方法会分析 XML 文件,并创建配置文件中包含的所有时间片的 ArrayList。startManager 方法会计划并启动我们前面讨论过的计时器。
您可能会问:“如果我们的群集已经在运行怎么办? 我们应该在部署管理规则以后重启它吗?” 为了给这些问题一个答案,让我们看一看哪个 JMX 提供类似的状态,然后进入我们使用的解决方案。通常当我们在注册 MBean 后需要执行任务时,我们实现 MBeanRegistration 接口并使用其 postRegister 方法来执行所需的逻辑。当在我们的例子中,我们无法做到这一点。为什么? 因为我们拥有一个包含实例权重配置的 XML 文件,并且我们将其地址放入一个 MBean 变量中;postRegister 将会在 setter 之前执行,所以在注册前阶段我们没有该配置文件地址来读取配置。我们可以做的是使用 setConfigurationFilePath 方法来检查群集状态并执行所需的操作(如果群集已经在运行)。
在 setConfigurationFilePath 方法中,我们只是简单地使用群集名称来创建完整的 ObjectName,然后检查使用 mBeanServer 的相应对象的 state 属性:1 表示群集正在运行,-1 表示群集出现故障,0 表示群集已停止。
public void setConfigurationFilePath(String configurationFilePath) { this.configurationFilePath = configurationFilePath; String clusterPrefix = "amx:j2eeType=X-J2EECluster,name="; initialize(); try { ObjectName name = new ObjectName(clusterPrefix + clusterName); Integer state = (Integer) mBeanServer.getAttribute(name, "state"); if (state == 1) { isTimerRunning = true; this.startManager(); } } catch (Exception ex) { ex.printStackTrace(); }
现在,在我们启动计时器的其他实例前,我们在 handleNotification 方法中添加代码来检查对象级别的属性,看计时器是否已经在运行。
创建并部署管理规则
假设您已经解压缩了该示例代码并编译了 JAR 文件,则本步骤所需的类文件包和示例配置文件已经可用。将整个包层次结构( 其内部是一个附加的示例代码中的文件夹 classes)复制到 domain_dir/applications/mbeans/,以便使这些类可以被域类查找器访问。启动应用服务器并使用以下 asadmin 命令将 MBean 部署到 DAS 实例,确保根据您复制配置文件的位置来更改该配置文件路径。
create-mbean --host localhost --port 4848 --user admin --name ClusterInstanceWeightsManager --attributes ConfigurationFilePath=c\:/config.xml samples.glassfish.management.clustermanager.ClusterWeightManager
部署 ClusterWeightManager MBean 等同于在 domain.xml 文件中包含一些配置元素并想相应的 MBeanServer 注册 ClusterWeightManager MBean。每次启动期间,应用服务器将会向 MBeanServer 注册所有包含在 domain.xml 中的 MBean。现在我们向管理代理注册了 ClusterWeightManager MBean,当它接收到通知时,它就可以启动逻辑并更新实例权重(如果需要)。但是在它可以接收任何通知之前,它应该宣布它有兴趣接收群集启动通知。创建群集管理规则即我们为某个群集事件注册 MBean 的兴趣的方式。
我们需要定义管理规则,当群集启动时我们将会自动将群集启动通知发送到我们的 MBean。该管理规则即将我们的 MBean 注册为群集事件的侦听器。管理规则的另一个任务是过滤事件,这样我们的类将仅接收我们对其定义了兴趣的事件类型。以下 asadmin 命令将会创建所需的管理规则。
create-management-rule --eventtype cluster --host localhost --port 4848 --user admin --ruleenabled=true --action ClusterInstanceWeightsManager --event loglevel INFO --recordevent=true --eventproperties name=start ClusterManagementRule
使用以下 asadmin 命令,我们可以查询已注册的 MBean 和管理规则。
>list-management-rules --user admin >list-mbeans --user admin
测试系统
为了使该配置自动应用到您的负载均衡器,您需要使用 GlassFish 应用服务器管理控制台做一些配置。所有请在导航树中找到您的负载均衡器,并确保选择负载均衡器配置页面中 General 选项卡下的 Automatically Apply Changes 复选框。要测试系统,请添加两个彼此接近的时间片,并让系统运行足够长的时间,以从一个时间片跨越到另一个,然后打开 GlassFish 管理控制台。从导航树中找到您的群集,选中群集配置页面中的实例选项卡。您应该注意到该实例权重会相应改变。
结束语
本文展示了一个使用 GlassFish 的自我管理功能执行一些管理任务的小示例,这些任务手动执行将会非常困难而且很难费时间。通过查看本文介绍的示例代码和概念,您应该可以创建您自己的 MBean 和管理规则,以执行日常管理任务。另一方面,AMX 可以成为应用服务器内部配置和状态的好途径。在 参考资料 部分,您可以找到很多很好的参考资料,以便深入了解本文中介绍或使用的概念或 API。
参考资料
Masoud Kalali 在编程领域已有八年的工作经验。
|