Alibaba Java診斷工具Arthas檢視Dubbo動態代理類

朱季謙發表於2022-04-07

原創/朱季謙

閱讀Dubbo原始碼過程中,會發現,Dubbo消費端在做遠端呼叫時,預設通過 Javassist 框架為服務介面生成動態代理類,呼叫javassist框架下的JavassistProxyFactory類的getProxy(Invoker invoker, Class<?>[] interfaces)方法,動態生成一個存放在JVM中的動態代理類。

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

那麼,問題來了,如果我們想要一睹該動態生成的代理類內部結構是怎樣的,如何才能便捷做到的?

這就是我想介紹的一款工具,它可以幫助我們檢視JDK或者javassist生成的動態代理類,當然,它的功能遠不止此,還可以在生產環境進行診斷。

Arthas 是Alibaba開源的Java診斷工具,官方線上文件地址:https://arthas.aliyun.com/doc/

根據官網上的介紹,它還可以解決以下問題————

當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:

這個類從哪個 jar 包載入的?為什麼會報各種類相關的 Exception?

我改的程式碼為什麼沒有執行到?難道是我沒 commit?分支搞錯了?

遇到問題無法線上上 debug,難道只能通過加日誌再重新發布嗎?

線上遇到某個使用者的資料處理有問題,但線上同樣無法 debug,線下無法重現!

是否有一個全域性視角來檢視系統的執行狀況?

有什麼辦法可以監控到JVM的實時執行狀態?

怎麼快速定位應用的熱點,生成火焰圖?

怎樣直接從JVM內查詢某個類的例項?

這些方案本文暫不展開,這裡只展開通過該工具檢視Dubbo生成的動態代理類。

我是直接在使用dubbo-parent原始碼中的例子,分別啟動了提供者與消費者。
image

首先,啟動提供者方法——

public class Application {
    public static void main(String[] args) throws Exception {
            startWithBootstrap();   
    }
    private static boolean isClassic(String[] args) {
        return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
    }

    private static void startWithBootstrap() {
        ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());

        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
        RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181");
        registryConfig.setTimeout(20000);

        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setHost("192.168.100.1");
        protocolConfig.setPort(20877);
        bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
                .registry(registryConfig)
                .service(service)
                .protocol(protocolConfig)
                .start()
                .await();
    }
}

注意,需要配置RegistryConfig自己的zookeeper, protocolConfig.setHost("xxx.xxx.xxx.xxx")設定成你本地內網的ip即可;

DemoServiceImpl類詳情——

public class DemoServiceImpl implements DemoService {
    private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);

    @Override
    public String sayHello(String name) {
        logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    }

    @Override
    public CompletableFuture<String> sayHelloAsync(String name) {
        return null;
    }

}

接著,啟動消費者,這裡可以設定一個休眠時間,這樣就可以一直維持消費者執行在記憶體當中——

public class Application {
    public static void main(String[] args) {
            runWithRefer();
    }


    private static void runWithRefer() {
        RegistryConfig registryConfig = new RegistryConfig("zookeeper://127.0.0.1:2181");
        registryConfig.setTimeout(30000);

        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setHost("192.168.200.1");
        protocolConfig.setPort(20899);
        ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
        reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
        reference.setRegistry(registryConfig);
        reference.setInterface(DemoService.class);
        DemoService service = reference.get();
        String message = service.sayHello("dubbo");
        System.out.println("列印了5555555"+message);
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

當Dubbo的服務提供者與消費者都正常執行時,說明此時JVM虛擬機器記憶體裡已經存在動態生成的代理類,這時,我們就可以開始通過arthas-boot.jar工具進行檢視了。

首先,將arthas-boot.jar工具下載到你本地,我的是Windows,隨便放到一個目錄當中,例如——
image

接著,直接在執行著Dubbo消費端程式的IDEA上開啟Terminal——

image

然後,輸入 java -jar C:\Users\92493\Downloads\12229238_g\arthas-boot.jar ,arthas正常執行成功話,將列出當前JVM上執行的程式——

image

可以看到我們剛剛啟動的provider程式與consumer程式,這時,只需要輸入對應程式前面的編號【5】,就可以將Arthas 關聯到啟動類為 org.apache.dubbo.demo.consumer.Application的 Java 程式上了——

image

到這一步,我們就可以通過指令 sc *.proxy *模糊查詢帶有proxy標誌的類名了,動態代理生成的類一般都是以Proxy標誌——

image

其中,這裡的org.apache.dubbo.common.bytecode.proxy0就是消費者生成的動態代理類,我們可以直接反編譯去檢視它內部結構——

[arthas@57676]$ jad org.apache.dubbo.common.bytecode.proxy0

控制檯就會列印出該動態代理類的內部結構——

/*

 * Decompiled with CFR.
 * 
 * Could not load the following classes:
 * com.alibaba.dubbo.rpc.service.EchoService
 * org.apache.dubbo.common.bytecode.ClassGenerator$DC
 * org.apache.dubbo.demo.DemoService
 * org.apache.dubbo.rpc.service.Destroyable
   */
   package org.apache.dubbo.common.bytecode;

import com.alibaba.dubbo.rpc.service.EchoService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.CompletableFuture;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.demo.DemoService;
import org.apache.dubbo.rpc.service.Destroyable;

public class proxy0 implements ClassGenerator.DC,Destroyable,EchoService,DemoService {
public static Method[] methods;
private InvocationHandler handler;

public String sayHello(String string) {
    Object[] objectArray = new Object[]{string};
    Object object = this.handler.invoke(this, methods[0], objectArray);
    return (String)object;
}

public CompletableFuture sayHelloAsync(String string) {
    Object[] objectArray = new Object[]{string};
    Object object = this.handler.invoke(this, methods[1], objectArray);
    return (CompletableFuture)object;
}

public Object $echo(Object object) {
    Object[] objectArray = new Object[]{object};
    Object object2 = this.handler.invoke(this, methods[2], objectArray);
    return object2;
}

public void $destroy() {
    Object[] objectArray = new Object[]{};
    Object object = this.handler.invoke(this, methods[3], objectArray);
}

public proxy0() {
}

public proxy0(InvocationHandler invocationHandler) {
    this.handler = invocationHandler;
  }
}

在Dubbo案例當中,當我們執行 String message = service.sayHello("dubbo")去呼叫遠端介面時,其實是呼叫了動態代理生成的方法——

public String sayHello(String string) {
    Object[] objectArray = new Object[]{string};
    Object object = this.handler.invoke(this, methods[0], objectArray);
    return (String)object;
}

舉一反三,這個Arthas工具類可以線上上生產環境檢視一些我們新部署的程式碼,看是否是新改動的。

相關文章