搶先一步
VMware 提供培訓和認證,助您加速發展。
瞭解更多在我曾經工作的一個專案中,我們有一個系統負責接收來自裝置的消,並決定是否將該資訊傳遞給使用者。該系統有多個決策層級,我們遇到的問題之一始終是:訊息在透過系統的過程中是否丟失了?
在我們轉向 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;
}
}
這個類的方法及其內容都相當直觀。對您來說可能比較新的是這個類上的註解。當每個 bean 部署到 JMX MBeanServer 時,Spring 的 JMX 支援會使用這些註解自動構建 MBean 管理介面。
最後,是將所有內容連線起來的問題。首先,我們將構成流程的元件連線起來。接下來,我們宣告將追蹤器應用於每個元件的切面。在本例中,我們使用了非常出色的 AspectJ 切入點語言。最後,我們設定 JMX exporter,使其能夠自動檢測帶有 @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>
接下來我們需要做的是有一個驅動類。在本例中,驅動類只是以小於 750ms 的隨機延遲傳送訊息。
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 來顯示結果。
根據我生疏的數學技能,從長遠來看,我期望看到 Component 1 被呼叫 100%,Component 2 被呼叫 50%,Component 3 被呼叫 25% 的時間。讓我們看看
很高興我的機率記對了。最好的部分是這仍然符合良好的設計原則。例如,所有元件都不知道任何關於追蹤的事情,因為那不是它們的職責。此外,這個子系統的所有追蹤需求都包含在一個類中,並且有一個實現,符合 AOP 的需求與實現 1:1 的目標。最後,有了關閉追蹤的功能,任何效能影響都或多或少地被抵消了。我知道,我知道增加一個整數並不昂貴,但如果你的追蹤做了昂貴的事情,這個功能就很有用,而且你無需擔心是否要將其投入生產;你可以簡單地停用監控,直到客戶來電尋求支援。
所以這些圖表確實很漂亮,如果你知道你預期的百分比,它們甚至可以告訴你一些資訊,但你還能做些什麼呢?比如最後 100 條透過的訊息及其決策?比如記錄訊息被丟棄的原因?比如丟棄決策與訊息未到達管道末端之間的關聯?如果你從未故意丟棄某條訊息,但它在進入 500ms 內未能到達末端(可能是由於執行緒問題),能夠知道該訊息已丟失,難道不好嗎?沿著同樣的思路,如果訊息從管道一端到達另一端的時間超過 250ms,給管理員傳送一封電子郵件怎麼樣?
追蹤/監控的可能性是無限的(並且是可插拔的!)。你會用它做什麼呢?
當然,還有原始碼。