ledisdb/server/replication.go

508 lines
8.9 KiB
Go
Raw Normal View History

package server
2014-06-08 12:43:59 +04:00
import (
2014-06-09 13:23:32 +04:00
"bytes"
"errors"
"fmt"
"net"
2014-06-08 12:43:59 +04:00
"os"
"path"
2014-10-21 13:35:03 +04:00
"strings"
2014-06-09 13:23:32 +04:00
"sync"
"time"
2015-05-04 17:42:28 +03:00
2020-04-24 09:10:03 +03:00
"github.com/ledisdb/ledisdb/ledis"
"github.com/ledisdb/ledisdb/rpl"
2015-05-04 17:42:28 +03:00
"github.com/siddontang/go/log"
"github.com/siddontang/go/num"
"github.com/siddontang/go/sync2"
"github.com/siddontang/goredis"
2014-06-08 12:43:59 +04:00
)
2014-06-09 13:23:32 +04:00
var (
errConnectMaster = errors.New("connect master error")
2014-11-01 18:28:28 +03:00
errReplClosed = errors.New("replication is closed")
2014-06-09 13:23:32 +04:00
)
2015-02-03 09:15:30 +03:00
const (
// slave needs to connect to its master
replConnectState int32 = iota + 1
// slave-master connection is in progress
replConnectingState
// perform the synchronization
replSyncState
// slave is online
replConnectedState
)
2015-02-11 05:13:21 +03:00
type syncBuffer struct {
m *master
bytes.Buffer
}
func (b *syncBuffer) Write(data []byte) (int, error) {
b.m.state.Set(replSyncState)
n, err := b.Buffer.Write(data)
return n, err
}
2014-06-09 13:23:32 +04:00
type master struct {
sync.Mutex
connLock sync.Mutex
2015-03-11 06:54:02 +03:00
conn *goredis.Conn
2014-06-09 13:23:32 +04:00
app *App
quit chan struct{}
addr string
2014-06-09 13:23:32 +04:00
wg sync.WaitGroup
2015-02-11 05:13:21 +03:00
syncBuf syncBuffer
2015-02-03 09:15:30 +03:00
state sync2.AtomicInt32
2014-06-09 13:23:32 +04:00
}
func newMaster(app *App) *master {
m := new(master)
m.app = app
2014-06-10 06:41:50 +04:00
m.quit = make(chan struct{}, 1)
2015-02-11 05:13:21 +03:00
m.syncBuf = syncBuffer{m: m}
2014-06-09 13:23:32 +04:00
2015-02-03 09:15:30 +03:00
m.state.Set(replConnectState)
2014-06-09 13:23:32 +04:00
return m
2014-06-08 12:43:59 +04:00
}
2014-06-09 13:23:32 +04:00
func (m *master) Close() {
m.state.Set(replConnectState)
if !m.isQuited() {
close(m.quit)
}
2014-06-09 13:23:32 +04:00
2014-11-01 18:28:28 +03:00
m.closeConn()
2014-06-09 13:23:32 +04:00
m.wg.Wait()
2014-06-08 12:43:59 +04:00
}
func (m *master) closeConn() {
m.connLock.Lock()
defer m.connLock.Unlock()
2014-06-09 13:23:32 +04:00
if m.conn != nil {
//for replication, we send quit command to close gracefully
m.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
m.conn.Close()
2014-06-08 12:43:59 +04:00
}
m.conn = nil
}
func (m *master) checkConn() error {
m.connLock.Lock()
defer m.connLock.Unlock()
2014-11-01 18:28:28 +03:00
var err error
if m.conn == nil {
2015-03-11 06:54:02 +03:00
m.conn, err = goredis.Connect(m.addr)
if err != nil {
return err
}
}
// already connected and has master password
if len(m.app.cfg.Replication.MasterPassword) != 0 {
var res string
res, err = goredis.String(m.conn.Do("auth", m.app.cfg.Replication.MasterPassword))
if err != nil || strings.ToUpper(res) != "OK" {
m.conn.Close()
m.conn = nil
if err == nil {
err = fmt.Errorf("master auth fail , res=%s , password=%s", res, m.app.cfg.Replication.MasterPassword)
}
return err
}
2014-11-01 18:28:28 +03:00
}
if _, err = m.conn.Do("PING"); err != nil {
m.conn.Close()
m.conn = nil
}
return err
2014-11-01 18:28:28 +03:00
}
2014-06-09 13:23:32 +04:00
func (m *master) stopReplication() error {
m.Close()
return nil
}
func (m *master) startReplication(masterAddr string, restart bool) error {
2014-06-09 13:23:32 +04:00
//stop last replcation, if avaliable
m.Close()
m.addr = masterAddr
2014-06-09 13:23:32 +04:00
2014-11-01 18:28:28 +03:00
m.app.cfg.SetReadonly(true)
m.quit = make(chan struct{}, 1)
if len(m.addr) == 0 {
return fmt.Errorf("no assign master addr")
}
m.wg.Add(1)
go m.runReplication(restart)
2014-06-09 13:23:32 +04:00
return nil
}
func (m *master) isQuited() bool {
2015-02-11 04:55:34 +03:00
select {
case <-m.quit:
return true
default:
return false
}
}
func (m *master) runReplication(restart bool) {
2015-02-03 09:15:30 +03:00
defer func() {
m.state.Set(replConnectState)
m.wg.Done()
}()
2014-06-09 13:23:32 +04:00
for {
2015-02-11 04:55:34 +03:00
m.state.Set(replConnectState)
if m.isQuited() {
return
}
if err := m.checkConn(); err != nil {
log.Errorf("check master %s connection error %s, try 3s later", m.addr, err.Error())
select {
case <-time.After(3 * time.Second):
case <-m.quit:
return
}
2015-02-11 04:55:34 +03:00
continue
}
if m.isQuited() {
2014-06-09 13:23:32 +04:00
return
}
2015-02-03 09:15:30 +03:00
m.state.Set(replConnectedState)
2014-10-21 13:35:03 +04:00
if err := m.replConf(); err != nil {
if strings.Contains(err.Error(), ledis.ErrRplNotSupport.Error()) {
log.Fatalf("master doesn't support replication, wait 10s and retry")
select {
case <-time.After(10 * time.Second):
case <-m.quit:
return
}
} else {
log.Errorf("replconf error %s", err.Error())
}
2015-02-11 04:55:34 +03:00
continue
2014-10-21 13:35:03 +04:00
}
if restart {
if err := m.fullSync(); err != nil {
2015-01-12 05:32:03 +03:00
log.Errorf("restart fullsync error %s", err.Error())
2015-02-11 04:55:34 +03:00
continue
}
2015-02-11 04:55:34 +03:00
m.state.Set(replConnectedState)
}
2014-06-09 13:23:32 +04:00
for {
2015-02-11 04:55:34 +03:00
if err := m.sync(); err != nil {
log.Errorf("sync error %s", err.Error())
break
}
m.state.Set(replConnectedState)
if m.isQuited() {
2014-06-09 13:23:32 +04:00
return
}
}
}
}
2014-10-21 13:35:03 +04:00
func (m *master) replConf() error {
_, port, err := net.SplitHostPort(m.app.cfg.Addr)
if err != nil {
return err
}
2015-03-11 06:54:02 +03:00
if s, err := goredis.String(m.conn.Do("replconf", "listening-port", port)); err != nil {
2014-10-21 13:35:03 +04:00
return err
2014-10-23 09:34:27 +04:00
} else if strings.ToUpper(s) != "OK" {
2014-10-21 13:35:03 +04:00
return fmt.Errorf("not ok but %s", s)
}
return nil
}
2014-06-09 13:23:32 +04:00
func (m *master) fullSync() error {
log.Info("begin full sync")
2014-10-23 09:34:27 +04:00
if err := m.conn.Send("fullsync"); err != nil {
2014-06-09 13:23:32 +04:00
return err
}
2015-02-11 04:55:34 +03:00
m.state.Set(replSyncState)
2014-06-09 13:23:32 +04:00
dumpPath := path.Join(m.app.cfg.DataDir, "master.dump")
2014-09-18 17:27:43 +04:00
f, err := os.OpenFile(dumpPath, os.O_CREATE|os.O_WRONLY, 0644)
2014-06-09 13:23:32 +04:00
if err != nil {
return err
}
defer os.Remove(dumpPath)
2014-10-23 09:34:27 +04:00
err = m.conn.ReceiveBulkTo(f)
2014-06-09 13:23:32 +04:00
f.Close()
if err != nil {
2015-01-12 05:32:03 +03:00
log.Errorf("read dump data error %s", err.Error())
2014-06-09 13:23:32 +04:00
return err
}
if _, err = m.app.ldb.LoadDumpFile(dumpPath); err != nil {
2015-01-12 05:32:03 +03:00
log.Errorf("load dump file error %s", err.Error())
2014-06-09 13:23:32 +04:00
return err
}
return nil
2014-06-09 13:23:32 +04:00
}
2014-09-25 18:33:09 +04:00
func (m *master) nextSyncLogID() (uint64, error) {
s, err := m.app.ldb.ReplicationStat()
if err != nil {
return 0, err
}
if s.LastID > s.CommitID {
return s.LastID + 1, nil
} else {
return s.CommitID + 1, nil
}
}
2014-06-09 13:23:32 +04:00
func (m *master) sync() error {
var err error
var syncID uint64
2014-09-25 18:33:09 +04:00
if syncID, err = m.nextSyncLogID(); err != nil {
return err
}
2014-06-09 13:23:32 +04:00
2014-10-23 09:34:27 +04:00
if err := m.conn.Send("sync", syncID); err != nil {
2014-06-09 13:23:32 +04:00
return err
}
2015-02-11 05:13:21 +03:00
m.state.Set(replConnectedState)
2015-02-11 04:55:34 +03:00
2014-06-09 13:23:32 +04:00
m.syncBuf.Reset()
2014-10-23 09:34:27 +04:00
if err = m.conn.ReceiveBulkTo(&m.syncBuf); err != nil {
if strings.Contains(err.Error(), ledis.ErrLogMissed.Error()) {
return m.fullSync()
} else {
return err
}
2014-06-09 13:23:32 +04:00
}
2015-02-11 05:13:21 +03:00
m.state.Set(replConnectedState)
2014-09-27 06:08:45 +04:00
buf := m.syncBuf.Bytes()
2014-06-09 13:23:32 +04:00
2014-10-22 05:11:14 +04:00
if len(buf) < 8 {
return fmt.Errorf("inavlid sync size %d", len(buf))
}
m.app.info.Replication.MasterLastLogID.Set(num.BytesToUint64(buf))
var t bytes.Buffer
m.app.info.dumpReplication(&t)
buf = buf[8:]
if len(buf) == 0 {
2014-06-09 13:23:32 +04:00
return nil
}
if err = m.app.ldb.StoreLogsFromData(buf); err != nil {
2014-06-09 13:23:32 +04:00
return err
}
return nil
2014-06-09 13:23:32 +04:00
}
2014-10-10 05:49:16 +04:00
func (app *App) slaveof(masterAddr string, restart bool, readonly bool) error {
2014-06-09 13:23:32 +04:00
app.m.Lock()
defer app.m.Unlock()
2014-10-10 05:49:16 +04:00
//in master mode and no slaveof, only set readonly
if len(app.cfg.SlaveOf) == 0 && len(masterAddr) == 0 {
2014-11-01 18:28:28 +03:00
app.cfg.SetReadonly(readonly)
2014-10-10 05:49:16 +04:00
return nil
}
if !app.ldb.ReplicationUsed() {
return fmt.Errorf("slaveof must enable replication")
}
app.cfg.SlaveOf = masterAddr
2014-06-09 13:23:32 +04:00
if len(masterAddr) == 0 {
log.Infof("slaveof no one, stop replication")
2014-09-25 12:03:29 +04:00
if err := app.m.stopReplication(); err != nil {
return err
}
2014-11-01 18:28:28 +03:00
app.cfg.SetReadonly(readonly)
2014-06-09 13:23:32 +04:00
} else {
return app.m.startReplication(masterAddr, restart)
2014-06-08 12:43:59 +04:00
}
return nil
}
func (app *App) tryReSlaveof() error {
app.m.Lock()
defer app.m.Unlock()
if !app.ldb.ReplicationUsed() {
return nil
}
if len(app.cfg.SlaveOf) == 0 {
return nil
} else {
return app.m.startReplication(app.cfg.SlaveOf, true)
}
}
func (app *App) addSlave(c *client) {
2014-10-21 13:35:03 +04:00
addr := c.slaveListeningAddr
app.slock.Lock()
defer app.slock.Unlock()
2014-10-21 13:35:03 +04:00
app.slaves[addr] = c
}
2014-10-21 18:51:17 +04:00
func (app *App) removeSlave(c *client, activeQuit bool) {
2014-10-21 13:35:03 +04:00
addr := c.slaveListeningAddr
app.slock.Lock()
defer app.slock.Unlock()
2014-10-21 13:35:03 +04:00
if _, ok := app.slaves[addr]; ok {
delete(app.slaves, addr)
2015-01-12 05:32:03 +03:00
log.Infof("remove slave %s", addr)
asyncNotifyUint64(app.slaveSyncAck, c.lastLogID.Get())
2014-10-09 07:47:14 +04:00
}
2014-10-21 13:35:03 +04:00
}
func (app *App) slaveAck(c *client) {
addr := c.slaveListeningAddr
app.slock.Lock()
defer app.slock.Unlock()
2014-10-21 13:35:03 +04:00
if _, ok := app.slaves[addr]; !ok {
//slave not add
return
2014-09-24 17:31:26 +04:00
}
2014-10-21 13:35:03 +04:00
2014-11-01 18:28:28 +03:00
asyncNotifyUint64(app.slaveSyncAck, c.lastLogID.Get())
2014-09-24 17:31:26 +04:00
}
func asyncNotifyUint64(ch chan uint64, v uint64) {
select {
case ch <- v:
default:
}
}
func (app *App) publishNewLog(l *rpl.Log) {
if !app.cfg.Replication.Sync {
//no sync replication, we will do async
return
}
2014-10-22 05:11:14 +04:00
app.info.Replication.PubLogNum.Add(1)
app.slock.Lock()
2014-10-22 16:54:37 +04:00
slaveNum := len(app.slaves)
total := (slaveNum + 1) / 2
2014-10-21 13:35:03 +04:00
if app.cfg.Replication.WaitMaxSlaveAcks > 0 {
total = num.MinInt(total, app.cfg.Replication.WaitMaxSlaveAcks)
}
n := 0
logId := l.ID
2014-10-21 13:35:03 +04:00
for _, s := range app.slaves {
2014-11-01 18:28:28 +03:00
lastLogID := s.lastLogID.Get()
if lastLogID == logId {
2014-10-09 07:47:14 +04:00
//slave has already owned this log
2014-10-21 13:35:03 +04:00
n++
2014-11-01 18:28:28 +03:00
} else if lastLogID > logId {
2015-01-12 05:32:03 +03:00
log.Errorf("invalid slave %s, lastlogid %d > %d", s.slaveListeningAddr, lastLogID, logId)
}
}
app.slock.Unlock()
2014-10-21 13:35:03 +04:00
if n >= total {
//at least total slaves have owned this log
return
}
2014-10-13 10:37:31 +04:00
startTime := time.Now()
done := make(chan struct{}, 1)
2014-10-22 16:54:37 +04:00
go func() {
n := 0
for i := 0; i < slaveNum; i++ {
2014-10-21 13:35:03 +04:00
id := <-app.slaveSyncAck
2014-10-21 18:51:17 +04:00
if id < logId {
2015-01-12 05:32:03 +03:00
log.Infof("some slave may close with last logid %d < %d", id, logId)
2014-10-22 16:54:37 +04:00
} else {
n++
if n >= total {
break
}
}
}
done <- struct{}{}
2014-10-22 16:54:37 +04:00
}()
select {
case <-done:
2014-10-11 12:00:59 +04:00
case <-time.After(time.Duration(app.cfg.Replication.WaitSyncTime) * time.Millisecond):
2014-10-05 13:24:44 +04:00
log.Info("replication wait timeout")
}
2014-10-13 10:37:31 +04:00
stopTime := time.Now()
2014-10-22 05:11:14 +04:00
app.info.Replication.PubLogAckNum.Add(1)
app.info.Replication.PubLogTotalAckTime.Add(stopTime.Sub(startTime))
}