天天看點

基于Arthas探究Apache Dubbo技術内幕

作者:技術聯盟總壇
基于Arthas探究Apache Dubbo技術内幕

一、 Arthas的介紹與安裝

Arthas 是Alibaba開源的Java診斷工具,深受開發者喜愛,當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:

  • 這個類從哪個 jar 包加載的?為什麼會報各種類相關的 Exception?
  • 我改的代碼為什麼沒有執行到?難道是我沒 commit?分支搞錯了?
  • 遇到問題無法線上上 debug,難道隻能通過加日志再重新釋出嗎?
  • 線上遇到某個使用者的資料處理有問題,但線上同樣無法 debug,線下無法重制!
  • 是否有一個全局視角來檢視系統的運作狀況?
  • 有什麼辦法可以監控到JVM的實時運作狀态?

Arthas的github位址為:https://github.com/alibaba/arthas

安裝Arthas第一步需要下載下傳arthas-boot.jar:

wget https://alibaba.github.io/arthas/arthas-boot.jar           

第二步啟動:

java -jar arthas-boot.jar           

然後會讓列出所有Java程序,讓你選擇進入那個程序,選擇1就會進入我們的服務提供者程序:

admindeMacBook-Pro :: ~ » java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.1.0
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 1224 com.books.dubbo.demo.provider.ApiProvider
  [2]: 1192  
  [3]: 1134 org.apache.zookeeper.server.quorum.QuorumPeerMain           

選擇1後就會關聯到程序1224:

[INFO] arthas home: /Users/luxu.zlx/.arthas/lib/3.1.0/arthas
[INFO] Try to attach process 1224
[INFO] Attach process 1224 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'


wiki: https://alibaba.github.io/arthas
version: 3.1.0
pid: 1224
time: 2019-05-06 11:23:04

$           

二、檢視擴充接口擴充卡類的源碼

在《Dubbo架構核心原理剖析》我們講解了Dubbo的擴充卡原理,知道每個擴充接口對應一個擴充卡類,并且這個擴充卡類是使用動态編譯技術生成的,正常情況下除非我們使用Debug才能看到擴充卡的源碼,但是使用arthas我們就可以在服務啟動的情況下檢視某個擴充卡源碼。

比如我們想看擴充接口org.apache.dubbo.rpc.Protocol的擴充卡類源碼,在可以在啟動dubbo服務和arthas後,在arthas的控制台執行下面指令:

$ jad org.apache.dubbo.rpc.Protocol$Adaptive

ClassLoader:
+-sun.misc.Launcher$AppClassLoader@4e25154f
  +-sun.misc.Launcher$ExtClassLoader@68837a77

Location:
/Users/luxu.zlx/.m2/repository/org/apache/dubbo/dubbo/2.7.1/dubbo-2.7.1.jar

/*
 * Decompiled with CFR 0_132.
 */
package org.apache.dubbo.rpc;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.RpcException;

public class Protocol$Adaptive
implements Protocol {
    @Override
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    @Override
    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public Invoker refer(Class class_, URL uRL) throws RpcException {
        String string;
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL2.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.refer(class_, uRL);
    }

    public Exporter export(Invoker invoker) throws RpcException {
        String string;
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
        String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
        }
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
        return protocol.export(invoker);
    }
}

Affect(row-cnt:1) cost in 618 ms.
$           

同理其他擴充接口的擴充卡類源碼可以使用類似方法得到。

三、 檢視服務提供端Wrapper類的源碼

在《Dubbo使用JavaAssist減少反射調用開銷》一節我們講到Dubbo會給每個服務提供者的實作生産一個Wrapper類,這個wrapper類裡面最終調用服務提供者的接口實作類,wrapper類的存在是為了減少反射的調用。那麼我們可以使用jad指令友善的檢視某一個服務實作類被wrapper後的類,以便探其實如何工作的。

可以在啟動dubbo服務提供端和arthas後,在arthas的控制台執行下面指令:

$ jad org.apache.dubbo.common.bytecode.Wrapper1

ClassLoader:
+-sun.misc.Launcher$AppClassLoader@4e25154f
  +-sun.misc.Launcher$ExtClassLoader@68837a77

Location:
/Users/luxu.zlx/.m2/repository/org/apache/dubbo/dubbo/2.7.1/dubbo-2.7.1.jar

/*
 * Decompiled with CFR 0_132.
 *
 * Could not load the following classes:
 *  com.books.dubbo.demo.api.PoJo
 *  com.books.dubbo.demo.api.Result
 *  com.books.dubbo.demo.provider.GreetingServiceImpl
 */
package org.apache.dubbo.common.bytecode;

import com.books.dubbo.demo.api.PoJo;
import com.books.dubbo.demo.api.Result;
import com.books.dubbo.demo.provider.GreetingServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.apache.dubbo.common.bytecode.ClassGenerator;
import org.apache.dubbo.common.bytecode.NoSuchMethodException;
import org.apache.dubbo.common.bytecode.NoSuchPropertyException;
import org.apache.dubbo.common.bytecode.Wrapper;

public class Wrapper1
extends Wrapper
implements ClassGenerator.DC {
    public static String[] pns;
    public static Map pts;
    public static String[] mns;
    public static String[] dmns;
    public static Class[] mts0;
    public static Class[] mts1;

    @Override
    public String[] getMethodNames() {
        return mns;
    }

    @Override
    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    @Override
    public String[] getPropertyNames() {
        return pns;
    }

    @Override
    public boolean hasProperty(String string) {
        return pts.containsKey(string);
    }

    @Override
    public Object getPropertyValue(Object object, String string) {
        try {
            GreetingServiceImpl greetingServiceImpl = (GreetingServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or setter method in class com.books.dubbo.demo.provider.GreetingServiceImpl.").toString());
    }

    @Override
    public void setPropertyValue(Object object, String string, Object object2) {
        try {
            GreetingServiceImpl greetingServiceImpl = (GreetingServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        throw new NoSuchPropertyException(new StringBuffer().append("Not found property \"").append(string).append("\" field or setter method in class com.books.dubbo.demo.provider.GreetingServiceImpl.").toString());
    }

    public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
        GreetingServiceImpl greetingServiceImpl;
        try {
            greetingServiceImpl = (GreetingServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            if ("sayHello".equals(string) && arrclass.length == 1) {
                return greetingServiceImpl.sayHello((String)arrobject[0]);
            }
            if ("testGeneric".equals(string) && arrclass.length == 1) {
                return greetingServiceImpl.testGeneric((PoJo)arrobject[0]);
            }
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.books.dubbo.demo.provider.GreetingServiceImpl.").toString());
    }

    public Class getPropertyType(String string) {
        return (Class)pts.get(string);
    }
}

Affect(row-cnt:1) cost in 230 ms.
$           

如上可知找到GreetingServiceImpl服務類被wrapper後的代碼。

四、 查詢Dubbo啟動後都有哪些Filter

在Dubbo中Filter鍊是一個亮點,通過Filter鍊可以對服務請求和服務處理流程進行幹預,有時候我們想要知道運作時到底有哪些Filter在工作,這時候使用arthas的trace指令顯得比較重要。

可以在啟動dubbo服務提供端和arthas後,在arthas的控制台執行下面指令:

$ trace org.apache.dubbo.rpc.Filter *
`---ts=2019-05-06 11:45:14;thread_name=DubboServerHandler-192.168.0.109:20880-thread-14;id=39;is_daemon=true;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@4e25154f
    `---[1006.264664ms] org.apache.dubbo.rpc.filter.EchoFilter:invoke()
        +---[0.014367ms] org.apache.dubbo.rpc.Invocation:getMethodName()
        +---[0.011227ms] java.lang.String:equals()
        `---[1006.148807ms] org.apache.dubbo.rpc.Invoker:invoke()
            `---[1006.056904ms] org.apache.dubbo.rpc.filter.ClassLoaderFilter:invoke()
                +---[min=0.00241ms,max=0.316038ms,total=0.331369ms,count=3] java.lang.Thread:currentThread()
                +---[0.010192ms] java.lang.Thread:getContextClassLoader()
                +---[0.034606ms] org.apache.dubbo.rpc.Invoker:getInterface()
                +---[0.002414ms] java.lang.Class:getClassLoader()
                +---[min=0.002935ms,max=0.006914ms,total=0.009849ms,count=2] java.lang.Thread:setContextClassLoader()
                `---[1005.10032ms] org.apache.dubbo.rpc.Invoker:invoke()
                    `---[1004.957999ms] org.apache.dubbo.rpc.filter.GenericFilter:invoke()
                        +---[0.018287ms] org.apache.dubbo.rpc.Invocation:getMethodName()
                        +---[0.013182ms] java.lang.String:equals()
                        `---[1004.81856ms] org.apache.dubbo.rpc.Invoker:invoke()
                            +---[1004.476265ms] org.apache.dubbo.rpc.filter.ContextFilter:invoke()
                            |   +---[0.014525ms] org.apache.dubbo.rpc.Invocation:getAttachments()
                            |   +---[0.03847ms] java.util.HashMap:<init>()
                            |   +---[min=0.001196ms,max=0.043312ms,total=0.087213ms,count=10] java.util.Map:remove()
                            |   +---[min=0.001419ms,max=0.012589ms,total=0.019495ms,count=3] org.apache.dubbo.rpc.RpcContext:getContext()
                            |   +---[0.008891ms] org.apache.dubbo.rpc.RpcContext:setInvoker()
                            |   +---[0.039752ms] org.apache.dubbo.rpc.RpcContext:setInvocation()
                            |   +---[min=9.47E-4ms,max=0.010978ms,total=0.011925ms,count=2] org.apache.dubbo.rpc.Invoker:getUrl()
                            |   +---[0.007281ms] org.apache.dubbo.common.URL:getHost()
                            |   +---[0.004561ms] org.apache.dubbo.common.URL:getPort()
                            |   +---[0.008838ms] org.apache.dubbo.rpc.RpcContext:setLocalAddress()
                            |   +---[min=7.99E-4ms,max=0.005502ms,total=0.006301ms,count=2] org.apache.dubbo.rpc.RpcContext:getAttachments()
                            |   +---[0.009082ms] java.util.Map:putAll()
                            |   +---[0.002867ms] org.apache.dubbo.rpc.RpcInvocation:setInvoker()
                            |   +---[1003.89518ms] org.apache.dubbo.rpc.Invoker:invoke()
                            |   |   `---[1003.31997ms] org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter:invoke()
                            |   |       +---[min=0.003088ms,max=0.00877ms,total=0.011858ms,count=2] java.lang.System:currentTimeMillis()
                            |   |       +---[1003.209055ms] org.apache.dubbo.rpc.Invoker:invoke()
                            |   |       |   +---[1002.79439ms] org.apache.dubbo.rpc.filter.TimeoutFilter:invoke()
                            |   |       |   |   +---[min=0.001023ms,max=0.007899ms,total=0.008922ms,count=2] org.apache.dubbo.rpc.Invocation:getAttachments()
                            |   |       |   |   +---[0.003378ms] java.lang.System:currentTimeMillis()
                            |   |       |   |   +---[0.008515ms] java.lang.String:valueOf()
                            |   |       |   |   +---[0.00812ms] java.util.Map:put()
                            |   |       |   |   `---[1002.161502ms] org.apache.dubbo.rpc.Invoker:invoke()
                            |   |       |   |       `---[1002.09319ms] org.apache.dubbo.monitor.support.MonitorFilter:invoke()
                            |   |       |   |           +---[0.007223ms] org.apache.dubbo.rpc.Invoker:getUrl()
                            |   |       |   |           +---[0.009356ms] org.apache.dubbo.common.URL:hasParameter()
                            |   |       |   |           `---[1002.006852ms] org.apache.dubbo.rpc.Invoker:invoke()
                            |   |       |   |               +---[1001.720803ms] org.apache.dubbo.rpc.filter.ExceptionFilter:invoke()
                            |   |       |   |               |   `---[1001.614361ms] org.apache.dubbo.rpc.Invoker:invoke()
                            |   |       |   |               `---[0.100831ms] org.apache.dubbo.rpc.filter.ExceptionFilter:onResponse()
                            |   |       |   |                   `---[0.021178ms] org.apache.dubbo.rpc.Result:hasException()
                            |   |       |   `---[0.270409ms] org.apache.dubbo.rpc.filter.TimeoutFilter:onResponse()
                            |   |       |       +---[0.01415ms] org.apache.dubbo.rpc.Invocation:getAttachment()
                            |   |       |       +---[0.003677ms] java.lang.System:currentTimeMillis()
                            |   |       |       +---[0.05753ms] java.lang.Long:valueOf()
                            |   |       |       +---[0.00897ms] java.lang.Long:longValue()
                            |   |       |       +---[min=0.002242ms,max=0.008982ms,total=0.011224ms,count=2] org.apache.dubbo.rpc.Invoker:getUrl()
                            |   |       |       +---[0.006836ms] org.apache.dubbo.rpc.Invocation:getMethodName()
                            |   |       |       `---[0.019452ms] org.apache.dubbo.common.URL:getMethodParameter()
                            |   |       `---[0.013511ms] java.util.concurrent.ConcurrentMap:size()
                            |   +---[0.018903ms] org.apache.dubbo.rpc.RpcContext:removeContext()
                            |   `---[0.009552ms] org.apache.dubbo.rpc.RpcContext:removeServerContext()
                            `---[0.168211ms] org.apache.dubbo.rpc.filter.ContextFilter:onResponse()
                                +---[0.016141ms] org.apache.dubbo.rpc.RpcContext:getServerContext()
                                +---[0.002753ms] org.apache.dubbo.rpc.RpcContext:getAttachments()
                                `---[0.010771ms] org.apache.dubbo.rpc.Result:addAttachments()           

可知目前運作的服務提供端Filter鍊中Filter有:

org.apache.dubbo.rpc.filter.EchoFilter
org.apache.dubbo.rpc.filter.ClassLoaderFilter
org.apache.dubbo.rpc.filter.GenericFilter
org.apache.dubbo.rpc.filter.ContextFilter
org.apache.dubbo.rpc.filter.TimeoutFilter
org.apache.dubbo.rpc.filter.ExceptionFilter           

同理大家可以在服務消費端啟動後看其運作都有哪些Filter。

五、 總結

Arthas 是Alibaba開源的Java診斷工具,大家在生産實踐過程中如遇到問題,可以使用該利器,将會事半功倍。