A lightweight Go library for managing Redis Lua scripts with atomic execution, SHA1 caching, and a built-in zero-dependency Redis client.
- Zero external dependencies — built-in RESP2 client, no
go-redisrequired - Lua script lifecycle — register, cache (SHA1), and execute atomically
- Production-grade connection pool — max connections, idle timeout, connection age, waiter queue
- Standalone Redis client — usable independently via
goscriptor/redissub-package - 20+ built-in commands — String, Hash, List, Set, Key operations
Note: This library uses
SELECTinternally for DB isolation. Redis Cluster is not supported.
go get github.com/yshengliao/goscriptorpackage main
import (
"context"
"fmt"
"github.com/yshengliao/goscriptor"
)
func main() {
opt := &goscriptor.Option{
Host: "127.0.0.1", Port: 6379,
DB: 0, PoolSize: 10,
}
scripts := map[string]string{
"hello": `return 'Hello, World!'`,
}
s, err := goscriptor.NewDB(opt, 1, "myapp|v1.0", scripts)
if err != nil {
panic(err)
}
defer s.Close()
ctx := context.Background()
res, _ := s.ExecSha(ctx, "hello", []string{})
fmt.Println(res) // Hello, World!
}package main
import (
"context"
"fmt"
"time"
"github.com/yshengliao/goscriptor/redis"
)
func main() {
c := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
PoolSize: 10,
})
defer c.Close()
ctx := context.Background()
c.Set(ctx, "name", "goscriptor", 5*time.Minute)
val, _ := c.Get(ctx, "name")
fmt.Println(val) // goscriptor
c.LPush(ctx, "queue", "task1", "task2")
item, _ := c.RPop(ctx, "queue")
fmt.Println(item) // task1
}goscriptor/
├── scriptor.go Scriptor — main API (Exec, ExecSha)
├── script.go ScriptDescriptor — register, cache, load
├── option.go Option — convenience constructor
├── reply.go RedisArrayReplyReader — type-safe reply parsing
├── errors.go Sentinel errors
├── redis/ Standalone Redis client (public sub-package)
│ ├── client.go Client, connection pool, pool stats
│ ├── resp.go RESP2 protocol encoder/decoder
│ └── commands.go 20+ built-in Redis commands
└── example/
└── main.go Usage example
| Setting | Default | Description |
|---|---|---|
PoolSize |
10 | Maximum active connections |
MinIdle |
1 | Minimum idle connections kept alive |
IdleTimeout |
5m | Idle connections closed after this duration |
MaxConnAge |
30m | Connections retired after this lifetime |
ReadTimeout |
3s | Per-command read deadline |
WriteTimeout |
3s | Per-command write deadline |
DialTimeout |
5s | Timeout for new TCP connections |
Set any timeout to -1 to disable it.
stats := client.PoolStats()
fmt.Printf("Active: %d, Idle: %d, Waiters: %d\n",
stats.Active, stats.Idle, stats.Waiters)| Category | Commands |
|---|---|
| String | Get, Set (with TTL), Del, Exists, Incr, IncrBy |
| Hash | HGet, HGetAll, HSet, HDel, HExists |
| List | LPush, RPush, LPop, RPop, LLen, LRange |
| Set | SAdd, SMembers, SRem, SIsMember, SCard |
| Key | Expire, TTL |
| Script | Eval, EvalSha, ScriptLoad, ScriptExists |
| Server | Ping, FlushAll, Do (raw command) |
# Unit tests (no Redis required)
go test ./...
# Integration tests (requires running Redis)
REDIS_ADDR=127.0.0.1:6379 go test -v ./...- 📖 English Documentation — API reference, connection pool guide
- 📖 繁體中文文件 — API 參考、連線池指南
- Performance: Achieved near zero-allocation for RESP2 serialization using
sync.Pool(PING: 20 B/op, 2 allocs/op). - Performance: Optimized
ReadReplyto avoid string allocations during integer parsing. - Bug Fix: Fixed a critical nil pointer dereference issue when waking waiting goroutines in the connection pool via
Close(). - Tests: Increased test coverage to 79% (pool exhaustion, waiter cancellation, robust RESP parsing).
- Replaced
go-redis/v9with built-in RESP2 client — zero external dependencies. - Production-grade connection pool (max active, idle timeout, max age, waiter queue, background reaper).
- Public
redis/sub-package with 20+ built-in commands (String, Hash, List, Set, Key). PoolStats()for runtime monitoring (Active / Idle / Waiters).- Reorganised project:
internal/redis/→ publicredis/,main/→example/, file renames. - Bilingual documentation (
docs/en/,docs/zh-tw/) with API reference, connection pool guide, migration guide. - Removed dead code (
ScriptDescriptor.Scriptsfield), added HGETALL odd-count guard. - Fixed type-switch double assertions in
RedisReplyValue. - Cleaned LLM artefacts (
doc.go, stale README, code-review.md).
- Migrated to Go 1.25,
go-redis/v9. - Removed
gopkg.in/guregu/null.v3— replaced with native pointer types. - Removed
testify,miniredis— all tests use stdlibtesting+ real Redis. - Introduced sentinel errors, explicit
context.Contextpassing, comma-ok assertions. - Removed
sync.Once, map pointer passing,UniversalClient. - Black-box tests (
package goscriptor_test),REDIS_ADDRenv-var gating.
MIT License — see LICENSE.
This project relies on real Redis for integration tests to ensure RESP2 correctness and connection pool reliability. The underlying custom client has been rigorously optimized for zero-allocation command formatting and bulk string parsing.
Run the tests and benchmarks locally (requires a running Redis instance at 127.0.0.1:6379):
$ REDIS_ADDR=127.0.0.1:6379 go test -bench=. -benchmem ./...Benchmark Results (Apple M3 Pro):
goos: darwin
goarch: arm64
pkg: github.com/yshengliao/goscriptor
cpu: Apple M3 Pro
BenchmarkPing-12 13921 85492 ns/op 20 B/op 2 allocs/op
BenchmarkGet-12 14032 84385 ns/op 96 B/op 4 allocs/op
PASS
ok github.com/yshengliao/goscriptor 4.150s
- Zero-Allocation formatting: Writing RESP2 commands leverages
sync.Pool, eliminating dynamic memory allocation during normal request lifecycles. - Minimal Parsing Allocation:
ReadReplyusesbufio.Reader.ReadLine()and custom byte parsing instead of strings, bringingPINGdown to just2 allocs/op(20 Bytes/op).