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.
- Users must authenticate with the password (
- **
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
Init Golang Project
Create Redis client with project structure
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)
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)
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)
}
Reference
https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/docker
All rights reserved