使用 AspectJ 和 JMX 追蹤訊息流程

工程 | Ben Hale | 2006 年 4 月 25 日 | ...

在我過去參與的一個專案中,我們有一個系統可以接收來自設備的訊息,並決定是否將該資訊傳遞給使用者。 有多個決策層級,我們一直發現自己會問的問題是,訊息是否在系統傳輸過程中遺失。

在我們轉向 Spring 之前,幾乎不可能回答這個問題。 我們嘗試使用日誌記錄,但由於需要做出決策的訊息量太大,因此充其量只是繁瑣。 其他嘗試使用偵錯工具,但由於訊息量和時序變化的組合,導致只有間歇性的成功。

不幸的是,在我能夠實施更合適的解決方案之前,我就離開了。但如果我還在,這可能是它應該有的樣子。 最後,我將討論一些在這種努力中可能有用的擴展。

首先,我們有一組介面及其實現


package flowtracingexample;

public interface Component1 {

	void forwardCall();

}

package flowtracingexample;

import java.util.Random;

public class DefaultComponent1 implements Component1 {
	
	private Component2 child;

	private Random r = new Random();
	
	public DefaultComponent1(Component2 child) {
		this.child = child;
	}

	public void forwardCall() {
		if (r.nextBoolean()) {
			child.forwardCall();
		}
	}

}

package flowtracingexample;

public interface Component2 {

	void forwardCall();

}

package flowtracingexample;

import java.util.Random;

public class DefaultComponent2 implements Component2 {
	
	private Component3 child;

	private Random r = new Random();
	
	public DefaultComponent2(Component3 child) {
		this.child = child;
	}

	public void forwardCall() {
		if (r.nextBoolean()) {
			child.forwardCall();
		}
	}

}

package flowtracingexample;

public interface Component3 {

	void forwardCall();

}

package flowtracingexample;

public class DefaultComponent3 implements Component3 {

	public void forwardCall() {
	}

}

這是一個非常簡單的例子,但重點是使用 fowardCall() 方法,訊息有 50% 的時間會傳遞到下一個子元件(在本例中按數字升序排列)。 請注意,這些 POJO 中沒有涉及追蹤的邏輯。

為了實現我們的追蹤行為,我們希望擁有一組計數器;每個元件一個。 此外,我們希望有方法來重置計數器、啟動和停止監控,以及確定是否正在進行監控。 為此,我們實作一個包含計數器的類別。


package flowtracingexample;

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource
public class FlowTracer {

	private long component1Count = 0;

	private long component2Count = 0;

	private long component3Count = 0;

	private boolean tracing = false;

	@ManagedAttribute
	public long getComponent1Count() {
		return this.component1Count;
	}

	@ManagedAttribute
	public long getComponent2Count() {
		return this.component2Count;
	}

	@ManagedAttribute
	public long getComponent3Count() {
		return this.component3Count;
	}

	@ManagedAttribute
	public boolean getTracing() {
		return this.tracing;
	}

	public void incrementComponent1Count() {
		if (this.tracing) {
			component1Count++;
		}
	}

	public void incrementComponent2Count() {
		if (this.tracing) {
			component2Count++;
		}
	}

	public void incrementComponent3Count() {
		if (tracing) {
			component3Count++;
		}
	}

	@ManagedOperation
	public void resetAllComponentCount() {
		resetComponent1Count();
		resetComponent2Count();
		resetComponent3Count();
	}

	@ManagedOperation
	public void resetComponent1Count() {
		this.component1Count = 0;
	}

	@ManagedOperation
	public void resetComponent2Count() {
		this.component2Count = 0;
	}

	@ManagedOperation
	public void resetComponent3Count() {
		this.component3Count = 0;
	}

	@ManagedOperation
	public void startTracing() {
		tracing = true;
	}

	@ManagedOperation
	public void stopTracing() {
		tracing = false;
	}
}

此類別的方法及其內容非常簡單明瞭。 對您來說可能較新的是此類別上的註解。 這些註解被 Spring 的 JMX 支援用來自動建立 MBean 管理介面,當每個 Bean 部署到 JMX MBeanServer 時。

  • ManagedResource:宣告此類別應作為 JMX MBean 公開
  • ManagedAttribute:宣告由這個 getter/setter 代表的 JavaBean 屬性應該是一個 MBean 屬性。 如果您想要對此屬性進行讀寫存取,則需要註解 getter 和 setter。
  • ManagedOperation:宣告此方法應作為 MBean 操作公開

最後,將所有內容連接在一起。 首先,我們將構成流程的元件連接在一起。 接下來,我們宣告將追蹤器放置在每個元件上的 Aspect。 在這種情況下,我們使用非常棒的 AspectJ 切入點語言。 最後,我們設定 JMX 匯出器以自動偵測具有 @ManagedResource 註解的類別實例。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- Components -->
	<bean id="component3" class="flowtracingexample.DefaultComponent3" />

	<bean id="component2"
		class="flowtracingexample.DefaultComponent2">
		<constructor-arg ref="component3" />
	</bean>

	<bean id="component1"
		class="flowtracingexample.DefaultComponent1">
		<constructor-arg ref="component2" />
	</bean>

	<!-- Aspect -->
	<bean id="flowTracer" class="flowtracingexample.FlowTracer" />

	<aop:config>
		<aop:aspect id="component1Aspect" ref="flowTracer">
			<aop:before method="incrementComponent1Count"
				pointcut="execution(public void flowtracingexample.Component1.forwardCall())" />
		</aop:aspect>

		<aop:aspect id="component2Aspect" ref="flowTracer">
			<aop:before method="incrementComponent2Count"
				pointcut="execution(public void flowtracingexample.Component2.forwardCall())" />
		</aop:aspect>

		<aop:aspect id="component3Aspect" ref="flowTracer">
			<aop:before method="incrementComponent3Count"
				pointcut="execution(public void flowtracingexample.Component3.forwardCall())" />
		</aop:aspect>
	</aop:config>

	<!-- JMX -->
	<bean class="org.springframework.jmx.export.MBeanExporter">
		<property name="autodetectModeName" value="AUTODETECT_ALL" />
		<property name="assembler">
			<bean
				class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
				<property name="attributeSource">
					<bean
						class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource" />
				</property>
			</bean>
		</property>
		<property name="namingStrategy">
			<bean
				class="org.springframework.jmx.export.naming.IdentityNamingStrategy" />
		</property>
	</bean>

</beans>

接下來我們需要做的是有一個驅動程式類別。 在這種情況下,驅動程式類別只是以低於 750 毫秒的隨機延遲發送訊息。


package flowtracingexample;

import java.io.IOException;
import java.util.Random;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FlowTracingExample {

	public static void main(String[] args) throws InterruptedException,
			IOException {
		ApplicationContext ctx = new ClassPathXmlApplicationContext(
				"classpath:flowtracingexample/applicationContext.xml");

		Component1 comp = (Component1) ctx.getBean("component1");
		Random r = new Random();

		System.out.print("Ready...");
		 System.in.read();

		for (;;) {
			comp.forwardCall();
			Thread.sleep(r.nextInt(750));
		}
	}
}

在我的情況下,我將在 Java VM Management 運行時運行此應用程式,因為它給了我一個免費的 MBean 伺服器(並且我喜歡漂亮的記憶體圖)。 如果您沒聽說過這個,這是 Java 5 VM 中的一個系統屬性,它會導致 VM 使用 JMX 來管理自身。 它有關於記憶體消耗、執行緒和數百萬其他事項的 Bean。 您只需在運行應用程式的命令列上輸入 -Dcom.sun.management.jmxremote 即可啟動它。 在另一個漂亮的 Java 5 新增功能中,我將使用 jconsole 來顯示我的結果。

根據我生疏的數學技能,從長遠來看,我希望看到元件 1 被調用 100% 的時間,元件 2 被調用 50% 的時間,元件 3 被調用 25% 的時間。 讓我們看看

Tracing Screen Shot

很高興看到我記得我的機率是對的。 最好的部分是這仍然符合良好的設計原則。 例如,沒有一個元件知道任何關於追蹤的事情,因為這不是它們要做的事情。 同樣,這個子系統的所有追蹤要求都包含在一個類別中,並且有一個實現,滿足 AOP 的 1:1 要求到實現目標。 最後,憑藉關閉追蹤的能力,任何效能影響或多或少都被中和了。 我知道,我知道增加一個整數並沒有那麼昂貴,但如果您的追蹤做了一些昂貴的事情,那麼擁有它會很好,而且您不必擔心是否要將其發送到生產環境; 您只需停用監控,直到您的客戶打電話來尋求支援。

所以這些圖表的確很漂亮,如果你知道你預期的百分比,甚至可能會告訴你一些東西,但是你還能做什麼呢? 最後 100 條訊息及其決策如何? 訊息被丟棄的原因的日誌如何? 丟棄決策與管道末端沒有訊息之間的關聯如何? 如果您從未故意丟棄訊息,但它沒有在其條目後 500 毫秒內到達末端,因此得知訊息已遺失(可能是由於執行緒問題)是否會很好? 同樣,如果從管道一端到另一端所需的時間超過 250 毫秒,則向管理員發送電子郵件如何?

追蹤/監控的可能性是無限的(並且是可插拔的!)。 你會怎麼做呢?

當然,還有 原始碼

取得 Spring 電子報

保持與 Spring 電子報的聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看所有