question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How can you "subscribe" to a Redis stream (5.0)?

See original GitHub issue

Hi,

I’ve implemented some of the commands to add (XADD) to and read (XREAD) from a Redis stream. However, what I’m not entirely clear about is how you can use these commands to subscribe to a stream like you would in PubSub?

For example, to get every new message that is added to a stream, should I send the XREAD command on an interval? Like so:

setInterval(async () => {
      const command = new Redis.Command('XREAD', ['STREAMS', stream, lastId || '0']);
      const events = await this.redis.sendCommand(command);
      console.log('Redis events received: ', events);
      if (events) cb(events[0]);
    }, 1000);

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:16
  • Comments:15 (4 by maintainers)

github_iconTop GitHub Comments

26reactions
luincommented, Jun 11, 2019

In the latest version (4.10.0), all stream-related methods are added. Parameter and reply formats of these methods are kept the same as their corresponding Redis commands:

const Redis = require('ioredis')
const redis = new Redis()

redis.xadd('mystream', '*', 'field', 'value')

Reply transformers are not applied for backward compatibility though, it’s not hard to implement your own one. Here’s a modification leaving out parts that is not necessary with 4.10.0 based on the awesome implementation from @vflopes :

const Redis = require('ioredis')

function parseObjectResponse(reply, customParser = null) {
  if (!Array.isArray(reply)) {
    return reply
  }
  const data = {}
  for (let i = 0; i < reply.length; i += 2) {
    if (customParser) {
      data[reply[i]] = customParser(reply[i], reply[i + 1])
      continue
    }
    data[reply[i]] = reply[i + 1]
  }
  return data
}

function parseMessageResponse(reply) {
  if (!Array.isArray(reply)) {
    return []
  }
  return reply.map((message) => {
    return {
      id: message[0],
      data: parseObjectResponse(message[1])
    }
  })
}

function parseStreamResponse(reply) {
  if (!Array.isArray(reply)) {
    return reply
  }
  const object = {}
  for (const stream of reply) {
    object[stream[0]] = parseMessageResponse(stream[1])
  }
  return object
}

const transformers = {
  xread: parseStreamResponse,
  xpending(reply) {
    if (!reply || reply.length === 0) {
      return []
    }
    if (reply.length === 4 && !isNaN(reply[0]))
      return {
        count: parseInt(reply[0]),
        minId: reply[1],
        maxId: reply[2],
        consumers: (reply[3] || []).map((consumer) => {
          return {
            name: consumer[0],
            count: parseInt(consumer[1])
          }
        })
      }
    return reply.map((message) => {
      return {
        id: message[0],
        consumerName: message[1],
        elapsedMilliseconds: parseInt(message[2]),
        deliveryCount: parseInt(message[3])
      }
    })
  },
  xreadgroup: parseStreamResponse,
  xrange: parseMessageResponse,
  xrevrange: parseMessageResponse,
  xclaim: parseMessageResponse,
  xinfo(reply) {
    return parseObjectResponse(reply, (key, value) => {
      switch (key) {
        case 'first-entry':
        case 'last-entry':
          if (!Array.isArray(value)) {
            return value
          }
          return {
            id: value[0],
              data: parseObjectResponse(value[1])
          }
          default:
            return value
      }
    })
  }
}

Object.keys(transformers).forEach((commandName) => {
  Redis.Command.setReplyTransformer(commandName, transformers[commandName])
})

For the question that @Ventis raised, you can use the block mode of XREAD to avoid fetching data periodically (The following example doesn’t have the transformers above applied):


const Redis = require('ioredis')
const redis = new Redis()

async function subscribeStream(stream, listener) {
  let lastID = '$'

  while (true) {
    // Implement your own `try/catch` logic,
    // (For example, logging the errors and continue to the next loop)
    const reply = await redis.xread('BLOCK', '5000', 'COUNT', 100, 'STREAMS', stream, lastID)
    if (!reply) {
      continue
    }
    const results = reply[0][1]
    const {length} = results
    if (!results.length) {
      continue
    }
    listener(results)
    lastID = results[length - 1][0]
  }
}

subscribeStream('mystream', console.log)
21reactions
luincommented, May 25, 2019

@mcleishk Should be released before June 20.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Redis Streams tutorial
The Redis stream data type was introduced in Redis 5.0. ... in a stream, usually what we want instead is to subscribe to...
Read more >
Redis Streams - Amazon AWS
Redis incorporated the publish-subscribe pattern in version 2.0.0. The pattern allows clients to subscribe to one or more channels and receive messages as...
Read more >
Redis Streams – Redis 5.0's Newest Data Type - Alibaba Cloud
In this article, we will look at real-world applications of Redis Streams, a new data type introduced in Redis 5.0.
Read more >
Getting Started with Redis Streams and Java
Lines 3-5 connect to Redis · Lines 7-10 create the message body, using a map, since Redis Streams messages are string key/values in...
Read more >
Redis Streams Explained - YouTube
Redis Streams allow us to aggregate numerous sources of information into one easily consumable source of truth. Join Justin as we learn ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found