Compare commits
13
Commits
master
..
b7e9560bdc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e9560bdc | ||
|
|
2aedf6314f | ||
|
|
b90d7ec808 | ||
|
|
a47462ffd4 | ||
|
|
dfe2b6c806 | ||
|
|
c68b77b1b7 | ||
|
|
597e1cf840 | ||
|
|
4382d345f6 | ||
|
|
a8180f5115 | ||
|
|
915b5dd0bb | ||
|
|
896d518c54 | ||
|
|
c70c2e9f1c | ||
|
|
65fe52ceb0 |
@@ -0,0 +1,18 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 blueocean-go
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||||
|
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
# reconnect
|
||||||
|
|
||||||
|
一个通用的Go语言重连库,支持Redis、PostgreSQL和etcd的自动重连功能。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 🔄 自动重连机制
|
||||||
|
- ⚙️ 可配置的重连策略
|
||||||
|
- 🔍 连接健康检查
|
||||||
|
- 📊 连接状态监控
|
||||||
|
- 🎯 支持多种服务(Redis、PostgreSQL、etcd)
|
||||||
|
- 🛡️ 线程安全
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.whblueocean.cn/blueocean-go/reconnect
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### Redis重连
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
options := &redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "",
|
||||||
|
DB: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("Redis重连成功!")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := reconnect.RedisManager(options, config)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 使用Redis客户端
|
||||||
|
redisClient := reconnect.NewRedisClient(options)
|
||||||
|
redisClient.Connect(ctx)
|
||||||
|
client := redisClient.GetClient()
|
||||||
|
// ... 使用client进行操作
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL重连
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dsn := "host=localhost user=postgres password=postgres dbname=testdb sslmode=disable"
|
||||||
|
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("PostgreSQL重连成功!")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := reconnect.PostgresManager(
|
||||||
|
dsn,
|
||||||
|
25, // maxConnections
|
||||||
|
5, // maxIdleConnections
|
||||||
|
30*time.Minute, // maxConnectionAge
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 使用数据库连接
|
||||||
|
pgClient := reconnect.NewPostgresClient(dsn, 25, 5, 30*time.Minute)
|
||||||
|
pgClient.Connect(ctx)
|
||||||
|
db := pgClient.GetDB()
|
||||||
|
// ... 使用db进行操作
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### etcd重连
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
etcdConfig := clientv3.Config{
|
||||||
|
Endpoints: []string{"localhost:2379"},
|
||||||
|
DialTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("etcd重连成功!")
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := reconnect.EtcdManager(etcdConfig, config)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 使用etcd客户端
|
||||||
|
etcdClient := reconnect.NewEtcdClient(etcdConfig)
|
||||||
|
etcdClient.Connect(ctx)
|
||||||
|
client := etcdClient.GetClient()
|
||||||
|
// ... 使用client进行操作
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
// MaxRetries 最大重试次数,0表示无限重试
|
||||||
|
MaxRetries int
|
||||||
|
|
||||||
|
// RetryInterval 重试间隔
|
||||||
|
RetryInterval time.Duration
|
||||||
|
|
||||||
|
// Timeout 连接超时时间
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// OnReconnect 重连成功后的回调函数
|
||||||
|
OnReconnect func()
|
||||||
|
|
||||||
|
// OnDisconnect 断开连接时的回调函数
|
||||||
|
OnDisconnect func(error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API文档
|
||||||
|
|
||||||
|
### Manager
|
||||||
|
|
||||||
|
重连管理器,负责监控连接状态并自动重连。
|
||||||
|
|
||||||
|
- `Start(ctx context.Context) error` - 启动连接并开始监控
|
||||||
|
- `Stop() error` - 停止重连管理器
|
||||||
|
- `IsConnected() bool` - 返回当前连接状态
|
||||||
|
- `WaitForError() error` - 等待错误(用于阻塞等待)
|
||||||
|
|
||||||
|
### RedisClient
|
||||||
|
|
||||||
|
Redis客户端包装器。
|
||||||
|
|
||||||
|
- `Connect(ctx context.Context) error` - 建立连接
|
||||||
|
- `Close() error` - 关闭连接
|
||||||
|
- `Ping(ctx context.Context) error` - 健康检查
|
||||||
|
- `IsConnected() bool` - 检查连接状态
|
||||||
|
- `GetClient() *redis.Client` - 获取底层Redis客户端
|
||||||
|
|
||||||
|
### PostgresClient
|
||||||
|
|
||||||
|
PostgreSQL客户端包装器。
|
||||||
|
|
||||||
|
- `Connect(ctx context.Context) error` - 建立连接
|
||||||
|
- `Close() error` - 关闭连接
|
||||||
|
- `Ping(ctx context.Context) error` - 健康检查
|
||||||
|
- `IsConnected() bool` - 检查连接状态
|
||||||
|
- `GetDB() *sql.DB` - 获取底层数据库连接
|
||||||
|
|
||||||
|
### EtcdClient
|
||||||
|
|
||||||
|
etcd客户端包装器。
|
||||||
|
|
||||||
|
- `Connect(ctx context.Context) error` - 建立连接
|
||||||
|
- `Close() error` - 关闭连接
|
||||||
|
- `Ping(ctx context.Context) error` - 健康检查
|
||||||
|
- `IsConnected() bool` - 检查连接状态
|
||||||
|
- `GetClient() *clientv3.Client` - 获取底层etcd客户端
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
更多示例代码请查看 `examples/` 目录。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 blueocean-go
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package reconnect
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// StrategyType 定义重连策略类型
|
|
||||||
type StrategyType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StrategyExponentialBackoff 指数退避策略
|
|
||||||
StrategyExponentialBackoff StrategyType = "exponential"
|
|
||||||
// StrategyFixedInterval 固定间隔策略
|
|
||||||
StrategyFixedInterval StrategyType = "fixed"
|
|
||||||
// StrategyLinearBackoff 线性退避策略
|
|
||||||
StrategyLinearBackoff StrategyType = "linear"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConnectionState 连接状态
|
|
||||||
type ConnectionState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StateDisconnected 断开连接
|
|
||||||
StateDisconnected ConnectionState = iota
|
|
||||||
// StateConnecting 正在连接
|
|
||||||
StateConnecting
|
|
||||||
// StateConnected 已连接
|
|
||||||
StateConnected
|
|
||||||
// StateReconnecting 正在重连
|
|
||||||
StateReconnecting
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s ConnectionState) String() string {
|
|
||||||
switch s {
|
|
||||||
case StateDisconnected:
|
|
||||||
return "disconnected"
|
|
||||||
case StateConnecting:
|
|
||||||
return "connecting"
|
|
||||||
case StateConnected:
|
|
||||||
return "connected"
|
|
||||||
case StateReconnecting:
|
|
||||||
return "reconnecting"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config 重连配置
|
|
||||||
type Config struct {
|
|
||||||
// Strategy 重连策略类型
|
|
||||||
Strategy StrategyType
|
|
||||||
|
|
||||||
// MaxRetries 最大重试次数,-1 表示无限重试
|
|
||||||
MaxRetries int
|
|
||||||
|
|
||||||
// InitialDelay 初始延迟时间
|
|
||||||
InitialDelay time.Duration
|
|
||||||
|
|
||||||
// MaxDelay 最大延迟时间
|
|
||||||
MaxDelay time.Duration
|
|
||||||
|
|
||||||
// Multiplier 指数退避乘数(仅用于指数退避策略)
|
|
||||||
Multiplier float64
|
|
||||||
|
|
||||||
// LinearIncrement 线性增量(仅用于线性退避策略)
|
|
||||||
LinearIncrement time.Duration
|
|
||||||
|
|
||||||
// HealthCheckInterval 健康检查间隔
|
|
||||||
HealthCheckInterval time.Duration
|
|
||||||
|
|
||||||
// HealthCheckTimeout 健康检查超时时间
|
|
||||||
HealthCheckTimeout time.Duration
|
|
||||||
|
|
||||||
// OnStateChange 状态变化回调函数
|
|
||||||
OnStateChange func(oldState, newState ConnectionState)
|
|
||||||
|
|
||||||
// OnReconnect 重连回调函数(重连成功时调用)
|
|
||||||
OnReconnect func(attempt int)
|
|
||||||
|
|
||||||
// OnError 错误回调函数
|
|
||||||
OnError func(err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig 返回默认配置
|
|
||||||
func DefaultConfig() Config {
|
|
||||||
return Config{
|
|
||||||
Strategy: StrategyExponentialBackoff,
|
|
||||||
MaxRetries: -1, // 无限重试
|
|
||||||
InitialDelay: 1 * time.Second,
|
|
||||||
MaxDelay: 30 * time.Second,
|
|
||||||
Multiplier: 2.0,
|
|
||||||
LinearIncrement: 1 * time.Second,
|
|
||||||
HealthCheckInterval: 10 * time.Second,
|
|
||||||
HealthCheckTimeout: 5 * time.Second,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate 验证配置
|
|
||||||
func (c *Config) Validate() {
|
|
||||||
if c.InitialDelay <= 0 {
|
|
||||||
c.InitialDelay = 1 * time.Second
|
|
||||||
}
|
|
||||||
if c.MaxDelay <= 0 {
|
|
||||||
c.MaxDelay = 30 * time.Second
|
|
||||||
}
|
|
||||||
if c.Multiplier <= 0 {
|
|
||||||
c.Multiplier = 2.0
|
|
||||||
}
|
|
||||||
if c.LinearIncrement <= 0 {
|
|
||||||
c.LinearIncrement = 1 * time.Second
|
|
||||||
}
|
|
||||||
if c.HealthCheckInterval <= 0 {
|
|
||||||
c.HealthCheckInterval = 10 * time.Second
|
|
||||||
}
|
|
||||||
if c.HealthCheckTimeout <= 0 {
|
|
||||||
c.HealthCheckTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
-170
@@ -1,170 +0,0 @@
|
|||||||
package reconnect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DatabaseConfig 数据库配置
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
// Dialector gorm dialector(如 postgres.Open(dsn), mysql.Open(dsn))
|
|
||||||
Dialector gorm.Dialector
|
|
||||||
// GormConfig gorm配置
|
|
||||||
GormConfig *gorm.Config
|
|
||||||
// MaxIdleConns 最大空闲连接数
|
|
||||||
MaxIdleConns int
|
|
||||||
// MaxOpenConns 最大打开连接数
|
|
||||||
MaxOpenConns int
|
|
||||||
// ConnMaxLifetime 连接最大生命周期
|
|
||||||
ConnMaxLifetime time.Duration
|
|
||||||
// ReconnectConfig 重连配置
|
|
||||||
ReconnectConfig Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatabaseClient 带重连功能的数据库客户端
|
|
||||||
type DatabaseClient struct {
|
|
||||||
config DatabaseConfig
|
|
||||||
db *gorm.DB
|
|
||||||
sqlDB *sql.DB
|
|
||||||
manager *ConnectionManager
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// databaseConnector 数据库连接器
|
|
||||||
type databaseConnector struct {
|
|
||||||
client *DatabaseClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *databaseConnector) Connect(ctx context.Context) error {
|
|
||||||
d.client.mu.Lock()
|
|
||||||
defer d.client.mu.Unlock()
|
|
||||||
|
|
||||||
gormConfig := d.client.config.GormConfig
|
|
||||||
if gormConfig == nil {
|
|
||||||
gormConfig = &gorm.Config{}
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := gorm.Open(d.client.config.Dialector, gormConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlDB, err := db.DB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置连接池
|
|
||||||
if d.client.config.MaxIdleConns > 0 {
|
|
||||||
sqlDB.SetMaxIdleConns(d.client.config.MaxIdleConns)
|
|
||||||
}
|
|
||||||
if d.client.config.MaxOpenConns > 0 {
|
|
||||||
sqlDB.SetMaxOpenConns(d.client.config.MaxOpenConns)
|
|
||||||
}
|
|
||||||
if d.client.config.ConnMaxLifetime > 0 {
|
|
||||||
sqlDB.SetConnMaxLifetime(d.client.config.ConnMaxLifetime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证连接
|
|
||||||
if err := sqlDB.PingContext(ctx); err != nil {
|
|
||||||
sqlDB.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.client.db = db
|
|
||||||
d.client.sqlDB = sqlDB
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *databaseConnector) Close() error {
|
|
||||||
d.client.mu.Lock()
|
|
||||||
defer d.client.mu.Unlock()
|
|
||||||
|
|
||||||
if d.client.sqlDB != nil {
|
|
||||||
err := d.client.sqlDB.Close()
|
|
||||||
d.client.db = nil
|
|
||||||
d.client.sqlDB = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// databaseHealthChecker 数据库健康检查器
|
|
||||||
type databaseHealthChecker struct {
|
|
||||||
client *DatabaseClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *databaseHealthChecker) HealthCheck(ctx context.Context) error {
|
|
||||||
d.client.mu.RLock()
|
|
||||||
sqlDB := d.client.sqlDB
|
|
||||||
d.client.mu.RUnlock()
|
|
||||||
|
|
||||||
if sqlDB == nil {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
|
||||||
|
|
||||||
return sqlDB.PingContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDatabaseClient 创建带重连功能的数据库客户端
|
|
||||||
func NewDatabaseClient(cfg DatabaseConfig) (*DatabaseClient, error) {
|
|
||||||
client := &DatabaseClient{
|
|
||||||
config: cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
connector := &databaseConnector{client: client}
|
|
||||||
checker := &databaseHealthChecker{client: client}
|
|
||||||
|
|
||||||
client.manager = NewConnectionManager(connector, checker, cfg.ReconnectConfig)
|
|
||||||
|
|
||||||
// 首次连接
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := client.manager.ConnectWithRetry(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDB 获取gorm.DB实例
|
|
||||||
func (c *DatabaseClient) GetDB() *gorm.DB {
|
|
||||||
c.mu.RLock()
|
|
||||||
defer c.mu.RUnlock()
|
|
||||||
return c.db
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSqlDB 获取sql.DB实例
|
|
||||||
func (c *DatabaseClient) GetSqlDB() *sql.DB {
|
|
||||||
c.mu.RLock()
|
|
||||||
defer c.mu.RUnlock()
|
|
||||||
return c.sqlDB
|
|
||||||
}
|
|
||||||
|
|
||||||
// State 获取连接状态
|
|
||||||
func (c *DatabaseClient) State() ConnectionState {
|
|
||||||
return c.manager.State()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close 关闭客户端
|
|
||||||
func (c *DatabaseClient) Close() error {
|
|
||||||
return c.manager.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerReconnect 手动触发重连
|
|
||||||
func (c *DatabaseClient) TriggerReconnect() {
|
|
||||||
c.manager.TriggerReconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping 执行Ping命令
|
|
||||||
func (c *DatabaseClient) Ping(ctx context.Context) error {
|
|
||||||
sqlDB := c.GetSqlDB()
|
|
||||||
if sqlDB == nil {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
|
||||||
return sqlDB.PingContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -2,143 +2,107 @@ package reconnect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EtcdClientConfig etcd客户端配置
|
// EtcdClient etcd客户端包装器
|
||||||
type EtcdClientConfig struct {
|
|
||||||
// Endpoints etcd服务地址列表
|
|
||||||
Endpoints []string
|
|
||||||
// DialTimeout 连接超时时间
|
|
||||||
DialTimeout time.Duration
|
|
||||||
// Username 用户名(可选)
|
|
||||||
Username string
|
|
||||||
// Password 密码(可选)
|
|
||||||
Password string
|
|
||||||
// ReconnectConfig 重连配置
|
|
||||||
ReconnectConfig Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// EtcdClient 带重连功能的etcd客户端
|
|
||||||
type EtcdClient struct {
|
type EtcdClient struct {
|
||||||
config EtcdClientConfig
|
config clientv3.Config
|
||||||
client *clientv3.Client
|
client *clientv3.Client
|
||||||
manager *ConnectionManager
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// etcdConnector etcd连接器
|
// NewEtcdClient 创建新的etcd客户端
|
||||||
type etcdConnector struct {
|
func NewEtcdClient(config clientv3.Config) *EtcdClient {
|
||||||
client *EtcdClient
|
return &EtcdClient{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *etcdConnector) Connect(ctx context.Context) error {
|
// Connect 建立etcd连接
|
||||||
e.client.mu.Lock()
|
func (e *EtcdClient) Connect(ctx context.Context) error {
|
||||||
defer e.client.mu.Unlock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
cfg := clientv3.Config{
|
if e.client != nil {
|
||||||
Endpoints: e.client.config.Endpoints,
|
_ = e.client.Close()
|
||||||
DialTimeout: e.client.config.DialTimeout,
|
|
||||||
Username: e.client.config.Username,
|
|
||||||
Password: e.client.config.Password,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.DialTimeout == 0 {
|
client, err := clientv3.New(e.config)
|
||||||
cfg.DialTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
cli, err := clientv3.New(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("etcd new client failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证连接
|
// 测试连接
|
||||||
ctx, cancel := context.WithTimeout(ctx, cfg.DialTimeout)
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
_, err = cli.Status(ctx, cfg.Endpoints[0])
|
_, err = client.Status(timeoutCtx, e.config.Endpoints[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Close()
|
_ = client.Close()
|
||||||
return err
|
return fmt.Errorf("etcd status check failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.client.client = cli
|
e.client = client
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *etcdConnector) Close() error {
|
// Close 关闭etcd连接
|
||||||
e.client.mu.Lock()
|
func (e *EtcdClient) Close() error {
|
||||||
defer e.client.mu.Unlock()
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
if e.client.client != nil {
|
if e.client != nil {
|
||||||
err := e.client.client.Close()
|
err := e.client.Close()
|
||||||
e.client.client = nil
|
e.client = nil
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// etcdHealthChecker etcd健康检查器
|
// Ping 检查etcd连接是否健康
|
||||||
type etcdHealthChecker struct {
|
func (e *EtcdClient) Ping(ctx context.Context) error {
|
||||||
client *EtcdClient
|
e.mu.RLock()
|
||||||
}
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
func (e *etcdHealthChecker) HealthCheck(ctx context.Context) error {
|
if e.client == nil {
|
||||||
e.client.mu.RLock()
|
return fmt.Errorf("etcd client is nil")
|
||||||
cli := e.client.client
|
|
||||||
endpoints := e.client.config.Endpoints
|
|
||||||
e.client.mu.RUnlock()
|
|
||||||
|
|
||||||
if cli == nil || len(endpoints) == 0 {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := cli.Status(ctx, endpoints[0])
|
if len(e.config.Endpoints) == 0 {
|
||||||
|
return fmt.Errorf("etcd endpoints is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := e.client.Status(timeoutCtx, e.config.Endpoints[0])
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEtcdClient 创建带重连功能的etcd客户端
|
// IsConnected 检查连接状态
|
||||||
func NewEtcdClient(cfg EtcdClientConfig) (*EtcdClient, error) {
|
func (e *EtcdClient) IsConnected() bool {
|
||||||
client := &EtcdClient{
|
e.mu.RLock()
|
||||||
config: cfg,
|
defer e.mu.RUnlock()
|
||||||
}
|
|
||||||
|
|
||||||
connector := &etcdConnector{client: client}
|
return e.client != nil
|
||||||
checker := &etcdHealthChecker{client: client}
|
|
||||||
|
|
||||||
client.manager = NewConnectionManager(connector, checker, cfg.ReconnectConfig)
|
|
||||||
|
|
||||||
// 首次连接
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := client.manager.ConnectWithRetry(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient 获取etcd客户端
|
// GetClient 获取底层的etcd客户端
|
||||||
func (c *EtcdClient) GetClient() *clientv3.Client {
|
func (e *EtcdClient) GetClient() *clientv3.Client {
|
||||||
c.mu.RLock()
|
e.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer e.mu.RUnlock()
|
||||||
return c.client
|
|
||||||
|
return e.client
|
||||||
}
|
}
|
||||||
|
|
||||||
// State 获取连接状态
|
// EtcdManager 创建etcd重连管理器
|
||||||
func (c *EtcdClient) State() ConnectionState {
|
func EtcdManager(config clientv3.Config, reconnectConfig *Config) *Manager {
|
||||||
return c.manager.State()
|
client := NewEtcdClient(config)
|
||||||
}
|
return NewManager(client, reconnectConfig)
|
||||||
|
|
||||||
// Close 关闭客户端
|
|
||||||
func (c *EtcdClient) Close() error {
|
|
||||||
return c.manager.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerReconnect 手动触发重连
|
|
||||||
func (c *EtcdClient) TriggerReconnect() {
|
|
||||||
c.manager.TriggerReconnect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// etcd配置
|
||||||
|
etcdConfig := clientv3.Config{
|
||||||
|
Endpoints: []string{"localhost:2379"},
|
||||||
|
DialTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建重连配置
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.MaxRetries = 0 // 无限重试
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("etcd重连成功!")
|
||||||
|
}
|
||||||
|
config.OnDisconnect = func(err error) {
|
||||||
|
fmt.Printf("etcd连接断开: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建重连管理器
|
||||||
|
manager := reconnect.EtcdManager(etcdConfig, config)
|
||||||
|
|
||||||
|
// 启动连接
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatalf("启动失败: %v", err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 获取etcd客户端并使用
|
||||||
|
etcdClient := reconnect.NewEtcdClient(etcdConfig)
|
||||||
|
if err := etcdClient.Connect(ctx); err != nil {
|
||||||
|
log.Fatalf("连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := etcdClient.GetClient()
|
||||||
|
if client == nil {
|
||||||
|
log.Fatal("客户端为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用etcd客户端
|
||||||
|
kv := clientv3.NewKV(client)
|
||||||
|
_, err := kv.Put(ctx, "key", "value")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("设置键值失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("设置键值成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := kv.Get(ctx, "key")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取键值失败: %v", err)
|
||||||
|
} else {
|
||||||
|
if len(resp.Kvs) > 0 {
|
||||||
|
fmt.Printf("获取键值: %s = %s\n", resp.Kvs[0].Key, resp.Kvs[0].Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持运行
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// PostgreSQL连接字符串
|
||||||
|
dsn := "host=localhost user=postgres password=postgres dbname=testdb sslmode=disable"
|
||||||
|
|
||||||
|
// 创建重连配置
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.MaxRetries = 0 // 无限重试
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("PostgreSQL重连成功!")
|
||||||
|
}
|
||||||
|
config.OnDisconnect = func(err error) {
|
||||||
|
fmt.Printf("PostgreSQL连接断开: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建重连管理器
|
||||||
|
manager := reconnect.PostgresManager(
|
||||||
|
dsn,
|
||||||
|
25, // maxConnections
|
||||||
|
5, // maxIdleConnections
|
||||||
|
30*time.Minute, // maxConnectionAge
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 启动连接
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatalf("启动失败: %v", err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 获取数据库连接并使用
|
||||||
|
pgClient := reconnect.NewPostgresClient(dsn, 25, 5, 30*time.Minute)
|
||||||
|
if err := pgClient.Connect(ctx); err != nil {
|
||||||
|
log.Fatalf("连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := pgClient.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
log.Fatal("数据库连接为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用数据库连接
|
||||||
|
var version string
|
||||||
|
err := db.QueryRow("SELECT version()").Scan(&version)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("查询失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("PostgreSQL版本: %s\n", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持运行
|
||||||
|
select {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blueocean-go/reconnect"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 创建Redis选项
|
||||||
|
options := &redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "", // 无密码
|
||||||
|
DB: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建重连配置
|
||||||
|
config := reconnect.DefaultConfig()
|
||||||
|
config.RetryInterval = 3 * time.Second
|
||||||
|
config.MaxRetries = 0 // 无限重试
|
||||||
|
config.OnReconnect = func() {
|
||||||
|
fmt.Println("Redis重连成功!")
|
||||||
|
}
|
||||||
|
config.OnDisconnect = func(err error) {
|
||||||
|
fmt.Printf("Redis连接断开: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建重连管理器
|
||||||
|
manager := reconnect.RedisManager(options, config)
|
||||||
|
|
||||||
|
// 启动连接
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := manager.Start(ctx); err != nil {
|
||||||
|
log.Fatalf("启动失败: %v", err)
|
||||||
|
}
|
||||||
|
defer manager.Stop()
|
||||||
|
|
||||||
|
// 获取Redis客户端并使用
|
||||||
|
redisClient := reconnect.NewRedisClient(options)
|
||||||
|
if err := redisClient.Connect(ctx); err != nil {
|
||||||
|
log.Fatalf("连接失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := redisClient.GetClient()
|
||||||
|
if client == nil {
|
||||||
|
log.Fatal("客户端为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Redis客户端
|
||||||
|
err := client.Set(ctx, "key", "value", 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("设置键值失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("设置键值成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := client.Get(ctx, "key").Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取键值失败: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("获取键值: %s\n", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保持运行
|
||||||
|
select {}
|
||||||
|
}
|
||||||
@@ -1,32 +1,31 @@
|
|||||||
module blueocean.local/reconnect
|
module git.whblueocean.cn/blueocean-go/reconnect
|
||||||
|
|
||||||
go 1.24.0
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/lib/pq v1.10.9
|
||||||
go.etcd.io/etcd/client/v3 v3.6.5
|
github.com/redis/go-redis/v9 v9.3.0
|
||||||
google.golang.org/grpc v1.75.1
|
go.etcd.io/etcd/client/v3 v3.5.10
|
||||||
gorm.io/gorm v1.31.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
go.uber.org/zap v1.17.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
golang.org/x/net v0.17.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,74 +1,56 @@
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
|
||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k=
|
||||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U=
|
||||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao=
|
||||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
|
||||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@@ -78,20 +60,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
@@ -100,22 +82,23 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
package reconnect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GRPCClientConfig gRPC客户端配置
|
|
||||||
type GRPCClientConfig struct {
|
|
||||||
// Target gRPC服务地址
|
|
||||||
Target string
|
|
||||||
// DialOptions gRPC拨号选项
|
|
||||||
DialOptions []grpc.DialOption
|
|
||||||
// ReconnectConfig 重连配置
|
|
||||||
ReconnectConfig Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// GRPCClient 带重连功能的gRPC客户端
|
|
||||||
type GRPCClient struct {
|
|
||||||
config GRPCClientConfig
|
|
||||||
conn *grpc.ClientConn
|
|
||||||
manager *ConnectionManager
|
|
||||||
mu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// grpcConnector gRPC连接器
|
|
||||||
type grpcConnector struct {
|
|
||||||
client *GRPCClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcConnector) Connect(ctx context.Context) error {
|
|
||||||
g.client.mu.Lock()
|
|
||||||
defer g.client.mu.Unlock()
|
|
||||||
|
|
||||||
opts := g.client.config.DialOptions
|
|
||||||
if len(opts) == 0 {
|
|
||||||
opts = []grpc.DialOption{
|
|
||||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := grpc.NewClient(g.client.config.Target, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待连接就绪
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
conn.Connect()
|
|
||||||
for {
|
|
||||||
state := conn.GetState()
|
|
||||||
if state == connectivity.Ready {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if state == connectivity.TransientFailure || state == connectivity.Shutdown {
|
|
||||||
conn.Close()
|
|
||||||
return context.DeadlineExceeded
|
|
||||||
}
|
|
||||||
if !conn.WaitForStateChange(ctx, state) {
|
|
||||||
conn.Close()
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.client.conn = conn
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcConnector) Close() error {
|
|
||||||
g.client.mu.Lock()
|
|
||||||
defer g.client.mu.Unlock()
|
|
||||||
|
|
||||||
if g.client.conn != nil {
|
|
||||||
err := g.client.conn.Close()
|
|
||||||
g.client.conn = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// grpcHealthChecker gRPC健康检查器
|
|
||||||
type grpcHealthChecker struct {
|
|
||||||
client *GRPCClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *grpcHealthChecker) HealthCheck(ctx context.Context) error {
|
|
||||||
g.client.mu.RLock()
|
|
||||||
conn := g.client.conn
|
|
||||||
g.client.mu.RUnlock()
|
|
||||||
|
|
||||||
if conn == nil {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
|
||||||
|
|
||||||
state := conn.GetState()
|
|
||||||
if state == connectivity.Ready || state == connectivity.Idle {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试连接并等待
|
|
||||||
conn.Connect()
|
|
||||||
if !conn.WaitForStateChange(ctx, state) {
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
newState := conn.GetState()
|
|
||||||
if newState != connectivity.Ready && newState != connectivity.Idle {
|
|
||||||
return context.DeadlineExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGRPCClient 创建带重连功能的gRPC客户端
|
|
||||||
func NewGRPCClient(cfg GRPCClientConfig) (*GRPCClient, error) {
|
|
||||||
client := &GRPCClient{
|
|
||||||
config: cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
connector := &grpcConnector{client: client}
|
|
||||||
checker := &grpcHealthChecker{client: client}
|
|
||||||
|
|
||||||
client.manager = NewConnectionManager(connector, checker, cfg.ReconnectConfig)
|
|
||||||
|
|
||||||
// 首次连接
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := client.manager.ConnectWithRetry(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConn 获取gRPC连接
|
|
||||||
func (c *GRPCClient) GetConn() *grpc.ClientConn {
|
|
||||||
c.mu.RLock()
|
|
||||||
defer c.mu.RUnlock()
|
|
||||||
return c.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// State 获取连接状态
|
|
||||||
func (c *GRPCClient) State() ConnectionState {
|
|
||||||
return c.manager.State()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close 关闭客户端
|
|
||||||
func (c *GRPCClient) Close() error {
|
|
||||||
return c.manager.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerReconnect 手动触发重连
|
|
||||||
func (c *GRPCClient) TriggerReconnect() {
|
|
||||||
c.manager.TriggerReconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
-226
@@ -1,226 +0,0 @@
|
|||||||
package reconnect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HealthChecker 健康检查接口
|
|
||||||
type HealthChecker interface {
|
|
||||||
// HealthCheck 执行健康检查,返回 nil 表示健康
|
|
||||||
HealthCheck(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connector 连接器接口
|
|
||||||
type Connector interface {
|
|
||||||
// Connect 建立连接
|
|
||||||
Connect(ctx context.Context) error
|
|
||||||
// Close 关闭连接
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionManager 连接管理器
|
|
||||||
type ConnectionManager struct {
|
|
||||||
config Config
|
|
||||||
strategy Strategy
|
|
||||||
connector Connector
|
|
||||||
checker HealthChecker
|
|
||||||
|
|
||||||
state atomic.Int32
|
|
||||||
attempts atomic.Int32
|
|
||||||
mu sync.RWMutex
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
wg sync.WaitGroup
|
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConnectionManager 创建连接管理器
|
|
||||||
func NewConnectionManager(connector Connector, checker HealthChecker, cfg Config) *ConnectionManager {
|
|
||||||
cfg.Validate()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
cm := &ConnectionManager{
|
|
||||||
config: cfg,
|
|
||||||
strategy: NewStrategy(cfg),
|
|
||||||
connector: connector,
|
|
||||||
checker: checker,
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.state.Store(int32(StateDisconnected))
|
|
||||||
return cm
|
|
||||||
}
|
|
||||||
|
|
||||||
// State 获取当前连接状态
|
|
||||||
func (cm *ConnectionManager) State() ConnectionState {
|
|
||||||
return ConnectionState(cm.state.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
// setState 设置连接状态
|
|
||||||
func (cm *ConnectionManager) setState(newState ConnectionState) {
|
|
||||||
oldState := ConnectionState(cm.state.Swap(int32(newState)))
|
|
||||||
if oldState != newState && cm.config.OnStateChange != nil {
|
|
||||||
cm.config.OnStateChange(oldState, newState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect 建立连接并启动健康检查
|
|
||||||
func (cm *ConnectionManager) Connect(ctx context.Context) error {
|
|
||||||
cm.setState(StateConnecting)
|
|
||||||
|
|
||||||
if err := cm.connector.Connect(ctx); err != nil {
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
if cm.config.OnError != nil {
|
|
||||||
cm.config.OnError(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.setState(StateConnected)
|
|
||||||
cm.strategy.Reset()
|
|
||||||
cm.attempts.Store(0)
|
|
||||||
|
|
||||||
// 启动健康检查
|
|
||||||
if cm.checker != nil && cm.config.HealthCheckInterval > 0 {
|
|
||||||
cm.wg.Add(1)
|
|
||||||
go cm.healthCheckLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectWithRetry 带重试的连接
|
|
||||||
func (cm *ConnectionManager) ConnectWithRetry(ctx context.Context) error {
|
|
||||||
cm.setState(StateConnecting)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
return ctx.Err()
|
|
||||||
case <-cm.ctx.Done():
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
return cm.ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
attempt := int(cm.attempts.Add(1))
|
|
||||||
|
|
||||||
if err := cm.connector.Connect(ctx); err != nil {
|
|
||||||
if cm.config.OnError != nil {
|
|
||||||
cm.config.OnError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否超过最大重试次数
|
|
||||||
if cm.config.MaxRetries >= 0 && attempt >= cm.config.MaxRetries {
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
delay := cm.strategy.NextDelay(attempt)
|
|
||||||
log.Printf("[reconnect] 连接失败 (尝试 %d): %v, %v 后重试...", attempt, err, delay)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
return ctx.Err()
|
|
||||||
case <-cm.ctx.Done():
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
return cm.ctx.Err()
|
|
||||||
case <-time.After(delay):
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接成功
|
|
||||||
cm.setState(StateConnected)
|
|
||||||
cm.strategy.Reset()
|
|
||||||
cm.attempts.Store(0)
|
|
||||||
|
|
||||||
if cm.config.OnReconnect != nil && attempt > 1 {
|
|
||||||
cm.config.OnReconnect(attempt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动健康检查
|
|
||||||
if cm.checker != nil && cm.config.HealthCheckInterval > 0 {
|
|
||||||
cm.wg.Add(1)
|
|
||||||
go cm.healthCheckLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[reconnect] 连接成功")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// healthCheckLoop 健康检查循环
|
|
||||||
func (cm *ConnectionManager) healthCheckLoop() {
|
|
||||||
defer cm.wg.Done()
|
|
||||||
|
|
||||||
ticker := time.NewTicker(cm.config.HealthCheckInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-cm.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
if cm.State() != StateConnected {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(cm.ctx, cm.config.HealthCheckTimeout)
|
|
||||||
err := cm.checker.HealthCheck(ctx)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[reconnect] 健康检查失败: %v, 开始重连...", err)
|
|
||||||
if cm.config.OnError != nil {
|
|
||||||
cm.config.OnError(err)
|
|
||||||
}
|
|
||||||
cm.reconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reconnect 执行重连
|
|
||||||
func (cm *ConnectionManager) reconnect() {
|
|
||||||
if cm.State() == StateReconnecting {
|
|
||||||
return // 已经在重连中
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.setState(StateReconnecting)
|
|
||||||
|
|
||||||
// 关闭旧连接
|
|
||||||
_ = cm.connector.Close()
|
|
||||||
|
|
||||||
// 重新连接
|
|
||||||
go func() {
|
|
||||||
if err := cm.ConnectWithRetry(cm.ctx); err != nil {
|
|
||||||
log.Printf("[reconnect] 重连失败: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close 关闭连接管理器
|
|
||||||
func (cm *ConnectionManager) Close() error {
|
|
||||||
var err error
|
|
||||||
cm.closeOnce.Do(func() {
|
|
||||||
cm.cancel()
|
|
||||||
cm.wg.Wait()
|
|
||||||
err = cm.connector.Close()
|
|
||||||
cm.setState(StateDisconnected)
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerReconnect 手动触发重连
|
|
||||||
func (cm *ConnectionManager) TriggerReconnect() {
|
|
||||||
cm.reconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
package reconnect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PostgresClient PostgreSQL客户端包装器
|
||||||
|
type PostgresClient struct {
|
||||||
|
dsn string
|
||||||
|
db *sql.DB
|
||||||
|
mu sync.RWMutex
|
||||||
|
maxAge time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostgresClient 创建新的PostgreSQL客户端
|
||||||
|
func NewPostgresClient(dsn string, maxConnections int, maxIdleConnections int, maxConnectionAge time.Duration) *PostgresClient {
|
||||||
|
return &PostgresClient{
|
||||||
|
dsn: dsn,
|
||||||
|
maxAge: maxConnectionAge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect 建立PostgreSQL连接
|
||||||
|
func (p *PostgresClient) Connect(ctx context.Context) error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.db != nil {
|
||||||
|
_ = p.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", p.dsn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("postgres open failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置连接池参数
|
||||||
|
db.SetMaxOpenConns(25)
|
||||||
|
db.SetMaxIdleConns(5)
|
||||||
|
if p.maxAge > 0 {
|
||||||
|
db.SetConnMaxLifetime(p.maxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试连接
|
||||||
|
if err := db.PingContext(ctx); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return fmt.Errorf("postgres ping failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.db = db
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭PostgreSQL连接
|
||||||
|
func (p *PostgresClient) Close() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.db != nil {
|
||||||
|
err := p.db.Close()
|
||||||
|
p.db = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping 检查PostgreSQL连接是否健康
|
||||||
|
func (p *PostgresClient) Ping(ctx context.Context) error {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
|
if p.db == nil {
|
||||||
|
return fmt.Errorf("postgres db is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.db.PingContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConnected 检查连接状态
|
||||||
|
func (p *PostgresClient) IsConnected() bool {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
|
return p.db != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDB 获取底层的数据库连接
|
||||||
|
func (p *PostgresClient) GetDB() *sql.DB {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
|
return p.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostgresManager 创建PostgreSQL重连管理器
|
||||||
|
func PostgresManager(dsn string, maxConnections int, maxIdleConnections int, maxConnectionAge time.Duration, config *Config) *Manager {
|
||||||
|
client := NewPostgresClient(dsn, maxConnections, maxIdleConnections, maxConnectionAge)
|
||||||
|
return NewManager(client, config)
|
||||||
|
}
|
||||||
|
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
package reconnect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reconnectable 定义可重连的接口
|
||||||
|
type Reconnectable interface {
|
||||||
|
// Connect 建立连接
|
||||||
|
Connect(ctx context.Context) error
|
||||||
|
// Close 关闭连接
|
||||||
|
Close() error
|
||||||
|
// Ping 检查连接是否健康
|
||||||
|
Ping(ctx context.Context) error
|
||||||
|
// IsConnected 检查连接状态
|
||||||
|
IsConnected() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config 重连配置
|
||||||
|
type Config struct {
|
||||||
|
// MaxRetries 最大重试次数,0表示无限重试
|
||||||
|
MaxRetries int
|
||||||
|
// RetryInterval 重试间隔
|
||||||
|
RetryInterval time.Duration
|
||||||
|
// Timeout 连接超时时间
|
||||||
|
Timeout time.Duration
|
||||||
|
// OnReconnect 重连成功后的回调函数
|
||||||
|
OnReconnect func()
|
||||||
|
// OnDisconnect 断开连接时的回调函数
|
||||||
|
OnDisconnect func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig 返回默认配置
|
||||||
|
func DefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
MaxRetries: 0, // 无限重试
|
||||||
|
RetryInterval: 3 * time.Second,
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager 重连管理器
|
||||||
|
type Manager struct {
|
||||||
|
client Reconnectable
|
||||||
|
config *Config
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
connected bool
|
||||||
|
errCh chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager 创建新的重连管理器
|
||||||
|
func NewManager(client Reconnectable, config *Config) *Manager {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &Manager{
|
||||||
|
client: client,
|
||||||
|
config: config,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
errCh: make(chan error, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动连接并开始监控
|
||||||
|
func (m *Manager) Start(ctx context.Context) error {
|
||||||
|
// 初始连接
|
||||||
|
if err := m.connect(ctx); err != nil {
|
||||||
|
return fmt.Errorf("initial connection failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动监控 goroutine
|
||||||
|
go m.monitor()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止重连管理器
|
||||||
|
func (m *Manager) Stop() error {
|
||||||
|
m.cancel()
|
||||||
|
if m.client != nil {
|
||||||
|
return m.client.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect 执行连接
|
||||||
|
func (m *Manager) connect(ctx context.Context) error {
|
||||||
|
connectCtx, cancel := context.WithTimeout(ctx, m.config.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := m.client.Connect(connectCtx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.connected = true
|
||||||
|
if m.config.OnReconnect != nil {
|
||||||
|
m.config.OnReconnect()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor 监控连接状态并自动重连
|
||||||
|
func (m *Manager) monitor() {
|
||||||
|
ticker := time.NewTicker(m.config.RetryInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
retryCount := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
// 检查连接状态
|
||||||
|
if !m.client.IsConnected() {
|
||||||
|
m.connected = false
|
||||||
|
if m.config.OnDisconnect != nil {
|
||||||
|
m.config.OnDisconnect(errors.New("connection lost"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超过最大重试次数
|
||||||
|
if m.config.MaxRetries > 0 && retryCount >= m.config.MaxRetries {
|
||||||
|
m.errCh <- fmt.Errorf("max retries (%d) exceeded", m.config.MaxRetries)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试重连
|
||||||
|
if err := m.connect(context.Background()); err != nil {
|
||||||
|
retryCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重连成功,重置计数器
|
||||||
|
retryCount = 0
|
||||||
|
} else {
|
||||||
|
// 连接正常,执行健康检查
|
||||||
|
if err := m.client.Ping(context.Background()); err != nil {
|
||||||
|
m.connected = false
|
||||||
|
if m.config.OnDisconnect != nil {
|
||||||
|
m.config.OnDisconnect(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsConnected 返回当前连接状态
|
||||||
|
func (m *Manager) IsConnected() bool {
|
||||||
|
return m.connected && m.client.IsConnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForError 等待错误(用于阻塞等待)
|
||||||
|
func (m *Manager) WaitForError() error {
|
||||||
|
return <-m.errCh
|
||||||
|
}
|
||||||
@@ -2,125 +2,87 @@ package reconnect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RedisClientConfig Redis客户端配置
|
// RedisClient Redis客户端包装器
|
||||||
type RedisClientConfig struct {
|
|
||||||
// Options Redis连接选项
|
|
||||||
Options *redis.Options
|
|
||||||
// ReconnectConfig 重连配置
|
|
||||||
ReconnectConfig Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedisClient 带重连功能的Redis客户端
|
|
||||||
type RedisClient struct {
|
type RedisClient struct {
|
||||||
config RedisClientConfig
|
options *redis.Options
|
||||||
client *redis.Client
|
client *redis.Client
|
||||||
manager *ConnectionManager
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// redisConnector Redis连接器
|
// NewRedisClient 创建新的Redis客户端
|
||||||
type redisConnector struct {
|
func NewRedisClient(options *redis.Options) *RedisClient {
|
||||||
client *RedisClient
|
return &RedisClient{
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisConnector) Connect(ctx context.Context) error {
|
// Connect 建立Redis连接
|
||||||
r.client.mu.Lock()
|
func (r *RedisClient) Connect(ctx context.Context) error {
|
||||||
defer r.client.mu.Unlock()
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
rdb := redis.NewClient(r.client.config.Options)
|
if r.client != nil {
|
||||||
|
_ = r.client.Close()
|
||||||
// 验证连接
|
|
||||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
||||||
rdb.Close()
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.client.client = rdb
|
client := redis.NewClient(r.options)
|
||||||
|
if err := client.Ping(ctx).Err(); err != nil {
|
||||||
|
return fmt.Errorf("redis ping failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = client
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redisConnector) Close() error {
|
// Close 关闭Redis连接
|
||||||
r.client.mu.Lock()
|
func (r *RedisClient) Close() error {
|
||||||
defer r.client.mu.Unlock()
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if r.client.client != nil {
|
if r.client != nil {
|
||||||
err := r.client.client.Close()
|
err := r.client.Close()
|
||||||
r.client.client = nil
|
r.client = nil
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// redisHealthChecker Redis健康检查器
|
// Ping 检查Redis连接是否健康
|
||||||
type redisHealthChecker struct {
|
func (r *RedisClient) Ping(ctx context.Context) error {
|
||||||
client *RedisClient
|
r.mu.RLock()
|
||||||
}
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
func (r *redisHealthChecker) HealthCheck(ctx context.Context) error {
|
if r.client == nil {
|
||||||
r.client.mu.RLock()
|
return fmt.Errorf("redis client is nil")
|
||||||
rdb := r.client.client
|
|
||||||
r.client.mu.RUnlock()
|
|
||||||
|
|
||||||
if rdb == nil {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rdb.Ping(ctx).Err()
|
return r.client.Ping(ctx).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRedisClient 创建带重连功能的Redis客户端
|
// IsConnected 检查连接状态
|
||||||
func NewRedisClient(cfg RedisClientConfig) (*RedisClient, error) {
|
func (r *RedisClient) IsConnected() bool {
|
||||||
client := &RedisClient{
|
r.mu.RLock()
|
||||||
config: cfg,
|
defer r.mu.RUnlock()
|
||||||
}
|
|
||||||
|
|
||||||
connector := &redisConnector{client: client}
|
return r.client != nil
|
||||||
checker := &redisHealthChecker{client: client}
|
|
||||||
|
|
||||||
client.manager = NewConnectionManager(connector, checker, cfg.ReconnectConfig)
|
|
||||||
|
|
||||||
// 首次连接
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := client.manager.ConnectWithRetry(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient 获取Redis客户端
|
// GetClient 获取底层的Redis客户端
|
||||||
func (c *RedisClient) GetClient() *redis.Client {
|
func (r *RedisClient) GetClient() *redis.Client {
|
||||||
c.mu.RLock()
|
r.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
return c.client
|
|
||||||
|
return r.client
|
||||||
}
|
}
|
||||||
|
|
||||||
// State 获取连接状态
|
// RedisManager 创建Redis重连管理器
|
||||||
func (c *RedisClient) State() ConnectionState {
|
func RedisManager(options *redis.Options, config *Config) *Manager {
|
||||||
return c.manager.State()
|
client := NewRedisClient(options)
|
||||||
|
return NewManager(client, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close 关闭客户端
|
|
||||||
func (c *RedisClient) Close() error {
|
|
||||||
return c.manager.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerReconnect 手动触发重连
|
|
||||||
func (c *RedisClient) TriggerReconnect() {
|
|
||||||
c.manager.TriggerReconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping 执行Ping命令
|
|
||||||
func (c *RedisClient) Ping(ctx context.Context) error {
|
|
||||||
client := c.GetClient()
|
|
||||||
if client == nil {
|
|
||||||
return context.Canceled
|
|
||||||
}
|
|
||||||
return client.Ping(ctx).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
-125
@@ -1,125 +0,0 @@
|
|||||||
package reconnect
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Strategy 重连策略接口
|
|
||||||
type Strategy interface {
|
|
||||||
// NextDelay 返回下一次重试的延迟时间
|
|
||||||
// attempt 是当前重试次数(从1开始)
|
|
||||||
NextDelay(attempt int) time.Duration
|
|
||||||
|
|
||||||
// Reset 重置策略状态
|
|
||||||
Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExponentialBackoff 指数退避策略
|
|
||||||
type ExponentialBackoff struct {
|
|
||||||
initialDelay time.Duration
|
|
||||||
maxDelay time.Duration
|
|
||||||
multiplier float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExponentialBackoff 创建指数退避策略
|
|
||||||
func NewExponentialBackoff(initialDelay, maxDelay time.Duration, multiplier float64) *ExponentialBackoff {
|
|
||||||
if multiplier <= 0 {
|
|
||||||
multiplier = 2.0
|
|
||||||
}
|
|
||||||
return &ExponentialBackoff{
|
|
||||||
initialDelay: initialDelay,
|
|
||||||
maxDelay: maxDelay,
|
|
||||||
multiplier: multiplier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextDelay 计算下一次延迟时间
|
|
||||||
func (e *ExponentialBackoff) NextDelay(attempt int) time.Duration {
|
|
||||||
if attempt <= 0 {
|
|
||||||
attempt = 1
|
|
||||||
}
|
|
||||||
delay := float64(e.initialDelay)
|
|
||||||
for i := 1; i < attempt; i++ {
|
|
||||||
delay *= e.multiplier
|
|
||||||
if time.Duration(delay) >= e.maxDelay {
|
|
||||||
return e.maxDelay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if time.Duration(delay) > e.maxDelay {
|
|
||||||
return e.maxDelay
|
|
||||||
}
|
|
||||||
return time.Duration(delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset 重置策略
|
|
||||||
func (e *ExponentialBackoff) Reset() {
|
|
||||||
// 无状态,无需重置
|
|
||||||
}
|
|
||||||
|
|
||||||
// FixedInterval 固定间隔策略
|
|
||||||
type FixedInterval struct {
|
|
||||||
interval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFixedInterval 创建固定间隔策略
|
|
||||||
func NewFixedInterval(interval time.Duration) *FixedInterval {
|
|
||||||
return &FixedInterval{
|
|
||||||
interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextDelay 返回固定延迟时间
|
|
||||||
func (f *FixedInterval) NextDelay(attempt int) time.Duration {
|
|
||||||
return f.interval
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset 重置策略
|
|
||||||
func (f *FixedInterval) Reset() {
|
|
||||||
// 无状态,无需重置
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinearBackoff 线性退避策略
|
|
||||||
type LinearBackoff struct {
|
|
||||||
initialDelay time.Duration
|
|
||||||
maxDelay time.Duration
|
|
||||||
increment time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLinearBackoff 创建线性退避策略
|
|
||||||
func NewLinearBackoff(initialDelay, maxDelay, increment time.Duration) *LinearBackoff {
|
|
||||||
return &LinearBackoff{
|
|
||||||
initialDelay: initialDelay,
|
|
||||||
maxDelay: maxDelay,
|
|
||||||
increment: increment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextDelay 计算下一次延迟时间
|
|
||||||
func (l *LinearBackoff) NextDelay(attempt int) time.Duration {
|
|
||||||
if attempt <= 0 {
|
|
||||||
attempt = 1
|
|
||||||
}
|
|
||||||
delay := l.initialDelay + time.Duration(attempt-1)*l.increment
|
|
||||||
if delay > l.maxDelay {
|
|
||||||
return l.maxDelay
|
|
||||||
}
|
|
||||||
return delay
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset 重置策略
|
|
||||||
func (l *LinearBackoff) Reset() {
|
|
||||||
// 无状态,无需重置
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStrategy 根据配置创建策略
|
|
||||||
func NewStrategy(cfg Config) Strategy {
|
|
||||||
switch cfg.Strategy {
|
|
||||||
case StrategyFixedInterval:
|
|
||||||
return NewFixedInterval(cfg.InitialDelay)
|
|
||||||
case StrategyLinearBackoff:
|
|
||||||
return NewLinearBackoff(cfg.InitialDelay, cfg.MaxDelay, cfg.LinearIncrement)
|
|
||||||
case StrategyExponentialBackoff:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return NewExponentialBackoff(cfg.InitialDelay, cfg.MaxDelay, cfg.Multiplier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user