0

Redis with golang

What is Redis?

Redis is an open source data structure server. 
It belongs to the class of NoSQL databases known as key/value stores.

The data type range from simple Strings, to Linked Lists, Sets, Json and more

It fast, open source, in memory key value data structure store

Since accessing RAM is 150,000 time faster than accessing a disk, and 500 times faster accessing SSD.

Imagine accessing a database to read 10,000 records. If the data is stored on disk it will take an average of 30 seconds, while it takes around 0.0002 seconds to read from RAM.

How to handle with lose our data?

“Persistence” refer to the writing of data to durable storage, such as a solid-state disk (SSD). Redis provides a range of persistence options. These include:

  • RDB (Redis Database): RDB persistence performs point-in-time snapshots of your dataset at specified intervals.
  • AOF (Append Only File): AOF persistence logs every write operation received by the server. These operations can then be replayed again at server startup, reconstructing the original dataset. Commands are logged using the same format as the Redis protocol itself.
  • No persistence: You can disable persistence completely. This is sometimes used when caching.
  • RDB + AOF: You can also combine both AOF and RDB in the same instance.

Redis in Golang

Start Redis server with docker

docker run -d \
  --name redis-stack-server \
  -p 6379:6379 \
  -e REDIS_ARGS**=**"--save 60 1000 --appendonly yes --requirepass yourpassword" \
  -v /local-data/:/data \
  redis/redis-stack-server:latest

In this command only run one Redis with none handle lose data, you can config this persistence with redis_args with tag -e

Explane this:

  • -save 60 1000: Configures periodic RDB snapshots:
    • Saves a snapshot every 60 seconds if at least 1000 keys have been modified.
  • -appendonly yes: Enables AOF (Append-Only File) persistence for better data durability.
  • -requirepass yourpassword: Sets the password for Redis.
    • Users must authenticate with the password (yourpassword) before accessing Redis commands.
  • **v /local-data/:/data:**Mounts the host directory /local-data/ to the container's /data directory

After run this command you can test connect with redis cli and PING this

redis-cli -p 6379 -a yourpassword

image.png Init Golang Project

Create Redis client with project structure

image.png

Create Redis Client Struct

package client

import (
	"context"
	redis "github.com/redis/go-redis/v9"
	"redis-learn/internal/infra/config"
	"time"
)

type RedisClient struct {
	redis *redis.Client
}

// NewRedisClient initializes a new RedisClient instance.
// It takes a Redis configuration (host, password, and DB) as input
// and returns a pointer to the RedisClient.
func NewRedisClient(redisConfig config.Redis) *RedisClient {
	rbd := redis.NewClient(
		&redis.Options{
			Addr:     redisConfig.Addr,
			Password: redisConfig.Password,
			DB:       redisConfig.DB,
		})

	return &RedisClient{
		redis: rbd,
	}
}

// Get retrieves the value of the specified key from Redis.
// Returns the value as a string and any error encountered.
func (r *RedisClient) Get(ctx context.Context, key string) (string, error) {
	return r.redis.Get(ctx, key).Result()
}

// Set stores a key-value pair in Redis with no expiration time.
// Returns an error if the operation fails.
func (r *RedisClient) Set(ctx context.Context, key string, value interface{}) error {
	return r.redis.Set(ctx, key, value, 0).Err()
}

// Del deletes the specified key from Redis.
// Returns an error if the operation fails.
func (r *RedisClient) Del(ctx context.Context, key string) error {
	return r.redis.Del(ctx, key).Err()
}

// Exists checks whether a given key exists in Redis.
// Returns the count of existing keys and any error encountered.
func (r *RedisClient) Exists(ctx context.Context, key string) (int64, error) {
	return r.redis.Exists(ctx, key).Result()
}

// SetWithTTL stores a key-value pair in Redis with a specified time-to-live (TTL).
// Returns an error if the operation fails.
func (r *RedisClient) SetWithTTL(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
	return r.redis.Set(ctx, key, value, ttl).Err()
}

// HSet sets a field in a hash stored at key in Redis.
// Returns an error if the operation fails.
func (r *RedisClient) HSet(ctx context.Context, key string, value interface{}) error {
	return r.redis.HSet(ctx, key, value).Err()
}

// HGetAll retrieves all fields and values of a hash stored at the specified key.
// The results are scanned into the provided destination structure.
// Returns an error if the operation fails.
func (r *RedisClient) HGetAll(ctx context.Context, key string, desc interface{}) error {
	return r.redis.HGetAll(ctx, key).Scan(desc)
}

// Ping sends a PING command to Redis to check connectivity.
// Returns an error if Redis is not reachable.
func (r *RedisClient) Ping(ctx context.Context) error {
	return r.redis.Ping(ctx).Err()
}

// Close gracefully shuts down the Redis client connection.
func (r *RedisClient) Close() {
	r.redis.Close()
}

Create main func test this

Config file same

redisClient := newRedisClient(config.Redis{
		Addr:     "localhost:6379",
		Password: "yourpassword",
		DB:       0,
	})

Create set value in Redis, and get this

err := redisClient.Set(ctx, "mykey", "hello world")
if err != nil {
	log.Panic("Redis client failed to set key")
}
	
value, err := redisClient.Get(ctx, "mykey")
if err != nil {
	log.Panic("Redis client failed to get key")
}
log.Printf("value: %s", value)

image.png

Create value with time to live

err := redisClient.SetWithTTL(ctx, "mykeyttl", "hello world 5000", time.Duration(5000)*time.Millisecond)
valueTTL, err := redisClient.Get(ctx, "mykeyttl")
if err != nil {
	log.Panic("Redis client failed to get key")
}
log.Printf("value: %s", valueTTL)

time.Sleep(time.Duration(5000) * time.Millisecond)

valueTTL, err = redisClient.Get(ctx, "mykeyttl")
if err != nil {
	log.Panic("Redis client failed to get key ttl " + err.Error())
}
log.Printf("value: %s", valueTTL)

image.png

How to store struct in Redis golang

In this example create user service for store, get, and delete user

  • Store User: This operation stores a user’s data in Redis, typically under a key that is unique to each user (like a user ID). This is helpful when you want fast access to user details.
  • Get User: This operation retrieves the user’s data based on the unique user key. It is useful for quickly fetching user data for profile management, authentication, or any feature that requires quick access to the user’s information.
  • Delete User: This operation deletes the user’s data from Redis, either when the user is removed or the session expires. Deleting user data ensures that Redis doesn’t hold unnecessary data, helping maintain memory efficiency.

Create User Struct, use tag redis for to parse fields from a hash directly into corresponding struct fields

type User struct {
	ID       string `redis:"id"`
	Username string `redis:"username"`
	Password string `redis:"password"`
	Email    string `redis:"email"`
}

User Service

type UserService interface {
	GetUser(ctx context.Context, userId string) (*model.User, error)
	CreateUser(ctx context.Context, user model.User) (*model.User, error)
	DeleteUser(ctx context.Context, userId string) bool
}

Checks if the userId is valid, then fetches the user’s data as a hash using HGetAll. If an error occurs, it returns an error; otherwise, it returns the user data.

func (u userService) GetUser(ctx context.Context, userId string) (*model.User, error) {
	if len(userId) == 0 {
		return nil, errors.New("invalid user id")
	}

	var user model.User
	err := u.redisClient.HGetAll(ctx, userId, &user)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

Uses HSet to store the user’s details (like ID, Name, Email) in a Redis hash. If successful, it returns the user data; otherwise, it returns an error.

func (u userService) CreateUser(
	ctx context.Context,
	user model.User,
) (*model.User, error) {
	err := u.redisClient.HSet(ctx, user.ID, user)
	if err != nil {
		return nil, err
	}
	return &user, nil
}

Uses Del to remove the key associated with the userId. It returns true if successful, or false if there’s an error.

func (u userService) DeleteUser(ctx context.Context, userId string) bool {
	err := u.redisClient.Del(ctx, userId)
	if err != nil {
		return false
	}
	return true
}

Create main func for test this

func main() {
	userId := "1a107787-b799-438c-89e1-bbd1e324c891"
	user := &model.User{
		ID:       userId,
		Username: "hydro",
		Password: "BHQBxoqgeY1x8qg",
		Email:    "hydro@mail.com",
	}

	userService := newUserService(newRedisClient(config.Redis{
		Addr:     "localhost:6379",
		Password: "yourpassword",
		DB:       0,
	}))

	user, err := userService.CreateUser(context.Background(), *user)
	if err != nil {
		log.Panic(err)
	}
	log.Printf("create user success: %s", user)

	user, err = userService.GetUser(context.Background(), userId)
	if err != nil {
		log.Panic(err)
	}
	log.Printf("get user: %s", user)

	success := userService.DeleteUser(context.Background(), userId)
	log.Printf("success: %t", success)
}

image.png

Reference

https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/docker

https://github.com/redis/go-redis

https://redis.io/docs/latest/develop/clients/go/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí