github.com/kaydxh/golang@v0.0.131/pkg/binlog/binlog.go (about)

     1  /*
     2   *Copyright (c) 2023, kaydxh
     3   *
     4   *Permission is hereby granted, free of charge, to any person obtaining a copy
     5   *of this software and associated documentation files (the "Software"), to deal
     6   *in the Software without restriction, including without limitation the rights
     7   *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   *copies of the Software, and to permit persons to whom the Software is
     9   *furnished to do so, subject to the following conditions:
    10   *
    11   *The above copyright notice and this permission notice shall be included in all
    12   *copies or substantial portions of the Software.
    13   *
    14   *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   *SOFTWARE.
    21   */
    22  package binlog
    23  
    24  import (
    25  	"context"
    26  	"encoding/json"
    27  	"fmt"
    28  	"os"
    29  	"path/filepath"
    30  	"sync"
    31  	"sync/atomic"
    32  	"time"
    33  
    34  	"github.com/google/uuid"
    35  	"github.com/kaydxh/golang/go/errors"
    36  	ds_ "github.com/kaydxh/golang/pkg/binlog/datastore"
    37  	mq_ "github.com/kaydxh/golang/pkg/mq"
    38  	taskq_ "github.com/kaydxh/golang/pkg/pool/taskqueue"
    39  	queue_ "github.com/kaydxh/golang/pkg/pool/taskqueue/queue"
    40  	"github.com/sirupsen/logrus"
    41  	"golang.org/x/sync/errgroup"
    42  )
    43  
    44  type MessageDecoderFunc func(ctx context.Context, data []byte) (interface{}, error)
    45  type MessageKeyDecodeFunc func(ctx context.Context, data []byte) (ds_.MessageKey, error)
    46  
    47  type BinlogOptions struct {
    48  	rootPath       string
    49  	prefixName     string
    50  	suffixName     string
    51  	flushBatchSize int
    52  	flushInterval  time.Duration
    53  	rotateInterval time.Duration
    54  	rotateSize     int64
    55  
    56  	remotePrefixPath string
    57  	archive          bool
    58  
    59  	msgDecodeFunc    MessageDecoderFunc
    60  	msgKeyDecodeFunc MessageKeyDecodeFunc
    61  }
    62  
    63  type Channel struct {
    64  	Name string
    65  }
    66  
    67  type BinlogService struct {
    68  	consumers []mq_.Consumer
    69  
    70  	dataStore ds_.DataStore
    71  
    72  	taskq *taskq_.Pool
    73  
    74  	opts BinlogOptions
    75  
    76  	inShutdown atomic.Bool
    77  	mu         sync.Mutex
    78  	cancel     func()
    79  }
    80  
    81  func defaultBinlogServiceOptions() BinlogOptions {
    82  	opts := BinlogOptions{
    83  		prefixName:     "segment",
    84  		suffixName:     "log",
    85  		flushBatchSize: 1024,
    86  		//flushBatchSize: 5,
    87  		flushInterval:  time.Second, // 1s
    88  		rotateInterval: time.Hour,
    89  		rotateSize:     100 * 1024 * 1024, //100M
    90  	}
    91  	path, err := os.Getwd()
    92  	if err != nil {
    93  		path = "/"
    94  	}
    95  	opts.rootPath = path
    96  	return opts
    97  }
    98  
    99  func NewBinlogService(dataStore ds_.DataStore, taskq *taskq_.Pool, consumers []mq_.Consumer, opts ...BinlogServiceOption) (*BinlogService, error) {
   100  	/*
   101  		if taskq == nil {
   102  			return nil, fmt.Errorf("taskq is empty")
   103  		}
   104  	*/
   105  	if len(consumers) == 0 {
   106  		return nil, fmt.Errorf("consumers is empty")
   107  	}
   108  
   109  	bs := &BinlogService{
   110  		dataStore: dataStore,
   111  		taskq:     taskq,
   112  		consumers: consumers,
   113  		opts:      defaultBinlogServiceOptions(),
   114  	}
   115  	bs.ApplyOptions(opts...)
   116  
   117  	return bs, nil
   118  }
   119  
   120  // send archive path to topic
   121  func (srv *BinlogService) rotateCallback(ctx context.Context, path string) {
   122  	logger := srv.logger()
   123  
   124  	if srv.taskq == nil {
   125  		return
   126  	}
   127  
   128  	if srv.opts.archive {
   129  		args := &ArchiveTaskArgs{
   130  			LocalFilePath:  path,
   131  			RemoteRootPath: filepath.Join(srv.opts.remotePrefixPath, filepath.Base(path)),
   132  		}
   133  		data, err := json.Marshal(args)
   134  		if err != nil {
   135  			logger.WithError(err).Errorf("failed to marshal args: %v", args)
   136  			return
   137  		}
   138  
   139  		id := uuid.NewString()
   140  		msg := &queue_.Message{
   141  			Id:     id,
   142  			Name:   id,
   143  			Scheme: ArchiveTaskScheme,
   144  			Args:   string(data),
   145  		}
   146  
   147  		_, err = srv.taskq.Publish(ctx, msg)
   148  		if err != nil {
   149  			logger.WithError(err).Errorf("failed to publish msg: %v", msg)
   150  			return
   151  		}
   152  	}
   153  }
   154  
   155  func (srv *BinlogService) logger() logrus.FieldLogger {
   156  	return logrus.WithField("module", "BinlogService")
   157  }
   158  
   159  func (srv *BinlogService) Run(ctx context.Context) error {
   160  	logger := srv.logger()
   161  	logger.Infoln("BinlogService Run")
   162  	if srv.inShutdown.Load() {
   163  		logger.Infoln("BinlogService Shutdown")
   164  		return fmt.Errorf("server closed")
   165  	}
   166  	go func() {
   167  		errors.HandleError(srv.Serve(ctx))
   168  	}()
   169  	return nil
   170  }
   171  
   172  func (srv *BinlogService) work(ctx context.Context, msgCh <-chan *ds_.Message) {
   173  	logger := srv.logger()
   174  	timer := time.NewTimer(srv.opts.flushInterval)
   175  	defer timer.Stop()
   176  
   177  	var (
   178  		flushBatchData []interface{}
   179  		lastMsgKey     ds_.MessageKey
   180  	)
   181  
   182  	operateFunc := func() {
   183  		for {
   184  			select {
   185  			case <-ctx.Done():
   186  				return
   187  			case <-timer.C:
   188  				logger.Info("start to flush timer")
   189  				if len(flushBatchData) > 0 {
   190  					_, err := srv.dataStore.WriteData(ctx, flushBatchData, lastMsgKey)
   191  					if err != nil {
   192  
   193  					}
   194  					flushBatchData = nil
   195  					logger.Info("finished to flush timer")
   196  				}
   197  				timer.Reset(srv.opts.flushInterval)
   198  			case msg, ok := <-msgCh:
   199  				if !ok {
   200  					return
   201  				}
   202  				var msgValue interface{}
   203  				var err error
   204  				if srv.opts.msgDecodeFunc != nil {
   205  					msgValue, err = srv.opts.msgDecodeFunc(ctx, msg.Value)
   206  					if err != nil {
   207  						break
   208  					}
   209  
   210  				} else {
   211  					msgValue = msg.Value
   212  				}
   213  
   214  				msgKey := ds_.MessageKey{
   215  					Key: string(msg.Key),
   216  				}
   217  				if srv.opts.msgKeyDecodeFunc != nil {
   218  					msgKey, err = srv.opts.msgKeyDecodeFunc(ctx, msg.Key)
   219  					if err != nil {
   220  						break
   221  					}
   222  				}
   223  
   224  				if msgKey.MsgType != ds_.MsgType_Insert {
   225  					if len(flushBatchData) > 0 {
   226  						_, err = srv.dataStore.WriteData(ctx, flushBatchData, lastMsgKey)
   227  						flushBatchData = nil
   228  					}
   229  					//todo do the msg
   230  					continue
   231  
   232  				} else {
   233  					// insert type
   234  					if len(flushBatchData) == 0 || msgKey.Equual(lastMsgKey) {
   235  						flushBatchData = append(flushBatchData, msgValue)
   236  						lastMsgKey = msgKey
   237  
   238  					} else {
   239  						logger.Infof("current msg key[%v] is not equal last msg key[%v]", msgKey, lastMsgKey)
   240  						if len(flushBatchData) > 0 {
   241  							_, err = srv.dataStore.WriteData(ctx, flushBatchData, lastMsgKey)
   242  							flushBatchData = nil
   243  						}
   244  						//todo do the msg
   245  						continue
   246  
   247  					}
   248  				}
   249  
   250  				if len(flushBatchData) >= srv.opts.flushBatchSize {
   251  					logger.Infof("flush batch data size[%v] >= flush batch size[%v]", len(flushBatchData), srv.opts.flushBatchSize)
   252  					_, err = srv.dataStore.WriteData(ctx, flushBatchData[:srv.opts.flushBatchSize], lastMsgKey)
   253  					flushBatchData = flushBatchData[srv.opts.flushBatchSize:]
   254  					if err != nil {
   255  						break
   256  					}
   257  
   258  					// https://github.com/golang/go/issues/27169
   259  					// https://tonybai.com/2016/12/21/how-to-use-timer-reset-in-golang-correctly/
   260  					if !timer.Stop() {
   261  						select {
   262  						case <-timer.C:
   263  						default:
   264  						}
   265  					}
   266  					timer.Reset(srv.opts.flushInterval)
   267  				}
   268  			}
   269  		}
   270  
   271  	}
   272  
   273  	go operateFunc()
   274  	return
   275  }
   276  
   277  func (srv *BinlogService) flush(ctx context.Context, consumer mq_.Consumer) error {
   278  	logger := srv.logger()
   279  
   280  	msgCh := make(chan *ds_.Message)
   281  	srv.work(ctx, msgCh)
   282  	for msg := range consumer.ReadStream(ctx) {
   283  		logger.Infof("recv message key[%v] value[%v] from channel[%v]", string(msg.Key()), string(msg.Value()), consumer.Topic())
   284  		if msg.Error() != nil {
   285  			logger.WithError(msg.Error()).Errorf("faild to read stream %v", consumer.Topic())
   286  			continue
   287  		}
   288  
   289  		msgWrap := &ds_.Message{
   290  			Key:   msg.Key(),
   291  			Value: msg.Value(),
   292  		}
   293  		msgCh <- msgWrap
   294  	}
   295  
   296  	return nil
   297  }
   298  
   299  func (srv *BinlogService) Serve(ctx context.Context) error {
   300  	logger := srv.logger()
   301  	logger.Infoln("ServiceBinlog Serve")
   302  
   303  	if srv.inShutdown.Load() {
   304  		err := fmt.Errorf("server closed")
   305  		logger.WithError(err).Errorf("ServiceBinlog Serve canceled")
   306  		return err
   307  	}
   308  
   309  	defer srv.inShutdown.Store(true)
   310  	ctx, cancel := context.WithCancel(ctx)
   311  	srv.mu.Lock()
   312  	srv.cancel = cancel
   313  	srv.mu.Unlock()
   314  
   315  	g, gCtx := errgroup.WithContext(ctx)
   316  	for _, consumer := range srv.consumers {
   317  		consumer := consumer
   318  		g.Go(func() error {
   319  			return srv.flush(gCtx, consumer)
   320  		})
   321  	}
   322  	err := g.Wait()
   323  	if err != nil {
   324  		logger.WithError(err).Errorf("wait flush worker")
   325  		return err
   326  	}
   327  	logger.Info("stopped binlog service")
   328  	return nil
   329  }
   330  
   331  func (srv *BinlogService) Shutdown() {
   332  	srv.inShutdown.Store(true)
   333  	srv.mu.Lock()
   334  	defer srv.mu.Unlock()
   335  
   336  	for _, consumer := range srv.consumers {
   337  		if consumer != nil {
   338  			consumer.Close()
   339  		}
   340  	}
   341  	if srv.cancel != nil {
   342  		srv.cancel()
   343  	}
   344  }