第1.3章 node调用dubbo

本文介绍如何利用node-zookeeper-dubbo库让Node.js应用能够调用Dubbo服务,并展示了具体的实现步骤与代码示例。

node-zookeeper-dubbo,执行下面的命令安装,我使用的是dubbox,需要修改一下源码:

npm install node-zookeeper-dubbo --save

1 修改后node-zookeeper-dubbo/libs/encode.js

/**
 * Created by panzhichao on 16/8/18.
 */
'use strict';
const Encoder     = require('hessian.js').EncoderV2;
const DEFAULT_LEN  = 8388608; // 8 * 1024 * 1024 default body max length

function Encode(opt) {
  this._opt = opt;
  const body = this._body(opt._method, opt._args);
  
  const head = this._head(body.length);
  
  return Buffer.concat([head, body]);
}

Encode.prototype._head = function (len) {
  const head = [0xda, 0xbb, 0xc2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  let i    = 15;
  if (len > DEFAULT_LEN) {
    throw new Error(`Data length too large: ${len}, max payload: ${DEFAULT_LEN}`);
  }
  while (len >= 256) {
    head.splice(i--, 1, len % 256);
    len >>= 8;
  }
  head.splice(i, 1, len);
  return new Buffer(head);
}

Encode.prototype._body = function (method, args) {
  const body = new Encoder();
  body.write(this._opt._dver || '2.5.3.6');
  body.write(this._opt._interface);
  body.write(this._opt._version);
  body.write(this._opt._method);
  // add by dzm at 2017-01-13
  if (args && args.length) {
    body.write(args.length);
  } else{
    body.write(0);
  }
  // 
  // body.write(this._argsType(args)); note by dengzm at 2017-01-16
  
  // end 
  if (args && args.length) {
    for (var i = 0, len = args.length; i < len; ++i) {
      body.write(args[i]);
    }
  }
  // edit by dzm at 2017-01-13 调整attachments的位置到
  body.write(this._attachments());
  
  return body.byteBuffer._bytes.slice(0, body.byteBuffer._offset);
}

Encode.prototype._argsType = function (args) {
  if (!(args && args.length)) {
    return '';
  }

  const typeRef = {
    boolean: 'Z', int: 'I', short: 'S',
    long   : 'J', double: 'D', float: 'F'
  };

  let parameterTypes = '';
  let type;
  for (var i = 0, l = args.length; i < l; i++) {
    type = args[i]['$class'];
    parameterTypes += type && ~type.indexOf('.')
        ? 'L' + type.replace(/\./gi, '/') + ';'
        : typeRef[type];

    // note by dzm at 2017-01-12
    // if (type.charAt(0) === '[') {
    //   parameterTypes += '[L' + type.slice(1).replace(/\./gi, '/') + ';';
    // } else {
    //   parameterTypes += type && ~type.indexOf('.')
    //     ? 'L' + type.replace(/\./gi, '/') + ';'
    //     : typeRef[type];
    // }
  }
  console.log('parameterTypes is ' + JSON.stringify(parameterTypes));

  return parameterTypes;
}

Encode.prototype._attachments = function () {
  const implicitArgs = {interface: this._opt._interface, path: this._opt._interface, timeout: this._opt._timeout}
  this._opt._version && (implicitArgs.version = this._opt._version)
  this._opt._group && (implicitArgs.group = this._opt._group);

  return {
    $class: 'java.util.HashMap',
    $     : implicitArgs
  }
}

exports.Encode = Encode;

2 更改node-zookeeper-dubbo/libs/register.js

/**
 * Created by panzhichao on 16/6/30.
 */
'use strict';

const url = require('url');
const os  = require('os');

const CREATE_MODES = {
  /**
   * The znode will not be automatically deleted upon client's disconnect.
   */
  PERSISTENT: 0,

  /**
   * The znode will not be automatically deleted upon client's disconnect,
   * and its name will be appended with a monotonically increasing number.
   */
  PERSISTENT_SEQUENTIAL: 2,

  /**
   * The znode will be deleted upon the client's disconnect.
   */
  EPHEMERAL: 1,

  /**
   * The znode will be deleted upon the client's disconnect, and its name
   * will be appended with a monotonically increasing number.
   */
  EPHEMERAL_SEQUENTIAL: 3
};

function isLoopback(addr) {
  return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
      .test(addr) ||
    /^fe80::1$/.test(addr) ||
    /^::1$/.test(addr) ||
    /^::$/.test(addr);
}

function ip() {
  const interfaces = os.networkInterfaces();
  return Object.keys(interfaces).map(function (nic) {
    const addresses = interfaces[nic].filter(function (details) {
      return details.family.toLowerCase() === 'ipv4' && !isLoopback(details.address);
    });
    return addresses.length ? addresses[0].address : undefined;
  }).filter(Boolean)[0];
}

function consumer() {
  const self  = this;
  const paths = [];
  const host  = ip();

  const dependencies = self.dependencies;
  let serv; //临时存储服务

  const info = {
    protocol: 'consumer',
    slashes : 'true',
    host    : '',
    query   : {
      application: self.application.name,
      category   : 'consumers',
      check      : 'false',
      dubbo      : self.dubboVer,
      interface  : '',
      revision   : '',
      version    : '',
      side       : 'consumer',
      timestamp  : (new Date()).getTime()
    }
  };

  for (const s in dependencies) {
    if (dependencies.hasOwnProperty(s)) {
      serv = dependencies[s];
    }
    info.host = `${host}/${serv.interface}`;

    info.query.interface = serv.interface;
    info.query.revision  = serv.version;
    info.query.version   = serv.version;
    // edit by dzm at 2017-01-17 解决dubbox注册的问题
    if (serv.group){
      info.query.group     = serv.group;
    }
    // end edit
    paths.push(`/${self._root}/${serv.interface}/consumers/${encodeURIComponent(url.format(info))}`);
  }

  for (let i = 0, l = paths.length; i < l; i++) {
    (function (path) {
      self.client.exists(path, function (err, stat) {
        if (err) {
          console.log(err.stack);
          return;
        }

        if (stat) {
          console.log('Node exists.');
          return;
        }
        self.client.create(path, CREATE_MODES.EPHEMERAL, function (err, node) {
          if (err) {
            console.error('Reg consumer failed:' + err.stack);
          }
        });
      });
    })(paths[i]);
  }
}

exports.consumer = consumer;

3 node调用代码示例
我采用的是express框架,所以下面的代码只是我路由代码,其他的不用改。

var express = require('express');
var router = express.Router();
const nzd=require('node-zookeeper-dubbo');


const opt={
  application:{name:'wcnapi'},
  // register:'192.168.5.128:2181,192.168.5.128:2182,192.168.5.128:2183',
  register:'192.168.5.172:2181,192.168.5.173:2181',
  dubboVer:'2.8.4',
  root:'dubbo',
  dependencies:{
    WcnService:{interface:'com.test.wcn.api.service.WcnService',version:'1.0.0',timeout:6000}
  }  
}

const Dubbo = new nzd(opt);


// const Dubbo=new nzd(opt);
/* 微信系统接口 */
router.get('/getLoginQrcode', function(req, res, next) {
  var arg1={$class:'java.lang.String',$:'1300000067'};
  Dubbo.WcnService.getLoginQrcode(arg1).then(function(data){
    res.send(data);
  }).catch(function(err){
    res.send(err);
  })
});

router.get('/test/1', function(req, res, next) {
  var arg1={$class:'java.lang.String',$:'1300000067'};
  var arg2={$class:'java.lang.Integer',$:1};
  var arg3={$class:'java.lang.Integer',$:10};
  Dubbo.WcnService.testApi(arg1,arg2,arg3).then(function(data){
    res.send(data);
  }).catch(function(err){
    res.send(err);
  })
});

router.get('/test03', function(req, res, next) {
  var arg = {
  	$class:'com.test.wcn.dto.TestDto',
  	$:{
  		id:{$class:'java.lang.String',$:'0101'},
  		name:{$class:'java.lang.String',$:'0101'},
  		age:{$class:'java.lang.Integer',$:10},
  		sex:{$class:'java.lang.Boolean',$:true}
  	}
  }
  Dubbo.WcnService.test03(arg).then(function(data){
    res.send(data);
  }).catch(function(err){
    res.send(err);
  })
});

module.exports = router;

4 应用到实际生产环境的用法
上面只是一个基本的用法,但是如果实际投产还是要设计一下;
第一步 express路由分离
app.js中的代码

var routes = require('./routes/routes');
var app = express();
routes(app);

routes.js的代码

// dubbo接口配置
const nzd=require('node-zookeeper-dubbo');
var config = require('../libs/config');
const opt={
  application:config.dubbo.application,
  register:config.dubbo.register,
  dubboVer:config.dubbo.dubboVer,
  root:config.dubbo.root,
  dependencies:{
    CmsService:{interface:'com.test.cms.api.service.CmsService',version:'1.0.0',timeout:6000},
    WcnService:{interface:'com.test.wcn.api.service.WcnService',version:'1.0.0',timeout:6000}
  }  
}
const Dubbo = new nzd(opt);
// 
var infoRoutes = require('./page/infoRoutes');
var indexRoutes = require('./page/indexRoutes');
var dubbo = require('./dubbo');
module.exports = function(app){
	dubbo(app,Dubbo);
	// 首页
	indexRoutes(app);
}

第二步 引入dubbo的接口

// 内容管理接口系统
var cms = require('./dubbo/cms');
// 微信接口系统
var wcn = require('./dubbo/wcn');
// // 
module.exports = function(app,Dubbo){
	cms(app,Dubbo);
	wcn(app,Dubbo);
	
}

第三步 dubbo接口的通用设计
下面定义的cms.js,同时支持http服务以及dubbo服务。既可以对外调用,又可以对内。

let dubbox;
function _queryArticleList(columnId,page,rows, callback){
  dubbox.CmsService.queryArticleList(columnId,page,rows).then(function(data){
      let resp = {success:true,msg:''};
      resp.data = data;
      callback(resp);
    }).catch(function(err){
      let resp = {success:false,msg:err};
      callback(resp);
    })
}

function _queryContent(articleId,callback){
  dubbox.CmsService.queryContent(articleId).then(function(data){
    let resp = {success:true,msg:''};
    resp.data = data;
    callback(resp);
  }).catch(function(err){
    let resp = {success:false,msg:err};
    callback(resp);
  })
}
/*输出区域*/
module.exports = function(app,Dubbo){
  dubbox = Dubbo;
  // 获取文章列表
  app.get('/cms/queryArticleList', function(req, res, next) {
    let columnId={$class:'java.lang.String',$:req.query.columnId};
    let page={$class:'java.lang.Integer',$:parseInt(req.query.page)};
    let rows={$class:'java.lang.Integer',$:parseInt(req.query.rows)};
    _queryArticleList(columnId,page,rows,function(resp){
      res.send(resp);
    })
  });

  app.get('/cms/queryContent', function(req, res, next) {
    let articleId={$class:'java.lang.String',$:req.query.articleId};
    _queryContent(articleId,function(resp){
      res.send(resp);
    })
  });
  
}
module.exports.queryArticleList = _queryArticleList;
module.exports.queryContent = _queryContent;
<think>嗯,用户问的是Dubbo是否支持跨语言调用,特别是Java和其他语言之间的通信。这个问题很实际,因为现在很多系统都是混合语言环境。 从提供的引用材料来看,引用[4]提到Dubbo支持多协议,引用[5]展示了protobuf的定义,这是个重要线索。protobuf本身就是跨语言的数据格式,这意味着Dubbo很可能通过某种方式实现了跨语言支持。 用户可能正在做系统集成,需要连接Java和非Java系统。ta的提问很具体,说明已经遇到实际需求了。考虑到Dubbo传统上是Java框架,用户可能担心其他语言的支持程度。 我记得Dubbo的跨语言方案主要靠gRPC协议。因为gRPC本身就是跨语言的,Dubbo3.0后全面兼容gRPC协议。这点在引用[5]的proto文件示例里也能印证,那个服务定义完全是gRPC风格的。 另外引用[2]提到Dubbo协议本身不适合大数据量传输,而跨语言场景往往需要更通用的数据格式,这进一步说明gRPC是更合适的选择。用户如果要用其他语言调用Dubbo服务,应该重点看Triple协议。 不过用户得注意版本兼容性,Dubbo2.x的跨语言支持比较有限,主要靠hessian2序列化,但非Java语言实现起来比较麻烦。现在用Dubbo3的Triple协议就省事多了。 用户可能还需要知道具体怎么做。引用[1]提到要定义远程接口,跨语言时这个接口要用IDL定义,就像引用[5]那样用proto文件。服务端和客户端都要依赖这个生成的代码。 最后想到,用户要是用Go或Python这类主流语言,Dubbo的跨语言支持已经很成熟了。但要是冷门语言,可能需要检查是否有对应的gRPC生态支持。</think>Dubbo **支持跨语言远程调用**,但需要根据协议和版本选择正确的实现方式。以下是具体分析: --- ### 一、跨语言支持的核心机制 1. **协议兼容性** Dubbo 3.0+ 默认支持 **Triple 协议(基于 HTTP/2 + gRPC)**,该协议使用 **Protobuf** 作为接口定义语言(IDL)和序列化工具[^4][^5]。 - ✅ **优势**:Protobuf 和 gRPC 生态天然支持多语言(Java/Go/Python/Node.js 等)。 - ❌ 传统 Dubbo 协议(Dubbo2)默认使用 Hessian2 序列化,仅部分语言有兼容实现(如 Java/Python),跨语言支持较弱[^2]。 2. **IDL 定义服务** 通过 `.proto` 文件定义服务(如引用[5]示例),Protobuf 工具生成多语言客户端/服务端存根(Stub): ```protobuf syntax = "proto3"; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } ``` --- ### 二、跨语言调用实现步骤 1. **服务提供方(Java)** - 使用 `dubbo-rpc-triple` 依赖暴露 Triple 协议服务[^4]。 - 实现 Protobuf 定义的接口,注册到注册中心(如 Zookeeper)。 2. **服务消费方(非 Java,如 Python)** - 用 Protobuf 工具生成对应语言客户端代码。 - 通过 Dubbo 注册中心发现服务地址,使用 gRPC 客户端调用: ```python # Python 示例(需安装 grpcio-tools) import grpc from hello_pb2 import HelloRequest from hello_pb2_grpc import GreeterStub channel = grpc.insecure_channel("dubbo-provider-ip:50051") stub = GreeterStub(channel) response = stub.SayHello(HelloRequest(name="World")) print(response.message) # 输出: Hello, World! ``` --- ### 三、适用场景与限制 | **场景** | **推荐协议** | **语言支持** | |--------------------------|--------------|----------------------------------| | Java 与非 Java 通信 | Triple | ✅ Go/Python/Node.js/C++ 等 | | 高性能小数据包(纯 Java)| Dubbo2 | ❌ 仅部分语言有兼容实现 | | 文件传输等大数据场景 | 不推荐 | Triple 支持流式传输但需优化[^2] | > 💡 **关键结论**: > - Dubbo **3.0 及以上版本**通过 **Triple 协议**实现**完整的跨语言调用**。 > - Dubbo2 需依赖第三方序列化扩展(如 JSON/Hessian),但稳定性和性能不如 Triple[^4]。 --- ### 四、操作建议 1. **升级到 Dubbo 3.x**,使用 Triple 协议定义服务[^4]。 2. **统一用 Protobuf 定义接口**,确保多语言兼容[^5]。 3. 非 Java 客户端**直接调用 gRPC 服务**,无需依赖 Dubbo SDK(注册中心地址需通过配置或 sidecar 获取)。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

warrah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值