Fix Services & Update the middleware && others
This commit is contained in:
@@ -4,46 +4,54 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gemini-balancer/internal/store"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
ReconnectDelay = 5 * time.Second
|
||||
ReloadTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// LoaderFunc
|
||||
type LoaderFunc[T any] func() (T, error)
|
||||
|
||||
// CacheSyncer
|
||||
type CacheSyncer[T any] struct {
|
||||
mu sync.RWMutex
|
||||
cache T
|
||||
loader LoaderFunc[T]
|
||||
store store.Store
|
||||
channelName string
|
||||
logger *logrus.Entry
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewCacheSyncer
|
||||
func NewCacheSyncer[T any](
|
||||
loader LoaderFunc[T],
|
||||
store store.Store,
|
||||
channelName string,
|
||||
logger *logrus.Logger,
|
||||
) (*CacheSyncer[T], error) {
|
||||
s := &CacheSyncer[T]{
|
||||
loader: loader,
|
||||
store: store,
|
||||
channelName: channelName,
|
||||
logger: logger.WithField("component", fmt.Sprintf("CacheSyncer[%s]", channelName)),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
if err := s.reload(); err != nil {
|
||||
return nil, fmt.Errorf("initial load for %s failed: %w", channelName, err)
|
||||
return nil, fmt.Errorf("initial load failed: %w", err)
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go s.listenForUpdates()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Get, Invalidate, Stop, reload 方法 .
|
||||
func (s *CacheSyncer[T]) Get() T {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@@ -51,33 +59,60 @@ func (s *CacheSyncer[T]) Get() T {
|
||||
}
|
||||
|
||||
func (s *CacheSyncer[T]) Invalidate() error {
|
||||
log.Printf("INFO: Publishing invalidation notification on channel '%s'", s.channelName)
|
||||
return s.store.Publish(context.Background(), s.channelName, []byte("reload"))
|
||||
s.logger.Info("Publishing invalidation notification")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.store.Publish(ctx, s.channelName, []byte("reload")); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to publish invalidation")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CacheSyncer[T]) Stop() {
|
||||
close(s.stopChan)
|
||||
s.wg.Wait()
|
||||
log.Printf("INFO: CacheSyncer for channel '%s' stopped.", s.channelName)
|
||||
s.logger.Info("CacheSyncer stopped")
|
||||
}
|
||||
|
||||
func (s *CacheSyncer[T]) reload() error {
|
||||
log.Printf("INFO: Reloading cache for channel '%s'...", s.channelName)
|
||||
newData, err := s.loader()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to reload cache for '%s': %v", s.channelName, err)
|
||||
return err
|
||||
s.logger.Info("Reloading cache...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ReloadTimeout)
|
||||
defer cancel()
|
||||
|
||||
type result struct {
|
||||
data T
|
||||
err error
|
||||
}
|
||||
resultChan := make(chan result, 1)
|
||||
|
||||
go func() {
|
||||
data, err := s.loader()
|
||||
resultChan <- result{data, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case res := <-resultChan:
|
||||
if res.err != nil {
|
||||
s.logger.WithError(res.err).Error("Failed to reload cache")
|
||||
return res.err
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.cache = res.data
|
||||
s.mu.Unlock()
|
||||
s.logger.Info("Cache reloaded successfully")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
s.logger.Error("Cache reload timeout")
|
||||
return fmt.Errorf("reload timeout after %v", ReloadTimeout)
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.cache = newData
|
||||
s.mu.Unlock()
|
||||
log.Printf("INFO: Cache for channel '%s' reloaded successfully.", s.channelName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenForUpdates ...
|
||||
func (s *CacheSyncer[T]) listenForUpdates() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.stopChan:
|
||||
@@ -85,31 +120,39 @@ func (s *CacheSyncer[T]) listenForUpdates() {
|
||||
default:
|
||||
}
|
||||
|
||||
subscription, err := s.store.Subscribe(context.Background(), s.channelName)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed to subscribe to '%s', retrying in 5s: %v", s.channelName, err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
log.Printf("INFO: Subscribed to channel '%s' for cache invalidation.", s.channelName)
|
||||
|
||||
subscriberLoop:
|
||||
for {
|
||||
if err := s.subscribeAndListen(); err != nil {
|
||||
s.logger.WithError(err).Warnf("Subscription error, retrying in %v", ReconnectDelay)
|
||||
select {
|
||||
case _, ok := <-subscription.Channel():
|
||||
if !ok {
|
||||
log.Printf("WARN: Subscription channel '%s' closed, will re-subscribe.", s.channelName)
|
||||
break subscriberLoop
|
||||
}
|
||||
log.Printf("INFO: Received invalidation notification on '%s', reloading cache.", s.channelName)
|
||||
if err := s.reload(); err != nil {
|
||||
log.Printf("ERROR: Failed to reload cache for '%s' after notification: %v", s.channelName, err)
|
||||
}
|
||||
case <-time.After(ReconnectDelay):
|
||||
case <-s.stopChan:
|
||||
subscription.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
subscription.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CacheSyncer[T]) subscribeAndListen() error {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
subscription, err := s.store.Subscribe(ctx, s.channelName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe: %w", err)
|
||||
}
|
||||
defer subscription.Close()
|
||||
s.logger.Info("Subscribed to channel")
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-subscription.Channel():
|
||||
if !ok {
|
||||
return fmt.Errorf("subscription channel closed")
|
||||
}
|
||||
s.logger.WithField("message", string(msg.Payload)).Info("Received invalidation notification")
|
||||
if err := s.reload(); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to reload after notification")
|
||||
}
|
||||
case <-s.stopChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user