persistence: Client → atomic.Pointer (устранение data race при reconnect)
This commit is contained in:
+2
-2
@@ -109,7 +109,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Восстановление состояния из Redis (тенанты + очереди)
|
// Восстановление состояния из Redis (тенанты + очереди)
|
||||||
if persistence.Client != nil {
|
if persistence.Client.Load() != nil {
|
||||||
// Загружаем тенантов
|
// Загружаем тенантов
|
||||||
tenantsRaw, err := persistence.LoadAllTenantsRaw()
|
tenantsRaw, err := persistence.LoadAllTenantsRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -139,7 +139,7 @@ func main() {
|
|||||||
|
|
||||||
// Бэкфилл индекса accessKey→tenant для read-through (идемпотентно; нужен для
|
// Бэкфилл индекса accessKey→tenant для read-through (идемпотентно; нужен для
|
||||||
// тенантов, созданных до появления индекса, и для multi-instance сценария).
|
// тенантов, созданных до появления индекса, и для multi-instance сценария).
|
||||||
if persistence.Client != nil {
|
if persistence.Client.Load() != nil {
|
||||||
for _, t := range tenantStore.List() {
|
for _, t := range tenantStore.List() {
|
||||||
if data, err := json.Marshal(t); err == nil {
|
if data, err := json.Marshal(t); err == nil {
|
||||||
persistence.SaveTenantRaw(t.ID, data)
|
persistence.SaveTenantRaw(t.ID, data)
|
||||||
|
|||||||
+48
-45
@@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@@ -30,7 +31,9 @@ import (
|
|||||||
|
|
||||||
// Client — глобальный Redis клиент.
|
// Client — глобальный Redis клиент.
|
||||||
// nil означает режим "только память" — все функции тихо no-op.
|
// nil означает режим "только память" — все функции тихо no-op.
|
||||||
var Client *redis.Client
|
// atomic.Pointer: reconnect() пишет новый клиент из health-check горутины,
|
||||||
|
// а остальные горутины читают — без атомарности это data race.
|
||||||
|
var Client atomic.Pointer[redis.Client]
|
||||||
|
|
||||||
// Параметры подключения — сохраняются для переподключения после обрыва.
|
// Параметры подключения — сохраняются для переподключения после обрыва.
|
||||||
var (
|
var (
|
||||||
@@ -78,7 +81,7 @@ func Connect(addr, username, password string) error {
|
|||||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||||
return fmt.Errorf("redis ping %s: %w", addr, err)
|
return fmt.Errorf("redis ping %s: %w", addr, err)
|
||||||
}
|
}
|
||||||
Client = rdb
|
Client.Store(rdb)
|
||||||
log.Infof("persistence: подключились к Redis %s", addr)
|
log.Infof("persistence: подключились к Redis %s", addr)
|
||||||
startRedisHealthCheck()
|
startRedisHealthCheck()
|
||||||
return nil
|
return nil
|
||||||
@@ -93,11 +96,11 @@ func startRedisHealthCheck() {
|
|||||||
ticker := time.NewTicker(30 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
err := Client.Ping(ctx).Err()
|
err := Client.Load().Ping(ctx).Err()
|
||||||
cancel()
|
cancel()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
@@ -128,7 +131,7 @@ func reconnect() error {
|
|||||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Client = rdb
|
Client.Store(rdb)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +225,7 @@ func marshalQueueMeta(queue *models.Queue) ([]byte, error) {
|
|||||||
// создаёт консистентный снапшот. Горутина только делает сетевой вызов.
|
// создаёт консистентный снапшот. Горутина только делает сетевой вызов.
|
||||||
// Для сохранения сообщений используй SaveMessage/SaveMessages.
|
// Для сохранения сообщений используй SaveMessage/SaveMessages.
|
||||||
func SaveQueue(key string, queue *models.Queue) {
|
func SaveQueue(key string, queue *models.Queue) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Сериализуем метаданные синхронно под мьютексом вызывающего → консистентный снапшот
|
// Сериализуем метаданные синхронно под мьютексом вызывающего → консистентный снапшот
|
||||||
@@ -238,7 +241,7 @@ func SaveQueue(key string, queue *models.Queue) {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HSet(ctx, redisHashQueues, key, string(data)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, redisHashQueues, key, string(data)).Err(); err != nil {
|
||||||
log.Errorf("persistence: HSet queue meta %q: %v", key, err)
|
log.Errorf("persistence: HSet queue meta %q: %v", key, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -248,7 +251,7 @@ func SaveQueue(key string, queue *models.Queue) {
|
|||||||
// Ключ: ssq:msg:{queueKey}, поле: msg.Uuid, значение: JSON(SqsMessage).
|
// Ключ: ssq:msg:{queueKey}, поле: msg.Uuid, значение: JSON(SqsMessage).
|
||||||
// Вызывать под Lock — json.Marshal делает консистентный снапшот сообщения.
|
// Вызывать под Lock — json.Marshal делает консистентный снапшот сообщения.
|
||||||
func SaveMessage(queueKey string, msg *models.SqsMessage) {
|
func SaveMessage(queueKey string, msg *models.SqsMessage) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(msg)
|
data, err := json.Marshal(msg)
|
||||||
@@ -260,7 +263,7 @@ func SaveMessage(queueKey string, msg *models.SqsMessage) {
|
|||||||
asyncWrite(func() {
|
asyncWrite(func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HSet(ctx, hashKey, msg.Uuid, string(data)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, hashKey, msg.Uuid, string(data)).Err(); err != nil {
|
||||||
log.Errorf("persistence: HSet message %s/%s: %v", queueKey, msg.Uuid, err)
|
log.Errorf("persistence: HSet message %s/%s: %v", queueKey, msg.Uuid, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -273,7 +276,7 @@ func SaveMessage(queueKey string, msg *models.SqsMessage) {
|
|||||||
// Ключ: ssq:msg:{queueKey}, поле: msg.Uuid, значение: JSON(SqsMessage).
|
// Ключ: ssq:msg:{queueKey}, поле: msg.Uuid, значение: JSON(SqsMessage).
|
||||||
// Если Client == nil (memory-only режим) — возвращает nil (персистить некуда).
|
// Если Client == nil (memory-only режим) — возвращает nil (персистить некуда).
|
||||||
func SaveMessageSync(queueKey string, msg *models.SqsMessage) error {
|
func SaveMessageSync(queueKey string, msg *models.SqsMessage) error {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(msg)
|
data, err := json.Marshal(msg)
|
||||||
@@ -283,7 +286,7 @@ func SaveMessageSync(queueKey string, msg *models.SqsMessage) error {
|
|||||||
hashKey := redisMsgHashPrefix + queueKey
|
hashKey := redisMsgHashPrefix + queueKey
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HSet(ctx, hashKey, msg.Uuid, string(data)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, hashKey, msg.Uuid, string(data)).Err(); err != nil {
|
||||||
return fmt.Errorf("HSet message %s/%s: %w", queueKey, msg.Uuid, err)
|
return fmt.Errorf("HSet message %s/%s: %w", queueKey, msg.Uuid, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -292,7 +295,7 @@ func SaveMessageSync(queueKey string, msg *models.SqsMessage) error {
|
|||||||
// SaveMessages — сохраняет несколько сообщений в Redis одним pipeline.
|
// SaveMessages — сохраняет несколько сообщений в Redis одним pipeline.
|
||||||
// Вызывать под Lock.
|
// Вызывать под Lock.
|
||||||
func SaveMessages(queueKey string, msgs []models.SqsMessage) {
|
func SaveMessages(queueKey string, msgs []models.SqsMessage) {
|
||||||
if Client == nil || len(msgs) == 0 {
|
if Client.Load() == nil || len(msgs) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Сериализуем все сообщения синхронно под Lock
|
// Сериализуем все сообщения синхронно под Lock
|
||||||
@@ -317,7 +320,7 @@ func SaveMessages(queueKey string, msgs []models.SqsMessage) {
|
|||||||
for k, v := range fields {
|
for k, v := range fields {
|
||||||
args = append(args, k, v)
|
args = append(args, k, v)
|
||||||
}
|
}
|
||||||
if err := Client.HSet(ctx, hashKey, args...).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, hashKey, args...).Err(); err != nil {
|
||||||
log.Errorf("persistence: HSet messages %s (%d msgs): %v", queueKey, len(fields), err)
|
log.Errorf("persistence: HSet messages %s (%d msgs): %v", queueKey, len(fields), err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -327,7 +330,7 @@ func SaveMessages(queueKey string, msgs []models.SqsMessage) {
|
|||||||
// Используется на пути SendMessageBatch: ответ 200 выдаётся только после записи
|
// Используется на пути SendMessageBatch: ответ 200 выдаётся только после записи
|
||||||
// всех сообщений (как в одиночном SendMessage). Возвращает ошибку для отката.
|
// всех сообщений (как в одиночном SendMessage). Возвращает ошибку для отката.
|
||||||
func SaveMessagesSync(queueKey string, msgs []models.SqsMessage) error {
|
func SaveMessagesSync(queueKey string, msgs []models.SqsMessage) error {
|
||||||
if Client == nil || len(msgs) == 0 {
|
if Client.Load() == nil || len(msgs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fields := make(map[string]string, len(msgs))
|
fields := make(map[string]string, len(msgs))
|
||||||
@@ -348,7 +351,7 @@ func SaveMessagesSync(queueKey string, msgs []models.SqsMessage) error {
|
|||||||
for k, v := range fields {
|
for k, v := range fields {
|
||||||
args = append(args, k, v)
|
args = append(args, k, v)
|
||||||
}
|
}
|
||||||
if err := Client.HSet(ctx, hashKey, args...).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, hashKey, args...).Err(); err != nil {
|
||||||
return fmt.Errorf("HSet messages %s (%d msgs): %w", queueKey, len(fields), err)
|
return fmt.Errorf("HSet messages %s (%d msgs): %w", queueKey, len(fields), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -359,13 +362,13 @@ func SaveMessagesSync(queueKey string, msgs []models.SqsMessage) error {
|
|||||||
// сообщение «воскреснет» из Redis (факт: удалённые очереди возвращались
|
// сообщение «воскреснет» из Redis (факт: удалённые очереди возвращались
|
||||||
// после рестарта, когда удаления были асинхронными).
|
// после рестарта, когда удаления были асинхронными).
|
||||||
func DeleteMessagePersist(queueKey string, msgUuid string) {
|
func DeleteMessagePersist(queueKey string, msgUuid string) {
|
||||||
if Client == nil || msgUuid == "" {
|
if Client.Load() == nil || msgUuid == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hashKey := redisMsgHashPrefix + queueKey
|
hashKey := redisMsgHashPrefix + queueKey
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HDel(ctx, hashKey, msgUuid).Err(); err != nil {
|
if err := Client.Load().HDel(ctx, hashKey, msgUuid).Err(); err != nil {
|
||||||
log.Errorf("persistence: HDel message %s/%s: %v", queueKey, msgUuid, err)
|
log.Errorf("persistence: HDel message %s/%s: %v", queueKey, msgUuid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,13 +376,13 @@ func DeleteMessagePersist(queueKey string, msgUuid string) {
|
|||||||
// DeleteMessagesPersist — удаляет несколько сообщений из Redis СИНХРОННО
|
// DeleteMessagesPersist — удаляет несколько сообщений из Redis СИНХРОННО
|
||||||
// (иначе удалённые сообщения воскреснут после рестарта пода).
|
// (иначе удалённые сообщения воскреснут после рестарта пода).
|
||||||
func DeleteMessagesPersist(queueKey string, uuids []string) {
|
func DeleteMessagesPersist(queueKey string, uuids []string) {
|
||||||
if Client == nil || len(uuids) == 0 {
|
if Client.Load() == nil || len(uuids) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hashKey := redisMsgHashPrefix + queueKey
|
hashKey := redisMsgHashPrefix + queueKey
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HDel(ctx, hashKey, uuids...).Err(); err != nil {
|
if err := Client.Load().HDel(ctx, hashKey, uuids...).Err(); err != nil {
|
||||||
log.Errorf("persistence: HDel messages %s (%d): %v", queueKey, len(uuids), err)
|
log.Errorf("persistence: HDel messages %s (%d): %v", queueKey, len(uuids), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,20 +391,20 @@ func DeleteMessagesPersist(queueKey string, uuids []string) {
|
|||||||
// Синхронность обязательна: при рестарте пода удалённые сообщения не должны
|
// Синхронность обязательна: при рестарте пода удалённые сообщения не должны
|
||||||
// воскреснуть (факт: асинхронное удаление очередей приводило к их возврату).
|
// воскреснуть (факт: асинхронное удаление очередей приводило к их возврату).
|
||||||
func PurgeMessagesPersist(queueKey string) {
|
func PurgeMessagesPersist(queueKey string) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hashKey := redisMsgHashPrefix + queueKey
|
hashKey := redisMsgHashPrefix + queueKey
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.Del(ctx, hashKey).Err(); err != nil {
|
if err := Client.Load().Del(ctx, hashKey).Err(); err != nil {
|
||||||
log.Errorf("persistence: DEL messages hash %s: %v", queueKey, err)
|
log.Errorf("persistence: DEL messages hash %s: %v", queueKey, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteQueue — удаляет метаданные очереди И все её сообщения из Redis асинхронно.
|
// DeleteQueue — удаляет метаданные очереди И все её сообщения из Redis асинхронно.
|
||||||
func DeleteQueue(key string) {
|
func DeleteQueue(key string) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSeq := nextQueueWriteSeq(key)
|
writeSeq := nextQueueWriteSeq(key)
|
||||||
@@ -415,11 +418,11 @@ func DeleteQueue(key string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Удаляем метаданные из общего HASH
|
// Удаляем метаданные из общего HASH
|
||||||
if err := Client.HDel(ctx, redisHashQueues, key).Err(); err != nil {
|
if err := Client.Load().HDel(ctx, redisHashQueues, key).Err(); err != nil {
|
||||||
log.Errorf("persistence: HDel queue meta %q: %v", key, err)
|
log.Errorf("persistence: HDel queue meta %q: %v", key, err)
|
||||||
}
|
}
|
||||||
// Удаляем весь HASH с сообщениями
|
// Удаляем весь HASH с сообщениями
|
||||||
if err := Client.Del(ctx, msgHashKey).Err(); err != nil {
|
if err := Client.Load().Del(ctx, msgHashKey).Err(); err != nil {
|
||||||
log.Errorf("persistence: DEL messages hash %q: %v", key, err)
|
log.Errorf("persistence: DEL messages hash %q: %v", key, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,14 +432,14 @@ func DeleteQueue(key string) {
|
|||||||
// Миграция v1→v2: если в ssq:queues лежит JSON со встроенными Messages,
|
// Миграция v1→v2: если в ssq:queues лежит JSON со встроенными Messages,
|
||||||
// они извлекаются в отдельный HASH и метаданные пересохраняются без Messages.
|
// они извлекаются в отдельный HASH и метаданные пересохраняются без Messages.
|
||||||
func LoadAllQueues() (map[string]*models.Queue, error) {
|
func LoadAllQueues() (map[string]*models.Queue, error) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// 1. Загружаем метаданные очередей
|
// 1. Загружаем метаданные очередей
|
||||||
raw, err := Client.HGetAll(ctx, redisHashQueues).Result()
|
raw, err := Client.Load().HGetAll(ctx, redisHashQueues).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("redis HGetAll queues: %w", err)
|
return nil, fmt.Errorf("redis HGetAll queues: %w", err)
|
||||||
}
|
}
|
||||||
@@ -496,7 +499,7 @@ func LoadAllQueues() (map[string]*models.Queue, error) {
|
|||||||
args = append(args, q.Messages[i].Uuid, string(data))
|
args = append(args, q.Messages[i].Uuid, string(data))
|
||||||
}
|
}
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if err := Client.HSet(ctx, hashKey, args...).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, hashKey, args...).Err(); err != nil {
|
||||||
log.Errorf("persistence: migrate HSet messages %q: %v", k, err)
|
log.Errorf("persistence: migrate HSet messages %q: %v", k, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,7 +507,7 @@ func LoadAllQueues() (map[string]*models.Queue, error) {
|
|||||||
// Пересохраняем метаданные без Messages
|
// Пересохраняем метаданные без Messages
|
||||||
metaData, err := marshalQueueMeta(q)
|
metaData, err := marshalQueueMeta(q)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err := Client.HSet(ctx, redisHashQueues, k, string(metaData)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, redisHashQueues, k, string(metaData)).Err(); err != nil {
|
||||||
log.Errorf("persistence: migrate HSet meta %q: %v", k, err)
|
log.Errorf("persistence: migrate HSet meta %q: %v", k, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,7 +517,7 @@ func LoadAllQueues() (map[string]*models.Queue, error) {
|
|||||||
// 3. Загружаем сообщения из отдельных HASH'ов для каждой очереди
|
// 3. Загружаем сообщения из отдельных HASH'ов для каждой очереди
|
||||||
for k, q := range queues {
|
for k, q := range queues {
|
||||||
hashKey := redisMsgHashPrefix + k
|
hashKey := redisMsgHashPrefix + k
|
||||||
msgRaw, err := Client.HGetAll(ctx, hashKey).Result()
|
msgRaw, err := Client.Load().HGetAll(ctx, hashKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("persistence: HGetAll messages %q: %v", k, err)
|
log.Errorf("persistence: HGetAll messages %q: %v", k, err)
|
||||||
continue
|
continue
|
||||||
@@ -565,7 +568,7 @@ func LoadAllQueues() (map[string]*models.Queue, error) {
|
|||||||
// Принимает []byte чтобы избежать циклического импорта с пакетом tenant.
|
// Принимает []byte чтобы избежать циклического импорта с пакетом tenant.
|
||||||
// Сериализацию делает вызывающий (tenant_store.go).
|
// Сериализацию делает вызывающий (tenant_store.go).
|
||||||
func SaveTenantRaw(id string, jsonData []byte) {
|
func SaveTenantRaw(id string, jsonData []byte) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Копируем bytes — вызывающий может переиспользовать буфер
|
// Копируем bytes — вызывающий может переиспользовать буфер
|
||||||
@@ -583,11 +586,11 @@ func SaveTenantRaw(id string, jsonData []byte) {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HSet(ctx, redisHashTenants, id, string(dataCopy)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, redisHashTenants, id, string(dataCopy)).Err(); err != nil {
|
||||||
log.Errorf("persistence: HSet tenant %q: %v", id, err)
|
log.Errorf("persistence: HSet tenant %q: %v", id, err)
|
||||||
}
|
}
|
||||||
if meta.AccessKey != "" {
|
if meta.AccessKey != "" {
|
||||||
if err := Client.HSet(ctx, redisHashTenantAK, meta.AccessKey, string(dataCopy)).Err(); err != nil {
|
if err := Client.Load().HSet(ctx, redisHashTenantAK, meta.AccessKey, string(dataCopy)).Err(); err != nil {
|
||||||
log.Errorf("persistence: HSet tenant_ak %q: %v", meta.AccessKey, err)
|
log.Errorf("persistence: HSet tenant_ak %q: %v", meta.AccessKey, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,12 +600,12 @@ func SaveTenantRaw(id string, jsonData []byte) {
|
|||||||
// LoadTenantByAccessKey — read-through тенанта по accessKey (для auth-middleware).
|
// LoadTenantByAccessKey — read-through тенанта по accessKey (для auth-middleware).
|
||||||
// Возвращает сырой JSON; десериализацию делает вызывающий.
|
// Возвращает сырой JSON; десериализацию делает вызывающий.
|
||||||
func LoadTenantByAccessKey(accessKey string) ([]byte, bool) {
|
func LoadTenantByAccessKey(accessKey string) ([]byte, bool) {
|
||||||
if Client == nil || accessKey == "" {
|
if Client.Load() == nil || accessKey == "" {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
v, err := Client.HGet(ctx, redisHashTenantAK, accessKey).Result()
|
v, err := Client.Load().HGet(ctx, redisHashTenantAK, accessKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -613,7 +616,7 @@ func LoadTenantByAccessKey(accessKey string) ([]byte, bool) {
|
|||||||
// Удаляет из ОБОИХ индексов: ssq:tenants (по id) и ssq:tenant_ak (по accessKey),
|
// Удаляет из ОБОИХ индексов: ssq:tenants (по id) и ssq:tenant_ak (по accessKey),
|
||||||
// иначе LoadTenantByAccessKey вернёт удалённого тенанта и отзыв ключей не сработает.
|
// иначе LoadTenantByAccessKey вернёт удалённого тенанта и отзыв ключей не сработает.
|
||||||
func DeleteTenant(id, accessKey string) {
|
func DeleteTenant(id, accessKey string) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSeq := nextTenantWriteSeq(id)
|
writeSeq := nextTenantWriteSeq(id)
|
||||||
@@ -623,11 +626,11 @@ func DeleteTenant(id, accessKey string) {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := Client.HDel(ctx, redisHashTenants, id).Err(); err != nil {
|
if err := Client.Load().HDel(ctx, redisHashTenants, id).Err(); err != nil {
|
||||||
log.Errorf("persistence: HDel tenant %q: %v", id, err)
|
log.Errorf("persistence: HDel tenant %q: %v", id, err)
|
||||||
}
|
}
|
||||||
if accessKey != "" {
|
if accessKey != "" {
|
||||||
if err := Client.HDel(ctx, redisHashTenantAK, accessKey).Err(); err != nil {
|
if err := Client.Load().HDel(ctx, redisHashTenantAK, accessKey).Err(); err != nil {
|
||||||
log.Errorf("persistence: HDel tenant_ak %q: %v", accessKey, err)
|
log.Errorf("persistence: HDel tenant_ak %q: %v", accessKey, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,12 +666,12 @@ func isLatestTenantWriteSeq(id string, seq uint64) bool {
|
|||||||
// LoadAllTenantsRaw — загружает всех тенантов из Redis при старте.
|
// LoadAllTenantsRaw — загружает всех тенантов из Redis при старте.
|
||||||
// Возвращает map[tenantID]rawJSON — десериализацию делает tenant_store.go.
|
// Возвращает map[tenantID]rawJSON — десериализацию делает tenant_store.go.
|
||||||
func LoadAllTenantsRaw() (map[string][]byte, error) {
|
func LoadAllTenantsRaw() (map[string][]byte, error) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
raw, err := Client.HGetAll(ctx, redisHashTenants).Result()
|
raw, err := Client.Load().HGetAll(ctx, redisHashTenants).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("redis HGetAll tenants: %w", err)
|
return nil, fmt.Errorf("redis HGetAll tenants: %w", err)
|
||||||
}
|
}
|
||||||
@@ -690,12 +693,12 @@ func LoadAllTenantsRaw() (map[string][]byte, error) {
|
|||||||
|
|
||||||
// TryAcquireLock — пытается взять лок (SETNX с TTL). true — лок наш.
|
// TryAcquireLock — пытается взять лок (SETNX с TTL). true — лок наш.
|
||||||
func TryAcquireLock(key, value string, ttl time.Duration) (bool, error) {
|
func TryAcquireLock(key, value string, ttl time.Duration) (bool, error) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return Client.SetNX(ctx, key, value, ttl).Result()
|
return Client.Load().SetNX(ctx, key, value, ttl).Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// refreshLockScript — продлить TTL, ТОЛЬКО если лок всё ещё наш (атомарно).
|
// refreshLockScript — продлить TTL, ТОЛЬКО если лок всё ещё наш (атомарно).
|
||||||
@@ -716,12 +719,12 @@ end`)
|
|||||||
|
|
||||||
// RefreshLock — продлевает TTL лока, если он всё ещё наш. false — лок потерян.
|
// RefreshLock — продлевает TTL лока, если он всё ещё наш. false — лок потерян.
|
||||||
func RefreshLock(key, value string, ttl time.Duration) (bool, error) {
|
func RefreshLock(key, value string, ttl time.Duration) (bool, error) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
n, err := refreshLockScript.Run(ctx, Client, []string{key}, value, ttl.Milliseconds()).Int()
|
n, err := refreshLockScript.Run(ctx, Client.Load(), []string{key}, value, ttl.Milliseconds()).Int()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -730,12 +733,12 @@ func RefreshLock(key, value string, ttl time.Duration) (bool, error) {
|
|||||||
|
|
||||||
// ReleaseLock — освобождает лок (если он наш). Вызывается при graceful shutdown.
|
// ReleaseLock — освобождает лок (если он наш). Вызывается при graceful shutdown.
|
||||||
func ReleaseLock(key, value string) {
|
func ReleaseLock(key, value string) {
|
||||||
if Client == nil {
|
if Client.Load() == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if _, err := releaseLockScript.Run(ctx, Client, []string{key}, value).Int(); err != nil {
|
if _, err := releaseLockScript.Run(ctx, Client.Load(), []string{key}, value).Int(); err != nil {
|
||||||
log.Errorf("persistence: release lock %q: %v", key, err)
|
log.Errorf("persistence: release lock %q: %v", key, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user