天天看點

dubbo-go 白話文 | 從零搭建 dubbogo 和 dubbo 的簡單用例前言解決問題細節叨叨參考

dubbo-go 白話文 | 從零搭建 dubbogo 和 dubbo 的簡單用例前言解決問題細節叨叨參考

作者 | 鐵城  dubbo-go 社群 committer

來源|

阿裡巴巴雲原生公衆号

本文将手把手教你使用 dubbogo 調用 dubbogo 或 dubbo 提供的服務提供方。

前言

本文基于 dubbogo 1.5.4 版本

最近開始參與 dubbogo 的一些開發測試,之前都是直接拿

samples

 的例子驗證功能,而這次為了複現一個功能問題,打算從零開始搭建一個 dubbo-go 和 dubbo 調用的工程,踩到了一些新人使用 dubbogo 的坑,把這個過程記錄下供大家參考。

通過本文你可以了解到:

  • 如何正常配置 dubbogo 消費方去調用 dubbo 和 dubbogo 服務提供方。
  • 通過一個實際的 BUG 介紹解決問題的思路。

解決問題

1. 準備 dubbo 服務提供者

1)基本定義

定義

DemoService

接口:

public interface DemoService {

    String sayHello(String name);

    String sayHello(User user);

    String sayHello(User user, String name);

}           

User

對象:

public class User implements Serializable {

    private String name;

    private int age;

    ......
}           

2)啟動 dubbo 服務提供者

用的

dubbo 官方示例代碼

public static void main(String[] args) throws IOException {
    // 服務實作
    DemoService demoService = new DemoServiceImpl();

    // 目前應用配置
    ApplicationConfig application = new ApplicationConfig();
    application.setName("demoProvider");

    // 連接配接注冊中心配置
    RegistryConfig registry = new RegistryConfig();
    registry.setAddress("127.0.0.1:2181");
    registry.setProtocol("zookeeper");
    registry.setUsername("");
    registry.setPassword("");

    // 服務提供者協定配置
    ProtocolConfig protocol = new ProtocolConfig();
    protocol.setName("dubbo");
    protocol.setPort(12345);
    protocol.setThreads(200);

    // 注意:ServiceConfig為重對象,内部封裝了與注冊中心的連接配接,以及開啟服務端口

    // 服務提供者暴露服務配置
    ServiceConfig<DemoService> service = new ServiceConfig<>(); // 此執行個體很重,封裝了與注冊中心的連接配接,請自行緩存,否則可能造成記憶體和連接配接洩漏
    service.setApplication(application);
    service.setRegistry(registry); // 多個注冊中心可以用setRegistries()
    service.setProtocol(protocol); // 多個協定可以用setProtocols()
    service.setInterface(DemoService.class);
    service.setRef(demoService);
    service.setVersion("1.0.0");
    service.setGroup("tc");
    service.setTimeout(60 * 1000);

    // 暴露及注冊服務
    service.export();

    System.in.read();
}           

檢視 zookeeper 看是否注冊成功:

$ls /dubbo/com.funnycode.DemoService/providers
[dubbo%3A%2F%2F127.0.0.1%3A12345%2Fcom.funnycode.DemoService%3Fanyhost%3Dtrue%26application%3DdemoProvider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dtc%26interface%3Dcom.funnycode.DemoService%26methods%3DsayHello%26pid%3D18167%26release%3D2.7.7%26revision%3D1.0.0%26side%3Dprovider%26threads%3D200%26timestamp%3D1606896020691%26version%3D1.0.0]
```

如上的輸出表示服務提供方已經啟動。

## 2. 準備 dubbogo 服務消費者

### 1)基本定義

定義 `User` 對象:

```
type User struct {
	Name string
	Age  int
}

func (User) JavaClassName() string {
	return "com.funnycode.User"
}
```

定義 `DemoProvider` 接口:

```
type DemoProvider struct {
	SayHello  func(ctx context.Context, name string) (string, error)            `dubbo:"sayHello"`
	SayHello2 func(ctx context.Context, user User) (string, error)              `dubbo:"sayHello"`
	SayHello3 func(ctx context.Context, user User, name string) (string, error) `dubbo:"sayHello"`
}

func (p *DemoProvider) Reference() string {
	return "DemoProvider"
}
```

### 2)啟動 dubbogo 消費者

```
func main() {
	config.Load()
	gxlog.CInfo("\n\n\nstart to test dubbo")

	res, err := demoProvider.SayHello(context.TODO(), "tc")
	if err != nil {
		panic(err)
	}

	gxlog.CInfo("response result: %v\n", res)

	user := User{
		Name: "tc",
		Age:  18,
	}

	res, err = demoProvider.SayHello2(context.TODO(), user)
	if err != nil {
		panic(err)
	}

	gxlog.CInfo("response result: %v\n", res)

	res, err = demoProvider.SayHello3(context.TODO(), user, "tc")
	if err != nil {
		panic(err)
	}

	gxlog.CInfo("response result: %v\n", res)

	initSignal()
}
```

### 3. 請求結果分析

### 1)直接調用

> 确認問題的存在。

第一個接口的參數是字元串,可以正常傳回 `[2020-12-03/18:59:12 main.main: client.go: 29] response result: Hello tc`。

第二、三兩個接口存在 `User` 對象,無法調用成功。錯誤資訊如下:

```
2020-12-02T17:10:47.739+0800    INFO    getty/listener.go:87    session{session session-closed, Read Bytes: 924, Write Bytes: 199, Read Pkgs: 0, Write Pkgs: 1} got error{java exception:Fail to decode request due to: java.lang.IllegalArgumentException: Service not found:com.funnycode.DemoService, sayHello
        at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:134)
        at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:80)
        at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
        at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
        at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
}, will be closed.           

錯誤正如

issue

 中描述的一模一樣,因為錯誤資訊傳回到了消費端,可以看到 Java 那邊的錯誤堆棧資訊,是以直接去看

DecodeableRpcInvocation.decode#134

2)斷點檢視

代碼如下:

// 反序列化
public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Decodeable {
    public Object decode(Channel channel, InputStream input) throws IOException {
      ......
      if (serviceDescriptor != null) {
          // 方法描述裡面根據方法名查找
          MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(getMethodName(), desc);
          if (methodDescriptor != null) {
              pts = methodDescriptor.getParameterClasses();
              this.setReturnTypes(methodDescriptor.getReturnTypes());
          }
      }
      // 表示沒有找到方法        
      if (pts == DubboCodec.EMPTY_CLASS_ARRAY) {
          if (!RpcUtils.isGenericCall(path, getMethodName()) && !RpcUtils.isEcho(path, getMethodName())) {
              throw new IllegalArgumentException("Service not found:" + path + ", " + getMethodName());
          }
          pts = ReflectUtils.desc2classArray(desc);
      }
      ......
    }
}           
  • 檢視

    MethodDescriptor

    ,即找方法是否存在,存在的話就會設定好

    ParameterClasses

  • 如果上面沒找到,

    pts == DubboCodec.EMPTY_CLASS_ARRAY

    就會滿足條件,進而判斷是否是泛化調用或者是 echo 調用,如果都不是則報服務找不到方法錯誤。
  • desc 是

    Ljava/lang/Object

    ,很明顯并沒有參數是 Object 的方法,是以必然是會報錯的。

補充說明:方法查詢。

**

代碼如下:

public MethodDescriptor getMethod(String methodName, String params) {
    Map<String, MethodDescriptor> methods = descToMethods.get(methodName);
    if (CollectionUtils.isNotEmptyMap(methods)) {
        return methods.get(params);
    }
    return null;
}           

優點:比之前的版本加了方法的元資訊緩存起來,不使用反射可以提高效率,可以了解用空間換時間。

dubbo-go 白話文 | 從零搭建 dubbogo 和 dubbo 的簡單用例前言解決問題細節叨叨參考

4. 解決問題

因為直接撸代碼并 hold 不住,是以通過比較來檢視問題所在。

1)啟動 dubbo 服務消費者

通過 api 模式啟動,參考官方例子。啟動這個是為了檢視 Java 版本的傳輸内容。

public static void main(String[] args) throws InterruptedException {
    // 目前應用配置
    ApplicationConfig application = new ApplicationConfig();
    application.setName("demoProvider2");

    // 連接配接注冊中心配置
    RegistryConfig registry = new RegistryConfig();
    registry.setAddress("127.0.0.1:2181");
    registry.setProtocol("zookeeper");
    registry.setUsername("");
    registry.setPassword("");
    // 注意:ReferenceConfig為重對象,内部封裝了與注冊中心的連接配接,以及與服務提供方的連接配接

    // 引用遠端服務
    ReferenceConfig<DemoService> reference
        = new ReferenceConfig<>(); // 此執行個體很重,封裝了與注冊中心的連接配接以及與提供者的連接配接,請自行緩存,否則可能造成記憶體和連接配接洩漏
    reference.setApplication(application);
    reference.setRegistry(registry); // 多個注冊中心可以用setRegistries()
    reference.setInterface(DemoService.class);
    reference.setVersion("1.0.0");
    reference.setGroup("tc");
    reference.setCheck(true);
    reference.setTimeout(1000 * 60);

    // 和本地bean一樣使用xxxService
    DemoService demoService = reference.get(); // 注意:此代理對象内部封裝了所有通訊細節,對象較重,請緩存複用
    System.out.println(demoService.sayHello(new User("tc", 18)));

    TimeUnit.MINUTES.sleep(10);
}           
dubbo-go 白話文 | 從零搭建 dubbogo 和 dubbo 的簡單用例前言解決問題細節叨叨參考

desc 肉眼可見的是 

Lcom/funnycode/User

,這個就是正确的對象了。

2)查找 dubbogo 為什麼不對

代碼位置:

protocol/dubbo/impl/hessian.go:120#marshalRequest

代碼實作:

func marshalRequest(encoder *hessian.Encoder, p DubboPackage) ([]byte, error) {
    service := p.Service
    request := EnsureRequestPayload(p.Body)
    encoder.Encode(DEFAULT_DUBBO_PROTOCOL_VERSION)
    encoder.Encode(service.Path)
    encoder.Encode(service.Version)
    encoder.Encode(service.Method)

    args, ok := request.Params.([]interface{})

    if !ok {
        logger.Infof("request args are: %+v", request.Params)
        return nil, perrors.Errorf("@params is not of type: []interface{}")
    }
    types, err := getArgsTypeList(args)
    if err != nil {
        return nil, perrors.Wrapf(err, " PackRequest(args:%+v)", args)
    }
    encoder.Encode(types)
    for _, v := range args {
        encoder.Encode(v)
    }

    ......
}           

斷點可以發現,types 傳回的時候就已經是

Object

了,沒有傳回

User

,那麼繼續跟進去檢視代碼。

  • protocol/dubbo/impl/hessian.go:394#getArgsTypeList

  • protocol/dubbo/impl/hessian.go:418#getArgType

func getArgType(v interface{}) string {
  // 常見的類型處理

  ......

  default:
    t := reflect.TypeOf(v)
    if reflect.Ptr == t.Kind() {
      t = reflect.TypeOf(reflect.ValueOf(v).Elem())
    }
    switch t.Kind() {
    case reflect.Struct:
      return "java.lang.Object"
    }
    ......
}           

很明顯當發現是

reflect.Struct

的時候就傳回了

java.lang.Object

,是以參數就變成了

Object

,那麼因為 Java 代碼那邊依賴這個類型是以就調用失敗了。

3)其它版本驗證

因為回報是 2.7.7 出錯,是以先考慮到在之前的版本是否功能正常,于是把服務提供者切換到 dubbo 2.7.3,發現調用仍然有錯誤,如下:

2020-12-02T21:52:25.945+0800    INFO    getty/listener.go:85    session{session session-closed, Read Bytes: 4586, Write Bytes: 232, Read Pkgs: 0, Write Pkgs: 1} got error{java exception:org.apache.dubbo.rpc.RpcException: Failed to invoke remote proxy method sayHello to registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demoProvider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.0.113%3A12345%2Fcom.funnycode.DemoService%3Fanyhost%3Dtrue%26application%3DdemoProvider%26bind.ip%3D192.168.0.113%26bind.port%3D12345%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dtc%26interface%3Dcom.funnycode.DemoService%26methods%3DsayHello%26pid%3D23889%26register%3Dtrue%26release%3D2.7.3%26revision%3D1.0.0%26side%3Dprovider%26threads%3D200%26timeout%3D60000%26timestamp%3D1606916702204%26version%3D1.0.0&pid=23889&registry=zookeeper&release=2.7.3&timestamp=1606916702193, cause: Not found method "sayHello" in class com.funnycode.DemoServiceImpl.
org.apache.dubbo.rpc.RpcException: Failed to invoke remote proxy method sayHello to registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demoProvider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.0.113%3A12345%2Fcom.funnycode.DemoService%3Fanyhost%3Dtrue%26application%3DdemoProvider%26bind.ip%3D192.168.0.113%26bind.port%3D12345%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dtc%26interface%3Dcom.funnycode.DemoService%26methods%3DsayHello%26pid%3D23889%26register%3Dtrue%26release%3D2.7.3%26revision%3D1.0.0%26side%3Dprovider%26threads%3D200%26timeout%3D60000%26timestamp%3D1606916702204%26version%3D1.0.0&pid=23889&registry=zookeeper&release=2.7.3&timestamp=1606916702193, cause: Not found method "sayHello" in class com.funnycode.DemoServiceImpl.
        at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:107)
        at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56)
        at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
        at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:55)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:92)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:48)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:81)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:96)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:148)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
        at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:157)
        at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
        at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
        at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
        at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
        at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.dubbo.common.bytecode.NoSuchMethodException: Not found method "sayHello" in class com.funnycode.DemoServiceImpl.
        at org.apache.dubbo.common.bytecode.Wrapper1.invokeMethod(Wrapper1.java)
        at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
        at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84)
        ... 27 more
}, will be closed.           

雖然和 2.7.7 的代碼是不一樣的,但是通過錯誤也能看出來是在代理增強類裡面方法找不到,大機率是反射找不到方法,是以歸根結底也是參數的問題。

4)修複問題

修複相對簡單,就是拿到

struct

定義的

JavaClassName

case reflect.Struct:
  v, ok := v.(hessian.POJO)
  if ok {
    return v.JavaClassName()
  }
  return "java.lang.Object"           

5)驗證結果

再次執行消費者,運作(提供方 2.7.7 和 2.7.3)正常,輸出如下:

[2020-12-03/20:04:06 main.main: client.go: 29] response result: Hello tc
...
[2020-12-03/20:04:09 main.main: client.go: 41] response result: Hello tc You are 18
...
[2020-12-03/20:04:09 main.main: client.go: 48] response result: Hello tc You are 18           

細節叨叨

1. 如何配置 dubbogo 消費者

細心的你是否已經發現,在我 dubbogo 的消費端接口叫

DemoProvider

,然後發現提供者叫

DemoService

,這個又是如何正常運作的?

實際上和

client.yml

中配置項

references

有關,在配置檔案詳細說明了

interface

version

group

等,你還可以通過 methods 配置方法的逾時時間等資訊。

references:
  "DemoProvider":
    # 可以指定多個registry,使用逗号隔開;不指定預設向所有注冊中心注冊
    registry: "zk1"
    protocol: "dubbo"
    interface: "com.funnycode.DemoService"
    cluster: "failover"
    version: "1.0.0"
    group: "tc"
    methods:
      - name: "SayHello"
        retries: 3
    ......           

2. 全局的 group 和 version 怎麼配置

配置檔案如下:

# application config
application:
  organization: "dubbogoproxy.com"
  name: "Demo Micro Service"
  module: "dubbogoproxy tc client"
  version: "1.0.0"
  group: "tc"
  owner: "ZX"
  environment: "dev"

references:
  "DemoProvider":
    # 可以指定多個registry,使用逗号隔開;不指定預設向所有注冊中心注冊
    registry: "zk1"
    protocol: "dubbo"
    interface: "com.funnycode.DemoService"
    cluster: "failover"
#    version: "1.0.0"
#    group: "tc"
    methods:
      - name: "SayHello"
        retries: 3           

從使用的習慣來講,肯定是

application

表示了全局的配置,但是我發現啟動的時候在

application

配置的

version

group

并不會指派給接口,啟動會報服務提供方找不到,如下:

2020-12-03T20:15:42.208+0800    DEBUG   zookeeper/registry.go:237       Create a zookeeper node:/dubbo/com.funnycode.DemoService/consumers/consumer%3A%2F%2F30.11.176.107%2FDemoProvider%3Fapp.version%3D1.0.0%26application%3DDemo+Micro+Service%26async%3Dfalse%26bean.name%3DDemoProvider%26cluster%3Dfailover%26environment%3Ddev%26generic%3Dfalse%26group%3D%26interface%3Dcom.funnycode.DemoService%26ip%3D30.11.176.107%26loadbalance%3D%26methods.SayHello.loadbalance%3D%26methods.SayHello.retries%3D3%26methods.SayHello.sticky%3Dfalse%26module%3Ddubbogoproxy+tc+client%26name%3DDemo+Micro+Service%26organization%3Ddubbogoproxy.com%26owner%3DZX%26pid%3D38692%26protocol%3Ddubbo%26provided-by%3D%26reference.filter%3Dcshutdown%26registry.role%3D0%26release%3Ddubbo-golang-1.3.0%26retries%3D%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1606997742%26version%3D           

version

group

都是空。必須把

DemoProvider

下的

version

group

注釋打開。

3. 怎麼指定調用的方法名

1)go 調用 java

dubbogo 調用 dubbo,因為 go 是大寫的方法名,java 裡面是小寫的方法名,是以會出現如下錯誤:

2020-12-02T17:10:47.739+0800    INFO    getty/listener.go:87    session{session session-closed, Read Bytes: 924, Write Bytes: 199, Read Pkgs: 0, Write Pkgs: 1} got error{java exception:Fail to decode request due to: java.lang.IllegalArgumentException: Service not found:com.funnycode.DemoService, SayHello
java.lang.IllegalArgumentException: Service not found:com.funnycode.DemoService, SayHello
        at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:134)
        at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:80)
        at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57)
        at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44)
        at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
}, will be closed.           

細心的讀者可能已經注意到了,我在消費端的接口聲明是有個

dubbo:"sayHello"

的,表示方法名是 sayHello,這樣在服務提供方就可以得到 sayHello 這個方法名。

還有我聲明的三個方法都指明它們的方法名叫

dubbo:"sayHello"

,這是因為 Java 可以方法名字一樣進行重載,而 go 是不能方法名重複的。

2)go 調用 go

直接貼能跑通的代碼。

我的提供者接口:

type DemoProvider struct{}

func (p *DemoProvider) SayHello(ctx context.Context, name string) (string, error) {
    return "Hello " + name, nil
}

func (p *DemoProvider) SayHello4(ctx context.Context, user *User) (string, error) {
    return "Hello " + user.Name + " You are " + strconv.Itoa(user.Age), nil
}

func (p *DemoProvider) SayHello5(ctx context.Context, user *User, name string) (string, error) {
    return "Hello " + name + " You are " + strconv.Itoa(user.Age), nil
}

func (p *DemoProvider) Reference() string {
    return "DemoProvider"
}

func (p *DemoProvider) MethodMapper() map[string]string {
    return map[string]string{
        "SayHello": "sayHello",
    }
}           

我的消費者接口:

type DemoProvider struct {
  // 調用 java 和 go
    SayHello  func(ctx context.Context, name string) (string, error)             `dubbo:"sayHello"`
  // 隻調用 java
    SayHello2 func(ctx context.Context, user *User) (string, error)              `dubbo:"sayHello"`
    SayHello3 func(ctx context.Context, user *User, name string) (string, error) `dubbo:"sayHello"`
  // 隻調用 go
    SayHello4 func(ctx context.Context, user *User) (string, error)
    SayHello5 func(ctx context.Context, user *User, name string) (string, error)
}           

啟動服務消費者:

func main() {
    config.Load()
    gxlog.CInfo("\n\n\nstart to test dubbo")

    res, err := demoProvider.SayHello(context.TODO(), "tc")
    if err != nil {
        panic(err)
    }

    gxlog.CInfo("response result: %v\n", res)

    user := &User{
        Name: "tc",
        Age:  18,
    }

    res, err = demoProvider.SayHello4(context.TODO(), user)
    if err != nil {
        panic(err)
    }

    gxlog.CInfo("response result: %v\n", res)

    res, err = demoProvider.SayHello5(context.TODO(), user, "tc")
    if err != nil {
        panic(err)
    }

    gxlog.CInfo("response result: %v\n", res)

    initSignal()
}           

這裡需要注意

MethodMapper

方法,有時候需要在這個方法中配置方法名的映射關系,否則還是會出現找不到方法的錯誤。

比如因為配置

dubbo:"sayHello"

,是以在 go 裡面請求

SayHello

變成了

sayHello

,那麼服務提供方通過

MethodMapper

方法配置後使得提供方也是

sayHello

,這樣 go 和 java 下暴露的都是小寫的

sayHello

4. 為什麼會用 hessian2

老司機都懂,在 dubbo 中 SPI 機制的預設值就是 hessian2

@SPI("hessian2")
public interface Serialization {
}           

而在 dubbo-go 中:

func NewDubboCodec(reader *bufio.Reader) *ProtocolCodec {
    s, _ := GetSerializerById(constant.S_Hessian2)
    return &ProtocolCodec{
        reader:     reader,
        pkgType:    0,
        bodyLen:    0,
        headerRead: false,
        serializer: s.(Serializer),
    }
}           

5. hessian 序列化源碼

可以自行斷點檢視,兩邊基本上一樣,我也是通過兩邊比出來的,RpcInvocation.getParameterTypesDesc() 就是方法的參數.
  • go 代碼

    protocol/dubbo/impl/hessian.go:120#marshalRequest

  • java 代碼

    org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#encodeRequestData(org.apache.dubbo.remoting.Channel, org.apache.dubbo.common.serialize.ObjectOutput, java.lang.Object, java.lang.String)

6. dubbogo 服務提供者的方法對象需要是指針對象

之前的例子都是 copy 的,這次是純手打的,才發現了這個問題。

如果你的提供類似:

func (p *DemoProvider) SayHello4(ctx context.Context, user User) (string, error)

,那麼會出現如下錯誤:

2020-12-03T12:42:32.834+0800    ERROR   getty/listener.go:280   OnMessage panic: reflect: Call using *main.User as type main.User
github.com/apache/dubbo-go/remoting/getty.(*RpcServerHandler).OnMessage.func1           

參數裡面的

User

需要改成

*User

7. dubbogo 服務消費者的方法對象可以是非指針對象

SayHello4 func(ctx context.Context, user *User) (string, error)
// or
SayHello4 func(ctx context.Context, user User) (string, error)           

因為在參數序列化的時候會對指針做操作:

t := reflect.TypeOf(v)
if reflect.Ptr == t.Kind() {
  t = reflect.TypeOf(reflect.ValueOf(v).Elem())
}           
完整代碼

8. 配置檔案說明

dubbogo 主要有三個配置檔案:

  • server.yaml 服務提供方的配置檔案
  • client.yaml 服務消費方的配置檔案
  • log.yaml 日志檔案

如果你什麼都不配置,會出現:

2021/01/11 15:31:41 [InitLog] warn: log configure file name is nil
2021/01/11 15:31:41 [consumerInit] application configure(consumer) file name is nil
2021/01/11 15:31:41 [providerInit] application configure(provider) file name is nil           

這樣是沒法正常使用的。如果你是服務提供方,必須要配置 server.yaml 檔案,如果你是服務消費方,必須要配置 client.yaml,實際我們的應用應該既是消費者又是提供者,是以往往兩個檔案都是需要配置的。

服務提供方正常啟動是會有如下輸出的:

2021-01-11T15:36:55.003+0800    INFO    protocol/protocol.go:205        The cached exporter keys is dubbo://:20000/DemoProvider?accesslog=&app.version=1.0.0&application=Demo+Micro+Service&auth=&bean.name=DemoProvider&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=tc&interface=com.funnycode.DemoService&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=3&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&methods.SayHello4.loadbalance=random&methods.SayHello4.retries=3&methods.SayHello4.tps.limit.interval=&methods.SayHello4.tps.limit.rate=&methods.SayHello4.tps.limit.strategy=&methods.SayHello4.weight=0&methods.SayHello5.loadbalance=random&methods.SayHello5.retries=3&methods.SayHello5.tps.limit.interval=&methods.SayHello5.tps.limit.rate=&methods.SayHello5.tps.limit.strategy=&methods.SayHello5.weight=0&module=dubbogoproxy+tc+client&name=Demo+Micro+Service&organization=dubbogoproxy.com&owner=ZX&param.sign=&registry.role=3&release=dubbo-golang-1.3.0&retries=&serialization=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cgeneric_service%2Cexecute%2Cpshutdown&side=provider&ssl-enabled=false&timestamp=1610350614&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=1.0.0&warmup=100!
2021-01-11T15:36:55.003+0800    INFO    dubbo/dubbo_protocol.go:86      Export service: dubbo://:20000/DemoProvider?accesslog=&app.version=1.0.0&application=Demo+Micro+Service&auth=&bean.name=DemoProvider&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=tc&interface=com.funnycode.DemoService&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=3&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&methods.SayHello4.loadbalance=random&methods.SayHello4.retries=3&methods.SayHello4.tps.limit.interval=&methods.SayHello4.tps.limit.rate=&methods.SayHello4.tps.limit.strategy=&methods.SayHello4.weight=0&methods.SayHello5.loadbalance=random&methods.SayHello5.retries=3&methods.SayHello5.tps.limit.interval=&methods.SayHello5.tps.limit.rate=&methods.SayHello5.tps.limit.strategy=&methods.SayHello5.weight=0&module=dubbogoproxy+tc+client&name=Demo+Micro+Service&organization=dubbogoproxy.com&owner=ZX&param.sign=&registry.role=3&release=dubbo-golang-1.3.0&retries=&serialization=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cgeneric_service%2Cexecute%2Cpshutdown&side=provider&ssl-enabled=false&timestamp=1610350614&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=1.0.0&warmup=100           

9. 複現代碼

參考

篇幅有限,就介紹到這裡。歡迎有興趣的同學

參與 dubbogo3.0 的建設

,感謝閱讀。如果你有任何疑問,歡迎釘釘搜尋群号:31363295,加入交流群。

作者簡介

鐵城(GithubID cityiron),dubbo-go 社群 committer,主要參與 dubbo-go 1.5 版本疊代、 dubbo-go 3.0 服務路由和雲原生方面工作、以及 dubbo-go-proxy 項目負責人。擅長使用 Java/Go 語言,專注于雲原生和微服務等技術方向。