github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/client/mq/kafka/consumer/consumer_group.go (about) 1 package consumer 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/weedge/lib/client/mq/kafka/auth" 7 "sync" 8 "time" 9 10 "github.com/weedge/lib/container/set" 11 "github.com/weedge/lib/log" 12 "github.com/weedge/lib/runtimer" 13 14 "github.com/Shopify/sarama" 15 ) 16 17 var consumerGroupNames *set.HashSet 18 19 func init() { 20 consumerGroupNames = set.NewSet() 21 } 22 23 type IConsumerMsg interface { 24 Consumer(msg *sarama.ConsumerMessage) error 25 } 26 27 // Consumer represents a Sarama consumer group consumer 28 type ConsumerGroup struct { 29 name string // the same to groupId 30 ready chan bool 31 config *sarama.Config 32 topicList []string 33 client sarama.ConsumerGroup 34 cancel context.CancelFunc 35 wg *sync.WaitGroup 36 msg IConsumerMsg 37 msgMeta string 38 } 39 40 // user just defined open consumer group option 41 func NewConsumerGroup(name string, msg IConsumerMsg, authOpts []auth.Option, options ...Option) (consumer *ConsumerGroup, err error) { 42 consumer = &ConsumerGroup{name: name, wg: &sync.WaitGroup{}, msg: msg} 43 44 consumerOpts := getConsumerOptions(authOpts, options...) 45 log.Info(fmt.Sprintf("consumer options:%+v", consumerOpts)) 46 47 consumer.ready = make(chan bool) 48 consumer.config = sarama.NewConfig() 49 consumer.config.Consumer.Return.Errors = true 50 consumer.topicList = consumerOpts.topicList 51 consumer.config.Version, err = sarama.ParseKafkaVersion(consumerOpts.version) 52 if err != nil { 53 return 54 } 55 56 switch consumerOpts.reBalanceStrategy { 57 case "sticky": 58 consumer.config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategySticky 59 case "roundrobin": 60 consumer.config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin 61 case "range": 62 consumer.config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange 63 default: 64 err = fmt.Errorf("un define consumer group rebalance strategy") 65 return 66 } 67 68 switch consumerOpts.initialOffset { 69 case "newest": 70 consumer.config.Consumer.Offsets.Initial = sarama.OffsetNewest 71 case "oldest": 72 consumer.config.Consumer.Offsets.Initial = sarama.OffsetOldest 73 default: 74 err = fmt.Errorf("un define consumer group rebalance strategy") 75 return 76 } 77 78 consumerOpts.AuthOptions.InitSSL(consumer.config) 79 80 consumerOpts.AuthOptions.InitSASLSCRAM(consumer.config) 81 82 consumer.client, err = sarama.NewConsumerGroup(consumerOpts.brokerList, consumerOpts.groupId, consumer.config) 83 if err != nil { 84 err = fmt.Errorf("error creating consumer group client: %v", err) 85 return 86 } 87 log.Info("init consumer group ok!") 88 89 return 90 } 91 92 func (consumer *ConsumerGroup) Start() { 93 var ctx context.Context 94 ctx, consumer.cancel = context.WithCancel(context.Background()) 95 consumer.startWithContext(ctx) 96 } 97 98 func (consumer *ConsumerGroup) StartWithTimeOut(timeout time.Duration) { 99 var ctx context.Context 100 ctx, consumer.cancel = context.WithTimeout(context.Background(), timeout) 101 consumer.startWithContext(ctx) 102 } 103 104 func (consumer *ConsumerGroup) StartWithDeadline(time time.Time) { 105 var ctx context.Context 106 ctx, consumer.cancel = context.WithDeadline(context.Background(), time) 107 consumer.startWithContext(ctx) 108 } 109 110 func (consumer *ConsumerGroup) startWithContext(ctx context.Context) { 111 if consumerGroupNames.Contains(consumer.name) { 112 log.Warn("have the same consumer to start name", consumer.name) 113 return 114 } 115 runtimer.GoSafely(nil, false, func() { 116 // Track errors 117 for err := range consumer.client.Errors() { 118 log.Error(err) 119 } 120 }, nil, nil) 121 122 runtimer.GoSafely(consumer.wg, false, func() { 123 // `Consume` should be called inside an infinite loop, when a 124 // server-side rebalance happens, the consumer session will need to be 125 // recreated to get the new claims 126 for { 127 if err := consumer.client.Consume(ctx, consumer.topicList, consumer); err != nil { 128 log.Error("Error from consumer: %v", err) 129 } 130 // check if context was cancelled, signaling that the consumer should stop 131 if ctx.Err() != nil { 132 log.Info("Sarama consumer stop!... ", time.Now().Format(time.RFC3339)) 133 return 134 } 135 consumer.ready = make(chan bool) 136 } 137 }, nil, nil) 138 139 <-consumer.ready // Await till the consumer has been set up 140 log.Info("Sarama consumer up and running!...") 141 consumerGroupNames.Add(consumer.name) 142 } 143 144 func (consumer *ConsumerGroup) Close() { 145 consumer.wg.Wait() 146 147 if consumer.cancel != nil { 148 consumer.cancel() 149 } 150 151 consumerGroupNames.Remove(consumer.name) 152 err := consumer.client.Close() 153 if err != nil { 154 log.Error("consumer.client.Close err", err.Error()) 155 } 156 } 157 158 // Setup is run at the beginning of a new session, before ConsumeClaim 159 func (consumer *ConsumerGroup) Setup(sarama.ConsumerGroupSession) error { 160 // Mark the consumer as ready 161 close(consumer.ready) 162 return nil 163 } 164 165 // Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited 166 func (consumer *ConsumerGroup) Cleanup(sarama.ConsumerGroupSession) error { 167 return nil 168 } 169 170 // ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages(). 171 func (consumer *ConsumerGroup) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { 172 // NOTE: 173 // Do not move the code below to a goroutine. 174 // The `ConsumeClaim` itself is called within a goroutine, see: 175 // https://github.com/Shopify/sarama/blob/main/consumer_group.go#L27-L29 176 for message := range claim.Messages() { 177 log.Info(fmt.Sprintf("Message claimed: value = %s, timestamp = %v, topic = %s", string(message.Value), message.Timestamp, message.Topic)) 178 err := consumer.msg.Consumer(message) 179 if err != nil { 180 log.Error(fmt.Sprintf("consumer.msg.Consumer error:%s", err.Error())) 181 continue 182 } 183 184 //commit msg ack to consumer ok 185 session.MarkMessage(message, consumer.msgMeta) 186 } 187 188 return nil 189 }