Alibaba Java診斷工具Arthas查看Dubbo動態代理類

原創/朱季謙

閱讀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工具類可以在線上生產環境查看一些我們新部署的代碼,看是否是新改動的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章