You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							171 lines
						
					
					
						
							4.9 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							171 lines
						
					
					
						
							4.9 KiB
						
					
					
				| package balance | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/types" | |
| ) | |
| 
 | |
| // BalanceDetector implements TaskDetector for balance tasks | |
| type BalanceDetector struct { | |
| 	enabled          bool | |
| 	threshold        float64 // Imbalance threshold (0.1 = 10%) | |
| 	minCheckInterval time.Duration | |
| 	minVolumeCount   int | |
| 	lastCheck        time.Time | |
| } | |
| 
 | |
| // Compile-time interface assertions | |
| var ( | |
| 	_ types.TaskDetector = (*BalanceDetector)(nil) | |
| ) | |
| 
 | |
| // NewBalanceDetector creates a new balance detector | |
| func NewBalanceDetector() *BalanceDetector { | |
| 	return &BalanceDetector{ | |
| 		enabled:          true, | |
| 		threshold:        0.1, // 10% imbalance threshold | |
| 		minCheckInterval: 1 * time.Hour, | |
| 		minVolumeCount:   10, // Don't balance small clusters | |
| 		lastCheck:        time.Time{}, | |
| 	} | |
| } | |
| 
 | |
| // GetTaskType returns the task type | |
| func (d *BalanceDetector) GetTaskType() types.TaskType { | |
| 	return types.TaskTypeBalance | |
| } | |
| 
 | |
| // ScanForTasks checks if cluster balance is needed | |
| func (d *BalanceDetector) ScanForTasks(volumeMetrics []*types.VolumeHealthMetrics, clusterInfo *types.ClusterInfo) ([]*types.TaskDetectionResult, error) { | |
| 	if !d.enabled { | |
| 		return nil, nil | |
| 	} | |
| 
 | |
| 	glog.V(2).Infof("Scanning for balance tasks...") | |
| 
 | |
| 	// Don't check too frequently | |
| 	if time.Since(d.lastCheck) < d.minCheckInterval { | |
| 		return nil, nil | |
| 	} | |
| 	d.lastCheck = time.Now() | |
| 
 | |
| 	// Skip if cluster is too small | |
| 	if len(volumeMetrics) < d.minVolumeCount { | |
| 		glog.V(2).Infof("Cluster too small for balance (%d volumes < %d minimum)", len(volumeMetrics), d.minVolumeCount) | |
| 		return nil, nil | |
| 	} | |
| 
 | |
| 	// Analyze volume distribution across servers | |
| 	serverVolumeCounts := make(map[string]int) | |
| 	for _, metric := range volumeMetrics { | |
| 		serverVolumeCounts[metric.Server]++ | |
| 	} | |
| 
 | |
| 	if len(serverVolumeCounts) < 2 { | |
| 		glog.V(2).Infof("Not enough servers for balance (%d servers)", len(serverVolumeCounts)) | |
| 		return nil, nil | |
| 	} | |
| 
 | |
| 	// Calculate balance metrics | |
| 	totalVolumes := len(volumeMetrics) | |
| 	avgVolumesPerServer := float64(totalVolumes) / float64(len(serverVolumeCounts)) | |
| 
 | |
| 	maxVolumes := 0 | |
| 	minVolumes := totalVolumes | |
| 	maxServer := "" | |
| 	minServer := "" | |
| 
 | |
| 	for server, count := range serverVolumeCounts { | |
| 		if count > maxVolumes { | |
| 			maxVolumes = count | |
| 			maxServer = server | |
| 		} | |
| 		if count < minVolumes { | |
| 			minVolumes = count | |
| 			minServer = server | |
| 		} | |
| 	} | |
| 
 | |
| 	// Check if imbalance exceeds threshold | |
| 	imbalanceRatio := float64(maxVolumes-minVolumes) / avgVolumesPerServer | |
| 	if imbalanceRatio <= d.threshold { | |
| 		glog.V(2).Infof("Cluster is balanced (imbalance ratio: %.2f <= %.2f)", imbalanceRatio, d.threshold) | |
| 		return nil, nil | |
| 	} | |
| 
 | |
| 	// Create balance task | |
| 	reason := fmt.Sprintf("Cluster imbalance detected: %.1f%% (max: %d on %s, min: %d on %s, avg: %.1f)", | |
| 		imbalanceRatio*100, maxVolumes, maxServer, minVolumes, minServer, avgVolumesPerServer) | |
| 
 | |
| 	task := &types.TaskDetectionResult{ | |
| 		TaskType:   types.TaskTypeBalance, | |
| 		Priority:   types.TaskPriorityNormal, | |
| 		Reason:     reason, | |
| 		ScheduleAt: time.Now(), | |
| 		Parameters: map[string]interface{}{ | |
| 			"imbalance_ratio":        imbalanceRatio, | |
| 			"threshold":              d.threshold, | |
| 			"max_volumes":            maxVolumes, | |
| 			"min_volumes":            minVolumes, | |
| 			"avg_volumes_per_server": avgVolumesPerServer, | |
| 			"max_server":             maxServer, | |
| 			"min_server":             minServer, | |
| 			"total_servers":          len(serverVolumeCounts), | |
| 		}, | |
| 	} | |
| 
 | |
| 	glog.V(1).Infof("🔄 Found balance task: %s", reason) | |
| 	return []*types.TaskDetectionResult{task}, nil | |
| } | |
| 
 | |
| // ScanInterval returns how often to scan | |
| func (d *BalanceDetector) ScanInterval() time.Duration { | |
| 	return d.minCheckInterval | |
| } | |
| 
 | |
| // IsEnabled returns whether the detector is enabled | |
| func (d *BalanceDetector) IsEnabled() bool { | |
| 	return d.enabled | |
| } | |
| 
 | |
| // SetEnabled sets whether the detector is enabled | |
| func (d *BalanceDetector) SetEnabled(enabled bool) { | |
| 	d.enabled = enabled | |
| 	glog.V(1).Infof("🔄 Balance detector enabled: %v", enabled) | |
| } | |
| 
 | |
| // SetThreshold sets the imbalance threshold | |
| func (d *BalanceDetector) SetThreshold(threshold float64) { | |
| 	d.threshold = threshold | |
| 	glog.V(1).Infof("🔄 Balance threshold set to: %.1f%%", threshold*100) | |
| } | |
| 
 | |
| // SetMinCheckInterval sets the minimum time between balance checks | |
| func (d *BalanceDetector) SetMinCheckInterval(interval time.Duration) { | |
| 	d.minCheckInterval = interval | |
| 	glog.V(1).Infof("🔄 Balance check interval set to: %v", interval) | |
| } | |
| 
 | |
| // SetMinVolumeCount sets the minimum volume count for balance operations | |
| func (d *BalanceDetector) SetMinVolumeCount(count int) { | |
| 	d.minVolumeCount = count | |
| 	glog.V(1).Infof("🔄 Balance minimum volume count set to: %d", count) | |
| } | |
| 
 | |
| // GetThreshold returns the current imbalance threshold | |
| func (d *BalanceDetector) GetThreshold() float64 { | |
| 	return d.threshold | |
| } | |
| 
 | |
| // GetMinCheckInterval returns the minimum check interval | |
| func (d *BalanceDetector) GetMinCheckInterval() time.Duration { | |
| 	return d.minCheckInterval | |
| } | |
| 
 | |
| // GetMinVolumeCount returns the minimum volume count | |
| func (d *BalanceDetector) GetMinVolumeCount() int { | |
| 	return d.minVolumeCount | |
| }
 |