使用 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 支援使用,以便在每個 bean 部署到 JMX MBeanServer 時自動構建 MBean 管理介面。

  • ManagedResource:宣告此類應作為 JMX MBean 公開
  • ManagedAttribute:宣告此 getter/setter 表示的 JavaBean 屬性應為 MBean 屬性。如果您希望對此屬性具有讀寫訪問許可權,則需要同時註解 getter 和 setter。
  • ManagedOperation:宣告此方法應作為 MBean 操作公開

最後,是把整個東西連線起來。首先,我們連線構成流的元件。接下來,我們宣告將跟蹤器放置在每個元件上的切面。在這種情況下,我們使用的是非常棒的 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 管理的這個應用程式,因為它為我提供了一個免費的 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 社群所有即將舉行的活動。

檢視所有