Mohit Khare
Mohit Khare Product Engineer @ Gojek | Blogs about tech, productivity and life.

Go with Redis

Go with Redis

If you are aware about both Golang and Redis - you’ll know they both are fast⚡️and easy to learn ✅. Well, Redis is written in C - it is built to be fast right 😉. Who doesn’t want to make their app fast? Let’s dive in to learn using Redis with Golang.

Intro to Redis

In case you are not aware about Redis, Let me introduce by stating official explanation which is quite self explanatory -

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries

Why Redis?

  • Fastest in-memory database.
  • Open source
  • Built-in replication
  • Persistent
  • Highly stable and used by alll big companies.

Read more on why Redis here.

One of the common use cases of Redis is in caching. Caching is the process of storing some data in a cache which is a temporary storage component where the data is stored. This stored data can then be served for faster responses.

Example - Suppose you maintain a counter which updates on every web page request. This is a frequently used operation. This is exactly where you would use Redis. You would not want to make DB query each time to update and fetch it. (why? because they are slow ⌛️)

Okay, now you probably love redis and are ready to use it. Let’s see how you can use redis in your Go applications.

Using Redis in Go

Let’s create a main.go - initialize redis and perform basic set/get operation.

Note : We will be using redigo library to use redis in golang. Install it using- go get github.com/gomodule/redigo/redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
	"fmt"
	"log"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// here 6379 is the default port of redis - you can change it if you want!
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		log.Fatal(err)
	}
	// ensures connection is closed before main() ends
	defer conn.Close()

	// Here, we set a simple Key-Value with key as name and value as Mohit
	// Do sends a command to the server and returns the received reply.
	_, err = conn.Do("SET", "name", "Mohit")
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the value we stored and parse as String
	name, err := redis.String(conn.Do("GET", "name"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(name)
}

On running go run main.go , we get -

1
Mohit

Congrats 🎉, you just performed your first redis read/write query.

This was easy, now let’s try to insert and retrieve structs. Here, we create a simple User struct with two attributes - name(string) and age(int).

Update your main.go as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import (
	"fmt"
	"log"

	"github.com/gomodule/redigo/redis"
)

type User struct {
	Name string `redis:"name"`
	Age  int    `redis:"age"`
}

func main() {
	// here 6379 is the default port of redis - you can change it if you want!
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		log.Fatal(err)
	}
	// ensures connection is closed before main() ends
	defer conn.Close()

	// Here, we set object has values with key
	// HMSET - https://redis.io/commands/hmset
	_, err = conn.Do("HMSET", "user:1", "name", "Mohit", "age", 23)
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the individual value we stored and parse as String
	name, err := redis.String(conn.Do("HGET", "user:1", "name"))
	if err != nil {
		log.Fatal(err)
	}

    // Retrieve the individual value we stored and parse as Int
	age, err := redis.Int(conn.Do("HGET", "user:1", "age"))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve whole object at once and parse it as struct.
	// You can parse it into a struct using a helper function.
	response, err := redis.Values(conn.Do("HGETALL", "user:1"))
	if err != nil {
		log.Fatal(err)
	}

	// Parsing into struct
	var user User
	err = redis.ScanStruct(response, &user)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(name, age)
	fmt.Println(user)
}

On running go run main.go , we get -

1
2
Mohit 23
{Mohit 23}

You get the idea, you can explore other data types in redis and use them.

Note: Conn object we use is not safe for concurrent use. In production apps, usually a redis Pool connections are used, where each pool executes command and returns the result from different goroutines.

Let’s reimplement the previous code using redis Pool.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/gomodule/redigo/redis"
)

type User struct {
	Name string `redis:"name"`
	Age  int    `redis:"age"`
}

// Create a new Pool with certain set of parameters.
// You can modify/add more as per your usecase - https://godoc.org/github.com/gomodule/redigo/redis#Pool
func NewPool(address string, connTimeoutInMs, readTimeoutInMs, maxIdle int) *redis.Pool {
	return &redis.Pool{
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", address,
				redis.DialConnectTimeout(time.Duration(connTimeoutInMs)*time.Millisecond),
				redis.DialReadTimeout(time.Duration(readTimeoutInMs)*time.Millisecond),
			)
			if err != nil {
				return nil, err
			}
			return c, err
		},
		IdleTimeout: 240 * time.Second,
		MaxIdle:     maxIdle,
	}
}

func main() {
	pool := NewPool("localhost:6379", 100, 100, 3)
	// Fetch a single Redis connection from the pool
	conn := pool.Get()
	// ensures connection is closed before main() ends
	defer conn.Close()

	// Here, we set a simple Key-Value with key as name and value as Mohit
	_, err := conn.Do("HMSET", "user:1", "name", "Mohit", "age", 23)
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the value we stored and parse as String
	name, err := redis.String(conn.Do("HGET", "user:1", "name"))
	if err != nil {
		log.Fatal(err)
	}

	age, err := redis.Int(conn.Do("HGET", "user:1", "age"))
	if err != nil {
		log.Fatal(err)
	}

	response, err := redis.Values(conn.Do("HGETALL", "user:1"))
	if err != nil {
		log.Fatal(err)
	}

	var user User
	err = redis.ScanStruct(response, &user)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(name, age)
	fmt.Println(user)
}

On running go run main.go , we get what we expected -

1
2
Mohit 23
{Mohit 23}

Explore other Redis Commands -

  • DEL - Deleting a Key
  • SETEX - Setting a key with an expiry
  • RENAME - Renaming the current existing key
  • FLUSHALL - Flushing everything so far saved

There are lot of other commands which you can use. Find them here.

I hope you are now ready to use Redis in your applications. Feel free to comment on how you use Redis. Do drop a 👍 and share it with your friends.


I hope you learned something interesting and new. Don’t miss out on latest blogs - Subscribe now.

Interested in technology and life stuff? I share updates/knowledge almost daily on Twitter. Reach out to me at mohitkhare.me

FightCorona

comments powered by Disqus