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  }