mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-09-06 00:06:38 +08:00
163 lines
3.7 KiB
Go
163 lines
3.7 KiB
Go
package health
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
)
|
|
|
|
// S3Target S3健康检查目标
|
|
type S3Target struct {
|
|
ID string
|
|
Bucket string
|
|
Endpoint string
|
|
Client *s3.Client
|
|
}
|
|
|
|
// GetID 实现 Target 接口
|
|
func (t *S3Target) GetID() string {
|
|
return t.ID
|
|
}
|
|
|
|
// GetType 实现 Target 接口
|
|
func (t *S3Target) GetType() string {
|
|
return "s3"
|
|
}
|
|
|
|
// GetEndpoint 实现 Target 接口
|
|
func (t *S3Target) GetEndpoint() string {
|
|
return t.Endpoint
|
|
}
|
|
|
|
// S3Checker S3健康检查器
|
|
type S3Checker struct {
|
|
config Config
|
|
opRecorder OperationRecorder
|
|
}
|
|
|
|
// NewS3Checker 创建S3健康检查器
|
|
func NewS3Checker(config Config) *S3Checker {
|
|
if config.Interval == 0 {
|
|
config.Interval = 30 * time.Second
|
|
}
|
|
if config.Timeout == 0 {
|
|
config.Timeout = 5 * time.Second
|
|
}
|
|
if config.Retries == 0 {
|
|
config.Retries = 1
|
|
}
|
|
|
|
return &S3Checker{
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// SetOperationRecorder 设置操作记录器
|
|
func (c *S3Checker) SetOperationRecorder(recorder OperationRecorder) {
|
|
c.opRecorder = recorder
|
|
}
|
|
|
|
// Check 执行S3健康检查
|
|
func (c *S3Checker) Check(ctx context.Context, target Target) Status {
|
|
s3Target, ok := target.(*S3Target)
|
|
if !ok {
|
|
return Status{
|
|
Healthy: false,
|
|
LastChecked: time.Now(),
|
|
Message: "invalid target type for S3 checker",
|
|
Error: fmt.Errorf("expected *S3Target, got %T", target),
|
|
}
|
|
}
|
|
|
|
// 创建带超时的context
|
|
checkCtx, cancel := context.WithTimeout(ctx, c.config.Timeout)
|
|
defer cancel()
|
|
|
|
var lastErr error
|
|
for i := 0; i < c.config.Retries; i++ {
|
|
if i > 0 {
|
|
// 重试前等待
|
|
select {
|
|
case <-ctx.Done():
|
|
return Status{
|
|
Healthy: false,
|
|
LastChecked: time.Now(),
|
|
Message: "health check cancelled",
|
|
Error: ctx.Err(),
|
|
}
|
|
case <-time.After(time.Second):
|
|
}
|
|
}
|
|
|
|
err := c.performCheck(checkCtx, s3Target)
|
|
if err == nil {
|
|
return Status{
|
|
Healthy: true,
|
|
LastChecked: time.Now(),
|
|
Message: fmt.Sprintf("S3 bucket %s is healthy", s3Target.Bucket),
|
|
}
|
|
}
|
|
lastErr = err
|
|
}
|
|
|
|
return Status{
|
|
Healthy: false,
|
|
LastChecked: time.Now(),
|
|
Message: fmt.Sprintf("S3 bucket %s is unhealthy after %d retries", s3Target.Bucket, c.config.Retries),
|
|
Error: lastErr,
|
|
}
|
|
}
|
|
|
|
func (c *S3Checker) performCheck(ctx context.Context, target *S3Target) error {
|
|
switch c.config.Strategy {
|
|
case StrategyDetailed:
|
|
return c.performDetailedCheck(ctx, target)
|
|
default:
|
|
return c.performSimpleCheck(ctx, target)
|
|
}
|
|
}
|
|
|
|
// performSimpleCheck 执行简单健康检查(轻量级)
|
|
func (c *S3Checker) performSimpleCheck(ctx context.Context, target *S3Target) error {
|
|
// 尝试列出1个对象,这是最轻量级的检查方式
|
|
_, err := target.Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
|
Bucket: aws.String(target.Bucket),
|
|
MaxKeys: aws.Int32(1),
|
|
})
|
|
|
|
// 记录操作(ListObjectsV2 是 Class A 操作)
|
|
if c.opRecorder != nil {
|
|
c.opRecorder.RecordOperation(target.GetID(), OperationTypeA)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// performDetailedCheck 执行详细健康检查(包含更多验证)
|
|
func (c *S3Checker) performDetailedCheck(ctx context.Context, target *S3Target) error {
|
|
// 先执行简单检查
|
|
if err := c.performSimpleCheck(ctx, target); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 可以添加更多详细检查,比如:
|
|
// 1. 检查存储桶策略
|
|
// 2. 测试写入权限(创建并删除测试对象)
|
|
// 3. 检查存储桶版本控制状态
|
|
// 4. 测量响应时间等性能指标
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetInterval 获取检查间隔
|
|
func (c *S3Checker) GetInterval() time.Duration {
|
|
return c.config.Interval
|
|
}
|
|
|
|
// GetTimeout 获取检查超时时间
|
|
func (c *S3Checker) GetTimeout() time.Duration {
|
|
return c.config.Timeout
|
|
} |