mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
We do not use Redis. The client was a go-redis fork and still said so
everywhere: `package redis`, `redis.NewClient`, `redis://`, `extra/redisotel`.
Consumers wrote `redis.Client` against a Valkey-backed store called KV.
package redis -> package kv (93 decls)
redis.X -> kv.X (2,794 call sites)
"redis: …" errors -> "kv: …" (206)
extra/redis{otel,cmd,prometheus,census} -> extra/kv{otel,cmd,prometheus,census}
redis:// , rediss:// -> kv:// , kvs:// (ParseURL)
Redis*/*Redis* idents -> KV*/*KV*
REDIS_VERSION, REDIS_ENDPOINTS_CONFIG_PATH -> KV_*
I previously argued this rename was unsafe because much of it was "wire
contract". That was wrong, and the categorisation shows why: RESP command names
are GET/SET/HSET — they never contain the word. Of 5,771 occurrences only two
kinds were server-visible at all: the ParseURL scheme (we choose what we pass;
our configs already use kv://) and the CLIENT SETINFO lib-name label, which the
server merely records. Nothing here changes a byte of protocol.
Two things deliberately keep the old spelling:
RediSearch — names a specific external module the FT.* commands map to.
StoreDist — contains the letters by accident (sto-reDis-t), not the word.
Breaking for consumers by design: every import site must move redis.X -> kv.X.
Minor bump, never a major — this module stays v9 forever.
176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/hanzokv/go/v9"
|
|
"github.com/hanzokv/go/v9/logging"
|
|
"github.com/hanzokv/go/v9/maintnotifications"
|
|
)
|
|
|
|
var ctx = context.Background()
|
|
var cntErrors atomic.Int64
|
|
var cntSuccess atomic.Int64
|
|
var startTime = time.Now()
|
|
|
|
// This example is not supposed to be run as is. It is just a test to see how pubsub behaves in relation to pool management.
|
|
// It was used to find regressions in pool management in maintnotifications mode.
|
|
// Please don't use it as a reference for how to use pubsub.
|
|
func main() {
|
|
startTime = time.Now()
|
|
wg := &sync.WaitGroup{}
|
|
rdb := kv.NewClient(&kv.Options{
|
|
Addr: ":6379",
|
|
MaintNotificationsConfig: &maintnotifications.Config{
|
|
Mode: maintnotifications.ModeEnabled,
|
|
EndpointType: maintnotifications.EndpointTypeExternalIP,
|
|
HandoffTimeout: 10 * time.Second,
|
|
RelaxedTimeout: 10 * time.Second,
|
|
PostHandoffRelaxedDuration: 10 * time.Second,
|
|
},
|
|
})
|
|
_ = rdb.FlushDB(ctx).Err()
|
|
maintnotificationsManager := rdb.GetMaintNotificationsManager()
|
|
if maintnotificationsManager == nil {
|
|
panic("maintnotifications manager is nil")
|
|
}
|
|
loggingHook := maintnotifications.NewLoggingHook(int(logging.LogLevelDebug))
|
|
maintnotificationsManager.AddNotificationHook(loggingHook)
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(2 * time.Second)
|
|
fmt.Printf("pool stats: %+v\n", rdb.PoolStats())
|
|
}
|
|
}()
|
|
err := rdb.Ping(ctx).Err()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if err := rdb.Set(ctx, "publishers", "0", 0).Err(); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := rdb.Set(ctx, "subscribers", "0", 0).Err(); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := rdb.Set(ctx, "published", "0", 0).Err(); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := rdb.Set(ctx, "received", "0", 0).Err(); err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println("published", rdb.Get(ctx, "published").Val())
|
|
fmt.Println("received", rdb.Get(ctx, "received").Val())
|
|
subCtx, cancelSubCtx := context.WithCancel(ctx)
|
|
pubCtx, cancelPublishers := context.WithCancel(ctx)
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go subscribe(subCtx, rdb, "test", i, wg)
|
|
}
|
|
time.Sleep(time.Second)
|
|
cancelSubCtx()
|
|
time.Sleep(time.Second)
|
|
subCtx, cancelSubCtx = context.WithCancel(ctx)
|
|
for i := 0; i < 10; i++ {
|
|
if err := rdb.Incr(ctx, "publishers").Err(); err != nil {
|
|
fmt.Println("incr error:", err)
|
|
cntErrors.Add(1)
|
|
}
|
|
wg.Add(1)
|
|
go floodThePool(pubCtx, rdb, wg)
|
|
}
|
|
|
|
for i := 0; i < 500; i++ {
|
|
if err := rdb.Incr(ctx, "subscribers").Err(); err != nil {
|
|
fmt.Println("incr error:", err)
|
|
cntErrors.Add(1)
|
|
}
|
|
|
|
wg.Add(1)
|
|
go subscribe(subCtx, rdb, "test2", i, wg)
|
|
}
|
|
time.Sleep(120 * time.Second)
|
|
fmt.Println("canceling publishers")
|
|
cancelPublishers()
|
|
time.Sleep(10 * time.Second)
|
|
fmt.Println("canceling subscribers")
|
|
cancelSubCtx()
|
|
wg.Wait()
|
|
published, err := rdb.Get(ctx, "published").Result()
|
|
received, err := rdb.Get(ctx, "received").Result()
|
|
publishers, err := rdb.Get(ctx, "publishers").Result()
|
|
subscribers, err := rdb.Get(ctx, "subscribers").Result()
|
|
fmt.Printf("publishers: %s\n", publishers)
|
|
fmt.Printf("published: %s\n", published)
|
|
fmt.Printf("subscribers: %s\n", subscribers)
|
|
fmt.Printf("received: %s\n", received)
|
|
publishedInt, err := rdb.Get(ctx, "published").Int()
|
|
subscribersInt, err := rdb.Get(ctx, "subscribers").Int()
|
|
fmt.Printf("if drained = published*subscribers: %d\n", publishedInt*subscribersInt)
|
|
|
|
time.Sleep(2 * time.Second)
|
|
fmt.Println("errors:", cntErrors.Load())
|
|
fmt.Println("success:", cntSuccess.Load())
|
|
fmt.Println("time:", time.Since(startTime))
|
|
}
|
|
|
|
func floodThePool(ctx context.Context, rdb *kv.Client, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
err := rdb.Publish(ctx, "test2", "hello").Err()
|
|
if err != nil {
|
|
if err.Error() != "context canceled" {
|
|
log.Println("publish error:", err)
|
|
cntErrors.Add(1)
|
|
}
|
|
}
|
|
|
|
err = rdb.Incr(ctx, "published").Err()
|
|
if err != nil {
|
|
if err.Error() != "context canceled" {
|
|
log.Println("incr error:", err)
|
|
cntErrors.Add(1)
|
|
}
|
|
}
|
|
time.Sleep(10 * time.Nanosecond)
|
|
}
|
|
}
|
|
|
|
func subscribe(ctx context.Context, rdb *kv.Client, topic string, subscriberId int, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
rec := rdb.Subscribe(ctx, topic)
|
|
recChan := rec.Channel()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
rec.Close()
|
|
return
|
|
default:
|
|
select {
|
|
case <-ctx.Done():
|
|
rec.Close()
|
|
return
|
|
case msg := <-recChan:
|
|
err := rdb.Incr(ctx, "received").Err()
|
|
if err != nil {
|
|
if err.Error() != "context canceled" {
|
|
log.Printf("%s\n", err.Error())
|
|
cntErrors.Add(1)
|
|
}
|
|
}
|
|
_ = msg // Use the message to avoid unused variable warning
|
|
}
|
|
}
|
|
}
|
|
}
|