README
¶
redis-kit
A unified Redis utility library for Go projects. This package provides common Redis operations including client management, distributed locking, rate limiting, and caching.
Features
- Client Management: Unified Redis client initialization and configuration
- Distributed Locking: Redis-based distributed locks with automatic fallback to local locks
- Rate Limiting: Flexible rate limiting with support for user/IP/destination-based limits
- Caching: Generic cache interface with Redis implementation
- Health Checks: Built-in health check functionality
Installation
go get github.com/soulteary/redis-kit
Usage
Client Management
import (
"github.com/soulteary/redis-kit/client"
"github.com/redis/go-redis/v9"
)
// Create a client with default configuration
client, err := client.NewClientWithDefaults("localhost:6379")
if err != nil {
log.Fatal(err)
}
defer client.Close(client)
// Or use custom configuration
cfg := client.DefaultConfig().
WithAddr("localhost:6379").
WithPassword("mypassword").
WithDB(0).
WithPoolSize(20)
client, err := client.NewClient(cfg)
Distributed Locking
import "github.com/soulteary/redis-kit/lock"
// Create a Redis locker
locker := lock.NewRedisLocker(client)
// Acquire a lock
success, err := locker.Lock("my-lock-key")
if err != nil {
log.Fatal(err)
}
if !success {
log.Println("Lock already held")
return
}
// Do work...
// Release the lock
defer locker.Unlock("my-lock-key")
// Or use hybrid locker (auto-fallback to local lock)
hybridLocker := lock.NewHybridLocker(client)
success, err := hybridLocker.Lock("my-lock-key")
Notes
Unlockrequires the same process to hold the lock value; unlocking a key without a local lock value returns an error to avoid deleting someone else's lock.HybridLockerfalls back to a local lock only when Redis operations fail. In multi-instance deployments, avoid relying on local fallback unless you accept split-brain behavior.
Rate Limiting
import (
"github.com/soulteary/redis-kit/ratelimit"
"time"
)
// Create a rate limiter
limiter := ratelimit.NewRateLimiter(client)
// Check rate limit
allowed, remaining, resetTime, err := limiter.CheckLimit(
ctx,
"user:123",
10, // limit: 10 requests
1 * time.Hour, // window: 1 hour
)
// Check cooldown
allowed, resetTime, err := limiter.CheckCooldown(
ctx,
"challenge:abc",
60 * time.Second, // cooldown: 60 seconds
)
// Convenience methods
allowed, remaining, resetTime, err := limiter.CheckUserLimit(ctx, "user123", 10, time.Hour)
allowed, remaining, resetTime, err := limiter.CheckIPLimit(ctx, "192.168.1.1", 5, time.Minute)
allowed, remaining, resetTime, err := limiter.CheckDestinationLimit(ctx, "user@example.com", 10, time.Hour)
Notes
- Rate limiting and cooldown checks use Redis Lua scripts (
EVAL) to ensure atomicity; make sure scripts are allowed in your Redis deployment.
Caching
import "github.com/soulteary/redis-kit/cache"
// Create a cache with key prefix
c := cache.NewCache(client, "myapp:")
// Set a value
type User struct {
ID string
Name string
}
user := User{ID: "123", Name: "Alice"}
err := c.Set(ctx, "user:123", user, 1*time.Hour)
// Get a value
var retrievedUser User
err := c.Get(ctx, "user:123", &retrievedUser)
// Check existence
exists, err := c.Exists(ctx, "user:123")
// Delete
err := c.Del(ctx, "user:123")
// Get TTL
ttl, err := c.TTL(ctx, "user:123")
// Set expiration
err := c.Expire(ctx, "user:123", 2*time.Hour)
Health Checks
import "github.com/soulteary/redis-kit/client"
// Simple health check
healthy := client.HealthCheck(ctx, client)
// Detailed health status
status := client.CheckHealth(ctx, client)
if !status.Healthy {
log.Printf("Redis unhealthy: %v (latency: %v)", status.Error, status.Latency)
}
Project Structure
redis-kit/
├── client/ # Client initialization and management
├── lock/ # Distributed locking
├── ratelimit/ # Rate limiting
├── cache/ # Generic caching interface
├── utils/ # Utility functions
└── testutil/ # Testing utilities (mock Redis)
Requirements
- Go 1.26 or later
- Redis server (optional for testing, mock Redis is provided)
Test Coverage
The library includes comprehensive tests with mock Redis support, so you can run tests without a real Redis instance:
# Run all tests
go test ./... -v
# Run tests with coverage
go test ./... -coverprofile=coverage.out -covermode=atomic
# Generate HTML coverage report
go tool cover -html=coverage.out -o coverage.html
# View coverage summary
go tool cover -func=coverage.out
Examples
Complete Example: Rate-Limited Cache with Locking
package main
import (
"context"
"fmt"
"time"
"github.com/soulteary/redis-kit/cache"
"github.com/soulteary/redis-kit/client"
"github.com/soulteary/redis-kit/lock"
"github.com/soulteary/redis-kit/ratelimit"
)
func main() {
ctx := context.Background()
// Initialize Redis client
redisClient, err := client.NewClientWithDefaults("localhost:6379")
if err != nil {
panic(err)
}
defer redisClient.Close()
// Health check
if !client.HealthCheck(ctx, redisClient) {
panic("Redis is not healthy")
}
// Create cache
userCache := cache.NewCache(redisClient, "user:")
// Create locker
locker := lock.NewHybridLocker(redisClient)
// Create rate limiter
limiter := ratelimit.NewRateLimiter(redisClient)
// Example: Get user with caching and rate limiting
userID := "user123"
// Check rate limit
allowed, remaining, resetTime, err := limiter.CheckUserLimit(ctx, userID, 10, time.Hour)
if err != nil {
panic(err)
}
if !allowed {
fmt.Printf("Rate limit exceeded. Reset at: %v\n", resetTime)
return
}
fmt.Printf("Rate limit OK. Remaining: %d\n", remaining)
// Try to acquire lock
lockKey := fmt.Sprintf("user:%s:lock", userID)
acquired, err := locker.Lock(lockKey)
if err != nil {
panic(err)
}
if !acquired {
fmt.Println("Could not acquire lock")
return
}
defer locker.Unlock(lockKey)
// Check cache first
type User struct {
ID string
Name string
}
var user User
exists, err := userCache.Exists(ctx, userID)
if err != nil {
panic(err)
}
if exists {
// Cache hit
err = userCache.Get(ctx, userID, &user)
if err != nil {
panic(err)
}
fmt.Printf("Cache hit: %+v\n", user)
} else {
// Cache miss - fetch from database
user = User{ID: userID, Name: "Alice"}
// Store in cache
err = userCache.Set(ctx, userID, user, 1*time.Hour)
if err != nil {
panic(err)
}
fmt.Printf("Cached: %+v\n", user)
}
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow Go best practices and conventions
- Add tests for new features
- Ensure all tests pass (
go test ./...) - Run
go fmtandgo vetbefore committing - Update documentation as needed
License
See LICENSE file for details.
Click to show internal directories.
Click to hide internal directories.