Skip to content

ilanl/redis-node-notifications

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redis/ Node - Notification Service

Your task is to write a simple application server that prints a message at a given time in the future. The server has only 1 API: echoAtTime - which receives two parameters, time and message, and writes that message to the server console at the given time.

Since we want the server to be able to withstand restarts it will use Redis to persist the messages and the time they should be sent at. You should also assume that there might be more than one server running behind a load balancer (load balancing implementation itself does not need to be provided as part of the answer)

In case the server was down when a message should have been printed, it should print it out when going back online.

The focus of the exercise is the efficient use of Redis and its data types as well as seeing your code in action. You can implement the exam in any language of your choice (preferably in Typescript/NodeJS).

Architecture

There's a driver for Redis NodeJS

Redis Data Types

https://redis.io/topics/data-types

Modules

Route: EchoAtTime

/echoAtTime?time=1575199971&message=Hello You

This GET route receieves two arguments:

This module uses the PubSub

  try {
    const { time, message } = req.query
    result = await pubsub.addTodo(time, message)
  } 
  catch (error) {
  ...
  }

Will yield JSON with status 400/500 in case of error, otherwise will reply:

  {
    result: "added todo at: 1:41:46 PM [1575200506000]"
  }

PubSub

A module that holds 2 connections to Redis:

  1. The redis repository
  2. Subscribing to messages like expiration events

Add Todo

  • Generate a score from given time. For example: time in milliseconds

  • Add the score in todos SortedSet by score

SortedSet

  • Save another entry as List to hold all the todos for this score
  await this.redis
      .multi()
      .rpush(`todos:${key}`, `${todo}`)
      .zadd('todos', `${key}`, `${key}`)
      .bgsave()
      .exec()

List

  • Get the minimum score by using range
  const rangeOfFirstTodo = await this.redis.zrange('todos', 0, 0)
  ...
  return rangeOfFirstTodo[0]
  • Set/Update todo:next separatly as String with Expiration=score (in milliseconds)
  await this.redis
      .multi()
      .set('todo:next', `${minKey}`)
      .pexpireat('todo:next', Number(minKey))
      .bgsave()
      .exec()

String

On Expiration

  • We use the expiration event to pop the first todo score from the sorted set
  const [key] = await this.redis.zpopmin('todos')
  • Then we update the todo:next to point to the next score and set its expiration accordingly
  await this.redis
      .multi()
      .set('todo:next', `${minKey}`)
      .pexpireat('todo:next', Number(minKey))
      .bgsave()
      .exec()
  • Then we enumerate the todos from the todos:score List, print each. Then we delete the list
  const todos = await this.redis.lrange(`todos:${key}`, 0, -1)
  const dueDate = new Date(parseInt(key)).toLocaleTimeString()
  const colorize = overDue ? clc.yellowBright : clc.greenBright
  todos.forEach((element, i) => {
    console.log(colorize(`[${i + 1}]`, dueDate, element))
  })
  this.redis.del(`todos:${key}`)

On Ready

We reach this event after connection is made to redis. Since overdue score no longer has a todo:next, we must query the todos SortedSet for all the past scores until score=currentTime (in milliseconds) We also reach this event after reconnection.

  • Then we show the list of each score that is in the past, then we remove those lists

  • After that, we remove those past scores from the todos SortedSet

  • Finally we update the update or set the next reminder with Expiration time

  const currentTime = new Date().getTime()
  const pastTodos = await this.redis.zrangebyscore('todos', '0', `${currentTime}`)
  if (pastTodos.length > 0) {
    for (const key of pastTodos) {
      await this.showTodo(key, true)
    }
    // remove from set
    await this.redis.zremrangebyscore('todos', 0, currentTime)
    await this.setNextReminder()
  }

Setup

Install Redis Server

  • Install Redis: brew install redis

Start server with:

  • redis-server

Stop server with:

  • redis-cli shutdown

https://medium.com/@petehouston/install-and-config-redis-on-mac-os-x-via-homebrew-eb8df9a4f298

Install Node

  • After installing node, run the test script:
 chmod +x test.sh 
 ./test.sh

This test does the following:

  • Kill all node process
  • Stop & Start Redis as a service
  • Send t2=currentTime+15s Todo3
  • Send t1=currentTime+5s Todo1
  • Send t1=currentTime+5s Todo2
  • After 5s we expect to see Todo1 and Todo2 (in this order)

test1

  • After 7s Redis is going down
  • After 20s (At this time, Todo3 is overdue), Redis goes back online
  • We expect to print Todo3, when we're back online

test2

About

Redis Scheduler

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors