github.com/diadata-org/diadata@v1.4.593/pkg/dia/helpers/kafkaHelper/Kafka.go (about) 1 package kafkaHelper 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "time" 10 11 "github.com/diadata-org/diadata/pkg/dia" 12 "github.com/diadata-org/diadata/pkg/utils" 13 "github.com/segmentio/kafka-go" 14 "github.com/segmentio/kafka-go/compress" 15 log "github.com/sirupsen/logrus" 16 ) 17 18 const ( 19 messageSizeMax = 1e8 20 ) 21 22 type KafkaMessage interface { 23 MarshalBinary() ([]byte, error) 24 } 25 26 type KafkaMessageWithAHash interface { 27 Hash() string 28 } 29 30 const ( 31 TopicIndexBlock = 0 32 33 TopicFiltersBlock = 1 34 TopicTrades = 2 35 TopicTradesBlock = 3 36 37 // The replica topics can be used to forward trades and blocks to other services in parallel. 38 TopicFiltersBlockReplica = 4 39 TopicTradesReplica = 5 40 TopicTradesBlockReplica = 6 41 42 TopicTradesEstimation = 7 43 44 TopicFiltersBlockDone = 14 45 46 TopicFiltersBlockTest = 21 47 TopicTradesTest = 22 48 TopicTradesBlockTest = 23 49 50 retryDelay = 2 * time.Second 51 ) 52 53 type Config struct { 54 KafkaUrl []string 55 } 56 57 var ( 58 KafkaConfig Config 59 topicSuffix string 60 ) 61 62 func GetTopic(topic int) string { 63 return getTopic(topic) 64 } 65 66 func getTopic(topic int) string { 67 topicMap := map[int]string{ 68 1: "filtersBlock", 69 2: "trades", 70 3: "tradesBlock", 71 4: "filtersBlockReplica" + topicSuffix, 72 5: "tradesReplica" + topicSuffix, 73 6: "tradesBlockReplica" + topicSuffix, 74 7: "tradesEstimation", 75 14: "filtersblockHistoricalDone", 76 21: "filtersblocktest", 77 22: "tradestest", 78 23: "tradesblocktest", 79 } 80 result, ok := topicMap[topic] 81 if !ok { 82 log.Error("getTopic cant find topic", topic) 83 } 84 return result 85 } 86 87 func init() { 88 KafkaConfig.KafkaUrl = []string{os.Getenv("KAFKAURL")} 89 topicSuffix = utils.Getenv("KAFKA_TOPIC_SUFFIX", "") 90 } 91 92 // WithRetryOnError 93 func ReadOffset(topic int) (offset int64, err error) { 94 for _, ip := range KafkaConfig.KafkaUrl { 95 var conn *kafka.Conn 96 conn, err = kafka.DialLeader(context.Background(), "tcp", ip, getTopic(topic), 0) 97 if err != nil { 98 log.Errorln("ReadOffset conn error: <", err, "> ", ip) 99 } else { 100 offset, err = conn.ReadLastOffset() 101 if err != nil { 102 log.Errorln("ReadOffset ReadLastOffset error: <", err, "> ") 103 } else { 104 return 105 } 106 defer func() { 107 cerr := conn.Close() 108 if err == nil { 109 err = cerr 110 } 111 }() 112 } 113 } 114 return 115 } 116 117 func ReadOffsetWithRetryOnError(topic int) (offset int64) { 118 // TO DO: check double infinite for loops. 119 for { 120 for { 121 for _, ip := range KafkaConfig.KafkaUrl { 122 conn, err := kafka.DialLeader(context.Background(), "tcp", ip, getTopic(topic), 0) 123 if err != nil { 124 log.Errorln("ReadOffsetWithRetryOnError conn error: <", err, "> ", ip, " topic:", topic) 125 time.Sleep(retryDelay) 126 } else { 127 defer func() { 128 err = conn.Close() 129 if err != nil { 130 log.Error(err) 131 } 132 }() 133 134 offset, err = conn.ReadLastOffset() 135 if err != nil { 136 log.Errorln("ReadOffsetWithRetryOnError ReadLastOffset error: <", err, "> ", ip, " topic:", topic) 137 time.Sleep(retryDelay) 138 } else { 139 return offset 140 } 141 } 142 } 143 } 144 } 145 } 146 147 func NewWriter(topic int) *kafka.Writer { 148 return kafka.NewWriter(kafka.WriterConfig{ 149 Brokers: KafkaConfig.KafkaUrl, 150 Topic: getTopic(topic), 151 Balancer: &kafka.LeastBytes{}, 152 Async: true, 153 }) 154 } 155 156 func NewSyncWriter(topic int) *kafka.Writer { 157 return kafka.NewWriter(kafka.WriterConfig{ 158 Brokers: KafkaConfig.KafkaUrl, 159 Topic: getTopic(topic), 160 Balancer: &kafka.LeastBytes{}, 161 Async: false, 162 BatchBytes: 1e9, // 1GB 163 }) 164 } 165 166 func NewSyncWriterWithCompression(topic int) *kafka.Writer { 167 return kafka.NewWriter(kafka.WriterConfig{ 168 Brokers: KafkaConfig.KafkaUrl, 169 Topic: getTopic(topic), 170 Balancer: &kafka.LeastBytes{}, 171 Async: false, 172 BatchBytes: 1e9, // 1GB 173 CompressionCodec: &compress.GzipCodec, 174 }) 175 } 176 177 func NewReader(topic int) *kafka.Reader { 178 r := kafka.NewReader(kafka.ReaderConfig{ 179 Brokers: KafkaConfig.KafkaUrl, 180 Topic: getTopic(topic), 181 Partition: 0, 182 MinBytes: 0, 183 MaxBytes: 10e6, // 10MB 184 }) 185 return r 186 } 187 188 func WriteMessage(w *kafka.Writer, m KafkaMessage) error { 189 key := []byte("helloKafka") 190 value, err := m.MarshalBinary() 191 if err == nil && value != nil { 192 err = w.WriteMessages(context.Background(), 193 kafka.Message{ 194 Key: key, 195 Value: value, 196 }, 197 ) 198 if err != nil { 199 log.Errorln("WriteMessage error:", err, "sizeMessage:", float64(len(value))/(1024.0*1024.0), "MB") 200 } 201 } else { 202 log.Errorln("Skipping write of message ", err, m) 203 } 204 return err 205 } 206 207 func NewReaderXElementsBeforeLastMessage(topic int, x int64) *kafka.Reader { 208 209 var offset int64 210 o, err := ReadOffset(topic) 211 212 if err == nil && o-x > 0 { 213 offset = o - x 214 } else { 215 log.Warningf("err %v on readOffset on topic %v", err, topic) 216 } 217 218 log.Println("NewReaderXElementsBeforeLastMessage: setting offset ", offset, "/", o) 219 220 r := kafka.NewReader(kafka.ReaderConfig{ 221 Brokers: KafkaConfig.KafkaUrl, 222 Topic: getTopic(topic), 223 Partition: 0, 224 MinBytes: 0, 225 MaxBytes: 10e6, // 10MB 226 }) 227 err = r.SetOffset(offset) 228 if err != nil { 229 log.Error(err) 230 } 231 return r 232 } 233 234 func NewReaderNextMessage(topic int) *kafka.Reader { 235 offset := ReadOffsetWithRetryOnError(topic) 236 r := NewReader(topic) 237 err := r.SetOffset(offset) 238 if err != nil { 239 log.Error(err) 240 } 241 log.Printf("Reading from offset %d/%d on topic %s", offset, offset, getTopic(topic)) 242 return r 243 } 244 245 func IsTopicEmpty(topic int) bool { 246 log.Println("IsTopicEmpty: ", topic) 247 offset := ReadOffsetWithRetryOnError(topic) 248 offset-- 249 return offset < 0 250 } 251 252 func GetLastElementWithRetryOnError(topic int) interface{} { 253 for { 254 i, err := GetLastElement(topic) 255 if err == nil { 256 return i 257 } 258 time.Sleep(retryDelay) 259 log.Println("GetLastElementWithRetryOnError retrying...") 260 } 261 } 262 263 func GetLastElement(topic int) (interface{}, error) { 264 offset := ReadOffsetWithRetryOnError(topic) 265 offset-- 266 if offset < 0 { 267 return nil, io.EOF 268 } else { 269 e, err := GetElements(topic, offset, 1) 270 if err == nil { 271 return e[0], nil 272 } else { 273 return nil, err 274 } 275 } 276 } 277 278 func GetElements(topic int, offset int64, nbElements int) ([]interface{}, error) { 279 280 var result []interface{} 281 282 var maxOffset = offset + int64(nbElements) 283 284 conn, err := kafka.DialLeader(context.Background(), "tcp", KafkaConfig.KafkaUrl[0], getTopic(topic), 0) 285 286 if err != nil { 287 log.Errorln("kafka error:", err) 288 return nil, err 289 } else { 290 291 newSeek, err := conn.Seek(int64(offset), kafka.SeekAbsolute) 292 293 if err != nil { 294 log.Errorln("kafka error on seek:", err) 295 return nil, err 296 } 297 298 log.Printf("kafka newSeek:%v", newSeek) 299 300 first, last, err := conn.ReadOffsets() 301 if err != nil { 302 return nil, err 303 } 304 log.Printf("kafka ReadOffsets:%v, %v", first, last) 305 306 batch := conn.ReadBatch(0, messageSizeMax*nbElements) 307 308 b := make([]byte, messageSizeMax) 309 for c := offset; c <= maxOffset; c++ { 310 z, err := batch.Read(b) 311 if err != nil { 312 log.Printf("error on batch read: %v", err) 313 return nil, err 314 } 315 b2 := b[:z] 316 317 switch topic { 318 case TopicFiltersBlock: 319 var e dia.FiltersBlock 320 err = e.UnmarshalBinary(b2) 321 if err == nil { 322 result = append(result, e) 323 } 324 case TopicTrades: 325 var e dia.Trade 326 err = e.UnmarshalBinary(b2) 327 if err == nil { 328 result = append(result, e) 329 } 330 case TopicTradesBlock: 331 var e dia.TradesBlock 332 err = e.UnmarshalBinary(b2) 333 if err == nil { 334 result = append(result, e) 335 } 336 default: 337 return nil, errors.New("missing case unknown topic in switch... function GetElements / Kafka.go") 338 } 339 340 if err != nil { 341 errorMsg := fmt.Sprintf("parsing error while processing offset: %v/%v", c, maxOffset) 342 return nil, errors.New(errorMsg) 343 } 344 if len(result) == nbElements { 345 break 346 } 347 } 348 349 return result, nil 350 } 351 }