gitee.com/woood2/luca@v1.0.4/cmd/consumer/internal/subscriber/retry_consumer.go (about)

     1  package subscriber
     2  
     3  import (
     4  	"context"
     5  	"gitee.com/woood2/luca/cmd/consumer/internal/alarm"
     6  	"github.com/Shopify/sarama"
     7  	"go.uber.org/zap"
     8  	"runtime/debug"
     9  	"time"
    10  )
    11  
    12  // RetryConsumer represents a Sarama consumer group consumer
    13  type RetryConsumer struct {
    14  	GroupID    string
    15  	MsgHandler MsgHandler
    16  	Logger     *zap.Logger
    17  }
    18  
    19  // Setup is run at the beginning of a new session, before ConsumeClaim
    20  func (consumer *RetryConsumer) Setup(sarama.ConsumerGroupSession) error {
    21  	return nil
    22  }
    23  
    24  // Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
    25  func (consumer *RetryConsumer) Cleanup(sarama.ConsumerGroupSession) error {
    26  	return nil
    27  }
    28  
    29  // ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().
    30  func (consumer *RetryConsumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
    31  	for message := range claim.Messages() {
    32  		retry := 1
    33  		delay := 2 * time.Second
    34  		maxDelay := 5 * time.Minute
    35  		ch := make(chan bool)
    36  	retryLoop:
    37  		for {
    38  			go func() {
    39  				rst := consumer.handle(message, retry) //长任务注意幂等性
    40  				select {
    41  				case ch <- rst:
    42  					return
    43  				case <-session.Context().Done():
    44  					select {
    45  					case ch <- rst:
    46  					default: //防止goroutine泄漏
    47  					}
    48  					return
    49  				}
    50  			}()
    51  			select {
    52  			case <-session.Context().Done():
    53  				select {
    54  				case <-time.NewTimer(5 * time.Second).C:
    55  					return nil
    56  				case b := <-ch:
    57  					if b {
    58  						session.MarkMessage(message, "")
    59  					} else {
    60  						alarm.Yell(retry, consumer.GroupID, message)
    61  					}
    62  					return nil
    63  				}
    64  			case b := <-ch:
    65  				if b {
    66  					session.MarkMessage(message, "")
    67  					select {
    68  					case <-session.Context().Done():
    69  						return nil
    70  					default:
    71  						break retryLoop
    72  					}
    73  				} else {
    74  					alarm.Yell(retry, consumer.GroupID, message)
    75  					select {
    76  					case <-session.Context().Done():
    77  						return nil
    78  					case <-time.NewTimer(delay).C:
    79  					}
    80  					retry++
    81  					delay = min(delay*2, maxDelay)
    82  				}
    83  			}
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  func (consumer *RetryConsumer) handle(msg *sarama.ConsumerMessage, retry int) bool {
    90  	defer func() {
    91  		if r := recover(); r != nil {
    92  			consumer.Logger.Error("panic recover",
    93  				zap.Any("err", r),
    94  				zap.String("topic", msg.Topic),
    95  				zap.String("groupID", consumer.GroupID),
    96  				zap.Int32("partition", msg.Partition),
    97  				zap.Int64("offset", msg.Offset),
    98  				zap.String("stack", string(debug.Stack())),
    99  			)
   100  		}
   101  	}()
   102  	ctx, cancel := context.WithCancel(context.Background())
   103  	defer cancel()
   104  	rst := consumer.MsgHandler(ctx, msg, retry)
   105  	return rst
   106  }
   107  
   108  func min(a, b time.Duration) time.Duration {
   109  	if a >= b {
   110  		return b
   111  	} else {
   112  		return a
   113  	}
   114  }