node-amqp + socketio:如何同步队列subscribe / unsubscribe?

时间:2014-01-27 18:44:44

标签: angularjs rabbitmq amqp socket.io node-amqp

我有一个angularjs应用程序,在服务器端有node和express。 我也有node-amqp和socket.io

我想实现以下行为

该应用程序有一个页面(路径,角度视图),显示一个包含实时数据的表格 使用socket.io和amqp实时更新数据,以便从位于应用程序之外的rabbitMQ服务器中流式传输数据。

当用户在浏览器上访问此页面/路线时

  1. 客户端发出套接字事件“subscribe”
  2. 服务器,在套接字事件“subscribe”上,
    • 宣布一个兔子队列
    • 将兔子队列绑定到交换所
    • 订阅来自兔子队列的消息/数据
    • 发出一个套接字事件“data”,将数据发送回用户/客户端
  3. 当用户离开页面时,或换句话说改变路线

    1. 客户端发出套接字事件“unsubscribe”
    2. 服务器,在套接字事件“unsubscribe”上,
      • 取消订阅队列
    3. 我的问题是:如何确保queue.subscribe和queue.unsubscribe同步? 如果用户执行快速路线更改序列:访问/离开/访问/离开/访问/离开 订阅和取消订阅的顺序有时会被恢复,并且服务器在新订阅完成之前第二次取消订阅先前的订阅。有什么建议吗? 这是我尝试过的,但不起作用:

      客户端:controller.js

      .controller('WatchdogCtrl', function($scope, watchSocket) {
      
          var data = {}
          $scope.data = []
      
          var socket = watchSocket
      
          socket.emit('subscribe', { exchange: 'bus', key: 'mis.service-state' })
          socket.on('data', function(message) {
              // refreshing  data 
              data[message.payload.id] = message.payload;
              var new-values = [];
              angular.forEach(data, function(value, index) {
                  this.push(value);
              }, new-values);
      
              $scope.data = new-values
              $scope.$apply()
          });
      
          $scope.$on('$destroy', function (event) {
              // unsubscribe from rabbit queue when leaving 
              socket.emit('unsubscribe')
          });
      })
      

      服务器端:server.js

      // set up amqp listener
      var amqp = require('amqp');
      // create rabbitmq connection with amqp
      var rabbitMQ = amqp.createConnection({url: "amqp://my:url"});
      rabbitMQ.on('ready', function() {
          console.log('Connection to rabbitMQ is ready')
      });
      
      // Hook Socket.io into Express
      var io = require('socket.io').listen(server);
      io.set('log level', 2);
      io.of('/watch').on('connection', function(socket) {
          var watchq;
          var defr;
          socket.on('subscribe', function(spec) {
              watchq = rabbitMQ.queue('watch-queue', function(queue) {
                  console.log('declare rabbit queue: "' + queue.name +'"');
                  console.log('bind queue '+ queue.name + ' to exch=' + spec.exchange + ', key=' + spec.key);
      
                  queue.bind(spec.exchange, spec.key)
                  defr = queue.subscribe(function(message, headers, deliveryInfo) {
                           socket.emit('data', {
                              key: deliveryInfo.routingKey,
                              payload: JSON.parse(message.data.toString('utf8'))
                           })
                         }).addCallback(function(ok) { 
                             var ctag = ok.consumerTag; 
                             console.log('subscribed to queue: ' + queue.name + ' ctag = ' + ctag)
                         });
      
              })
          })
      
          socket.on('unsubscribe', function() {
              //needs fix: this does not ensure subscribe/unsubscribe synchronization…..
              defr.addCallback(function(ok) {
                  console.log('unsubscribe form queue:', watchq.name, ', ctag =', ok.consumerTag)
                  watchq.unsubscribe(ok.consumerTag);
              })
          })
      
      });
      

      服务器console.log消息:(访问#3并离开#3不同步)

      declare rabbit queue: "watch-queue"
      bind queue watch-queue to exch=bus, key=mis.service-state
      subscribed to queue: watch-queue ctag = node-amqp-8359-0.6418165327049792 //<-- visit#1
      unsubscribe form queue: watch-queue , ctag = node-amqp-8359-0.6418165327049792 //<--leave#1
      declare rabbit queue: "watch-queue"
      bind queue watch-queue to exch=bus, key=mis.service-state
      subscribed to queue: watch-queue ctag = node-amqp-8359-0.455362161854282 //<-- visit#2
      unsubscribe form queue: watch-queue , ctag = node-amqp-8359-0.455362161854282 //<-- leave#2
      unsubscribe form queue: watch-queue , ctag = node-amqp-8359-0.455362161854282 //<-- leave#3
      declare rabbit queue: "watch-queue"
      bind queue watch-queue to exch=bus, key=mis.service-state
      subscribed to queue: watch-queue ctag = node-amqp-8359-0.4509762797970325 //<-- visit#3
      

1 个答案:

答案 0 :(得分:3)

我们的设置与您的设置非常相似。我们创建一个匿名的独占队列,如果未使用,则会有一个到期时间。 匿名队列获取代理为其生成的唯一名称。客户端断开连接后立即删除独占队列(一旦信道被拆除)。队列的过期时间是RabbitMQ扩展,但我们使用的amqplib支持。我确信node-amqp也对这种扩展有一些支持。

还为每个套接字创建一个通道(但重用相同的连接)。这给出了套接字和匿名队列之间的一对一映射。对该队列的任何绑定都等同于单个套接字的绑定。因此,我们固有地知道什么套接字应该获得什么消息,没有任何特殊的队列命名约定或检查路由密钥等。

关闭套接字时关闭RabbitMQ通道(再次,不是连接)。虽然我们可能会在以后添加此类事件,但不需要特殊的取消订阅事件。

这也意味着如果同一浏览器在没有任何竞争条件的情况下打开多个标签,则它们可以有多个队列。