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.
		
		
		
		
		
			
		
			
				
					
					
						
							202 lines
						
					
					
						
							7.6 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							202 lines
						
					
					
						
							7.6 KiB
						
					
					
				| package broker | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"sync" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/filer_client" | |
| 	"github.com/seaweedfs/seaweedfs/weed/mq/offset" | |
| 	"github.com/seaweedfs/seaweedfs/weed/mq/topic" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb" | |
| ) | |
| 
 | |
| // BrokerOffsetManager manages offset assignment for all partitions in a broker | |
| type BrokerOffsetManager struct { | |
| 	mu                   sync.RWMutex | |
| 	offsetIntegration    *offset.SMQOffsetIntegration | |
| 	storage              offset.OffsetStorage | |
| 	consumerGroupStorage offset.ConsumerGroupOffsetStorage | |
| } | |
| 
 | |
| // NewBrokerOffsetManagerWithFilerAccessor creates a new broker offset manager using existing filer client accessor | |
| func NewBrokerOffsetManagerWithFilerAccessor(filerAccessor *filer_client.FilerClientAccessor) *BrokerOffsetManager { | |
| 	// Create filer storage using the accessor directly - no duplicate connection management | |
| 	filerStorage := offset.NewFilerOffsetStorageWithAccessor(filerAccessor) | |
| 
 | |
| 	// Create consumer group storage using the accessor directly | |
| 	consumerGroupStorage := offset.NewFilerConsumerGroupOffsetStorageWithAccessor(filerAccessor) | |
| 
 | |
| 	return &BrokerOffsetManager{ | |
| 		offsetIntegration:    offset.NewSMQOffsetIntegration(filerStorage), | |
| 		storage:              filerStorage, | |
| 		consumerGroupStorage: consumerGroupStorage, | |
| 	} | |
| } | |
| 
 | |
| // AssignOffset assigns the next offset for a partition | |
| func (bom *BrokerOffsetManager) AssignOffset(t topic.Topic, p topic.Partition) (int64, error) { | |
| 	partition := topicPartitionToSchemaPartition(t, p) | |
| 
 | |
| 	// Use the integration layer's offset assigner to ensure consistency with subscriptions | |
| 	result := bom.offsetIntegration.AssignSingleOffset(t.Namespace, t.Name, partition) | |
| 	if result.Error != nil { | |
| 		return 0, result.Error | |
| 	} | |
| 
 | |
| 	return result.Assignment.Offset, nil | |
| } | |
| 
 | |
| // AssignBatchOffsets assigns a batch of offsets for a partition | |
| func (bom *BrokerOffsetManager) AssignBatchOffsets(t topic.Topic, p topic.Partition, count int64) (baseOffset, lastOffset int64, err error) { | |
| 	partition := topicPartitionToSchemaPartition(t, p) | |
| 
 | |
| 	// Use the integration layer's offset assigner to ensure consistency with subscriptions | |
| 	result := bom.offsetIntegration.AssignBatchOffsets(t.Namespace, t.Name, partition, count) | |
| 	if result.Error != nil { | |
| 		return 0, 0, result.Error | |
| 	} | |
| 
 | |
| 	return result.Batch.BaseOffset, result.Batch.LastOffset, nil | |
| } | |
| 
 | |
| // GetHighWaterMark returns the high water mark for a partition | |
| func (bom *BrokerOffsetManager) GetHighWaterMark(t topic.Topic, p topic.Partition) (int64, error) { | |
| 	partition := topicPartitionToSchemaPartition(t, p) | |
| 
 | |
| 	// Use the integration layer's offset assigner to ensure consistency with subscriptions | |
| 	return bom.offsetIntegration.GetHighWaterMark(t.Namespace, t.Name, partition) | |
| } | |
| 
 | |
| // CreateSubscription creates an offset-based subscription | |
| func (bom *BrokerOffsetManager) CreateSubscription( | |
| 	subscriptionID string, | |
| 	t topic.Topic, | |
| 	p topic.Partition, | |
| 	offsetType schema_pb.OffsetType, | |
| 	startOffset int64, | |
| ) (*offset.OffsetSubscription, error) { | |
| 	partition := topicPartitionToSchemaPartition(t, p) | |
| 	return bom.offsetIntegration.CreateSubscription(subscriptionID, t.Namespace, t.Name, partition, offsetType, startOffset) | |
| } | |
| 
 | |
| // GetSubscription retrieves an existing subscription | |
| func (bom *BrokerOffsetManager) GetSubscription(subscriptionID string) (*offset.OffsetSubscription, error) { | |
| 	return bom.offsetIntegration.GetSubscription(subscriptionID) | |
| } | |
| 
 | |
| // CloseSubscription closes a subscription | |
| func (bom *BrokerOffsetManager) CloseSubscription(subscriptionID string) error { | |
| 	return bom.offsetIntegration.CloseSubscription(subscriptionID) | |
| } | |
| 
 | |
| // ListActiveSubscriptions returns all active subscriptions | |
| func (bom *BrokerOffsetManager) ListActiveSubscriptions() ([]*offset.OffsetSubscription, error) { | |
| 	return bom.offsetIntegration.ListActiveSubscriptions() | |
| } | |
| 
 | |
| // GetPartitionOffsetInfo returns comprehensive offset information for a partition | |
| func (bom *BrokerOffsetManager) GetPartitionOffsetInfo(t topic.Topic, p topic.Partition) (*offset.PartitionOffsetInfo, error) { | |
| 	partition := topicPartitionToSchemaPartition(t, p) | |
| 
 | |
| 	// Use the integration layer to ensure consistency with subscriptions | |
| 	return bom.offsetIntegration.GetPartitionOffsetInfo(t.Namespace, t.Name, partition) | |
| } | |
| 
 | |
| // topicPartitionToSchemaPartition converts topic.Topic and topic.Partition to schema_pb.Partition | |
| func topicPartitionToSchemaPartition(t topic.Topic, p topic.Partition) *schema_pb.Partition { | |
| 	return &schema_pb.Partition{ | |
| 		RingSize:   int32(p.RingSize), | |
| 		RangeStart: int32(p.RangeStart), | |
| 		RangeStop:  int32(p.RangeStop), | |
| 		UnixTimeNs: p.UnixTimeNs, | |
| 	} | |
| } | |
| 
 | |
| // OffsetAssignmentResult contains the result of offset assignment for logging/metrics | |
| type OffsetAssignmentResult struct { | |
| 	Topic      topic.Topic | |
| 	Partition  topic.Partition | |
| 	BaseOffset int64 | |
| 	LastOffset int64 | |
| 	Count      int64 | |
| 	Timestamp  int64 | |
| 	Error      error | |
| } | |
| 
 | |
| // AssignOffsetsWithResult assigns offsets and returns detailed result for logging/metrics | |
| func (bom *BrokerOffsetManager) AssignOffsetsWithResult(t topic.Topic, p topic.Partition, count int64) *OffsetAssignmentResult { | |
| 	baseOffset, lastOffset, err := bom.AssignBatchOffsets(t, p, count) | |
| 
 | |
| 	result := &OffsetAssignmentResult{ | |
| 		Topic:     t, | |
| 		Partition: p, | |
| 		Count:     count, | |
| 		Error:     err, | |
| 	} | |
| 
 | |
| 	if err == nil { | |
| 		result.BaseOffset = baseOffset | |
| 		result.LastOffset = lastOffset | |
| 		result.Timestamp = time.Now().UnixNano() | |
| 	} | |
| 
 | |
| 	return result | |
| } | |
| 
 | |
| // GetOffsetMetrics returns metrics about offset usage across all partitions | |
| func (bom *BrokerOffsetManager) GetOffsetMetrics() *offset.OffsetMetrics { | |
| 	// Use the integration layer to ensure consistency with subscriptions | |
| 	return bom.offsetIntegration.GetOffsetMetrics() | |
| } | |
| 
 | |
| // Shutdown gracefully shuts down the offset manager | |
| func (bom *BrokerOffsetManager) Shutdown() { | |
| 	bom.mu.Lock() | |
| 	defer bom.mu.Unlock() | |
| 
 | |
| 	// Reset the underlying storage to ensure clean restart behavior | |
| 	// This is important for testing where we want offsets to start from 0 after shutdown | |
| 	if bom.storage != nil { | |
| 		if resettable, ok := bom.storage.(interface{ Reset() error }); ok { | |
| 			resettable.Reset() | |
| 		} | |
| 	} | |
| 
 | |
| 	// Reset the integration layer to ensure clean restart behavior | |
| 	bom.offsetIntegration.Reset() | |
| } | |
| 
 | |
| // Consumer Group Offset Management | |
|  | |
| // SaveConsumerGroupOffset saves the committed offset for a consumer group | |
| func (bom *BrokerOffsetManager) SaveConsumerGroupOffset(t topic.Topic, p topic.Partition, consumerGroup string, offset int64) error { | |
| 	if bom.consumerGroupStorage == nil { | |
| 		return fmt.Errorf("consumer group storage not configured") | |
| 	} | |
| 	return bom.consumerGroupStorage.SaveConsumerGroupOffset(t, p, consumerGroup, offset) | |
| } | |
| 
 | |
| // LoadConsumerGroupOffset loads the committed offset for a consumer group | |
| func (bom *BrokerOffsetManager) LoadConsumerGroupOffset(t topic.Topic, p topic.Partition, consumerGroup string) (int64, error) { | |
| 	if bom.consumerGroupStorage == nil { | |
| 		return -1, fmt.Errorf("consumer group storage not configured") | |
| 	} | |
| 	return bom.consumerGroupStorage.LoadConsumerGroupOffset(t, p, consumerGroup) | |
| } | |
| 
 | |
| // ListConsumerGroups returns all consumer groups for a topic partition | |
| func (bom *BrokerOffsetManager) ListConsumerGroups(t topic.Topic, p topic.Partition) ([]string, error) { | |
| 	if bom.consumerGroupStorage == nil { | |
| 		return nil, fmt.Errorf("consumer group storage not configured") | |
| 	} | |
| 	return bom.consumerGroupStorage.ListConsumerGroups(t, p) | |
| } | |
| 
 | |
| // DeleteConsumerGroupOffset removes the offset file for a consumer group | |
| func (bom *BrokerOffsetManager) DeleteConsumerGroupOffset(t topic.Topic, p topic.Partition, consumerGroup string) error { | |
| 	if bom.consumerGroupStorage == nil { | |
| 		return fmt.Errorf("consumer group storage not configured") | |
| 	} | |
| 	return bom.consumerGroupStorage.DeleteConsumerGroupOffset(t, p, consumerGroup) | |
| }
 |