func (app *App) slaveof(masterAddr string, restart bool, readonly bool) error { app.m.Lock() defer app.m.Unlock() //in master mode and no slaveof, only set readonly if len(app.cfg.SlaveOf) == 0 && len(masterAddr) == 0 { app.cfg.SetReadonly(readonly) return nil } if !app.ldb.ReplicationUsed() { return fmt.Errorf("slaveof must enable replication") } app.cfg.SlaveOf = masterAddr if len(masterAddr) == 0 { log.Infof("slaveof no one, stop replication") if err := app.m.stopReplication(); err != nil { return err } app.cfg.SetReadonly(readonly) } else { return app.m.startReplication(masterAddr, restart) } return nil }
func (app *App) removeSlave(c *client, activeQuit bool) { addr := c.slaveListeningAddr app.slock.Lock() defer app.slock.Unlock() if _, ok := app.slaves[addr]; ok { delete(app.slaves, addr) log.Infof("remove slave %s", addr) asyncNotifyUint64(app.slaveSyncAck, c.lastLogID.Get()) } }
func (app *App) publishNewLog(l *rpl.Log) { if !app.cfg.Replication.Sync { //no sync replication, we will do async return } app.info.Replication.PubLogNum.Add(1) app.slock.Lock() slaveNum := len(app.slaves) total := (slaveNum + 1) / 2 if app.cfg.Replication.WaitMaxSlaveAcks > 0 { total = num.MinInt(total, app.cfg.Replication.WaitMaxSlaveAcks) } n := 0 logId := l.ID for _, s := range app.slaves { lastLogID := s.lastLogID.Get() if lastLogID == logId { //slave has already owned this log n++ } else if lastLogID > logId { log.Errorf("invalid slave %s, lastlogid %d > %d", s.slaveListeningAddr, lastLogID, logId) } } app.slock.Unlock() if n >= total { //at least total slaves have owned this log return } startTime := time.Now() done := make(chan struct{}, 1) go func() { n := 0 for i := 0; i < slaveNum; i++ { id := <-app.slaveSyncAck if id < logId { log.Infof("some slave may close with last logid %d < %d", id, logId) } else { n++ if n >= total { break } } } done <- struct{}{} }() select { case <-done: case <-time.After(time.Duration(app.cfg.Replication.WaitSyncTime) * time.Millisecond): log.Info("replication wait timeout") } stopTime := time.Now() app.info.Replication.PubLogAckNum.Add(1) app.info.Replication.PubLogTotalAckTime.Add(stopTime.Sub(startTime)) }
//XMIGRATE host port type key destination-db timeout //will block any other write operations //maybe only for xcodis func xmigrateCommand(c *client) error { args := c.args if len(args) != 6 { return ErrCmdParams } addr := fmt.Sprintf("%s:%s", string(args[0]), string(args[1])) if addr == c.app.cfg.Addr { //same server, can not migrate return fmt.Errorf("migrate in same server is not allowed") } tp := strings.ToUpper(string(args[2])) key := args[3] db, err := parseMigrateDB(c, args[4]) if err != nil { return err } timeout, err := ledis.StrInt64(args[5], nil) if err != nil { return err } else if timeout < 0 { return fmt.Errorf("invalid timeout %d", timeout) } conn, err := getMigrateDBConn(c, addr, db) if err != nil { return err } defer conn.Close() // if key is in migrating, we will wait 500ms and retry again for i := 0; i < 10; i++ { if tp == "ALL" { // if tp is ALL, we will migrate the key in all types // this feature is useful for xcodis RESTORE or other commands that we don't know the data type exactly err = migrateAllTypeKeys(c, conn, key, timeout) } else { err = migrateKey(c, conn, tp, key, timeout) } if err != errKeyInMigrating { break } else { log.Infof("%s key %s is in migrating, wait 500ms and retry", tp, key) time.Sleep(500 * time.Millisecond) } } if err != nil { if err == errNoKey { c.resp.writeStatus(NOKEY) return nil } else { return err } } c.resp.writeStatus(OK) return nil }