該方案通過擴展在dubbo下擴展出兼容dubbox的DubboxProtocol,從而在dubbo的框架下提供一種兼容dubbox的方案。
詳細代碼參考:https://github.com/aquariuspj/dubbo-extensions.git
- dubbo與dubbox不兼容的原因
- 解決方案
- 使用說明
- 附錄1 dubbo與dubbox協議相互調用的報錯信息
- 附錄2,DecodeableRpcInvocation.decode()方法源碼對比
- 附錄3,DubboxCodec.encodeRequestData()方法源碼對比
dubbo與dubbox不兼容的原因
測試版本:
框架 | 源碼地址 | branch/tag | version |
---|---|---|---|
dubbox | https://github.com/dangdangdotcom/dubbox | branch:master | 2.8.4 |
dubbo | https://github.com/apache/dubbo | tag:2.7.7 | 2.7.7 |
經過對兩個版本的DecodeableRpcInvocation decode方法進行對比,可以發現二者的主要差異點在於反序列化的元素上【參考《附錄2,DecodeableRpcInvocation.decode()方法源碼對比》】:
框架 | 反序列化順序 |
---|---|
dubbox | 1.dubboVersion; 2.path; 3.version; 4.methodName; 5.desc. |
dubbo | 1.dubboVersion; 2.path; 3.version; 4.methodName; 5.argNum; 6.argNum<-1時,有desc. |
我們已知兩個版本的反序列化順序,那么可以猜測兩個版本的序列化順序也一定和反序列化保持一致的,通過代碼跟蹤發現確實如此。【參考《附錄3,DubboxCodec.encodeRequestData()方法源碼對比》】。
解決方案
- 通過修改dubbox源碼,判斷dubboVersion執行不同的序列化/反序列化邏輯來兼容dubbo。
- 通過修改dubbo源碼,判斷dubboVersion執行不同的序列化/反序列化邏輯來兼容dubbox。
- 通過在dubbox和dubbo上同時擴展dubbox協議來相互兼容,同時防止二者的dubbo協議互串。
- 其他。
本項目采用第三種解決方案。
實現思路為:
- 通過Dubbo的SPI機制,先在Dubbo中擴展出兼容Dubbox的DubboxProtocol協議,
- 通過Dubbox的SPI機制,在dubbox中為dubbo協議新增dubbox的別名,從而與(1.)能夠相互識別DubboxProtocol。
- 各自在注冊中心上暴露基於dubbox協議的服務。
- 相互調用。
使用說明
詳細代碼可以參考dubbox-protocol-samples項目。
dubbo項目調用dubbox項目
-
在dubbox中擴展dubbox協議: 在dubbox項目中的resource/META-INF/dubbo目錄下新建com.alibaba.dubbo.rpc.Protocol文件,並填入內容
dubbox=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
; -
在dubbo中擴展dubbox協議: 已發布到Maven中央倉庫,可以直接引入。
<dependency> <groupId>cn.luckyee.dubbo</groupId> <artifactId>dubbox-protocol-all</artifactId> <version>1.1-RELEASE</version> </dependency>
-
將原dubbox項目中的服務注冊為dubbox協議 (注意,zkgroup一定要分開,否則dubbox項目的dubbo協議會被dubbo項目誤認為dubbo協議從而導致調用失敗)
<dubbo:registry id="zk01" address="zookeeper://127.0.0.1:2181" group="zkg01" /> <dubbo:registry id="zk02" address="zookeeper://127.0.0.1:2181" group="zkg02" /> <dubbo:protocol name="dubbo" port="20890" default="false" /> <dubbo:protocol name="dubbox" port="20891" default="false" /> <dubbo:service interface="cn.luckyee.dubbox.protocol.samples.api.DubboxProtocolDemoService" ref="dubboxProtocolDemoService" registry="zk01" protocol="dubbo" version="1.0.0" /> <dubbo:service interface="cn.luckyee.dubbox.protocol.samples.api.DubboxProtocolDemoService" ref="dubboxProtocolDemoService" registry="zk02" protocol="dubbox" version="1.0.0" />
-
將dubbo項目中的consumer配置到同一個group引入該dubbox服務
配置文件
dubbo.registries.zk01.address=zookeeper://127.0.0.1:2181 dubbo.registries.zk01.group=zkg01 dubbo.registries.zk02.address=zookeeper://127.0.0.1:2181 dubbo.registries.zk02.group=zkg02 dubbo.protocols.dubbox.name=dubbox dubbo.protocols.dubbo.name=dubbo
引入服務
@DubboReference(registry = {"zk02"}, version = "1.0.0", check = false) DubboxProtocolDemoService dubboxProtocolDemoService;
dubbox項目調用dubbo項目
-
在dubbox中擴展dubbox協議: 在dubbox項目中的resource/META-INF/dubbo目錄下新建com.alibaba.dubbo.rpc.Protocol文件,並填入內容
dubbox=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
; -
在dubbo中擴展dubbox協議: 已發布到Maven中央倉庫,可以直接引入。
<dependency> <groupId>cn.luckyee.dubbo</groupId> <artifactId>dubbox-protocol-all</artifactId> <version>1.1-RELEASE</version> </dependency>
-
將原dubbo項目中的服務注冊為dubbox協議
假設原始服務如下:
@Service @DubboService(registry = "zk01", protocol = "dubbo", version = "1.0.0") public class DubboProtocolDemoServiceImpl implements DubboProtocolDemoService { …… }
建議新增包裝接口與實現類:
@DubboService(registry = "zk02", protocol = "dubbox", version = "1.0.0") public class DubboProtocolDemoServiceImpl2 implements DubboProtocolDemoService2 { // 此處是暴露在zk01的dubbo協議的原服務 @Autowired DubboProtocolDemoServiceImpl dubboProtocolDemoService; @Override public String service(String req1, String req2) { return dubboProtocolDemoService.service(req1, req2); } }
配置文件
dubbo.protocols.dubbo.name=dubbo dubbo.protocols.dubbo.port=20892 dubbo.protocols.dubbox.name=dubbox dubbo.protocols.dubbox.port=20893 dubbo.registries.zk01.address=zookeeper://127.0.0.1:2181 dubbo.registries.zk01.group=zkg01 dubbo.registries.zk02.address=zookeeper://127.0.0.1:2181 dubbo.registries.zk02.group=zkg02
-
將dubbox項目中的consumer配置到同一個group並引入該dubbo服務
<dubbo:registry id="zk01" address="zookeeper://127.0.0.1:2181" group="zkg01" default="false"/> <dubbo:registry id="zk02" address="zookeeper://127.0.0.1:2181" group="zkg02" default="false"/> <dubbo:protocol name="dubbox" /> <dubbo:protocol name="dubbo" /> <dubbo:reference id="dubboxProtocolDemoService" interface="cn.luckyee.dubbox.protocol.samples.api.DubboxProtocolDemoService" protocol="dubbo" registry="zk01" check="false" version="1.0.0" /> <dubbo:reference id="dubboProtocolDemoService2" interface="cn.luckyee.dubbox.protocol.samples.api.DubboProtocolDemoService2" protocol="dubbox" registry="zk02" check="false" version="1.0.0" />
經過上述配置,dubbo項目能夠通過dubbox協議與dubbox項目互通。
附錄1 dubbo與dubbox協議相互調用的報錯信息
1. dubbo consumer 調用 dubbox provider.
dubbo consumer錯誤信息
Exception in thread "main" org.apache.dubbo.rpc.RpcException: Failed to invoke the method service in the service cn.luckyee.demo.dubbo.api.S000001. Tried 3 times of the providers [192.168.1.4:20890] (1/2) from the registry 127.0.0.1:2181 on the consumer 192.168.1.4 using the dubbo version 2.7.7. Last error is: Failed to invoke remote method: service, provider: dubbo://192.168.1.4:20890/cn.luckyee.demo.dubbo.api.S000001?anyhost=true&application=dubbo-client&check=false&default=false&dubbo=2.8.4&generic=false&init=false&interface=cn.luckyee.demo.dubbo.api.S000001&methods=service&pid=23144&qos.enable=false®ister.ip=192.168.1.4&remote.application=dubbox-server&revision=1.0.0&side=consumer&sticky=false×tamp=1593945022144&version=1.0.0, cause: org.apache.dubbo.remoting.RemotingException: Fail to decode request due to: RpcInvocation [……]
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:113)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:259)
at org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor.intercept(ClusterInterceptor.java:47)
at org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster$InterceptorInvokerNode.invoke(AbstractCluster.java:92)
at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:82)
at org.apache.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:74)
at org.apache.dubbo.common.bytecode.proxy0.service(proxy0.java)
……
dubbox provider錯誤信息
com.alibaba.com.caucho.hessian.io.HessianProtocolException: expected integer at 0x12 java.lang.String (Ljava/lang/Object;)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.error(Hessian2Input.java:2720)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.expect(Hessian2Input.java:2691)
at com.alibaba.com.caucho.hessian.io.Hessian2Input.readInt(Hessian2Input.java:773)
at com.alibaba.dubbo.common.serialize.support.hessian.Hessian2ObjectInput.readInt(Hessian2ObjectInput.java:58)
at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:106)
at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:74)
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:138)
at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:134)
at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:95)
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:46)
……
dubbox consumer 調用 duubo provider.
dubbo provider和dubbox consumer錯誤信息一樣,說明provider將錯誤信息返回給了consumer。
java.lang.IllegalArgumentException: Service not found: ……
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:134) ~[dubbo-2.7.7.jar:2.7.7]
at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:80) ~[dubbo-2.7.7.jar:2.7.7]
at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57) [dubbo-2.7.7.jar:2.7.7]
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44) [dubbo-2.7.7.jar:2.7.7]
……
附錄2,DecodeableRpcInvocation.decode()方法源碼對比
dubbo DecodeableRpcInvocation
@Override
public Object decode(Channel channel, InputStream input) throws IOException {
……
String dubboVersion = in.readUTF();
……
String path = in.readUTF();
……
setAttachment(VERSION_KEY, in.readUTF());
setMethodName(in.readUTF());
String desc = in.readUTF();
……
args[i] = in.readObject(pts[i]);
……
Map<String, Object> map = in.readAttachments();
……
return this;
}
dubbox DecodeableRpcInvocation
public Object decode(Channel channel, InputStream input) throws IOException {
……
setAttachment(Constants.DUBBO_VERSION_KEY, in.readUTF());
setAttachment(Constants.PATH_KEY, in.readUTF());
setAttachment(Constants.VERSION_KEY, in.readUTF());
setMethodName(in.readUTF());
……
int argNum = in.readInt();
if (argNum >= 0) {
……
args[i] = in.readObject();
……
} else {
String desc = in.readUTF();
……
args[i] = in.readObject(pts[i]);
……
}
Map<String, String> map = (Map<String, String>) in.readObject(Map.class);
……
return this;
}
附錄3,DubboxCodec.encodeRequestData()方法源碼對比
dubbo DubboxCodec
@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(version);
// https://github.com/apache/dubbo/issues/6138
String serviceName = inv.getAttachment(INTERFACE_KEY);
if (serviceName == null) {
serviceName = inv.getAttachment(PATH_KEY);
}
out.writeUTF(serviceName);
out.writeUTF(inv.getAttachment(VERSION_KEY));
out.writeUTF(inv.getMethodName());
out.writeUTF(inv.getParameterTypesDesc());
Object[] args = inv.getArguments();
if (args != null) {
for (int i = 0; i < args.length; i++) {
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
}
out.writeAttachments(inv.getObjectAttachments());
}
dubbox DubboxCodec
@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(inv.getAttachment(Constants.DUBBO_VERSION_KEY, DUBBO_VERSION));
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
out.writeUTF(inv.getMethodName());
// NOTICE modified by lishen
// TODO
if (getSerialization(channel) instanceof OptimizedSerialization && !containComplexArguments(inv)) {
out.writeInt(inv.getParameterTypes().length);
} else {
out.writeInt(-1);
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
}
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++){
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
out.writeObject(inv.getAttachments());
}
如有任何問題,歡迎交流指教。
首發於 https://www.cnblogs.com/prpl 和 https://xyz.luckyee.xyz:55543/ ,未經許可不允許轉載,謝謝合作。