github.com/hdt3213/godis@v1.2.9/aof/aof.go (about) 1 package aof 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "strconv" 8 "strings" 9 "sync" 10 "time" 11 12 rdb "github.com/hdt3213/rdb/core" 13 14 "github.com/hdt3213/godis/config" 15 16 "github.com/hdt3213/godis/interface/database" 17 "github.com/hdt3213/godis/lib/logger" 18 "github.com/hdt3213/godis/lib/utils" 19 "github.com/hdt3213/godis/redis/connection" 20 "github.com/hdt3213/godis/redis/parser" 21 "github.com/hdt3213/godis/redis/protocol" 22 ) 23 24 // CmdLine is alias for [][]byte, represents a command line 25 type CmdLine = [][]byte 26 27 const ( 28 aofQueueSize = 1 << 16 29 ) 30 31 const ( 32 // FsyncAlways do fsync for every command 33 FsyncAlways = "always" 34 // FsyncEverySec do fsync every second 35 FsyncEverySec = "everysec" 36 // FsyncNo lets operating system decides when to do fsync 37 FsyncNo = "no" 38 ) 39 40 type payload struct { 41 cmdLine CmdLine 42 dbIndex int 43 wg *sync.WaitGroup 44 } 45 46 // Listener will be called-back after receiving a aof payload 47 // with a listener we can forward the updates to slave nodes etc. 48 type Listener interface { 49 // Callback will be called-back after receiving a aof payload 50 Callback([]CmdLine) 51 } 52 53 // Persister receive msgs from channel and write to AOF file 54 type Persister struct { 55 ctx context.Context 56 cancel context.CancelFunc 57 db database.DBEngine 58 tmpDBMaker func() database.DBEngine 59 // aofChan is the channel to receive aof payload(listenCmd will send payload to this channel) 60 aofChan chan *payload 61 // aofFile is the file handler of aof file 62 aofFile *os.File 63 // aofFilename is the path of aof file 64 aofFilename string 65 // aofFsync is the strategy of fsync 66 aofFsync string 67 // aof goroutine will send msg to main goroutine through this channel when aof tasks finished and ready to shut down 68 aofFinished chan struct{} 69 // pause aof for start/finish aof rewrite progress 70 pausingAof sync.Mutex 71 currentDB int 72 listeners map[Listener]struct{} 73 // reuse cmdLine buffer 74 buffer []CmdLine 75 } 76 77 // NewPersister creates a new aof.Persister 78 func NewPersister(db database.DBEngine, filename string, load bool, fsync string, tmpDBMaker func() database.DBEngine) (*Persister, error) { 79 persister := &Persister{} 80 persister.aofFilename = filename 81 persister.aofFsync = strings.ToLower(fsync) 82 persister.db = db 83 persister.tmpDBMaker = tmpDBMaker 84 persister.currentDB = 0 85 // load aof file if needed 86 if load { 87 persister.LoadAof(0) 88 } 89 aofFile, err := os.OpenFile(persister.aofFilename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600) 90 if err != nil { 91 return nil, err 92 } 93 persister.aofFile = aofFile 94 persister.aofChan = make(chan *payload, aofQueueSize) 95 persister.aofFinished = make(chan struct{}) 96 persister.listeners = make(map[Listener]struct{}) 97 // start aof goroutine to write aof file in background and fsync periodically if needed (see fsyncEverySecond) 98 go func() { 99 persister.listenCmd() 100 }() 101 ctx, cancel := context.WithCancel(context.Background()) 102 persister.ctx = ctx 103 persister.cancel = cancel 104 // fsync every second if needed 105 if persister.aofFsync == FsyncEverySec { 106 persister.fsyncEverySecond() 107 } 108 return persister, nil 109 } 110 111 // RemoveListener removes a listener from aof handler, so we can close the listener 112 func (persister *Persister) RemoveListener(listener Listener) { 113 persister.pausingAof.Lock() 114 defer persister.pausingAof.Unlock() 115 delete(persister.listeners, listener) 116 } 117 118 // SaveCmdLine send command to aof goroutine through channel 119 func (persister *Persister) SaveCmdLine(dbIndex int, cmdLine CmdLine) { 120 // aofChan will be set as nil temporarily during load aof see Persister.LoadAof 121 if persister.aofChan == nil { 122 return 123 } 124 125 if persister.aofFsync == FsyncAlways { 126 p := &payload{ 127 cmdLine: cmdLine, 128 dbIndex: dbIndex, 129 } 130 persister.writeAof(p) 131 return 132 } 133 134 persister.aofChan <- &payload{ 135 cmdLine: cmdLine, 136 dbIndex: dbIndex, 137 } 138 139 } 140 141 // listenCmd listen aof channel and write into file 142 func (persister *Persister) listenCmd() { 143 for p := range persister.aofChan { 144 persister.writeAof(p) 145 } 146 persister.aofFinished <- struct{}{} 147 } 148 149 func (persister *Persister) writeAof(p *payload) { 150 persister.buffer = persister.buffer[:0] // reuse underlying array 151 persister.pausingAof.Lock() // prevent other goroutines from pausing aof 152 defer persister.pausingAof.Unlock() 153 // ensure aof is in the right database 154 if p.dbIndex != persister.currentDB { 155 // select db 156 selectCmd := utils.ToCmdLine("SELECT", strconv.Itoa(p.dbIndex)) 157 persister.buffer = append(persister.buffer, selectCmd) 158 data := protocol.MakeMultiBulkReply(selectCmd).ToBytes() 159 _, err := persister.aofFile.Write(data) 160 if err != nil { 161 logger.Warn(err) 162 return // skip this command 163 } 164 persister.currentDB = p.dbIndex 165 } 166 // save command 167 data := protocol.MakeMultiBulkReply(p.cmdLine).ToBytes() 168 persister.buffer = append(persister.buffer, p.cmdLine) 169 _, err := persister.aofFile.Write(data) 170 if err != nil { 171 logger.Warn(err) 172 } 173 for listener := range persister.listeners { 174 listener.Callback(persister.buffer) 175 } 176 if persister.aofFsync == FsyncAlways { 177 _ = persister.aofFile.Sync() 178 } 179 } 180 181 // LoadAof read aof file, can only be used before Persister.listenCmd started 182 func (persister *Persister) LoadAof(maxBytes int) { 183 // persister.db.Exec may call persister.AddAof 184 // delete aofChan to prevent loaded commands back into aofChan 185 aofChan := persister.aofChan 186 persister.aofChan = nil 187 defer func(aofChan chan *payload) { 188 persister.aofChan = aofChan 189 }(aofChan) 190 191 file, err := os.Open(persister.aofFilename) 192 if err != nil { 193 if _, ok := err.(*os.PathError); ok { 194 return 195 } 196 logger.Warn(err) 197 return 198 } 199 defer file.Close() 200 201 // load rdb preamble if needed 202 decoder := rdb.NewDecoder(file) 203 err = persister.db.LoadRDB(decoder) 204 if err != nil { 205 // no rdb preamble 206 file.Seek(0, io.SeekStart) 207 } else { 208 // has rdb preamble 209 _, _ = file.Seek(int64(decoder.GetReadCount())+1, io.SeekStart) 210 maxBytes = maxBytes - decoder.GetReadCount() 211 } 212 var reader io.Reader 213 if maxBytes > 0 { 214 reader = io.LimitReader(file, int64(maxBytes)) 215 } else { 216 reader = file 217 } 218 ch := parser.ParseStream(reader) 219 fakeConn := connection.NewFakeConn() // only used for save dbIndex 220 for p := range ch { 221 if p.Err != nil { 222 if p.Err == io.EOF { 223 break 224 } 225 logger.Error("parse error: " + p.Err.Error()) 226 continue 227 } 228 if p.Data == nil { 229 logger.Error("empty payload") 230 continue 231 } 232 r, ok := p.Data.(*protocol.MultiBulkReply) 233 if !ok { 234 logger.Error("require multi bulk protocol") 235 continue 236 } 237 ret := persister.db.Exec(fakeConn, r.Args) 238 if protocol.IsErrorReply(ret) { 239 logger.Error("exec err", string(ret.ToBytes())) 240 } 241 if strings.ToLower(string(r.Args[0])) == "select" { 242 // execSelect success, here must be no error 243 dbIndex, err := strconv.Atoi(string(r.Args[1])) 244 if err == nil { 245 persister.currentDB = dbIndex 246 } 247 } 248 } 249 } 250 251 // Fsync flushes aof file to disk 252 func (persister *Persister) Fsync() { 253 persister.pausingAof.Lock() 254 if err := persister.aofFile.Sync(); err != nil { 255 logger.Errorf("fsync failed: %v", err) 256 } 257 persister.pausingAof.Unlock() 258 } 259 260 // Close gracefully stops aof persistence procedure 261 func (persister *Persister) Close() { 262 if persister.aofFile != nil { 263 close(persister.aofChan) 264 <-persister.aofFinished // wait for aof finished 265 err := persister.aofFile.Close() 266 if err != nil { 267 logger.Warn(err) 268 } 269 } 270 persister.cancel() 271 } 272 273 // fsyncEverySecond fsync aof file every second 274 func (persister *Persister) fsyncEverySecond() { 275 ticker := time.NewTicker(time.Second) 276 go func() { 277 for { 278 select { 279 case <-ticker.C: 280 persister.Fsync() 281 case <-persister.ctx.Done(): 282 return 283 } 284 } 285 }() 286 } 287 288 func (persister *Persister) generateAof(ctx *RewriteCtx) error { 289 // rewrite aof tmpFile 290 tmpFile := ctx.tmpFile 291 // load aof tmpFile 292 tmpAof := persister.newRewriteHandler() 293 tmpAof.LoadAof(int(ctx.fileSize)) 294 for i := 0; i < config.Properties.Databases; i++ { 295 // select db 296 data := protocol.MakeMultiBulkReply(utils.ToCmdLine("SELECT", strconv.Itoa(i))).ToBytes() 297 _, err := tmpFile.Write(data) 298 if err != nil { 299 return err 300 } 301 // dump db 302 tmpAof.db.ForEach(i, func(key string, entity *database.DataEntity, expiration *time.Time) bool { 303 cmd := EntityToCmd(key, entity) 304 if cmd != nil { 305 _, _ = tmpFile.Write(cmd.ToBytes()) 306 } 307 if expiration != nil { 308 cmd := MakeExpireCmd(key, *expiration) 309 if cmd != nil { 310 _, _ = tmpFile.Write(cmd.ToBytes()) 311 } 312 } 313 return true 314 }) 315 } 316 return nil 317 }