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 }