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  }