@@ -2,14 +2,23 @@ package kafka
22
33import (
44 "context"
5+ "encoding/json"
6+ "errors"
57 "fmt"
68 "log"
7- "encoding/json"
9+ "os"
10+ "os/signal"
11+ "sync"
12+ "syscall"
13+
814 "github.com/IBM/sarama"
915 "github.com/Leo7Deng/ChatApp/models"
1016 "github.com/Leo7Deng/ChatApp/websockets"
17+ // "github.com/gocql/gocql"
1118)
1219
20+ var hub * websockets.Hub
21+
1322// StartConsumer continuously logs incoming messages
1423func StartConsumer (ctx context.Context ) {
1524 config := sarama .NewConfig ()
@@ -46,38 +55,180 @@ func StartConsumer(ctx context.Context) {
4655 }
4756}
4857
49- func WebsocketConsumer (ctx context.Context , hub * websockets.Hub ) {
58+ func WebsocketConsumer (ctx context.Context , websocketHub * websockets.Hub ) {
59+ hub = websocketHub
60+ keepRunning := true
5061 config := sarama .NewConfig ()
5162 config .Consumer .Return .Errors = true
63+ config .Consumer .Group .Rebalance .GroupStrategies = []sarama.BalanceStrategy {sarama .NewBalanceStrategyRoundRobin ()}
5264
53- client , err := sarama . NewConsumer ( brokers , config )
54- if err != nil {
55- log . Fatalf ( "Failed to start Kafka consumer: %v" , err )
65+
66+ consumer := Consumer {
67+ ready : make ( chan bool ),
5668 }
57- defer client .Close ()
5869
59- partitionConsumer , err := client .ConsumePartition (topic , 0 , sarama .OffsetNewest )
70+ ctx , cancel := context .WithCancel (context .Background ())
71+ client , err := sarama .NewConsumerGroup (brokers , "websocket-group" , config )
6072 if err != nil {
61- log .Fatalf ( "Failed to consume Kafka topic : %v" , err )
73+ log .Panicf ( "Error creating consumer group client : %v" , err )
6274 }
63- defer partitionConsumer .Close ()
6475
65- fmt .Println ("Kafka Consumer started..." )
76+ consumptionIsPaused := false
77+ wg := & sync.WaitGroup {}
78+ wg .Add (1 )
79+ go func () {
80+ defer wg .Done ()
81+ for {
82+ err := client .Consume (ctx , []string {topic }, & consumer )
83+ if err != nil {
84+ if errors .Is (err , sarama .ErrClosedConsumerGroup ) {
85+ return
86+ }
87+ log .Printf ("Error from consumer: %v" , err )
88+ }
89+
90+ // If context is canceled, stop the consumer
91+ if ctx .Err () != nil {
92+ return
93+ }
94+
95+ // Reset readiness so we wait for the next session
96+ consumer .ready = make (chan bool )
97+ }
98+ }()
99+
100+
101+ <- consumer .ready // Await till the consumer has been set up
102+ log .Println ("Sarama consumer up and running!..." )
103+
104+ sigusr1 := make (chan os.Signal , 1 )
105+ signal .Notify (sigusr1 , syscall .SIGUSR1 )
106+
107+ sigterm := make (chan os.Signal , 1 )
108+ signal .Notify (sigterm , syscall .SIGINT , syscall .SIGTERM )
109+
110+ for keepRunning {
111+ select {
112+ case <- ctx .Done ():
113+ log .Println ("terminating: context cancelled" )
114+ keepRunning = false
115+ case <- sigterm :
116+ log .Println ("terminating: via signal" )
117+ keepRunning = false
118+ case <- sigusr1 :
119+ toggleConsumptionFlow (client , & consumptionIsPaused )
120+ }
121+ }
122+ cancel ()
123+ wg .Wait ()
124+ if err = client .Close (); err != nil {
125+ log .Panicf ("Error closing client: %v" , err )
126+ }
127+ }
128+
129+ func toggleConsumptionFlow (client sarama.ConsumerGroup , isPaused * bool ) {
130+ if * isPaused {
131+ client .ResumeAll ()
132+ log .Println ("Resuming consumption" )
133+ } else {
134+ client .PauseAll ()
135+ log .Println ("Pausing consumption" )
136+ }
66137
138+ * isPaused = ! * isPaused
139+ }
140+
141+ // Consumer represents a Sarama consumer group consumer
142+ type Consumer struct {
143+ ready chan bool
144+ }
145+
146+ // Setup is run at the beginning of a new session, before ConsumeClaim
147+ func (consumer * Consumer ) Setup (sarama.ConsumerGroupSession ) error {
148+ // Mark the consumer as ready
149+ close (consumer .ready )
150+ return nil
151+ }
152+
153+ // Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
154+ func (consumer * Consumer ) Cleanup (sarama.ConsumerGroupSession ) error {
155+ return nil
156+ }
157+
158+ // ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().
159+ // Once the Messages() channel is closed, the Handler must finish its processing
160+ // loop and exit.
161+ func (consumer * Consumer ) ConsumeClaim (session sarama.ConsumerGroupSession , claim sarama.ConsumerGroupClaim ) error {
162+ // NOTE:
163+ // Do not move the code below to a goroutine.
164+ // The `ConsumeClaim` itself is called within a goroutine, see:
165+ // https://github.com/IBM/sarama/blob/main/consumer_group.go#L27-L29
67166 for {
68167 select {
69- case msg := <- partitionConsumer .Messages ():
168+ case message , ok := <- claim .Messages ():
169+ if ! ok {
170+ log .Printf ("message channel was closed" )
171+ return nil
172+ }
173+ partition , offset := message .Partition , message .Offset
174+ log .Printf ("Kafka websocket consumer: %s | Partition: %d | Offset: %d\n " , message .Value , partition , offset )
70175 var websocketMessage models.WebsocketMessage
71- fmt .Printf ("Kafka consumer viewed: %s\n " , string (msg .Value ))
72- err := json .Unmarshal (msg .Value , & websocketMessage )
176+ err := json .Unmarshal (message .Value , & websocketMessage )
73177 if err != nil {
74178 fmt .Printf ("Failed to unmarshal message: %v\n " , err )
75179 }
76180 websocketMessage .Origin = "server"
77181 hub .SendWebsocketMessage (websocketMessage )
78- case <- ctx .Done ():
79- fmt .Println ("Consumer shutting down..." )
80- return
182+ session .MarkMessage (message , "" )
183+ // Should return when `session.Context()` is done.
184+ // If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see:
185+ // https://github.com/IBM/sarama/issues/1192
186+ case <- session .Context ().Done ():
187+ return nil
81188 }
82189 }
83- }
190+ }
191+
192+
193+
194+
195+
196+
197+
198+
199+
200+
201+
202+
203+
204+
205+ // //////////////////
206+ // group, err := sarama.NewConsumerGroup(brokers, "websocket-group", config)
207+ // if err != nil {
208+ // log.Fatalf("Failed to start Kafka consumer group: %v", err)
209+ // }
210+ // defer group.Close()
211+
212+ // fmt.Println("Kafka Consumer started...")
213+
214+ // for {
215+ // select {
216+ // case msg := <-partitionConsumer.Messages():
217+ // var websocketMessage models.WebsocketMessage
218+ // fmt.Printf("Kafka consumer viewed: %s\n", string(msg.Value))
219+ // err := json.Unmarshal(msg.Value, &websocketMessage)
220+ // if err != nil {
221+ // fmt.Printf("Failed to unmarshal message: %v\n", err)
222+ // }
223+ // websocketMessage.Origin = "server"
224+ // hub.SendWebsocketMessage(websocketMessage)
225+ // case <-ctx.Done():
226+ // fmt.Println("Consumer shutting down...")
227+ // return
228+ // }
229+ // }
230+ // }
231+
232+ // func CassandraConsumer(ctx context.Context, cassandraSession *gocql.Session) {
233+
234+ // }
0 commit comments