|
- package nodb
-
- import (
- "bufio"
- "encoding/binary"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "github.com/lunny/log"
- "github.com/lunny/nodb/config"
- )
-
- type BinLogHead struct {
- CreateTime uint32
- BatchId uint32
- PayloadLen uint32
- }
-
- func (h *BinLogHead) Len() int {
- return 12
- }
-
- func (h *BinLogHead) Write(w io.Writer) error {
- if err := binary.Write(w, binary.BigEndian, h.CreateTime); err != nil {
- return err
- }
-
- if err := binary.Write(w, binary.BigEndian, h.BatchId); err != nil {
- return err
- }
-
- if err := binary.Write(w, binary.BigEndian, h.PayloadLen); err != nil {
- return err
- }
-
- return nil
- }
-
- func (h *BinLogHead) handleReadError(err error) error {
- if err == io.EOF {
- return io.ErrUnexpectedEOF
- } else {
- return err
- }
- }
-
- func (h *BinLogHead) Read(r io.Reader) error {
- var err error
- if err = binary.Read(r, binary.BigEndian, &h.CreateTime); err != nil {
- return err
- }
-
- if err = binary.Read(r, binary.BigEndian, &h.BatchId); err != nil {
- return h.handleReadError(err)
- }
-
- if err = binary.Read(r, binary.BigEndian, &h.PayloadLen); err != nil {
- return h.handleReadError(err)
- }
-
- return nil
- }
-
- func (h *BinLogHead) InSameBatch(ho *BinLogHead) bool {
- if h.CreateTime == ho.CreateTime && h.BatchId == ho.BatchId {
- return true
- } else {
- return false
- }
- }
-
- /*
- index file format:
- ledis-bin.00001
- ledis-bin.00002
- ledis-bin.00003
-
- log file format
-
- Log: Head|PayloadData
-
- Head: createTime|batchId|payloadData
-
- */
-
- type BinLog struct {
- sync.Mutex
-
- path string
-
- cfg *config.BinLogConfig
-
- logFile *os.File
-
- logWb *bufio.Writer
-
- indexName string
- logNames []string
- lastLogIndex int64
-
- batchId uint32
-
- ch chan struct{}
- }
-
- func NewBinLog(cfg *config.Config) (*BinLog, error) {
- l := new(BinLog)
-
- l.cfg = &cfg.BinLog
- l.cfg.Adjust()
-
- l.path = path.Join(cfg.DataDir, "binlog")
-
- if err := os.MkdirAll(l.path, os.ModePerm); err != nil {
- return nil, err
- }
-
- l.logNames = make([]string, 0, 16)
-
- l.ch = make(chan struct{})
-
- if err := l.loadIndex(); err != nil {
- return nil, err
- }
-
- return l, nil
- }
-
- func (l *BinLog) flushIndex() error {
- data := strings.Join(l.logNames, "\n")
-
- bakName := fmt.Sprintf("%s.bak", l.indexName)
- f, err := os.OpenFile(bakName, os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- log.Error("create binlog bak index error %s", err.Error())
- return err
- }
-
- if _, err := f.WriteString(data); err != nil {
- log.Error("write binlog index error %s", err.Error())
- f.Close()
- return err
- }
-
- f.Close()
-
- if err := os.Rename(bakName, l.indexName); err != nil {
- log.Error("rename binlog bak index error %s", err.Error())
- return err
- }
-
- return nil
- }
-
- func (l *BinLog) loadIndex() error {
- l.indexName = path.Join(l.path, fmt.Sprintf("ledis-bin.index"))
- if _, err := os.Stat(l.indexName); os.IsNotExist(err) {
- //no index file, nothing to do
- } else {
- indexData, err := ioutil.ReadFile(l.indexName)
- if err != nil {
- return err
- }
-
- lines := strings.Split(string(indexData), "\n")
- for _, line := range lines {
- line = strings.Trim(line, "\r\n ")
- if len(line) == 0 {
- continue
- }
-
- if _, err := os.Stat(path.Join(l.path, line)); err != nil {
- log.Error("load index line %s error %s", line, err.Error())
- return err
- } else {
- l.logNames = append(l.logNames, line)
- }
- }
- }
- if l.cfg.MaxFileNum > 0 && len(l.logNames) > l.cfg.MaxFileNum {
- //remove oldest logfile
- if err := l.Purge(len(l.logNames) - l.cfg.MaxFileNum); err != nil {
- return err
- }
- }
-
- var err error
- if len(l.logNames) == 0 {
- l.lastLogIndex = 1
- } else {
- lastName := l.logNames[len(l.logNames)-1]
-
- if l.lastLogIndex, err = strconv.ParseInt(path.Ext(lastName)[1:], 10, 64); err != nil {
- log.Error("invalid logfile name %s", err.Error())
- return err
- }
-
- //like mysql, if server restart, a new binlog will create
- l.lastLogIndex++
- }
-
- return nil
- }
-
- func (l *BinLog) getLogFile() string {
- return l.FormatLogFileName(l.lastLogIndex)
- }
-
- func (l *BinLog) openNewLogFile() error {
- var err error
- lastName := l.getLogFile()
-
- logPath := path.Join(l.path, lastName)
- if l.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0666); err != nil {
- log.Error("open new logfile error %s", err.Error())
- return err
- }
-
- if l.cfg.MaxFileNum > 0 && len(l.logNames) == l.cfg.MaxFileNum {
- l.purge(1)
- }
-
- l.logNames = append(l.logNames, lastName)
-
- if l.logWb == nil {
- l.logWb = bufio.NewWriterSize(l.logFile, 1024)
- } else {
- l.logWb.Reset(l.logFile)
- }
-
- if err = l.flushIndex(); err != nil {
- return err
- }
-
- return nil
- }
-
- func (l *BinLog) checkLogFileSize() bool {
- if l.logFile == nil {
- return false
- }
-
- st, _ := l.logFile.Stat()
- if st.Size() >= int64(l.cfg.MaxFileSize) {
- l.closeLog()
- return true
- }
-
- return false
- }
-
- func (l *BinLog) closeLog() {
- l.lastLogIndex++
-
- l.logFile.Close()
- l.logFile = nil
- }
-
- func (l *BinLog) purge(n int) {
- for i := 0; i < n; i++ {
- logPath := path.Join(l.path, l.logNames[i])
- os.Remove(logPath)
- }
-
- copy(l.logNames[0:], l.logNames[n:])
- l.logNames = l.logNames[0 : len(l.logNames)-n]
- }
-
- func (l *BinLog) Close() {
- if l.logFile != nil {
- l.logFile.Close()
- l.logFile = nil
- }
- }
-
- func (l *BinLog) LogNames() []string {
- return l.logNames
- }
-
- func (l *BinLog) LogFileName() string {
- return l.getLogFile()
- }
-
- func (l *BinLog) LogFilePos() int64 {
- if l.logFile == nil {
- return 0
- } else {
- st, _ := l.logFile.Stat()
- return st.Size()
- }
- }
-
- func (l *BinLog) LogFileIndex() int64 {
- return l.lastLogIndex
- }
-
- func (l *BinLog) FormatLogFileName(index int64) string {
- return fmt.Sprintf("ledis-bin.%07d", index)
- }
-
- func (l *BinLog) FormatLogFilePath(index int64) string {
- return path.Join(l.path, l.FormatLogFileName(index))
- }
-
- func (l *BinLog) LogPath() string {
- return l.path
- }
-
- func (l *BinLog) Purge(n int) error {
- l.Lock()
- defer l.Unlock()
-
- if len(l.logNames) == 0 {
- return nil
- }
-
- if n >= len(l.logNames) {
- n = len(l.logNames)
- //can not purge current log file
- if l.logNames[n-1] == l.getLogFile() {
- n = n - 1
- }
- }
-
- l.purge(n)
-
- return l.flushIndex()
- }
-
- func (l *BinLog) PurgeAll() error {
- l.Lock()
- defer l.Unlock()
-
- l.closeLog()
- return l.openNewLogFile()
- }
-
- func (l *BinLog) Log(args ...[]byte) error {
- l.Lock()
- defer l.Unlock()
-
- var err error
-
- if l.logFile == nil {
- if err = l.openNewLogFile(); err != nil {
- return err
- }
- }
-
- head := &BinLogHead{}
-
- head.CreateTime = uint32(time.Now().Unix())
- head.BatchId = l.batchId
-
- l.batchId++
-
- for _, data := range args {
- head.PayloadLen = uint32(len(data))
-
- if err := head.Write(l.logWb); err != nil {
- return err
- }
-
- if _, err := l.logWb.Write(data); err != nil {
- return err
- }
- }
-
- if err = l.logWb.Flush(); err != nil {
- log.Error("write log error %s", err.Error())
- return err
- }
-
- l.checkLogFileSize()
-
- close(l.ch)
- l.ch = make(chan struct{})
-
- return nil
- }
-
- func (l *BinLog) Wait() <-chan struct{} {
- return l.ch
- }
|