ledisdb/server/snapshot.go

241 lines
4.5 KiB
Go
Raw Normal View History

2014-10-10 13:57:18 +04:00
package server
import (
"fmt"
"github.com/siddontang/go/log"
"github.com/siddontang/ledisdb/config"
"io"
"io/ioutil"
"os"
"path"
"sort"
"sync"
"time"
)
const (
2014-10-10 18:45:22 +04:00
snapshotTimeFormat = "2006-01-02T15:04:05.999999999"
2014-10-10 13:57:18 +04:00
)
type snapshotStore struct {
sync.Mutex
cfg *config.Config
names []string
quit chan struct{}
}
func snapshotName(t time.Time) string {
2014-10-11 13:44:31 +04:00
return fmt.Sprintf("dmp-%s", t.Format(snapshotTimeFormat))
2014-10-10 13:57:18 +04:00
}
func parseSnapshotName(name string) (time.Time, error) {
var timeString string
2014-10-11 13:44:31 +04:00
if _, err := fmt.Sscanf(name, "dmp-%s", &timeString); err != nil {
println(err.Error())
2014-10-10 13:57:18 +04:00
return time.Time{}, err
}
when, err := time.Parse(snapshotTimeFormat, timeString)
if err != nil {
return time.Time{}, err
}
return when, nil
}
func newSnapshotStore(cfg *config.Config) (*snapshotStore, error) {
if len(cfg.Snapshot.Path) == 0 {
cfg.Snapshot.Path = path.Join(cfg.DataDir, "snapshot")
}
if err := os.MkdirAll(cfg.Snapshot.Path, 0755); err != nil {
return nil, err
}
s := new(snapshotStore)
s.cfg = cfg
s.names = make([]string, 0, s.cfg.Snapshot.MaxNum)
s.quit = make(chan struct{})
2014-10-11 06:14:23 +04:00
if err := s.checkSnapshots(); err != nil {
return nil, err
}
go s.run()
return s, nil
}
func (s *snapshotStore) Close() {
close(s.quit)
}
func (s *snapshotStore) checkSnapshots() error {
cfg := s.cfg
2014-10-10 13:57:18 +04:00
snapshots, err := ioutil.ReadDir(cfg.Snapshot.Path)
if err != nil {
2014-10-11 06:14:23 +04:00
log.Error("read %s error: %s", cfg.Snapshot.Path, err.Error())
return err
2014-10-10 13:57:18 +04:00
}
2014-10-11 06:14:23 +04:00
names := []string{}
2014-10-10 13:57:18 +04:00
for _, info := range snapshots {
2014-10-11 06:14:23 +04:00
if path.Ext(info.Name()) == ".tmp" {
log.Error("temp snapshot file name %s, try remove", info.Name())
os.Remove(path.Join(cfg.Snapshot.Path, info.Name()))
continue
}
2014-10-10 13:57:18 +04:00
if _, err := parseSnapshotName(info.Name()); err != nil {
2014-10-11 13:44:31 +04:00
log.Error("invalid snapshot file name %s, err: %s", info.Name(), err.Error())
2014-10-10 13:57:18 +04:00
continue
}
2014-10-11 06:14:23 +04:00
names = append(names, info.Name())
2014-10-10 13:57:18 +04:00
}
//from old to new
2014-10-11 06:14:23 +04:00
sort.Strings(names)
2014-10-10 13:57:18 +04:00
2014-10-11 06:14:23 +04:00
s.names = names
2014-10-10 13:57:18 +04:00
2014-10-11 06:14:23 +04:00
s.purge(false)
2014-10-10 13:57:18 +04:00
2014-10-11 06:14:23 +04:00
return nil
2014-10-10 13:57:18 +04:00
}
func (s *snapshotStore) run() {
2014-10-11 06:14:23 +04:00
t := time.NewTicker(60 * time.Minute)
2014-10-10 13:57:18 +04:00
defer t.Stop()
for {
select {
case <-t.C:
2014-10-10 18:45:22 +04:00
s.Lock()
2014-10-11 06:14:23 +04:00
if err := s.checkSnapshots(); err != nil {
log.Error("check snapshots error %s", err.Error())
}
2014-10-10 18:45:22 +04:00
s.Unlock()
2014-10-10 13:57:18 +04:00
case <-s.quit:
return
}
}
}
2014-10-10 18:45:22 +04:00
func (s *snapshotStore) purge(create bool) {
2014-10-10 13:57:18 +04:00
var names []string
2014-10-10 18:45:22 +04:00
maxNum := s.cfg.Snapshot.MaxNum
num := len(s.names) - maxNum
2014-10-10 13:57:18 +04:00
2014-10-10 18:45:22 +04:00
if create {
num++
if num > len(s.names) {
num = len(s.names)
}
2014-10-10 13:57:18 +04:00
}
2014-10-10 18:45:22 +04:00
if num > 0 {
names = s.names[0:num]
2014-10-10 13:57:18 +04:00
2014-10-10 18:45:22 +04:00
n := copy(s.names, s.names[num:])
s.names = s.names[0:n]
}
for _, name := range names {
if err := os.Remove(s.snapshotPath(name)); err != nil {
log.Error("purge snapshot %s error %s", name, err.Error())
2014-10-10 13:57:18 +04:00
}
}
}
func (s *snapshotStore) snapshotPath(name string) string {
return path.Join(s.cfg.Snapshot.Path, name)
}
type snapshotDumper interface {
Dump(w io.Writer) error
}
2014-10-10 18:45:22 +04:00
type snapshot struct {
io.ReadCloser
f *os.File
}
func (st *snapshot) Read(b []byte) (int, error) {
return st.f.Read(b)
}
func (st *snapshot) Close() error {
2014-10-11 13:44:31 +04:00
return st.f.Close()
2014-10-10 18:45:22 +04:00
}
func (st *snapshot) Size() int64 {
s, _ := st.f.Stat()
return s.Size()
}
2014-10-11 13:44:31 +04:00
func (s *snapshotStore) Create(d snapshotDumper) (*snapshot, time.Time, error) {
2014-10-10 13:57:18 +04:00
s.Lock()
defer s.Unlock()
2014-10-11 13:44:31 +04:00
s.purge(true)
2014-10-10 18:45:22 +04:00
2014-10-10 13:57:18 +04:00
now := time.Now()
name := snapshotName(now)
2014-10-11 06:14:23 +04:00
tmpName := name + ".tmp"
2014-10-11 13:44:31 +04:00
if len(s.names) > 0 {
2014-10-11 06:14:23 +04:00
lastTime, _ := parseSnapshotName(s.names[len(s.names)-1])
if !now.After(lastTime) {
return nil, time.Time{}, fmt.Errorf("create snapshot file time %s is behind %s ",
now.Format(snapshotTimeFormat), lastTime.Format(snapshotTimeFormat))
}
2014-10-10 13:57:18 +04:00
}
2014-10-11 06:14:23 +04:00
f, err := os.OpenFile(s.snapshotPath(tmpName), os.O_RDWR|os.O_CREATE, 0644)
2014-10-10 13:57:18 +04:00
if err != nil {
return nil, time.Time{}, err
}
if err := d.Dump(f); err != nil {
f.Close()
2014-10-11 06:14:23 +04:00
os.Remove(s.snapshotPath(tmpName))
2014-10-10 13:57:18 +04:00
return nil, time.Time{}, err
}
2014-10-11 13:44:31 +04:00
f.Close()
if err := os.Rename(s.snapshotPath(tmpName), s.snapshotPath(name)); err != nil {
return nil, time.Time{}, err
}
2014-10-10 13:57:18 +04:00
2014-10-11 13:44:31 +04:00
if f, err = os.Open(s.snapshotPath(name)); err != nil {
return nil, time.Time{}, err
2014-10-11 06:14:23 +04:00
}
2014-10-11 13:44:31 +04:00
s.names = append(s.names, name)
2014-10-10 13:57:18 +04:00
2014-10-11 13:44:31 +04:00
return &snapshot{f: f}, now, nil
2014-10-10 13:57:18 +04:00
}
2014-10-10 18:45:22 +04:00
func (s *snapshotStore) OpenLatest() (*snapshot, time.Time, error) {
2014-10-10 13:57:18 +04:00
s.Lock()
defer s.Unlock()
if len(s.names) == 0 {
return nil, time.Time{}, nil
}
name := s.names[len(s.names)-1]
t, _ := parseSnapshotName(name)
f, err := os.Open(s.snapshotPath(name))
if err != nil {
return nil, time.Time{}, err
}
2014-10-11 13:44:31 +04:00
return &snapshot{f: f}, t, err
2014-10-10 13:57:18 +04:00
}