📬 Asynq: simple, reliable & efficient distributed task queue for your next Go project
Tags: go, tutorial, beginners, showdev
Published: 2021-04-06 23:53:12

## Introduction

Hi, DEV friends! 😉 It's time to share a great find that you must try in your next project. I'm talking about simple, reliable and efficient distributed task queue written on Go and called [Asynq](https://github.com/hibiken/asynq).

I already have experience using Asynq in production on one of my work projects (_microservice for sending scheduled messages to subscribers of Telegram bot_). After using it successfully, I wanted to tell you more about it so you can appreciate it too!

All right, let's get started! 👇

<a name="toc"></a>
### 📝 Table of contents

- [What is Asynq?](#what-is-asynq)
- [The project we will create](#the-project-we-will-create)
- [Full code of this project](#full-code-of-this-project)
- [Let's write some code](#lets-write-some-code)
  - [Creating tasks payloads](#creating-tasks-payloads)
  - [Creating tasks handlers](#creating-tasks-handlers)
  - [Creating Asynq worker server](#creating-asynq-worker-server)
  - [Creating Asynq client](#creating-asynq-client)
- [Asynq web UI](#asynq-web-ui)
  - [List of servers and workers](#list-of-servers-and-workers)
  - [Redis server information](#redis-server-information)
  - [Adaptive dark theme](#adaptive-dark-theme)
- [Asynq CLI](#asynq-cli)

<a name="#what-is-asynq"></a>
## What is Asynq?

Follow official [Asynq](https://github.com/hibiken/asynq) GitHub page:

> _Asynq is a Go library for queueing tasks and processing them asynchronously with workers. It's backed by Redis and is designed to be scalable yet easy to get started. [...] Task queues are used as a mechanism to distribute work across multiple machines. A system can consist of multiple worker servers and brokers, giving way to high availability and horizontal scaling._

Asynq is developed and maintained by [Ken Hibino](https://github.com/hibiken), who works as a software engineer at Google. So you can be sure of the quality of the code.

Most awesome queueing tasks features are:

- Guaranteed at least one execution of a task
- Scheduling of tasks
- Durability since tasks are written to Redis
- Retries of failed tasks
- Automatic recovery of tasks in the event of a worker crash
- Weighted priority queues
- Strict priority queues
- Low latency to add a task since writes are fast in Redis
- De-duplication of tasks using unique option
- Allow timeout and deadline per task
- Flexible handler interface with support for middlewares
- Ability to pause queue to stop processing tasks from the queue
- Periodic Tasks

Built-in scaling tools:

- Support Redis Cluster for automatic sharding and high availability
- Support Redis Sentinels for high availability

And, of course, useful tools for admins:

- Web UI to inspect and remote-control queues and tasks
- CLI to inspect and remote-control queues and tasks

> 🎯 By the way, I created [an official logo](https://github.com/koddr/asynq-logo) for Asynq. 

[↑ Table of contents](#toc)

<a name="#the-project-we-will-create"></a>
## The project we will create

I would like to show with a simple example how you can easily work with Asynq in your Golang project. Suppose we have the task of sending a welcome email as soon as the user registers and, after a while, sending another reminder email to the user about something.

Here are the points we will stick to:

1. Three queues for tasks with different priorities;
2. Generating multiple tasks of different types at once and for different queues on the client;
3. Separation into task handlers and payloads;

[↑ Table of contents](#toc)

<a name="#full-code-of-this-project"></a>
## Full code of this project

For clarity, you can download the full example and run it on your machine. Especially for you, I put it on GitHub:

{% github koddr/tutorial-go-asynq no-readme %}

[↑ Table of contents](#toc)

<a name="#lets-write-some-code"></a>
## Let's write some code

Okay! We'll move on to the most interesting part, the writing of the code. I have supplied the code examples with detailed comments, so I won't dwell on them too much in the text of the article. 

> 🔥 Please look at the code!

[↑ Table of contents](#toc)

<a name="#creating-tasks-payloads"></a>
### Creating tasks payloads

Let's define the payloads of our tasks. Let's create two types to send a message to Email: a welcome message (_comes right away_) and a reminder message (_comes after a while_).

```go
// ./tasks/payloads.go

package tasks

import (
	"time"

	"github.com/hibiken/asynq"
)

const (
	// TypeWelcomeEmail is a name of the task type
	// for sending a welcome email.
	TypeWelcomeEmail = "email:welcome"

	// TypeReminderEmail is a name of the task type
	// for sending a reminder email.
	TypeReminderEmail = "email:reminder"
)

// NewWelcomeEmailTask task payload for a new welcome email.
func NewWelcomeEmailTask(id int) *asynq.Task {
	// Specify task payload.
	payload := map[string]interface{}{
		"user_id": id, // set user ID
	}

	// Return a new task with given type and payload.
	return asynq.NewTask(TypeWelcomeEmail, payload)
}

// NewReminderEmailTask task payload for a reminder email.
func NewReminderEmailTask(id int, ts time.Time) *asynq.Task {
	// Specify task payload.
	payload := map[string]interface{}{
		"user_id": id,          // set user ID
		"sent_in": ts.String(), // set time to sending
	}

	// Return a new task with given type and payload.
	return asynq.NewTask(TypeReminderEmail, payload)
}
```

[↑ Table of contents](#toc)

<a name="#creating-tasks-handlers"></a>
### Creating tasks handlers

Task handlers are our business logic, which is responsible for the specific behavior of tasks when triggered. To keep it simple, I will display a normal message in the console of the Asynq worker server.

```go
// ./tasks/handlers.go

package tasks

import (
	"context"
	"fmt"

	"github.com/hibiken/asynq"
)

// HandleWelcomeEmailTask handler for welcome email task.
func HandleWelcomeEmailTask(c context.Context, t *asynq.Task) error {
	// Get user ID from given task.
	id, err := t.Payload.GetInt("user_id")
	if err != nil {
		return err
	}

	// Dummy message to the worker's output.
	fmt.Printf("Send Welcome Email to User ID %d\n", id)

	return nil
}

// HandleReminderEmailTask for reminder email task.
func HandleReminderEmailTask(c context.Context, t *asynq.Task) error {
	// Get int with the user ID from the given task.
	id, err := t.Payload.GetInt("user_id")
	if err != nil {
		return err
	}

	// Get string with the sent time from the given task.
	time, err := t.Payload.GetString("sent_in")
	if err != nil {
		return err
	}

	// Dummy message to the worker's output.
	fmt.Printf("Send Reminder Email to User ID %d\n", id)
	fmt.Printf("Reason: time is up (%v)\n", time)

	return nil
}
```

[↑ Table of contents](#toc)

<a name="#creating-a-worker-server"></a>
### Creating Asynq worker server

The central part of our project. It is this component that will be responsible for the logic of sending messages and queuing them (if we need it).

```go
// ./worker/server.go

package main

import (
	"log"

	"tutorial-go-asynq/tasks"

	"github.com/hibiken/asynq"
)

func main() {
	// Create and configuring Redis connection.
	redisConnection := asynq.RedisClientOpt{
		Addr: "localhost:6379", // Redis server address
	}

	// Create and configuring Asynq worker server.
	worker := asynq.NewServer(redisConnection, asynq.Config{
		// Specify how many concurrent workers to use.
		Concurrency: 10,
		// Specify multiple queues with different priority.
		Queues: map[string]int{
			"critical": 6, // processed 60% of the time
			"default":  3, // processed 30% of the time
			"low":      1, // processed 10% of the time
		},
	})

	// Create a new task's mux instance.
	mux := asynq.NewServeMux()

	// Define a task handler for the welcome email task.
	mux.HandleFunc(
		tasks.TypeWelcomeEmail,       // task type
		tasks.HandleWelcomeEmailTask, // handler function
	)

	// Define a task handler for the reminder email task.
	mux.HandleFunc(
		tasks.TypeReminderEmail,       // task type
		tasks.HandleReminderEmailTask, // handler function
	)

	// Run worker server.
	if err := worker.Run(mux); err != nil {
		log.Fatal(err)
	}
}
```

And my favorite part of Asynq. If your application is going to grow, you will definitely want to make a more scalable system and Asynq can help you with that perfectly, because:

- You can create a personal Asynq worker server for each queue;
- Each Asynq worker server can be configured with any number of concurrent active workers to use;
- Next, you can use a simple Docker Compose [solution](https://docs.docker.com/compose/compose-file/compose-file-v3/#deploy) to automatically start the right amount of each Asynq worker server replicas, when needed;

And if that's not enough anyway, you can easily start a [Redis Cluster](https://redis.io/topics/cluster-tutorial) using Asynq built-in adapter... literally in a couple of minutes!

> ☝️ I won't describe the configuration process, since the Wiki page of the project has [detailed instructions](https://github.com/hibiken/asynq/wiki/Redis-Cluster).

[↑ Table of contents](#toc)

<a name="#creating-asynq-client"></a>
### Creating Asynq client

The client part can be anything, really. The main thing is that it can create new tasks and send them to the queue:

```go
// ./client/main.go

package main

import (
	"log"
	"math/rand"
	"time"

	"tutorial-go-asynq/tasks"

	"github.com/hibiken/asynq"
)

func main() {
	// Create a new Redis connection for the client.
	redisConnection := asynq.RedisClientOpt{
		Addr: "localhost:6379", // Redis server address
	}

	// Create a new Asynq client.
	client := asynq.NewClient(redisConnection)
	defer client.Close()

	// Infinite loop to create tasks as Asynq client.
	for {
		// Generate a random user ID.
		userID := rand.Intn(1000) + 10

		// Set a delay duration to 2 minutes.
		delay := 2 * time.Minute

		// Define tasks.
		task1 := tasks.NewWelcomeEmailTask(userID)
		task2 := tasks.NewReminderEmailTask(userID, time.Now().Add(delay))

		// Process the task immediately in critical queue.
		if _, err := client.Enqueue(
			task1,                   // task payload
			asynq.Queue("critical"), // set queue for task
		); err != nil {
			log.Fatal(err)
		}

		// Process the task 2 minutes later in low queue.
		if _, err := client.Enqueue(
			task2,                  // task payload
			asynq.Queue("low"),     // set queue for task
			asynq.ProcessIn(delay), // set time to process task
		); err != nil {
			log.Fatal(err)
		}
	}
}
```

[↑ Table of contents](#toc)

<a name="#asynq-web-ui"></a>
## Asynq web UI

Unfortunately, at the moment, the Asyncq web UI installation is only available by copying binary from a [releases page](https://github.com/hibiken/asynqmon/releases) or running from a Docker container, like this:

```bash
# Pull the latest image
docker pull hibiken/asynqmon

# Run Asynqmon
docker run --rm \
    --name asynqmon \
    -p 8080:8080 \
    hibiken/asynqmon
```

> 👋 Author of Asyncq and myself are already working on simplifying this process for many platforms in a future version (hopefully `v1.0.0`). _If you have a desire to help, you are welcome!_

In the meantime, this is Asynqmon repository:

{% github hibiken/asynqmon no-readme %}

The installed and running web UI will look like this:

![web ui hover charts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zypiuxa7xewqngun7j4j.png)

[↑ Table of contents](#toc)

<a name="#list-of-servers-and-workers"></a>
### List of servers and workers

Shows Asynq worker server statistics with detailed information about queues and active workers:

![web ui servers and workers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a0mcs5gqlktjuhdppo2.png)

Supports mass operations with tasks in a convenient tabular list with information on a given queue:

![web ui servers and workers mass operations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xziuix8jeclkg3pzf5e6.png)

[↑ Table of contents](#toc)

<a name="#redis-server-information"></a>
### Redis server information

It graphically displays all the necessary information about memory usage, server uptime, the number of connected clients at the moment and much more:

![web ui redis information](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36cngx300e9raf8z2uvf.png)

By the way, a full list of your Redis server configuration can be found at _INFO Command Output_ section at the bottom of this page (_data will be updated automatically_).

[↑ Table of contents](#toc)

<a name="#adaptive-dark-theme"></a>
### Adaptive dark theme

Oh, yes! **The future has arrived**. A dark theme with an adaptive mode for the most fashionable Asynq users 🎉

![dark theme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z349wynfk3kr7iwrirfe.png)

[↑ Table of contents](#toc)

<a name="#asynq-cli"></a>
## Asynq CLI

Install the Asynq CLI tool by running the following command:

```bash
go get -u github.com/hibiken/asynq/tools/asynq
```

To see the current state of the queues and their statistics:

```bash
asynq stats
```

![asynq stats](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qz26z9h9r3kxbwimt017.gif)

[↑ Table of contents](#toc)

<a name=""></a>
## Photos and videos by

- Vic Shóstak https://shostak.dev
- Ken Hibino https://github.com/hibiken

<a name=""></a>
## P.S.

If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻

And of course, you can help me make developers' lives even better! Just connect to one of my projects as a contributor. It's easy!

My projects that need your help (and stars) 👇

- 🔥 [gowebly][gowebly_url]: A next-generation CLI tool for easily build amazing web applications with Go on the backend, using htmx & hyperscript and the most popular atomic/utility-first CSS frameworks on the frontend.
- ✨ [create-go-app][cgapp_url]: Create a new production-ready project with Go backend, frontend and deploy automation by running one CLI command.
- 🏃 [yatr][yatr_url]: Yet Another Task Runner allows you to organize and automate your routine operations that you normally do in Makefile (or else) for each project.
- 📚 [gosl][gosl_url]: The Go Snippet Library provides snippets collection for working with routine operations in your Go programs with a super user-friendly API and the most efficient performance.
- 🏄‍♂️ [csv2api][csv2api_url]: The parser reads the CSV file with the raw data, filters the records, identifies fields to be changed, and sends a request to update the data to the specified endpoint of your REST API.
- 🚴 [json2csv][json2csv_url]: The parser can read given folder with JSON files, filtering and qualifying input data with intent & stop words dictionaries and save results to CSV files by given chunk size.

<!-- Footer links -->

[gowebly_url]: https://github.com/gowebly
[cgapp_url]: https://github.com/create-go-app
[yatr_url]: https://github.com/koddr/yatr
[gosl_url]: https://github.com/koddr/gosl
[csv2api_url]: https://github.com/koddr/csv2api
[json2csv_url]: https://github.com/koddr/json2csv