diff --git a/app/persistence/redis.go b/app/persistence/redis.go index 3db85ed..d7a9205 100644 --- a/app/persistence/redis.go +++ b/app/persistence/redis.go @@ -31,6 +31,13 @@ import ( // nil означает режим "только память" — все функции тихо no-op. var Client *redis.Client +// Параметры подключения — сохраняются для переподключения после обрыва. +var ( + redisAddr string + redisUsername string + redisPassword string +) + var ( seqMu sync.Mutex queueWriteSeq = map[string]uint64{} @@ -53,6 +60,10 @@ func Connect(addr, username, password string) error { log.Info("persistence: REDIS_ADDR не задан, работаем в memory-only режиме") return nil } + redisAddr = addr + redisUsername = username + redisPassword = password + rdb := redis.NewClient(&redis.Options{ Addr: addr, Username: username, @@ -66,6 +77,55 @@ func Connect(addr, username, password string) error { } Client = rdb log.Infof("persistence: подключились к Redis %s", addr) + startRedisHealthCheck() + return nil +} + +// startRedisHealthCheck — фоновая проверка соединения и переподключение. +// После обрыва Redis-клиент остаётся не-nil, но записи молча падают. Здесь мы +// периодически пингуем Redis и при сбое создаём новый клиент (reconnect), чтобы +// персистентность восстановилась автоматически. +func startRedisHealthCheck() { + go func() { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + for range ticker.C { + if Client == nil { + continue + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + err := Client.Ping(ctx).Err() + cancel() + if err == nil { + continue + } + log.Warnf("persistence: Redis недоступен (%v), пробуем переподключиться", err) + if reErr := reconnect(); reErr != nil { + log.Errorf("persistence: переподключение не удалось: %v", reErr) + } else { + log.Infof("persistence: переподключились к Redis %s", redisAddr) + } + } + }() +} + +// reconnect — создаёт новый Redis-клиент с сохранёнными параметрами подключения. +func reconnect() error { + if redisAddr == "" { + return fmt.Errorf("redis addr пуст") + } + rdb := redis.NewClient(&redis.Options{ + Addr: redisAddr, + Username: redisUsername, + Password: redisPassword, + DB: 0, + }) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := rdb.Ping(ctx).Err(); err != nil { + return err + } + Client = rdb return nil }