mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-06 01:07:06 +08:00
rename MultiDB to Server; rename AofHandler to Persister
This commit is contained in:
26
aof/aof.go
26
aof/aof.go
@@ -32,10 +32,10 @@ type Listener interface {
|
||||
Callback([]CmdLine)
|
||||
}
|
||||
|
||||
// Handler receive msgs from channel and write to AOF file
|
||||
type Handler struct {
|
||||
db database.EmbedDB
|
||||
tmpDBMaker func() database.EmbedDB
|
||||
// Persister receive msgs from channel and write to AOF file
|
||||
type Persister struct {
|
||||
db database.DBEngine
|
||||
tmpDBMaker func() database.DBEngine
|
||||
aofChan chan *payload
|
||||
aofFile *os.File
|
||||
aofFilename string
|
||||
@@ -47,9 +47,9 @@ type Handler struct {
|
||||
listeners map[Listener]struct{}
|
||||
}
|
||||
|
||||
// NewAOFHandler creates a new aof.Handler
|
||||
func NewAOFHandler(db database.EmbedDB, filename string, load bool, tmpDBMaker func() database.EmbedDB) (*Handler, error) {
|
||||
handler := &Handler{}
|
||||
// NewPersister creates a new aof.Persister
|
||||
func NewPersister(db database.DBEngine, filename string, load bool, tmpDBMaker func() database.DBEngine) (*Persister, error) {
|
||||
handler := &Persister{}
|
||||
handler.aofFilename = filename
|
||||
handler.db = db
|
||||
handler.tmpDBMaker = tmpDBMaker
|
||||
@@ -71,14 +71,14 @@ func NewAOFHandler(db database.EmbedDB, filename string, load bool, tmpDBMaker f
|
||||
}
|
||||
|
||||
// RemoveListener removes a listener from aof handler, so we can close the listener
|
||||
func (handler *Handler) RemoveListener(listener Listener) {
|
||||
func (handler *Persister) RemoveListener(listener Listener) {
|
||||
handler.pausingAof.Lock()
|
||||
defer handler.pausingAof.Unlock()
|
||||
delete(handler.listeners, listener)
|
||||
}
|
||||
|
||||
// AddAof send command to aof goroutine through channel
|
||||
func (handler *Handler) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
func (handler *Persister) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
if handler.aofChan != nil {
|
||||
handler.aofChan <- &payload{
|
||||
cmdLine: cmdLine,
|
||||
@@ -88,7 +88,7 @@ func (handler *Handler) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
}
|
||||
|
||||
// handleAof listen aof channel and write into file
|
||||
func (handler *Handler) handleAof() {
|
||||
func (handler *Persister) handleAof() {
|
||||
// serialized execution
|
||||
var cmdLines []CmdLine
|
||||
handler.currentDB = 0
|
||||
@@ -122,8 +122,8 @@ func (handler *Handler) handleAof() {
|
||||
handler.aofFinished <- struct{}{}
|
||||
}
|
||||
|
||||
// LoadAof read aof file, can only be used before Handler.handleAof started
|
||||
func (handler *Handler) LoadAof(maxBytes int) {
|
||||
// LoadAof read aof file, can only be used before Persister.handleAof started
|
||||
func (handler *Persister) LoadAof(maxBytes int) {
|
||||
// handler.db.Exec may call handler.addAof
|
||||
// delete aofChan to prevent loaded commands back into aofChan
|
||||
aofChan := handler.aofChan
|
||||
@@ -175,7 +175,7 @@ func (handler *Handler) LoadAof(maxBytes int) {
|
||||
}
|
||||
|
||||
// Close gracefully stops aof persistence procedure
|
||||
func (handler *Handler) Close() {
|
||||
func (handler *Persister) Close() {
|
||||
if handler.aofFile != nil {
|
||||
close(handler.aofChan)
|
||||
<-handler.aofFinished // wait for aof finished
|
||||
|
@@ -19,7 +19,7 @@ import (
|
||||
// todo: forbid concurrent rewrite
|
||||
|
||||
// Rewrite2RDB rewrite aof data into rdb
|
||||
func (handler *Handler) Rewrite2RDB(rdbFilename string) error {
|
||||
func (handler *Persister) Rewrite2RDB(rdbFilename string) error {
|
||||
ctx, err := handler.startRewrite2RDB(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -42,7 +42,7 @@ func (handler *Handler) Rewrite2RDB(rdbFilename string) error {
|
||||
// Rewrite2RDBForReplication asynchronously rewrite aof data into rdb and returns a channel to receive following data
|
||||
// parameter listener would receive following updates of rdb
|
||||
// parameter hook allows you to do something during aof pausing
|
||||
func (handler *Handler) Rewrite2RDBForReplication(rdbFilename string, listener Listener, hook func()) error {
|
||||
func (handler *Persister) Rewrite2RDBForReplication(rdbFilename string, listener Listener, hook func()) error {
|
||||
ctx, err := handler.startRewrite2RDB(listener, hook)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -62,7 +62,7 @@ func (handler *Handler) Rewrite2RDBForReplication(rdbFilename string, listener L
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) startRewrite2RDB(newListener Listener, hook func()) (*RewriteCtx, error) {
|
||||
func (handler *Persister) startRewrite2RDB(newListener Listener, hook func()) (*RewriteCtx, error) {
|
||||
handler.pausingAof.Lock() // pausing aof
|
||||
defer handler.pausingAof.Unlock()
|
||||
|
||||
@@ -93,7 +93,7 @@ func (handler *Handler) startRewrite2RDB(newListener Listener, hook func()) (*Re
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) rewrite2RDB(ctx *RewriteCtx) error {
|
||||
func (handler *Persister) rewrite2RDB(ctx *RewriteCtx) error {
|
||||
// load aof tmpFile
|
||||
tmpHandler := handler.newRewriteHandler()
|
||||
tmpHandler.LoadAof(int(ctx.fileSize))
|
||||
|
@@ -13,8 +13,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (handler *Handler) newRewriteHandler() *Handler {
|
||||
h := &Handler{}
|
||||
func (handler *Persister) newRewriteHandler() *Persister {
|
||||
h := &Persister{}
|
||||
h.aofFilename = handler.aofFilename
|
||||
h.db = handler.tmpDBMaker()
|
||||
return h
|
||||
@@ -28,7 +28,7 @@ type RewriteCtx struct {
|
||||
}
|
||||
|
||||
// Rewrite carries out AOF rewrite
|
||||
func (handler *Handler) Rewrite() error {
|
||||
func (handler *Persister) Rewrite() error {
|
||||
ctx, err := handler.StartRewrite()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -44,7 +44,7 @@ func (handler *Handler) Rewrite() error {
|
||||
|
||||
// DoRewrite actually rewrite aof file
|
||||
// makes DoRewrite public for testing only, please use Rewrite instead
|
||||
func (handler *Handler) DoRewrite(ctx *RewriteCtx) error {
|
||||
func (handler *Persister) DoRewrite(ctx *RewriteCtx) error {
|
||||
tmpFile := ctx.tmpFile
|
||||
|
||||
// load aof tmpFile
|
||||
@@ -78,7 +78,7 @@ func (handler *Handler) DoRewrite(ctx *RewriteCtx) error {
|
||||
}
|
||||
|
||||
// StartRewrite prepares rewrite procedure
|
||||
func (handler *Handler) StartRewrite() (*RewriteCtx, error) {
|
||||
func (handler *Persister) StartRewrite() (*RewriteCtx, error) {
|
||||
handler.pausingAof.Lock() // pausing aof
|
||||
defer handler.pausingAof.Unlock()
|
||||
|
||||
@@ -106,7 +106,7 @@ func (handler *Handler) StartRewrite() (*RewriteCtx, error) {
|
||||
}
|
||||
|
||||
// FinishRewrite finish rewrite procedure
|
||||
func (handler *Handler) FinishRewrite(ctx *RewriteCtx) {
|
||||
func (handler *Persister) FinishRewrite(ctx *RewriteCtx) {
|
||||
handler.pausingAof.Lock() // pausing aof
|
||||
defer handler.pausingAof.Unlock()
|
||||
|
||||
|
@@ -33,7 +33,7 @@ type Cluster struct {
|
||||
peerPicker PeerPicker
|
||||
nodeConnections map[string]*pool.Pool
|
||||
|
||||
db database.EmbedDB
|
||||
db database.DBEngine
|
||||
transactions *dict.SimpleDict // id -> Transaction
|
||||
|
||||
idGenerator *idgenerator.IDGenerator
|
||||
|
@@ -31,12 +31,12 @@ func TestEmptyMulti(t *testing.T) {
|
||||
testNodeA.db.Exec(conn, utils.ToCmdLine("FLUSHALL"))
|
||||
result := testNodeA.Exec(conn, toArgs("MULTI"))
|
||||
asserts.AssertNotError(t, result)
|
||||
result = testNodeA.Exec(conn, utils.ToCmdLine("PING"))
|
||||
result = testNodeA.Exec(conn, utils.ToCmdLine("GET", "a"))
|
||||
asserts.AssertNotError(t, result)
|
||||
result = testNodeA.Exec(conn, utils.ToCmdLine("EXEC"))
|
||||
asserts.AssertNotError(t, result)
|
||||
mbr := result.(*protocol.MultiRawReply)
|
||||
asserts.AssertStatusReply(t, mbr.Replies[0], "PONG")
|
||||
asserts.AssertNullBulk(t, mbr.Replies[0])
|
||||
}
|
||||
|
||||
func TestMultiExecOnOthers(t *testing.T) {
|
||||
|
@@ -223,7 +223,7 @@ func TestRewriteAOF2(t *testing.T) {
|
||||
aofWriteDB.Exec(conn, utils.ToCmdLine("SET", key, key))
|
||||
}
|
||||
|
||||
ctx, err := aofWriteDB.aofHandler.StartRewrite()
|
||||
ctx, err := aofWriteDB.persister.StartRewrite()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@@ -234,8 +234,8 @@ func TestRewriteAOF2(t *testing.T) {
|
||||
key := "a" + strconv.Itoa(i)
|
||||
aofWriteDB.Exec(conn, utils.ToCmdLine("SET", key, key))
|
||||
}
|
||||
aofWriteDB.aofHandler.DoRewrite(ctx)
|
||||
aofWriteDB.aofHandler.FinishRewrite(ctx)
|
||||
aofWriteDB.persister.DoRewrite(ctx)
|
||||
aofWriteDB.persister.FinishRewrite(ctx)
|
||||
|
||||
aofWriteDB.Close() // wait for aof finished
|
||||
aofReadDB := NewStandaloneServer() // start new db and read aof file
|
||||
|
@@ -1,380 +1,302 @@
|
||||
// Package database is a memory database with redis compatible interface
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
"github.com/hdt3213/godis/datastruct/dict"
|
||||
"github.com/hdt3213/godis/datastruct/lock"
|
||||
"github.com/hdt3213/godis/interface/database"
|
||||
"github.com/hdt3213/godis/interface/redis"
|
||||
"github.com/hdt3213/godis/lib/logger"
|
||||
"github.com/hdt3213/godis/lib/utils"
|
||||
"github.com/hdt3213/godis/pubsub"
|
||||
"github.com/hdt3213/godis/lib/timewheel"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MultiDB is a set of multiple database set
|
||||
type MultiDB struct {
|
||||
dbSet []*atomic.Value // *DB
|
||||
const (
|
||||
dataDictSize = 1 << 16
|
||||
ttlDictSize = 1 << 10
|
||||
lockerSize = 1024
|
||||
)
|
||||
|
||||
// handle publish/subscribe
|
||||
hub *pubsub.Hub
|
||||
// handle aof persistence
|
||||
aofHandler *aof.Handler
|
||||
// DB stores data and execute user's commands
|
||||
type DB struct {
|
||||
index int
|
||||
// key -> DataEntity
|
||||
data dict.Dict
|
||||
// key -> expireTime (time.Time)
|
||||
ttlMap dict.Dict
|
||||
// key -> version(uint32)
|
||||
versionMap dict.Dict
|
||||
|
||||
// for replication
|
||||
role int32
|
||||
slaveStatus *slaveStatus
|
||||
masterStatus *masterStatus
|
||||
// dict.Dict will ensure concurrent-safety of its method
|
||||
// use this mutex for complicated command only, eg. rpush, incr ...
|
||||
locker *lock.Locks
|
||||
addAof func(CmdLine)
|
||||
}
|
||||
|
||||
// NewStandaloneServer creates a standalone redis server, with multi database and all other funtions
|
||||
func NewStandaloneServer() *MultiDB {
|
||||
mdb := &MultiDB{}
|
||||
if config.Properties.Databases == 0 {
|
||||
config.Properties.Databases = 16
|
||||
// ExecFunc is interface for command executor
|
||||
// args don't include cmd line
|
||||
type ExecFunc func(db *DB, args [][]byte) redis.Reply
|
||||
|
||||
// PreFunc analyses command line when queued command to `multi`
|
||||
// returns related write keys and read keys
|
||||
type PreFunc func(args [][]byte) ([]string, []string)
|
||||
|
||||
// CmdLine is alias for [][]byte, represents a command line
|
||||
type CmdLine = [][]byte
|
||||
|
||||
// UndoFunc returns undo logs for the given command line
|
||||
// execute from head to tail when undo
|
||||
type UndoFunc func(db *DB, args [][]byte) []CmdLine
|
||||
|
||||
// makeDB create DB instance
|
||||
func makeDB() *DB {
|
||||
db := &DB{
|
||||
data: dict.MakeConcurrent(dataDictSize),
|
||||
ttlMap: dict.MakeConcurrent(ttlDictSize),
|
||||
versionMap: dict.MakeConcurrent(dataDictSize),
|
||||
locker: lock.Make(lockerSize),
|
||||
addAof: func(line CmdLine) {},
|
||||
}
|
||||
mdb.dbSet = make([]*atomic.Value, config.Properties.Databases)
|
||||
for i := range mdb.dbSet {
|
||||
singleDB := makeDB()
|
||||
singleDB.index = i
|
||||
holder := &atomic.Value{}
|
||||
holder.Store(singleDB)
|
||||
mdb.dbSet[i] = holder
|
||||
}
|
||||
mdb.hub = pubsub.MakeHub()
|
||||
validAof := false
|
||||
if config.Properties.AppendOnly {
|
||||
aofHandler, err := NewAofHandler(mdb, config.Properties.AppendFilename, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mdb.bindAofHandler(aofHandler)
|
||||
validAof = true
|
||||
}
|
||||
if config.Properties.RDBFilename != "" && !validAof {
|
||||
// load rdb
|
||||
loadRdbFile(mdb)
|
||||
}
|
||||
mdb.slaveStatus = initReplSlaveStatus()
|
||||
mdb.initMaster()
|
||||
mdb.startReplCron()
|
||||
mdb.role = masterRole // The initialization process does not require atomicity
|
||||
return mdb
|
||||
return db
|
||||
}
|
||||
|
||||
func NewAofHandler(db database.EmbedDB, filename string, load bool) (*aof.Handler, error) {
|
||||
return aof.NewAOFHandler(db, filename, load, func() database.EmbedDB {
|
||||
return MakeBasicMultiDB()
|
||||
})
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
if mdb.aofHandler != nil {
|
||||
mdb.aofHandler.AddAof(dbIndex, cmdLine)
|
||||
// makeBasicDB create DB instance only with basic abilities.
|
||||
// It is not concurrent safe
|
||||
func makeBasicDB() *DB {
|
||||
db := &DB{
|
||||
data: dict.MakeSimple(),
|
||||
ttlMap: dict.MakeSimple(),
|
||||
versionMap: dict.MakeSimple(),
|
||||
locker: lock.Make(1),
|
||||
addAof: func(line CmdLine) {},
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) bindAofHandler(aofHandler *aof.Handler) {
|
||||
mdb.aofHandler = aofHandler
|
||||
// bind AddAof
|
||||
for _, db := range mdb.dbSet {
|
||||
singleDB := db.Load().(*DB)
|
||||
singleDB.addAof = func(line CmdLine) {
|
||||
if config.Properties.AppendOnly { // config may be changed during runtime
|
||||
mdb.aofHandler.AddAof(singleDB.index, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MakeBasicMultiDB create a MultiDB only with basic abilities for aof rewrite and other usages
|
||||
func MakeBasicMultiDB() *MultiDB {
|
||||
mdb := &MultiDB{}
|
||||
mdb.dbSet = make([]*atomic.Value, config.Properties.Databases)
|
||||
for i := range mdb.dbSet {
|
||||
holder := &atomic.Value{}
|
||||
holder.Store(makeBasicDB())
|
||||
mdb.dbSet[i] = holder
|
||||
}
|
||||
return mdb
|
||||
}
|
||||
|
||||
// Exec executes command
|
||||
// parameter `cmdLine` contains command and its arguments, for example: "set key value"
|
||||
func (mdb *MultiDB) Exec(c redis.Connection, cmdLine [][]byte) (result redis.Reply) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
|
||||
result = &protocol.UnknownErrReply{}
|
||||
}
|
||||
}()
|
||||
|
||||
// Exec executes command within one database
|
||||
func (db *DB) Exec(c redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||
// transaction control commands and other commands which cannot execute within transaction
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
// authenticate
|
||||
if cmdName == "auth" {
|
||||
return Auth(c, cmdLine[1:])
|
||||
}
|
||||
if !isAuthenticated(c) {
|
||||
return protocol.MakeErrReply("NOAUTH Authentication required")
|
||||
}
|
||||
if cmdName == "slaveof" {
|
||||
if c != nil && c.InMultiState() {
|
||||
return protocol.MakeErrReply("cannot use slave of database within multi")
|
||||
}
|
||||
if len(cmdLine) != 3 {
|
||||
return protocol.MakeArgNumErrReply("SLAVEOF")
|
||||
}
|
||||
return mdb.execSlaveOf(c, cmdLine[1:])
|
||||
}
|
||||
|
||||
// read only slave
|
||||
role := atomic.LoadInt32(&mdb.role)
|
||||
if role == slaveRole && !c.IsMaster() {
|
||||
// only allow read only command, forbid all special commands except `auth` and `slaveof`
|
||||
if !isReadOnlyCommand(cmdName) {
|
||||
return protocol.MakeErrReply("READONLY You can't write against a read only slave.")
|
||||
}
|
||||
}
|
||||
|
||||
// special commands which cannot execute within transaction
|
||||
if cmdName == "subscribe" {
|
||||
if len(cmdLine) < 2 {
|
||||
return protocol.MakeArgNumErrReply("subscribe")
|
||||
}
|
||||
return pubsub.Subscribe(mdb.hub, c, cmdLine[1:])
|
||||
} else if cmdName == "publish" {
|
||||
return pubsub.Publish(mdb.hub, cmdLine[1:])
|
||||
} else if cmdName == "unsubscribe" {
|
||||
return pubsub.UnSubscribe(mdb.hub, c, cmdLine[1:])
|
||||
} else if cmdName == "bgrewriteaof" {
|
||||
// aof.go imports router.go, router.go cannot import BGRewriteAOF from aof.go
|
||||
return BGRewriteAOF(mdb, cmdLine[1:])
|
||||
} else if cmdName == "rewriteaof" {
|
||||
return RewriteAOF(mdb, cmdLine[1:])
|
||||
} else if cmdName == "flushall" {
|
||||
return mdb.flushAll()
|
||||
} else if cmdName == "flushdb" {
|
||||
if !validateArity(1, cmdLine) {
|
||||
if cmdName == "multi" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
if c.InMultiState() {
|
||||
return protocol.MakeErrReply("ERR command 'FlushDB' cannot be used in MULTI")
|
||||
return StartMulti(c)
|
||||
} else if cmdName == "discard" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return mdb.flushDB(c.GetDBIndex())
|
||||
} else if cmdName == "save" {
|
||||
return SaveRDB(mdb, cmdLine[1:])
|
||||
} else if cmdName == "bgsave" {
|
||||
return BGSaveRDB(mdb, cmdLine[1:])
|
||||
} else if cmdName == "select" {
|
||||
if c != nil && c.InMultiState() {
|
||||
return protocol.MakeErrReply("cannot select database within multi")
|
||||
return DiscardMulti(c)
|
||||
} else if cmdName == "exec" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
if len(cmdLine) != 2 {
|
||||
return protocol.MakeArgNumErrReply("select")
|
||||
return execMulti(db, c)
|
||||
} else if cmdName == "watch" {
|
||||
if !validateArity(-2, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return execSelect(c, mdb, cmdLine[1:])
|
||||
} else if cmdName == "copy" {
|
||||
if len(cmdLine) < 3 {
|
||||
return protocol.MakeArgNumErrReply("copy")
|
||||
return Watch(db, c, cmdLine[1:])
|
||||
}
|
||||
if c != nil && c.InMultiState() {
|
||||
return EnqueueCmd(c, cmdLine)
|
||||
}
|
||||
|
||||
return db.execNormalCommand(cmdLine)
|
||||
}
|
||||
|
||||
func (db *DB) execNormalCommand(cmdLine [][]byte) redis.Reply {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd, ok := cmdTable[cmdName]
|
||||
if !ok {
|
||||
return protocol.MakeErrReply("ERR unknown command '" + cmdName + "'")
|
||||
}
|
||||
if !validateArity(cmd.arity, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
|
||||
prepare := cmd.prepare
|
||||
write, read := prepare(cmdLine[1:])
|
||||
db.addVersion(write...)
|
||||
db.RWLocks(write, read)
|
||||
defer db.RWUnLocks(write, read)
|
||||
fun := cmd.executor
|
||||
return fun(db, cmdLine[1:])
|
||||
}
|
||||
|
||||
// execWithLock executes normal commands, invoker should provide locks
|
||||
func (db *DB) execWithLock(cmdLine [][]byte) redis.Reply {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd, ok := cmdTable[cmdName]
|
||||
if !ok {
|
||||
return protocol.MakeErrReply("ERR unknown command '" + cmdName + "'")
|
||||
}
|
||||
if !validateArity(cmd.arity, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
fun := cmd.executor
|
||||
return fun(db, cmdLine[1:])
|
||||
}
|
||||
|
||||
func validateArity(arity int, cmdArgs [][]byte) bool {
|
||||
argNum := len(cmdArgs)
|
||||
if arity >= 0 {
|
||||
return argNum == arity
|
||||
}
|
||||
return argNum >= -arity
|
||||
}
|
||||
|
||||
/* ---- Data Access ----- */
|
||||
|
||||
// GetEntity returns DataEntity bind to given key
|
||||
func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
|
||||
raw, ok := db.data.Get(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if db.IsExpired(key) {
|
||||
return nil, false
|
||||
}
|
||||
entity, _ := raw.(*database.DataEntity)
|
||||
return entity, true
|
||||
}
|
||||
|
||||
// PutEntity a DataEntity into DB
|
||||
func (db *DB) PutEntity(key string, entity *database.DataEntity) int {
|
||||
return db.data.Put(key, entity)
|
||||
}
|
||||
|
||||
// PutIfExists edit an existing DataEntity
|
||||
func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
|
||||
return db.data.PutIfExists(key, entity)
|
||||
}
|
||||
|
||||
// PutIfAbsent insert an DataEntity only if the key not exists
|
||||
func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
|
||||
return db.data.PutIfAbsent(key, entity)
|
||||
}
|
||||
|
||||
// Remove the given key from db
|
||||
func (db *DB) Remove(key string) {
|
||||
db.data.Remove(key)
|
||||
db.ttlMap.Remove(key)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.Cancel(taskKey)
|
||||
}
|
||||
|
||||
// Removes the given keys from db
|
||||
func (db *DB) Removes(keys ...string) (deleted int) {
|
||||
deleted = 0
|
||||
for _, key := range keys {
|
||||
_, exists := db.data.Get(key)
|
||||
if exists {
|
||||
db.Remove(key)
|
||||
deleted++
|
||||
}
|
||||
return execCopy(mdb, c, cmdLine[1:])
|
||||
} else if cmdName == "replconf" {
|
||||
return mdb.execReplConf(c, cmdLine[1:])
|
||||
} else if cmdName == "psync" {
|
||||
return mdb.execPSync(c, cmdLine[1:])
|
||||
}
|
||||
// todo: support multi database transaction
|
||||
|
||||
// normal commands
|
||||
dbIndex := c.GetDBIndex()
|
||||
selectedDB, errReply := mdb.selectDB(dbIndex)
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return selectedDB.Exec(c, cmdLine)
|
||||
return deleted
|
||||
}
|
||||
|
||||
// AfterClientClose does some clean after client close connection
|
||||
func (mdb *MultiDB) AfterClientClose(c redis.Connection) {
|
||||
pubsub.UnsubscribeAll(mdb.hub, c)
|
||||
// Flush clean database
|
||||
// deprecated
|
||||
// for test only
|
||||
func (db *DB) Flush() {
|
||||
db.data.Clear()
|
||||
db.ttlMap.Clear()
|
||||
db.locker = lock.Make(lockerSize)
|
||||
}
|
||||
|
||||
// Close graceful shutdown database
|
||||
func (mdb *MultiDB) Close() {
|
||||
// stop slaveStatus first
|
||||
mdb.slaveStatus.close()
|
||||
if mdb.aofHandler != nil {
|
||||
mdb.aofHandler.Close()
|
||||
}
|
||||
mdb.stopMaster()
|
||||
}
|
||||
|
||||
func execSelect(c redis.Connection, mdb *MultiDB, args [][]byte) redis.Reply {
|
||||
dbIndex, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply("ERR invalid DB index")
|
||||
}
|
||||
if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
c.SelectDB(dbIndex)
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) flushDB(dbIndex int) redis.Reply {
|
||||
if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
newDB := makeDB()
|
||||
mdb.loadDB(dbIndex, newDB)
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) loadDB(dbIndex int, newDB *DB) redis.Reply {
|
||||
if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
oldDB := mdb.mustSelectDB(dbIndex)
|
||||
newDB.index = dbIndex
|
||||
newDB.addAof = oldDB.addAof // inherit oldDB
|
||||
mdb.dbSet[dbIndex].Store(newDB)
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) flushAll() redis.Reply {
|
||||
for i := range mdb.dbSet {
|
||||
mdb.flushDB(i)
|
||||
}
|
||||
if mdb.aofHandler != nil {
|
||||
mdb.aofHandler.AddAof(0, utils.ToCmdLine("FlushAll"))
|
||||
}
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) selectDB(dbIndex int) (*DB, *protocol.StandardErrReply) {
|
||||
if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
|
||||
return nil, protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
return mdb.dbSet[dbIndex].Load().(*DB), nil
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) mustSelectDB(dbIndex int) *DB {
|
||||
selectedDB, err := mdb.selectDB(dbIndex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return selectedDB
|
||||
}
|
||||
|
||||
// ForEach traverses all the keys in the given database
|
||||
func (mdb *MultiDB) ForEach(dbIndex int, cb func(key string, data *database.DataEntity, expiration *time.Time) bool) {
|
||||
mdb.mustSelectDB(dbIndex).ForEach(cb)
|
||||
}
|
||||
|
||||
// ExecMulti executes multi commands transaction Atomically and Isolated
|
||||
func (mdb *MultiDB) ExecMulti(conn redis.Connection, watching map[string]uint32, cmdLines []CmdLine) redis.Reply {
|
||||
selectedDB, errReply := mdb.selectDB(conn.GetDBIndex())
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return selectedDB.ExecMulti(conn, watching, cmdLines)
|
||||
}
|
||||
/* ---- Lock Function ----- */
|
||||
|
||||
// RWLocks lock keys for writing and reading
|
||||
func (mdb *MultiDB) RWLocks(dbIndex int, writeKeys []string, readKeys []string) {
|
||||
mdb.mustSelectDB(dbIndex).RWLocks(writeKeys, readKeys)
|
||||
func (db *DB) RWLocks(writeKeys []string, readKeys []string) {
|
||||
db.locker.RWLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
// RWUnLocks unlock keys for writing and reading
|
||||
func (mdb *MultiDB) RWUnLocks(dbIndex int, writeKeys []string, readKeys []string) {
|
||||
mdb.mustSelectDB(dbIndex).RWUnLocks(writeKeys, readKeys)
|
||||
func (db *DB) RWUnLocks(writeKeys []string, readKeys []string) {
|
||||
db.locker.RWUnLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
// GetUndoLogs return rollback commands
|
||||
func (mdb *MultiDB) GetUndoLogs(dbIndex int, cmdLine [][]byte) []CmdLine {
|
||||
return mdb.mustSelectDB(dbIndex).GetUndoLogs(cmdLine)
|
||||
/* ---- TTL Functions ---- */
|
||||
|
||||
func genExpireTask(key string) string {
|
||||
return "expire:" + key
|
||||
}
|
||||
|
||||
// ExecWithLock executes normal commands, invoker should provide locks
|
||||
func (mdb *MultiDB) ExecWithLock(conn redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||
db, errReply := mdb.selectDB(conn.GetDBIndex())
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return db.execWithLock(cmdLine)
|
||||
}
|
||||
|
||||
// BGRewriteAOF asynchronously rewrites Append-Only-File
|
||||
func BGRewriteAOF(db *MultiDB, args [][]byte) redis.Reply {
|
||||
go db.aofHandler.Rewrite()
|
||||
return protocol.MakeStatusReply("Background append only file rewriting started")
|
||||
}
|
||||
|
||||
// RewriteAOF start Append-Only-File rewriting and blocked until it finished
|
||||
func RewriteAOF(db *MultiDB, args [][]byte) redis.Reply {
|
||||
err := db.aofHandler.Rewrite()
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply(err.Error())
|
||||
}
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// SaveRDB start RDB writing and blocked until it finished
|
||||
func SaveRDB(db *MultiDB, args [][]byte) redis.Reply {
|
||||
if db.aofHandler == nil {
|
||||
return protocol.MakeErrReply("please enable aof before using save")
|
||||
}
|
||||
rdbFilename := config.Properties.RDBFilename
|
||||
if rdbFilename == "" {
|
||||
rdbFilename = "dump.rdb"
|
||||
}
|
||||
err := db.aofHandler.Rewrite2RDB(rdbFilename)
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply(err.Error())
|
||||
}
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// BGSaveRDB asynchronously save RDB
|
||||
func BGSaveRDB(db *MultiDB, args [][]byte) redis.Reply {
|
||||
if db.aofHandler == nil {
|
||||
return protocol.MakeErrReply("please enable aof before using save")
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
rdbFilename := config.Properties.RDBFilename
|
||||
if rdbFilename == "" {
|
||||
rdbFilename = "dump.rdb"
|
||||
// Expire sets ttlCmd of key
|
||||
func (db *DB) Expire(key string, expireTime time.Time) {
|
||||
db.ttlMap.Put(key, expireTime)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.At(expireTime, taskKey, func() {
|
||||
keys := []string{key}
|
||||
db.RWLocks(keys, nil)
|
||||
defer db.RWUnLocks(keys, nil)
|
||||
// check-lock-check, ttl may be updated during waiting lock
|
||||
logger.Info("expire " + key)
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := db.aofHandler.Rewrite2RDB(rdbFilename)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expired := time.Now().After(expireTime)
|
||||
if expired {
|
||||
db.Remove(key)
|
||||
}
|
||||
}()
|
||||
return protocol.MakeStatusReply("Background saving started")
|
||||
})
|
||||
}
|
||||
|
||||
// GetDBSize returns keys count and ttl key count
|
||||
func (mdb *MultiDB) GetDBSize(dbIndex int) (int, int) {
|
||||
db := mdb.mustSelectDB(dbIndex)
|
||||
return db.data.Len(), db.ttlMap.Len()
|
||||
// Persist cancel ttlCmd of key
|
||||
func (db *DB) Persist(key string) {
|
||||
db.ttlMap.Remove(key)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.Cancel(taskKey)
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) startReplCron() {
|
||||
go func(mdb *MultiDB) {
|
||||
ticker := time.Tick(time.Second * 10)
|
||||
for range ticker {
|
||||
mdb.slaveCron()
|
||||
mdb.masterCron()
|
||||
}
|
||||
}(mdb)
|
||||
// IsExpired check whether a key is expired
|
||||
func (db *DB) IsExpired(key string) bool {
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expired := time.Now().After(expireTime)
|
||||
if expired {
|
||||
db.Remove(key)
|
||||
}
|
||||
return expired
|
||||
}
|
||||
|
||||
/* --- add version --- */
|
||||
|
||||
func (db *DB) addVersion(keys ...string) {
|
||||
for _, key := range keys {
|
||||
versionCode := db.GetVersion(key)
|
||||
db.versionMap.Put(key, versionCode+1)
|
||||
}
|
||||
}
|
||||
|
||||
// GetVersion returns version code for given key
|
||||
func (db *DB) GetVersion(key string) uint32 {
|
||||
entity, ok := db.versionMap.Get(key)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return entity.(uint32)
|
||||
}
|
||||
|
||||
// ForEach traverses all the keys in the database
|
||||
func (db *DB) ForEach(cb func(key string, data *database.DataEntity, expiration *time.Time) bool) {
|
||||
db.data.ForEach(func(key string, raw interface{}) bool {
|
||||
entity, _ := raw.(*database.DataEntity)
|
||||
var expiration *time.Time
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if ok {
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expiration = &expireTime
|
||||
}
|
||||
return cb(key, entity, expiration)
|
||||
})
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
package database
|
||||
|
||||
/*
|
||||
[MultiDB](https://github.com/HDT3213/godis/blob/master/database/database.go) is an implemention of interface [DB](https://github.com/HDT3213/godis/blob/master/interface/database/db.go).
|
||||
[Server](https://github.com/HDT3213/godis/blob/master/database/database.go) is an implemention of interface [DB](https://github.com/HDT3213/godis/blob/master/interface/database/db.go).
|
||||
|
||||
[server.Handler](https://github.com/HDT3213/godis/blob/master/redis/server/server.go) holds an instance of MultiDB as storage engine, and pass command line to MultiDB through db.Exec method.
|
||||
[server.Handler](https://github.com/HDT3213/godis/blob/master/redis/server/server.go) holds an instance of Server as storage engine, and pass command line to Server through db.Exec method.
|
||||
|
||||
MultiDB is a multi-database engine which supports `SELECT` command. Besides multiple database instance, it holds pubsub.Hub and aof.Handler for publish-subscription and AOF persistence.
|
||||
Server is a multi-database engine which supports `SELECT` command. Besides multiple database instance, it holds pubsub.Hub and aof.Handler for publish-subscription and AOF persistence.
|
||||
|
||||
MultiDB.Exec is the main entry for MultiDB, it handles authentication, publish-subscription, aof as well as system commands itself, and invoke Exec function of selected db for other commands.
|
||||
Server.Exec is the main entry for Server, it handles authentication, publish-subscription, aof as well as system commands itself, and invoke Exec function of selected db for other commands.
|
||||
|
||||
[godis.DB.Exec](https://github.com/HDT3213/godis/blob/master/database/single_db.go) handles transaction control command (such as watch, multi, exec) itself, and invokes DB.execNormalCommand to handle normal commands. The word, normal command, is commands which read or write limited keys, can execute within transaction, and supports rollback. For example, get, set, lpush are normal commands, while flushdb, keys are not.
|
||||
|
||||
|
@@ -51,7 +51,7 @@ func execExists(db *DB, args [][]byte) redis.Reply {
|
||||
}
|
||||
|
||||
// execFlushDB removes all data in current db
|
||||
// deprecated, use MultiDB.flushDB
|
||||
// deprecated, use Server.flushDB
|
||||
func execFlushDB(db *DB, args [][]byte) redis.Reply {
|
||||
db.Flush()
|
||||
db.addAof(utils.ToCmdLine3("flushdb", args...))
|
||||
@@ -315,7 +315,7 @@ func undoExpire(db *DB, args [][]byte) []CmdLine {
|
||||
|
||||
// execCopy usage: COPY source destination [DB destination-db] [REPLACE]
|
||||
// This command copies the value stored at the source key to the destination key.
|
||||
func execCopy(mdb *MultiDB, conn redis.Connection, args [][]byte) redis.Reply {
|
||||
func execCopy(mdb *Server, conn redis.Connection, args [][]byte) redis.Reply {
|
||||
dbIndex := conn.GetDBIndex()
|
||||
db := mdb.mustSelectDB(dbIndex) // Current DB
|
||||
replaceFlag := false
|
||||
|
@@ -1,38 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
"github.com/hdt3213/godis/datastruct/dict"
|
||||
List "github.com/hdt3213/godis/datastruct/list"
|
||||
SortedSet "github.com/hdt3213/godis/datastruct/sortedset"
|
||||
"github.com/hdt3213/godis/interface/database"
|
||||
"github.com/hdt3213/godis/lib/logger"
|
||||
"github.com/hdt3213/rdb/core"
|
||||
rdb "github.com/hdt3213/rdb/parser"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func loadRdbFile(mdb *MultiDB) {
|
||||
func (server *Server) loadRdbFile() error {
|
||||
rdbFile, err := os.Open(config.Properties.RDBFilename)
|
||||
if err != nil {
|
||||
logger.Error("open rdb file failed " + err.Error())
|
||||
return
|
||||
return fmt.Errorf("open rdb file failed " + err.Error())
|
||||
}
|
||||
defer func() {
|
||||
_ = rdbFile.Close()
|
||||
}()
|
||||
decoder := rdb.NewDecoder(rdbFile)
|
||||
err = importRDB(decoder, mdb)
|
||||
err = server.loadRDB(decoder)
|
||||
if err != nil {
|
||||
logger.Error("dump rdb file failed " + err.Error())
|
||||
return
|
||||
return fmt.Errorf("dump rdb file failed " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importRDB(dec *core.Decoder, mdb *MultiDB) error {
|
||||
func (server *Server) loadRDB(dec *core.Decoder) error {
|
||||
return dec.Parse(func(o rdb.RedisObject) bool {
|
||||
db := mdb.mustSelectDB(o.GetDBIndex())
|
||||
db := server.mustSelectDB(o.GetDBIndex())
|
||||
var entity *database.DataEntity
|
||||
switch o.GetType() {
|
||||
case rdb.StringType:
|
||||
@@ -78,3 +78,40 @@ func importRDB(dec *core.Decoder, mdb *MultiDB) error {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func NewPersister(db database.DBEngine, filename string, load bool) (*aof.Persister, error) {
|
||||
return aof.NewPersister(db, filename, load, func() database.DBEngine {
|
||||
return MakeAuxiliaryServer()
|
||||
})
|
||||
}
|
||||
|
||||
func (server *Server) AddAof(dbIndex int, cmdLine CmdLine) {
|
||||
if server.persister != nil {
|
||||
server.persister.AddAof(dbIndex, cmdLine)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) bindPersister(aofHandler *aof.Persister) {
|
||||
server.persister = aofHandler
|
||||
// bind AddAof
|
||||
for _, db := range server.dbSet {
|
||||
singleDB := db.Load().(*DB)
|
||||
singleDB.addAof = func(line CmdLine) {
|
||||
if config.Properties.AppendOnly { // config may be changed during runtime
|
||||
server.persister.AddAof(singleDB.index, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MakeAuxiliaryServer create a Server only with basic capabilities for aof rewrite and other usages
|
||||
func MakeAuxiliaryServer() *Server {
|
||||
mdb := &Server{}
|
||||
mdb.dbSet = make([]*atomic.Value, config.Properties.Databases)
|
||||
for i := range mdb.dbSet {
|
||||
holder := &atomic.Value{}
|
||||
holder.Store(makeBasicDB())
|
||||
mdb.dbSet[i] = holder
|
||||
}
|
||||
return mdb
|
||||
}
|
@@ -85,14 +85,14 @@ type masterStatus struct {
|
||||
rewriting atomic.Boolean
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) bgSaveForReplication() {
|
||||
func (server *Server) bgSaveForReplication() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
logger.Errorf("panic: %v", e)
|
||||
}
|
||||
}()
|
||||
if err := mdb.saveForReplication(); err != nil {
|
||||
if err := server.saveForReplication(); err != nil {
|
||||
logger.Errorf("save for replication error: %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -100,23 +100,23 @@ func (mdb *MultiDB) bgSaveForReplication() {
|
||||
}
|
||||
|
||||
// saveForReplication does bg-save and send rdb to waiting slaves
|
||||
func (mdb *MultiDB) saveForReplication() error {
|
||||
func (server *Server) saveForReplication() error {
|
||||
rdbFile, err := ioutil.TempFile("", "*.rdb")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp rdb failed: %v", err)
|
||||
}
|
||||
rdbFilename := rdbFile.Name()
|
||||
mdb.masterStatus.mu.Lock()
|
||||
mdb.masterStatus.bgSaveState = bgSaveRunning
|
||||
mdb.masterStatus.rdbFilename = rdbFilename // todo: can reuse config.Properties.RDBFilename?
|
||||
server.masterStatus.mu.Lock()
|
||||
server.masterStatus.bgSaveState = bgSaveRunning
|
||||
server.masterStatus.rdbFilename = rdbFilename // todo: can reuse config.Properties.RDBFilename?
|
||||
aofListener := &replAofListener{
|
||||
mdb: mdb,
|
||||
backlog: mdb.masterStatus.backlog,
|
||||
mdb: server,
|
||||
backlog: server.masterStatus.backlog,
|
||||
}
|
||||
mdb.masterStatus.aofListener = aofListener
|
||||
mdb.masterStatus.mu.Unlock()
|
||||
server.masterStatus.aofListener = aofListener
|
||||
server.masterStatus.mu.Unlock()
|
||||
|
||||
err = mdb.aofHandler.Rewrite2RDBForReplication(rdbFilename, aofListener, nil)
|
||||
err = server.persister.Rewrite2RDBForReplication(rdbFilename, aofListener, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,18 +124,18 @@ func (mdb *MultiDB) saveForReplication() error {
|
||||
|
||||
// change bgSaveState and get waitSlaves for sending
|
||||
waitSlaves := make(map[*slaveClient]struct{})
|
||||
mdb.masterStatus.mu.Lock()
|
||||
mdb.masterStatus.bgSaveState = bgSaveFinish
|
||||
for slave := range mdb.masterStatus.waitSlaves {
|
||||
server.masterStatus.mu.Lock()
|
||||
server.masterStatus.bgSaveState = bgSaveFinish
|
||||
for slave := range server.masterStatus.waitSlaves {
|
||||
waitSlaves[slave] = struct{}{}
|
||||
}
|
||||
mdb.masterStatus.waitSlaves = nil
|
||||
mdb.masterStatus.mu.Unlock()
|
||||
server.masterStatus.waitSlaves = nil
|
||||
server.masterStatus.mu.Unlock()
|
||||
|
||||
for slave := range waitSlaves {
|
||||
err = mdb.masterFullReSyncWithSlave(slave)
|
||||
err = server.masterFullReSyncWithSlave(slave)
|
||||
if err != nil {
|
||||
mdb.removeSlave(slave)
|
||||
server.removeSlave(slave)
|
||||
logger.Errorf("masterFullReSyncWithSlave error: %v", err)
|
||||
continue
|
||||
}
|
||||
@@ -143,7 +143,7 @@ func (mdb *MultiDB) saveForReplication() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) rewriteRDB() error {
|
||||
func (server *Server) rewriteRDB() error {
|
||||
rdbFile, err := ioutil.TempFile("", "*.rdb")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp rdb failed: %v", err)
|
||||
@@ -152,25 +152,25 @@ func (mdb *MultiDB) rewriteRDB() error {
|
||||
newBacklog := &replBacklog{}
|
||||
aofListener := &replAofListener{
|
||||
backlog: newBacklog,
|
||||
mdb: mdb,
|
||||
mdb: server,
|
||||
}
|
||||
hook := func() {
|
||||
// pausing aof first, then lock masterStatus.
|
||||
// use the same order as replAofListener to avoid dead lock
|
||||
mdb.masterStatus.mu.Lock()
|
||||
defer mdb.masterStatus.mu.Unlock()
|
||||
newBacklog.beginOffset = mdb.masterStatus.backlog.currentOffset
|
||||
server.masterStatus.mu.Lock()
|
||||
defer server.masterStatus.mu.Unlock()
|
||||
newBacklog.beginOffset = server.masterStatus.backlog.currentOffset
|
||||
}
|
||||
err = mdb.aofHandler.Rewrite2RDBForReplication(rdbFilename, aofListener, hook)
|
||||
err = server.persister.Rewrite2RDBForReplication(rdbFilename, aofListener, hook)
|
||||
if err != nil { // wait rdb result
|
||||
return err
|
||||
}
|
||||
mdb.masterStatus.mu.Lock()
|
||||
mdb.masterStatus.rdbFilename = rdbFilename
|
||||
mdb.masterStatus.backlog = newBacklog
|
||||
mdb.aofHandler.RemoveListener(mdb.masterStatus.aofListener)
|
||||
mdb.masterStatus.aofListener = aofListener
|
||||
mdb.masterStatus.mu.Unlock()
|
||||
server.masterStatus.mu.Lock()
|
||||
server.masterStatus.rdbFilename = rdbFilename
|
||||
server.masterStatus.backlog = newBacklog
|
||||
server.persister.RemoveListener(server.masterStatus.aofListener)
|
||||
server.masterStatus.aofListener = aofListener
|
||||
server.masterStatus.mu.Unlock()
|
||||
// It is ok to know that new backlog is ready later, so we change readyToSend without sync
|
||||
// But setting readyToSend=true must after new backlog is really ready (that means master.mu.Unlock)
|
||||
aofListener.readyToSend = true
|
||||
@@ -178,21 +178,21 @@ func (mdb *MultiDB) rewriteRDB() error {
|
||||
}
|
||||
|
||||
// masterFullReSyncWithSlave send replication header, rdb file and all backlogs to slave
|
||||
func (mdb *MultiDB) masterFullReSyncWithSlave(slave *slaveClient) error {
|
||||
func (server *Server) masterFullReSyncWithSlave(slave *slaveClient) error {
|
||||
// write replication header
|
||||
header := "+FULLRESYNC " + mdb.masterStatus.replId + " " +
|
||||
strconv.FormatInt(mdb.masterStatus.backlog.beginOffset, 10) + protocol.CRLF
|
||||
header := "+FULLRESYNC " + server.masterStatus.replId + " " +
|
||||
strconv.FormatInt(server.masterStatus.backlog.beginOffset, 10) + protocol.CRLF
|
||||
_, err := slave.conn.Write([]byte(header))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write replication header to slave failed: %v", err)
|
||||
}
|
||||
// send rdb
|
||||
rdbFile, err := os.Open(mdb.masterStatus.rdbFilename)
|
||||
rdbFile, err := os.Open(server.masterStatus.rdbFilename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open rdb file %s for replication error: %v", mdb.masterStatus.rdbFilename, err)
|
||||
return fmt.Errorf("open rdb file %s for replication error: %v", server.masterStatus.rdbFilename, err)
|
||||
}
|
||||
slave.state = slaveStateSendingRDB
|
||||
rdbInfo, _ := os.Stat(mdb.masterStatus.rdbFilename)
|
||||
rdbInfo, _ := os.Stat(server.masterStatus.rdbFilename)
|
||||
rdbSize := rdbInfo.Size()
|
||||
rdbHeader := "$" + strconv.FormatInt(rdbSize, 10) + protocol.CRLF
|
||||
_, err = slave.conn.Write([]byte(rdbHeader))
|
||||
@@ -205,36 +205,36 @@ func (mdb *MultiDB) masterFullReSyncWithSlave(slave *slaveClient) error {
|
||||
}
|
||||
|
||||
// send backlog
|
||||
mdb.masterStatus.mu.RLock()
|
||||
backlog, currentOffset := mdb.masterStatus.backlog.getSnapshot()
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
server.masterStatus.mu.RLock()
|
||||
backlog, currentOffset := server.masterStatus.backlog.getSnapshot()
|
||||
server.masterStatus.mu.RUnlock()
|
||||
_, err = slave.conn.Write(backlog)
|
||||
if err != nil {
|
||||
return fmt.Errorf("full resync write backlog to slave failed: %v", err)
|
||||
}
|
||||
|
||||
// set slave as online
|
||||
mdb.setSlaveOnline(slave, currentOffset)
|
||||
server.setSlaveOnline(slave, currentOffset)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cannotPartialSync = errors.New("cannot do partial sync")
|
||||
|
||||
func (mdb *MultiDB) masterTryPartialSyncWithSlave(slave *slaveClient, replId string, slaveOffset int64) error {
|
||||
mdb.masterStatus.mu.RLock()
|
||||
if replId != mdb.masterStatus.replId {
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
func (server *Server) masterTryPartialSyncWithSlave(slave *slaveClient, replId string, slaveOffset int64) error {
|
||||
server.masterStatus.mu.RLock()
|
||||
if replId != server.masterStatus.replId {
|
||||
server.masterStatus.mu.RUnlock()
|
||||
return cannotPartialSync
|
||||
}
|
||||
if !mdb.masterStatus.backlog.isValidOffset(slaveOffset) {
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
if !server.masterStatus.backlog.isValidOffset(slaveOffset) {
|
||||
server.masterStatus.mu.RUnlock()
|
||||
return cannotPartialSync
|
||||
}
|
||||
backlog, currentOffset := mdb.masterStatus.backlog.getSnapshotAfter(slaveOffset)
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
backlog, currentOffset := server.masterStatus.backlog.getSnapshotAfter(slaveOffset)
|
||||
server.masterStatus.mu.RUnlock()
|
||||
|
||||
// send replication header
|
||||
header := "+CONTINUE " + mdb.masterStatus.replId + protocol.CRLF
|
||||
header := "+CONTINUE " + server.masterStatus.replId + protocol.CRLF
|
||||
_, err := slave.conn.Write([]byte(header))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write replication header to slave failed: %v", err)
|
||||
@@ -246,27 +246,27 @@ func (mdb *MultiDB) masterTryPartialSyncWithSlave(slave *slaveClient, replId str
|
||||
}
|
||||
|
||||
// set slave online
|
||||
mdb.setSlaveOnline(slave, currentOffset)
|
||||
server.setSlaveOnline(slave, currentOffset)
|
||||
return nil
|
||||
}
|
||||
|
||||
// masterSendUpdatesToSlave only sends data to online slaves after bgSave is finished
|
||||
// if bgSave is running, updates will be sent after the saving finished
|
||||
func (mdb *MultiDB) masterSendUpdatesToSlave() error {
|
||||
func (server *Server) masterSendUpdatesToSlave() error {
|
||||
onlineSlaves := make(map[*slaveClient]struct{})
|
||||
mdb.masterStatus.mu.RLock()
|
||||
beginOffset := mdb.masterStatus.backlog.beginOffset
|
||||
backlog, currentOffset := mdb.masterStatus.backlog.getSnapshot()
|
||||
for slave := range mdb.masterStatus.onlineSlaves {
|
||||
server.masterStatus.mu.RLock()
|
||||
beginOffset := server.masterStatus.backlog.beginOffset
|
||||
backlog, currentOffset := server.masterStatus.backlog.getSnapshot()
|
||||
for slave := range server.masterStatus.onlineSlaves {
|
||||
onlineSlaves[slave] = struct{}{}
|
||||
}
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
server.masterStatus.mu.RUnlock()
|
||||
for slave := range onlineSlaves {
|
||||
slaveBeginOffset := slave.offset - beginOffset
|
||||
_, err := slave.conn.Write(backlog[slaveBeginOffset:])
|
||||
if err != nil {
|
||||
logger.Errorf("send updates backlog to slave failed: %v", err)
|
||||
mdb.removeSlave(slave)
|
||||
server.removeSlave(slave)
|
||||
continue
|
||||
}
|
||||
slave.offset = currentOffset
|
||||
@@ -274,48 +274,48 @@ func (mdb *MultiDB) masterSendUpdatesToSlave() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) execPSync(c redis.Connection, args [][]byte) redis.Reply {
|
||||
func (server *Server) execPSync(c redis.Connection, args [][]byte) redis.Reply {
|
||||
replId := string(args[0])
|
||||
replOffset, err := strconv.ParseInt(string(args[1]), 10, 64)
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
||||
}
|
||||
mdb.masterStatus.mu.Lock()
|
||||
defer mdb.masterStatus.mu.Unlock()
|
||||
slave := mdb.masterStatus.slaveMap[c]
|
||||
server.masterStatus.mu.Lock()
|
||||
defer server.masterStatus.mu.Unlock()
|
||||
slave := server.masterStatus.slaveMap[c]
|
||||
if slave == nil {
|
||||
slave = &slaveClient{
|
||||
conn: c,
|
||||
}
|
||||
c.SetSlave()
|
||||
mdb.masterStatus.slaveMap[c] = slave
|
||||
server.masterStatus.slaveMap[c] = slave
|
||||
}
|
||||
if mdb.masterStatus.bgSaveState == bgSaveIdle {
|
||||
if server.masterStatus.bgSaveState == bgSaveIdle {
|
||||
slave.state = slaveStateWaitSaveEnd
|
||||
mdb.masterStatus.waitSlaves[slave] = struct{}{}
|
||||
mdb.bgSaveForReplication()
|
||||
} else if mdb.masterStatus.bgSaveState == bgSaveRunning {
|
||||
server.masterStatus.waitSlaves[slave] = struct{}{}
|
||||
server.bgSaveForReplication()
|
||||
} else if server.masterStatus.bgSaveState == bgSaveRunning {
|
||||
slave.state = slaveStateWaitSaveEnd
|
||||
mdb.masterStatus.waitSlaves[slave] = struct{}{}
|
||||
} else if mdb.masterStatus.bgSaveState == bgSaveFinish {
|
||||
server.masterStatus.waitSlaves[slave] = struct{}{}
|
||||
} else if server.masterStatus.bgSaveState == bgSaveFinish {
|
||||
go func() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
logger.Errorf("panic: %v", e)
|
||||
}
|
||||
}()
|
||||
err := mdb.masterTryPartialSyncWithSlave(slave, replId, replOffset)
|
||||
err := server.masterTryPartialSyncWithSlave(slave, replId, replOffset)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err != nil && err != cannotPartialSync {
|
||||
mdb.removeSlave(slave)
|
||||
server.removeSlave(slave)
|
||||
logger.Errorf("masterTryPartialSyncWithSlave error: %v", err)
|
||||
return
|
||||
}
|
||||
// assert err == cannotPartialSync
|
||||
if err := mdb.masterFullReSyncWithSlave(slave); err != nil {
|
||||
mdb.removeSlave(slave)
|
||||
if err := server.masterFullReSyncWithSlave(slave); err != nil {
|
||||
server.removeSlave(slave)
|
||||
logger.Errorf("masterFullReSyncWithSlave error: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -324,13 +324,13 @@ func (mdb *MultiDB) execPSync(c redis.Connection, args [][]byte) redis.Reply {
|
||||
return &protocol.NoReply{}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) execReplConf(c redis.Connection, args [][]byte) redis.Reply {
|
||||
func (server *Server) execReplConf(c redis.Connection, args [][]byte) redis.Reply {
|
||||
if len(args)%2 != 0 {
|
||||
return protocol.MakeSyntaxErrReply()
|
||||
}
|
||||
mdb.masterStatus.mu.RLock()
|
||||
slave := mdb.masterStatus.slaveMap[c]
|
||||
mdb.masterStatus.mu.RUnlock()
|
||||
server.masterStatus.mu.RLock()
|
||||
slave := server.masterStatus.slaveMap[c]
|
||||
server.masterStatus.mu.RUnlock()
|
||||
for i := 0; i < len(args); i += 2 {
|
||||
key := strings.ToLower(string(args[i]))
|
||||
value := string(args[i+1])
|
||||
@@ -348,55 +348,56 @@ func (mdb *MultiDB) execReplConf(c redis.Connection, args [][]byte) redis.Reply
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) removeSlave(slave *slaveClient) {
|
||||
mdb.masterStatus.mu.Lock()
|
||||
defer mdb.masterStatus.mu.Unlock()
|
||||
func (server *Server) removeSlave(slave *slaveClient) {
|
||||
server.masterStatus.mu.Lock()
|
||||
defer server.masterStatus.mu.Unlock()
|
||||
_ = slave.conn.Close()
|
||||
delete(mdb.masterStatus.slaveMap, slave.conn)
|
||||
delete(mdb.masterStatus.waitSlaves, slave)
|
||||
delete(mdb.masterStatus.onlineSlaves, slave)
|
||||
delete(server.masterStatus.slaveMap, slave.conn)
|
||||
delete(server.masterStatus.waitSlaves, slave)
|
||||
delete(server.masterStatus.onlineSlaves, slave)
|
||||
logger.Info("disconnect with slave " + slave.conn.Name())
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) setSlaveOnline(slave *slaveClient, currentOffset int64) {
|
||||
mdb.masterStatus.mu.Lock()
|
||||
defer mdb.masterStatus.mu.Unlock()
|
||||
func (server *Server) setSlaveOnline(slave *slaveClient, currentOffset int64) {
|
||||
server.masterStatus.mu.Lock()
|
||||
defer server.masterStatus.mu.Unlock()
|
||||
slave.state = slaveStateOnline
|
||||
slave.offset = currentOffset
|
||||
mdb.masterStatus.onlineSlaves[slave] = struct{}{}
|
||||
server.masterStatus.onlineSlaves[slave] = struct{}{}
|
||||
}
|
||||
|
||||
var pingBytes = protocol.MakeMultiBulkReply(utils.ToCmdLine("ping")).ToBytes()
|
||||
|
||||
const maxBacklogSize = 10 * 1024 * 1024 // 10MB
|
||||
|
||||
func (mdb *MultiDB) masterCron() {
|
||||
mdb.masterStatus.mu.Lock()
|
||||
if len(mdb.masterStatus.slaveMap) == 0 { // no slaves, do nothing
|
||||
func (server *Server) masterCron() {
|
||||
server.masterStatus.mu.Lock()
|
||||
if len(server.masterStatus.slaveMap) == 0 { // no slaves, do nothing
|
||||
return
|
||||
}
|
||||
if mdb.masterStatus.bgSaveState == bgSaveFinish {
|
||||
mdb.masterStatus.backlog.appendBytes(pingBytes)
|
||||
if server.masterStatus.bgSaveState == bgSaveFinish {
|
||||
server.masterStatus.backlog.appendBytes(pingBytes)
|
||||
}
|
||||
backlogSize := len(mdb.masterStatus.backlog.buf)
|
||||
mdb.masterStatus.mu.Unlock()
|
||||
if err := mdb.masterSendUpdatesToSlave(); err != nil {
|
||||
backlogSize := len(server.masterStatus.backlog.buf)
|
||||
server.masterStatus.mu.Unlock()
|
||||
if err := server.masterSendUpdatesToSlave(); err != nil {
|
||||
logger.Errorf("masterSendUpdatesToSlave error: %v", err)
|
||||
}
|
||||
if backlogSize > maxBacklogSize && !mdb.masterStatus.rewriting.Get() {
|
||||
if backlogSize > maxBacklogSize && !server.masterStatus.rewriting.Get() {
|
||||
go func() {
|
||||
mdb.masterStatus.rewriting.Set(true)
|
||||
defer mdb.masterStatus.rewriting.Set(false)
|
||||
if err := mdb.rewriteRDB(); err != nil {
|
||||
mdb.masterStatus.rewriting.Set(false)
|
||||
server.masterStatus.rewriting.Set(true)
|
||||
defer server.masterStatus.rewriting.Set(false)
|
||||
if err := server.rewriteRDB(); err != nil {
|
||||
server.masterStatus.rewriting.Set(false)
|
||||
logger.Errorf("rewrite error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// replAofListener is an implementation for aof.Listener
|
||||
type replAofListener struct {
|
||||
mdb *MultiDB
|
||||
mdb *Server
|
||||
backlog *replBacklog // may NOT be mdb.masterStatus.backlog
|
||||
readyToSend bool
|
||||
}
|
||||
@@ -417,8 +418,8 @@ func (listener *replAofListener) Callback(cmdLines []CmdLine) {
|
||||
}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) initMaster() {
|
||||
mdb.masterStatus = &masterStatus{
|
||||
func (server *Server) initMaster() {
|
||||
server.masterStatus = &masterStatus{
|
||||
mu: sync.RWMutex{},
|
||||
replId: utils.RandHexString(40),
|
||||
backlog: &replBacklog{},
|
||||
@@ -430,28 +431,28 @@ func (mdb *MultiDB) initMaster() {
|
||||
}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) stopMaster() {
|
||||
mdb.masterStatus.mu.Lock()
|
||||
defer mdb.masterStatus.mu.Unlock()
|
||||
func (server *Server) stopMaster() {
|
||||
server.masterStatus.mu.Lock()
|
||||
defer server.masterStatus.mu.Unlock()
|
||||
|
||||
// disconnect with slave
|
||||
for _, slave := range mdb.masterStatus.slaveMap {
|
||||
for _, slave := range server.masterStatus.slaveMap {
|
||||
_ = slave.conn.Close()
|
||||
delete(mdb.masterStatus.slaveMap, slave.conn)
|
||||
delete(mdb.masterStatus.waitSlaves, slave)
|
||||
delete(mdb.masterStatus.onlineSlaves, slave)
|
||||
delete(server.masterStatus.slaveMap, slave.conn)
|
||||
delete(server.masterStatus.waitSlaves, slave)
|
||||
delete(server.masterStatus.onlineSlaves, slave)
|
||||
}
|
||||
|
||||
// clean master status
|
||||
if mdb.aofHandler != nil {
|
||||
mdb.aofHandler.RemoveListener(mdb.masterStatus.aofListener)
|
||||
if server.persister != nil {
|
||||
server.persister.RemoveListener(server.masterStatus.aofListener)
|
||||
}
|
||||
_ = os.Remove(mdb.masterStatus.rdbFilename)
|
||||
mdb.masterStatus.rdbFilename = ""
|
||||
mdb.masterStatus.replId = ""
|
||||
mdb.masterStatus.backlog = &replBacklog{}
|
||||
mdb.masterStatus.slaveMap = make(map[redis.Connection]*slaveClient)
|
||||
mdb.masterStatus.waitSlaves = make(map[*slaveClient]struct{})
|
||||
mdb.masterStatus.onlineSlaves = make(map[*slaveClient]struct{})
|
||||
mdb.masterStatus.bgSaveState = bgSaveIdle
|
||||
_ = os.Remove(server.masterStatus.rdbFilename)
|
||||
server.masterStatus.rdbFilename = ""
|
||||
server.masterStatus.replId = ""
|
||||
server.masterStatus.backlog = &replBacklog{}
|
||||
server.masterStatus.slaveMap = make(map[redis.Connection]*slaveClient)
|
||||
server.masterStatus.waitSlaves = make(map[*slaveClient]struct{})
|
||||
server.masterStatus.onlineSlaves = make(map[*slaveClient]struct{})
|
||||
server.masterStatus.bgSaveState = bgSaveIdle
|
||||
}
|
||||
|
@@ -19,8 +19,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func mockServer() *MultiDB {
|
||||
server := &MultiDB{}
|
||||
func mockServer() *Server {
|
||||
server := &Server{}
|
||||
server.dbSet = make([]*atomic.Value, 16)
|
||||
for i := range server.dbSet {
|
||||
singleDB := makeDB()
|
||||
@@ -50,11 +50,11 @@ func TestReplicationMasterSide(t *testing.T) {
|
||||
AppendFilename: aofFilename,
|
||||
}
|
||||
master := mockServer()
|
||||
aofHandler, err := NewAofHandler(master, config.Properties.AppendFilename, true)
|
||||
aofHandler, err := NewPersister(master, config.Properties.AppendFilename, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
master.bindAofHandler(aofHandler)
|
||||
master.bindPersister(aofHandler)
|
||||
slave := mockServer()
|
||||
replConn := connection.NewFakeConn()
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestReplicationMasterSide(t *testing.T) {
|
||||
}
|
||||
|
||||
rdbDec := rdb.NewDecoder(bytes.NewReader(rdbReply.Arg))
|
||||
err = importRDB(rdbDec, slave)
|
||||
err = slave.loadRDB(rdbDec)
|
||||
if err != nil {
|
||||
t.Error("import rdb failed: " + err.Error())
|
||||
return
|
||||
@@ -213,11 +213,11 @@ func TestReplicationMasterRewriteRDB(t *testing.T) {
|
||||
AppendFilename: aofFilename,
|
||||
}
|
||||
master := mockServer()
|
||||
aofHandler, err := NewAofHandler(master, config.Properties.AppendFilename, true)
|
||||
aofHandler, err := NewPersister(master, config.Properties.AppendFilename, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
master.bindAofHandler(aofHandler)
|
||||
master.bindPersister(aofHandler)
|
||||
|
||||
masterConn := connection.NewFakeConn()
|
||||
resp := master.Exec(masterConn, utils.ToCmdLine("SET", "a", "a"))
|
||||
@@ -276,7 +276,7 @@ func TestReplicationMasterRewriteRDB(t *testing.T) {
|
||||
}
|
||||
|
||||
rdbDec := rdb.NewDecoder(bytes.NewReader(rdbReply.Arg))
|
||||
err = importRDB(rdbDec, slave)
|
||||
err = slave.loadRDB(rdbDec)
|
||||
if err != nil {
|
||||
t.Error("import rdb failed: " + err.Error())
|
||||
return
|
||||
|
@@ -55,10 +55,10 @@ func initReplSlaveStatus() *slaveStatus {
|
||||
return &slaveStatus{}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) execSlaveOf(c redis.Connection, args [][]byte) redis.Reply {
|
||||
func (server *Server) execSlaveOf(c redis.Connection, args [][]byte) redis.Reply {
|
||||
if strings.ToLower(string(args[0])) == "no" &&
|
||||
strings.ToLower(string(args[1])) == "one" {
|
||||
mdb.slaveOfNone()
|
||||
server.slaveOfNone()
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
host := string(args[0])
|
||||
@@ -66,26 +66,26 @@ func (mdb *MultiDB) execSlaveOf(c redis.Connection, args [][]byte) redis.Reply {
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
||||
}
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
atomic.StoreInt32(&mdb.role, slaveRole)
|
||||
mdb.slaveStatus.masterHost = host
|
||||
mdb.slaveStatus.masterPort = port
|
||||
server.slaveStatus.mutex.Lock()
|
||||
atomic.StoreInt32(&server.role, slaveRole)
|
||||
server.slaveStatus.masterHost = host
|
||||
server.slaveStatus.masterPort = port
|
||||
// use buffered channel in case receiver goroutine exited before controller send stop signal
|
||||
atomic.AddInt32(&mdb.slaveStatus.configVersion, 1)
|
||||
mdb.slaveStatus.mutex.Unlock()
|
||||
go mdb.setupMaster()
|
||||
atomic.AddInt32(&server.slaveStatus.configVersion, 1)
|
||||
server.slaveStatus.mutex.Unlock()
|
||||
go server.setupMaster()
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) slaveOfNone() {
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
defer mdb.slaveStatus.mutex.Unlock()
|
||||
mdb.slaveStatus.masterHost = ""
|
||||
mdb.slaveStatus.masterPort = 0
|
||||
mdb.slaveStatus.replId = ""
|
||||
mdb.slaveStatus.replOffset = -1
|
||||
mdb.slaveStatus.stopSlaveWithMutex()
|
||||
mdb.role = masterRole
|
||||
func (server *Server) slaveOfNone() {
|
||||
server.slaveStatus.mutex.Lock()
|
||||
defer server.slaveStatus.mutex.Unlock()
|
||||
server.slaveStatus.masterHost = ""
|
||||
server.slaveStatus.masterPort = 0
|
||||
server.slaveStatus.replId = ""
|
||||
server.slaveStatus.replOffset = -1
|
||||
server.slaveStatus.stopSlaveWithMutex()
|
||||
server.role = masterRole
|
||||
}
|
||||
|
||||
// stopSlaveWithMutex stops in-progress connectWithMaster/fullSync/receiveAOF
|
||||
@@ -114,7 +114,7 @@ func (repl *slaveStatus) close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) setupMaster() {
|
||||
func (server *Server) setupMaster() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
@@ -122,28 +122,28 @@ func (mdb *MultiDB) setupMaster() {
|
||||
}()
|
||||
var configVersion int32
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
mdb.slaveStatus.ctx = ctx
|
||||
mdb.slaveStatus.cancel = cancel
|
||||
configVersion = mdb.slaveStatus.configVersion
|
||||
mdb.slaveStatus.mutex.Unlock()
|
||||
isFullReSync, err := mdb.connectWithMaster(configVersion)
|
||||
server.slaveStatus.mutex.Lock()
|
||||
server.slaveStatus.ctx = ctx
|
||||
server.slaveStatus.cancel = cancel
|
||||
configVersion = server.slaveStatus.configVersion
|
||||
server.slaveStatus.mutex.Unlock()
|
||||
isFullReSync, err := server.connectWithMaster(configVersion)
|
||||
if err != nil {
|
||||
// connect failed, abort master
|
||||
logger.Error(err)
|
||||
mdb.slaveOfNone()
|
||||
server.slaveOfNone()
|
||||
return
|
||||
}
|
||||
if isFullReSync {
|
||||
err = mdb.loadMasterRDB(configVersion)
|
||||
err = server.loadMasterRDB(configVersion)
|
||||
if err != nil {
|
||||
// load failed, abort master
|
||||
logger.Error(err)
|
||||
mdb.slaveOfNone()
|
||||
server.slaveOfNone()
|
||||
return
|
||||
}
|
||||
}
|
||||
err = mdb.receiveAOF(ctx, configVersion)
|
||||
err = server.receiveAOF(ctx, configVersion)
|
||||
if err != nil {
|
||||
// full sync failed, abort
|
||||
logger.Error(err)
|
||||
@@ -153,11 +153,11 @@ func (mdb *MultiDB) setupMaster() {
|
||||
|
||||
// connectWithMaster finishes handshake with master
|
||||
// returns: isFullReSync, error
|
||||
func (mdb *MultiDB) connectWithMaster(configVersion int32) (bool, error) {
|
||||
addr := mdb.slaveStatus.masterHost + ":" + strconv.Itoa(mdb.slaveStatus.masterPort)
|
||||
func (server *Server) connectWithMaster(configVersion int32) (bool, error) {
|
||||
addr := server.slaveStatus.masterHost + ":" + strconv.Itoa(server.slaveStatus.masterPort)
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
mdb.slaveOfNone() // abort
|
||||
server.slaveOfNone() // abort
|
||||
return false, errors.New("connect master failed " + err.Error())
|
||||
}
|
||||
masterChan := parser.ParseStream(conn)
|
||||
@@ -179,7 +179,7 @@ func (mdb *MultiDB) connectWithMaster(configVersion int32) (bool, error) {
|
||||
!strings.HasPrefix(reply.Error(), "NOPERM") &&
|
||||
!strings.HasPrefix(reply.Error(), "ERR operation not permitted") {
|
||||
logger.Error("Error reply to PING from master: " + string(reply.ToBytes()))
|
||||
mdb.slaveOfNone() // abort
|
||||
server.slaveOfNone() // abort
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
@@ -189,16 +189,16 @@ func (mdb *MultiDB) connectWithMaster(configVersion int32) (bool, error) {
|
||||
req := protocol.MakeMultiBulkReply(cmdLine)
|
||||
_, err := conn.Write(req.ToBytes())
|
||||
if err != nil {
|
||||
mdb.slaveOfNone() // abort
|
||||
server.slaveOfNone() // abort
|
||||
return errors.New("send failed " + err.Error())
|
||||
}
|
||||
resp := <-masterChan
|
||||
if resp.Err != nil {
|
||||
mdb.slaveOfNone() // abort
|
||||
server.slaveOfNone() // abort
|
||||
return errors.New("read response failed: " + resp.Err.Error())
|
||||
}
|
||||
if !protocol.IsOKReply(resp.Data) {
|
||||
mdb.slaveOfNone() // abort
|
||||
server.slaveOfNone() // abort
|
||||
return errors.New("unexpected auth response: " + string(resp.Data.ToBytes()))
|
||||
}
|
||||
return nil
|
||||
@@ -243,34 +243,34 @@ func (mdb *MultiDB) connectWithMaster(configVersion int32) (bool, error) {
|
||||
}
|
||||
|
||||
// update connection
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
defer mdb.slaveStatus.mutex.Unlock()
|
||||
if mdb.slaveStatus.configVersion != configVersion {
|
||||
server.slaveStatus.mutex.Lock()
|
||||
defer server.slaveStatus.mutex.Unlock()
|
||||
if server.slaveStatus.configVersion != configVersion {
|
||||
// slaveStatus conf changed during connecting and waiting mutex
|
||||
return false, configChangedErr
|
||||
}
|
||||
mdb.slaveStatus.masterConn = conn
|
||||
mdb.slaveStatus.masterChan = masterChan
|
||||
mdb.slaveStatus.lastRecvTime = time.Now()
|
||||
return mdb.psyncHandshake()
|
||||
server.slaveStatus.masterConn = conn
|
||||
server.slaveStatus.masterChan = masterChan
|
||||
server.slaveStatus.lastRecvTime = time.Now()
|
||||
return server.psyncHandshake()
|
||||
}
|
||||
|
||||
// psyncHandshake send `psync` to master and sync repl-id/offset with master
|
||||
// invoker should provide with slaveStatus.mutex
|
||||
func (mdb *MultiDB) psyncHandshake() (bool, error) {
|
||||
func (server *Server) psyncHandshake() (bool, error) {
|
||||
replId := "?"
|
||||
var replOffset int64 = -1
|
||||
if mdb.slaveStatus.replId != "" {
|
||||
replId = mdb.slaveStatus.replId
|
||||
replOffset = mdb.slaveStatus.replOffset
|
||||
if server.slaveStatus.replId != "" {
|
||||
replId = server.slaveStatus.replId
|
||||
replOffset = server.slaveStatus.replOffset
|
||||
}
|
||||
psyncCmdLine := utils.ToCmdLine("psync", replId, strconv.FormatInt(replOffset, 10))
|
||||
psyncReq := protocol.MakeMultiBulkReply(psyncCmdLine)
|
||||
_, err := mdb.slaveStatus.masterConn.Write(psyncReq.ToBytes())
|
||||
_, err := server.slaveStatus.masterConn.Write(psyncReq.ToBytes())
|
||||
if err != nil {
|
||||
return false, errors.New("send failed " + err.Error())
|
||||
}
|
||||
psyncPayload := <-mdb.slaveStatus.masterChan
|
||||
psyncPayload := <-server.slaveStatus.masterChan
|
||||
if psyncPayload.Err != nil {
|
||||
return false, errors.New("read response failed: " + psyncPayload.Err.Error())
|
||||
}
|
||||
@@ -287,12 +287,12 @@ func (mdb *MultiDB) psyncHandshake() (bool, error) {
|
||||
var isFullReSync bool
|
||||
if headers[0] == "FULLRESYNC" {
|
||||
logger.Info("full re-sync with master")
|
||||
mdb.slaveStatus.replId = headers[1]
|
||||
mdb.slaveStatus.replOffset, err = strconv.ParseInt(headers[2], 10, 64)
|
||||
server.slaveStatus.replId = headers[1]
|
||||
server.slaveStatus.replOffset, err = strconv.ParseInt(headers[2], 10, 64)
|
||||
isFullReSync = true
|
||||
} else if headers[0] == "CONTINUE" {
|
||||
logger.Info("continue partial sync")
|
||||
mdb.slaveStatus.replId = headers[1]
|
||||
server.slaveStatus.replId = headers[1]
|
||||
isFullReSync = false
|
||||
} else {
|
||||
return false, errors.New("illegal psync resp: " + psyncHeader.Status)
|
||||
@@ -301,12 +301,12 @@ func (mdb *MultiDB) psyncHandshake() (bool, error) {
|
||||
if err != nil {
|
||||
return false, errors.New("get illegal repl offset: " + headers[2])
|
||||
}
|
||||
logger.Info(fmt.Sprintf("repl id: %s, current offset: %d", mdb.slaveStatus.replId, mdb.slaveStatus.replOffset))
|
||||
logger.Info(fmt.Sprintf("repl id: %s, current offset: %d", server.slaveStatus.replId, server.slaveStatus.replOffset))
|
||||
return isFullReSync, nil
|
||||
}
|
||||
|
||||
func makeRdbLoader(upgradeAof bool) (*MultiDB, string, error) {
|
||||
rdbLoader := MakeBasicMultiDB()
|
||||
func makeRdbLoader(upgradeAof bool) (*Server, string, error) {
|
||||
rdbLoader := MakeAuxiliaryServer()
|
||||
if !upgradeAof {
|
||||
return rdbLoader, "", nil
|
||||
}
|
||||
@@ -316,17 +316,17 @@ func makeRdbLoader(upgradeAof bool) (*MultiDB, string, error) {
|
||||
return nil, "", fmt.Errorf("create temp rdb failed: %v", err)
|
||||
}
|
||||
newAofFilename := newAofFile.Name()
|
||||
aofHandler, err := NewAofHandler(rdbLoader, newAofFilename, false)
|
||||
aofHandler, err := NewPersister(rdbLoader, newAofFilename, false)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
rdbLoader.bindAofHandler(aofHandler)
|
||||
rdbLoader.bindPersister(aofHandler)
|
||||
return rdbLoader, newAofFilename, nil
|
||||
}
|
||||
|
||||
// loadMasterRDB downloads rdb after handshake has been done
|
||||
func (mdb *MultiDB) loadMasterRDB(configVersion int32) error {
|
||||
rdbPayload := <-mdb.slaveStatus.masterChan
|
||||
func (server *Server) loadMasterRDB(configVersion int32) error {
|
||||
rdbPayload := <-server.slaveStatus.masterChan
|
||||
if rdbPayload.Err != nil {
|
||||
return errors.New("read response failed: " + rdbPayload.Err.Error())
|
||||
}
|
||||
@@ -342,47 +342,47 @@ func (mdb *MultiDB) loadMasterRDB(configVersion int32) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = importRDB(rdbDec, rdbLoader)
|
||||
err = rdbLoader.loadRDB(rdbDec)
|
||||
if err != nil {
|
||||
return errors.New("dump rdb failed: " + err.Error())
|
||||
}
|
||||
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
defer mdb.slaveStatus.mutex.Unlock()
|
||||
if mdb.slaveStatus.configVersion != configVersion {
|
||||
server.slaveStatus.mutex.Lock()
|
||||
defer server.slaveStatus.mutex.Unlock()
|
||||
if server.slaveStatus.configVersion != configVersion {
|
||||
// slaveStatus conf changed during connecting and waiting mutex
|
||||
return configChangedErr
|
||||
}
|
||||
for i, h := range rdbLoader.dbSet {
|
||||
newDB := h.Load().(*DB)
|
||||
mdb.loadDB(i, newDB)
|
||||
server.loadDB(i, newDB)
|
||||
}
|
||||
|
||||
if config.Properties.AppendOnly {
|
||||
// use new aof file
|
||||
mdb.aofHandler.Close()
|
||||
server.persister.Close()
|
||||
err = os.Rename(newAofFilename, config.Properties.AppendFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aofHandler, err := NewAofHandler(mdb, config.Properties.AppendFilename, false)
|
||||
aofHandler, err := NewPersister(server, config.Properties.AppendFilename, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mdb.bindAofHandler(aofHandler)
|
||||
server.bindPersister(aofHandler)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) receiveAOF(ctx context.Context, configVersion int32) error {
|
||||
conn := connection.NewConn(mdb.slaveStatus.masterConn)
|
||||
func (server *Server) receiveAOF(ctx context.Context, configVersion int32) error {
|
||||
conn := connection.NewConn(server.slaveStatus.masterConn)
|
||||
conn.SetMaster()
|
||||
mdb.slaveStatus.running.Add(1)
|
||||
defer mdb.slaveStatus.running.Done()
|
||||
server.slaveStatus.running.Add(1)
|
||||
defer server.slaveStatus.running.Done()
|
||||
for {
|
||||
select {
|
||||
case payload, open := <-mdb.slaveStatus.masterChan:
|
||||
case payload, open := <-server.slaveStatus.masterChan:
|
||||
if !open {
|
||||
return errors.New("master channel unexpected close")
|
||||
}
|
||||
@@ -393,18 +393,18 @@ func (mdb *MultiDB) receiveAOF(ctx context.Context, configVersion int32) error {
|
||||
if !ok {
|
||||
return errors.New("unexpected payload: " + string(payload.Data.ToBytes()))
|
||||
}
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
if mdb.slaveStatus.configVersion != configVersion {
|
||||
server.slaveStatus.mutex.Lock()
|
||||
if server.slaveStatus.configVersion != configVersion {
|
||||
// slaveStatus conf changed during connecting and waiting mutex
|
||||
return configChangedErr
|
||||
}
|
||||
mdb.Exec(conn, cmdLine.Args)
|
||||
server.Exec(conn, cmdLine.Args)
|
||||
n := len(cmdLine.ToBytes()) // todo: directly get size from socket
|
||||
mdb.slaveStatus.replOffset += int64(n)
|
||||
mdb.slaveStatus.lastRecvTime = time.Now()
|
||||
server.slaveStatus.replOffset += int64(n)
|
||||
server.slaveStatus.lastRecvTime = time.Now()
|
||||
logger.Info(fmt.Sprintf("receive %d bytes from master, current offset %d, %s",
|
||||
n, mdb.slaveStatus.replOffset, strconv.Quote(string(cmdLine.ToBytes()))))
|
||||
mdb.slaveStatus.mutex.Unlock()
|
||||
n, server.slaveStatus.replOffset, strconv.Quote(string(cmdLine.ToBytes()))))
|
||||
server.slaveStatus.mutex.Unlock()
|
||||
case <-ctx.Done():
|
||||
_ = conn.Close()
|
||||
return nil
|
||||
@@ -412,8 +412,8 @@ func (mdb *MultiDB) receiveAOF(ctx context.Context, configVersion int32) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) slaveCron() {
|
||||
repl := mdb.slaveStatus
|
||||
func (server *Server) slaveCron() {
|
||||
repl := server.slaveStatus
|
||||
if repl.masterConn == nil {
|
||||
return
|
||||
}
|
||||
@@ -426,7 +426,7 @@ func (mdb *MultiDB) slaveCron() {
|
||||
minLastRecvTime := time.Now().Add(-replTimeout)
|
||||
if repl.lastRecvTime.Before(minLastRecvTime) {
|
||||
// reconnect with master
|
||||
err := mdb.reconnectWithMaster()
|
||||
err := server.reconnectWithMaster()
|
||||
if err != nil {
|
||||
logger.Error("send failed " + err.Error())
|
||||
}
|
||||
@@ -449,11 +449,11 @@ func (repl *slaveStatus) sendAck2Master() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (mdb *MultiDB) reconnectWithMaster() error {
|
||||
func (server *Server) reconnectWithMaster() error {
|
||||
logger.Info("reconnecting with master")
|
||||
mdb.slaveStatus.mutex.Lock()
|
||||
defer mdb.slaveStatus.mutex.Unlock()
|
||||
mdb.slaveStatus.stopSlaveWithMutex()
|
||||
go mdb.setupMaster()
|
||||
server.slaveStatus.mutex.Lock()
|
||||
defer server.slaveStatus.mutex.Unlock()
|
||||
server.slaveStatus.stopSlaveWithMutex()
|
||||
go server.setupMaster()
|
||||
return nil
|
||||
}
|
||||
|
@@ -37,12 +37,12 @@ func TestReplicationSlaveSide(t *testing.T) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
aofHandler, err := NewAofHandler(server, config.Properties.AppendFilename, true)
|
||||
aofHandler, err := NewPersister(server, config.Properties.AppendFilename, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
server.bindAofHandler(aofHandler)
|
||||
server.bindPersister(aofHandler)
|
||||
server.Exec(conn, utils.ToCmdLine("set", "zz", "zz"))
|
||||
masterCli.Start()
|
||||
|
||||
@@ -150,9 +150,9 @@ func TestReplicationSlaveSide(t *testing.T) {
|
||||
}
|
||||
|
||||
// check slave aof file
|
||||
aofLoader := MakeBasicMultiDB()
|
||||
aofHandler2, err := NewAofHandler(aofLoader, config.Properties.AppendFilename, true)
|
||||
aofLoader.bindAofHandler(aofHandler2)
|
||||
aofLoader := MakeAuxiliaryServer()
|
||||
aofHandler2, err := NewPersister(aofLoader, config.Properties.AppendFilename, true)
|
||||
aofLoader.bindPersister(aofHandler2)
|
||||
ret = aofLoader.Exec(conn, utils.ToCmdLine("get", "zz"))
|
||||
asserts.AssertNullBulk(t, ret)
|
||||
ret = aofLoader.Exec(conn, utils.ToCmdLine("get", "1"))
|
||||
|
350
database/server.go
Normal file
350
database/server.go
Normal file
@@ -0,0 +1,350 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hdt3213/godis/aof"
|
||||
"github.com/hdt3213/godis/config"
|
||||
"github.com/hdt3213/godis/interface/database"
|
||||
"github.com/hdt3213/godis/interface/redis"
|
||||
"github.com/hdt3213/godis/lib/logger"
|
||||
"github.com/hdt3213/godis/lib/utils"
|
||||
"github.com/hdt3213/godis/pubsub"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Server is a redis-server with full capabilities including multiple database, rdb loader, replication
|
||||
type Server struct {
|
||||
dbSet []*atomic.Value // *DB
|
||||
|
||||
// handle publish/subscribe
|
||||
hub *pubsub.Hub
|
||||
// handle aof persistence
|
||||
persister *aof.Persister
|
||||
|
||||
// for replication
|
||||
role int32
|
||||
slaveStatus *slaveStatus
|
||||
masterStatus *masterStatus
|
||||
}
|
||||
|
||||
// NewStandaloneServer creates a standalone redis server, with multi database and all other funtions
|
||||
func NewStandaloneServer() *Server {
|
||||
server := &Server{}
|
||||
if config.Properties.Databases == 0 {
|
||||
config.Properties.Databases = 16
|
||||
}
|
||||
server.dbSet = make([]*atomic.Value, config.Properties.Databases)
|
||||
for i := range server.dbSet {
|
||||
singleDB := makeDB()
|
||||
singleDB.index = i
|
||||
holder := &atomic.Value{}
|
||||
holder.Store(singleDB)
|
||||
server.dbSet[i] = holder
|
||||
}
|
||||
server.hub = pubsub.MakeHub()
|
||||
validAof := false
|
||||
if config.Properties.AppendOnly {
|
||||
aofHandler, err := NewPersister(server, config.Properties.AppendFilename, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.bindPersister(aofHandler)
|
||||
validAof = true
|
||||
}
|
||||
if config.Properties.RDBFilename != "" && !validAof {
|
||||
// load rdb
|
||||
err := server.loadRdbFile()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
server.slaveStatus = initReplSlaveStatus()
|
||||
server.initMaster()
|
||||
server.startReplCron()
|
||||
server.role = masterRole // The initialization process does not require atomicity
|
||||
return server
|
||||
}
|
||||
|
||||
// Exec executes command
|
||||
// parameter `cmdLine` contains command and its arguments, for example: "set key value"
|
||||
func (server *Server) Exec(c redis.Connection, cmdLine [][]byte) (result redis.Reply) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
|
||||
result = &protocol.UnknownErrReply{}
|
||||
}
|
||||
}()
|
||||
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
// ping
|
||||
if cmdName == "ping" {
|
||||
return Ping(c, cmdLine[1:])
|
||||
}
|
||||
// authenticate
|
||||
if cmdName == "auth" {
|
||||
return Auth(c, cmdLine[1:])
|
||||
}
|
||||
if !isAuthenticated(c) {
|
||||
return protocol.MakeErrReply("NOAUTH Authentication required")
|
||||
}
|
||||
if cmdName == "slaveof" {
|
||||
if c != nil && c.InMultiState() {
|
||||
return protocol.MakeErrReply("cannot use slave of database within multi")
|
||||
}
|
||||
if len(cmdLine) != 3 {
|
||||
return protocol.MakeArgNumErrReply("SLAVEOF")
|
||||
}
|
||||
return server.execSlaveOf(c, cmdLine[1:])
|
||||
}
|
||||
|
||||
// read only slave
|
||||
role := atomic.LoadInt32(&server.role)
|
||||
if role == slaveRole && !c.IsMaster() {
|
||||
// only allow read only command, forbid all special commands except `auth` and `slaveof`
|
||||
if !isReadOnlyCommand(cmdName) {
|
||||
return protocol.MakeErrReply("READONLY You can't write against a read only slave.")
|
||||
}
|
||||
}
|
||||
|
||||
// special commands which cannot execute within transaction
|
||||
if cmdName == "subscribe" {
|
||||
if len(cmdLine) < 2 {
|
||||
return protocol.MakeArgNumErrReply("subscribe")
|
||||
}
|
||||
return pubsub.Subscribe(server.hub, c, cmdLine[1:])
|
||||
} else if cmdName == "publish" {
|
||||
return pubsub.Publish(server.hub, cmdLine[1:])
|
||||
} else if cmdName == "unsubscribe" {
|
||||
return pubsub.UnSubscribe(server.hub, c, cmdLine[1:])
|
||||
} else if cmdName == "bgrewriteaof" {
|
||||
// aof.go imports router.go, router.go cannot import BGRewriteAOF from aof.go
|
||||
return BGRewriteAOF(server, cmdLine[1:])
|
||||
} else if cmdName == "rewriteaof" {
|
||||
return RewriteAOF(server, cmdLine[1:])
|
||||
} else if cmdName == "flushall" {
|
||||
return server.flushAll()
|
||||
} else if cmdName == "flushdb" {
|
||||
if !validateArity(1, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
if c.InMultiState() {
|
||||
return protocol.MakeErrReply("ERR command 'FlushDB' cannot be used in MULTI")
|
||||
}
|
||||
return server.flushDB(c.GetDBIndex())
|
||||
} else if cmdName == "save" {
|
||||
return SaveRDB(server, cmdLine[1:])
|
||||
} else if cmdName == "bgsave" {
|
||||
return BGSaveRDB(server, cmdLine[1:])
|
||||
} else if cmdName == "select" {
|
||||
if c != nil && c.InMultiState() {
|
||||
return protocol.MakeErrReply("cannot select database within multi")
|
||||
}
|
||||
if len(cmdLine) != 2 {
|
||||
return protocol.MakeArgNumErrReply("select")
|
||||
}
|
||||
return execSelect(c, server, cmdLine[1:])
|
||||
} else if cmdName == "copy" {
|
||||
if len(cmdLine) < 3 {
|
||||
return protocol.MakeArgNumErrReply("copy")
|
||||
}
|
||||
return execCopy(server, c, cmdLine[1:])
|
||||
} else if cmdName == "replconf" {
|
||||
return server.execReplConf(c, cmdLine[1:])
|
||||
} else if cmdName == "psync" {
|
||||
return server.execPSync(c, cmdLine[1:])
|
||||
}
|
||||
// todo: support multi database transaction
|
||||
|
||||
// normal commands
|
||||
dbIndex := c.GetDBIndex()
|
||||
selectedDB, errReply := server.selectDB(dbIndex)
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return selectedDB.Exec(c, cmdLine)
|
||||
}
|
||||
|
||||
// AfterClientClose does some clean after client close connection
|
||||
func (server *Server) AfterClientClose(c redis.Connection) {
|
||||
pubsub.UnsubscribeAll(server.hub, c)
|
||||
}
|
||||
|
||||
// Close graceful shutdown database
|
||||
func (server *Server) Close() {
|
||||
// stop slaveStatus first
|
||||
server.slaveStatus.close()
|
||||
if server.persister != nil {
|
||||
server.persister.Close()
|
||||
}
|
||||
server.stopMaster()
|
||||
}
|
||||
|
||||
func execSelect(c redis.Connection, mdb *Server, args [][]byte) redis.Reply {
|
||||
dbIndex, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply("ERR invalid DB index")
|
||||
}
|
||||
if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
c.SelectDB(dbIndex)
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
func (server *Server) flushDB(dbIndex int) redis.Reply {
|
||||
if dbIndex >= len(server.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
newDB := makeDB()
|
||||
server.loadDB(dbIndex, newDB)
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (server *Server) loadDB(dbIndex int, newDB *DB) redis.Reply {
|
||||
if dbIndex >= len(server.dbSet) || dbIndex < 0 {
|
||||
return protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
oldDB := server.mustSelectDB(dbIndex)
|
||||
newDB.index = dbIndex
|
||||
newDB.addAof = oldDB.addAof // inherit oldDB
|
||||
server.dbSet[dbIndex].Store(newDB)
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (server *Server) flushAll() redis.Reply {
|
||||
for i := range server.dbSet {
|
||||
server.flushDB(i)
|
||||
}
|
||||
if server.persister != nil {
|
||||
server.persister.AddAof(0, utils.ToCmdLine("FlushAll"))
|
||||
}
|
||||
return &protocol.OkReply{}
|
||||
}
|
||||
|
||||
func (server *Server) selectDB(dbIndex int) (*DB, *protocol.StandardErrReply) {
|
||||
if dbIndex >= len(server.dbSet) || dbIndex < 0 {
|
||||
return nil, protocol.MakeErrReply("ERR DB index is out of range")
|
||||
}
|
||||
return server.dbSet[dbIndex].Load().(*DB), nil
|
||||
}
|
||||
|
||||
func (server *Server) mustSelectDB(dbIndex int) *DB {
|
||||
selectedDB, err := server.selectDB(dbIndex)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return selectedDB
|
||||
}
|
||||
|
||||
// ForEach traverses all the keys in the given database
|
||||
func (server *Server) ForEach(dbIndex int, cb func(key string, data *database.DataEntity, expiration *time.Time) bool) {
|
||||
server.mustSelectDB(dbIndex).ForEach(cb)
|
||||
}
|
||||
|
||||
// ExecMulti executes multi commands transaction Atomically and Isolated
|
||||
func (server *Server) ExecMulti(conn redis.Connection, watching map[string]uint32, cmdLines []CmdLine) redis.Reply {
|
||||
selectedDB, errReply := server.selectDB(conn.GetDBIndex())
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return selectedDB.ExecMulti(conn, watching, cmdLines)
|
||||
}
|
||||
|
||||
// RWLocks lock keys for writing and reading
|
||||
func (server *Server) RWLocks(dbIndex int, writeKeys []string, readKeys []string) {
|
||||
server.mustSelectDB(dbIndex).RWLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
// RWUnLocks unlock keys for writing and reading
|
||||
func (server *Server) RWUnLocks(dbIndex int, writeKeys []string, readKeys []string) {
|
||||
server.mustSelectDB(dbIndex).RWUnLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
// GetUndoLogs return rollback commands
|
||||
func (server *Server) GetUndoLogs(dbIndex int, cmdLine [][]byte) []CmdLine {
|
||||
return server.mustSelectDB(dbIndex).GetUndoLogs(cmdLine)
|
||||
}
|
||||
|
||||
// ExecWithLock executes normal commands, invoker should provide locks
|
||||
func (server *Server) ExecWithLock(conn redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||
db, errReply := server.selectDB(conn.GetDBIndex())
|
||||
if errReply != nil {
|
||||
return errReply
|
||||
}
|
||||
return db.execWithLock(cmdLine)
|
||||
}
|
||||
|
||||
// BGRewriteAOF asynchronously rewrites Append-Only-File
|
||||
func BGRewriteAOF(db *Server, args [][]byte) redis.Reply {
|
||||
go db.persister.Rewrite()
|
||||
return protocol.MakeStatusReply("Background append only file rewriting started")
|
||||
}
|
||||
|
||||
// RewriteAOF start Append-Only-File rewriting and blocked until it finished
|
||||
func RewriteAOF(db *Server, args [][]byte) redis.Reply {
|
||||
err := db.persister.Rewrite()
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply(err.Error())
|
||||
}
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// SaveRDB start RDB writing and blocked until it finished
|
||||
func SaveRDB(db *Server, args [][]byte) redis.Reply {
|
||||
if db.persister == nil {
|
||||
return protocol.MakeErrReply("please enable aof before using save")
|
||||
}
|
||||
rdbFilename := config.Properties.RDBFilename
|
||||
if rdbFilename == "" {
|
||||
rdbFilename = "dump.rdb"
|
||||
}
|
||||
err := db.persister.Rewrite2RDB(rdbFilename)
|
||||
if err != nil {
|
||||
return protocol.MakeErrReply(err.Error())
|
||||
}
|
||||
return protocol.MakeOkReply()
|
||||
}
|
||||
|
||||
// BGSaveRDB asynchronously save RDB
|
||||
func BGSaveRDB(db *Server, args [][]byte) redis.Reply {
|
||||
if db.persister == nil {
|
||||
return protocol.MakeErrReply("please enable aof before using save")
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
rdbFilename := config.Properties.RDBFilename
|
||||
if rdbFilename == "" {
|
||||
rdbFilename = "dump.rdb"
|
||||
}
|
||||
err := db.persister.Rewrite2RDB(rdbFilename)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
return protocol.MakeStatusReply("Background saving started")
|
||||
}
|
||||
|
||||
// GetDBSize returns keys count and ttl key count
|
||||
func (server *Server) GetDBSize(dbIndex int) (int, int) {
|
||||
db := server.mustSelectDB(dbIndex)
|
||||
return db.data.Len(), db.ttlMap.Len()
|
||||
}
|
||||
|
||||
func (server *Server) startReplCron() {
|
||||
go func(mdb *Server) {
|
||||
ticker := time.Tick(time.Second * 10)
|
||||
for range ticker {
|
||||
mdb.slaveCron()
|
||||
mdb.masterCron()
|
||||
}
|
||||
}(server)
|
||||
}
|
@@ -1,302 +0,0 @@
|
||||
// Package database is a memory database with redis compatible interface
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/hdt3213/godis/datastruct/dict"
|
||||
"github.com/hdt3213/godis/datastruct/lock"
|
||||
"github.com/hdt3213/godis/interface/database"
|
||||
"github.com/hdt3213/godis/interface/redis"
|
||||
"github.com/hdt3213/godis/lib/logger"
|
||||
"github.com/hdt3213/godis/lib/timewheel"
|
||||
"github.com/hdt3213/godis/redis/protocol"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
dataDictSize = 1 << 16
|
||||
ttlDictSize = 1 << 10
|
||||
lockerSize = 1024
|
||||
)
|
||||
|
||||
// DB stores data and execute user's commands
|
||||
type DB struct {
|
||||
index int
|
||||
// key -> DataEntity
|
||||
data dict.Dict
|
||||
// key -> expireTime (time.Time)
|
||||
ttlMap dict.Dict
|
||||
// key -> version(uint32)
|
||||
versionMap dict.Dict
|
||||
|
||||
// dict.Dict will ensure concurrent-safety of its method
|
||||
// use this mutex for complicated command only, eg. rpush, incr ...
|
||||
locker *lock.Locks
|
||||
addAof func(CmdLine)
|
||||
}
|
||||
|
||||
// ExecFunc is interface for command executor
|
||||
// args don't include cmd line
|
||||
type ExecFunc func(db *DB, args [][]byte) redis.Reply
|
||||
|
||||
// PreFunc analyses command line when queued command to `multi`
|
||||
// returns related write keys and read keys
|
||||
type PreFunc func(args [][]byte) ([]string, []string)
|
||||
|
||||
// CmdLine is alias for [][]byte, represents a command line
|
||||
type CmdLine = [][]byte
|
||||
|
||||
// UndoFunc returns undo logs for the given command line
|
||||
// execute from head to tail when undo
|
||||
type UndoFunc func(db *DB, args [][]byte) []CmdLine
|
||||
|
||||
// makeDB create DB instance
|
||||
func makeDB() *DB {
|
||||
db := &DB{
|
||||
data: dict.MakeConcurrent(dataDictSize),
|
||||
ttlMap: dict.MakeConcurrent(ttlDictSize),
|
||||
versionMap: dict.MakeConcurrent(dataDictSize),
|
||||
locker: lock.Make(lockerSize),
|
||||
addAof: func(line CmdLine) {},
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// makeBasicDB create DB instance only with basic abilities.
|
||||
// It is not concurrent safe
|
||||
func makeBasicDB() *DB {
|
||||
db := &DB{
|
||||
data: dict.MakeSimple(),
|
||||
ttlMap: dict.MakeSimple(),
|
||||
versionMap: dict.MakeSimple(),
|
||||
locker: lock.Make(1),
|
||||
addAof: func(line CmdLine) {},
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Exec executes command within one database
|
||||
func (db *DB) Exec(c redis.Connection, cmdLine [][]byte) redis.Reply {
|
||||
// transaction control commands and other commands which cannot execute within transaction
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
if cmdName == "multi" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return StartMulti(c)
|
||||
} else if cmdName == "discard" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return DiscardMulti(c)
|
||||
} else if cmdName == "exec" {
|
||||
if len(cmdLine) != 1 {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return execMulti(db, c)
|
||||
} else if cmdName == "watch" {
|
||||
if !validateArity(-2, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
return Watch(db, c, cmdLine[1:])
|
||||
}
|
||||
if c != nil && c.InMultiState() {
|
||||
return EnqueueCmd(c, cmdLine)
|
||||
}
|
||||
|
||||
return db.execNormalCommand(cmdLine)
|
||||
}
|
||||
|
||||
func (db *DB) execNormalCommand(cmdLine [][]byte) redis.Reply {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd, ok := cmdTable[cmdName]
|
||||
if !ok {
|
||||
return protocol.MakeErrReply("ERR unknown command '" + cmdName + "'")
|
||||
}
|
||||
if !validateArity(cmd.arity, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
|
||||
prepare := cmd.prepare
|
||||
write, read := prepare(cmdLine[1:])
|
||||
db.addVersion(write...)
|
||||
db.RWLocks(write, read)
|
||||
defer db.RWUnLocks(write, read)
|
||||
fun := cmd.executor
|
||||
return fun(db, cmdLine[1:])
|
||||
}
|
||||
|
||||
// execWithLock executes normal commands, invoker should provide locks
|
||||
func (db *DB) execWithLock(cmdLine [][]byte) redis.Reply {
|
||||
cmdName := strings.ToLower(string(cmdLine[0]))
|
||||
cmd, ok := cmdTable[cmdName]
|
||||
if !ok {
|
||||
return protocol.MakeErrReply("ERR unknown command '" + cmdName + "'")
|
||||
}
|
||||
if !validateArity(cmd.arity, cmdLine) {
|
||||
return protocol.MakeArgNumErrReply(cmdName)
|
||||
}
|
||||
fun := cmd.executor
|
||||
return fun(db, cmdLine[1:])
|
||||
}
|
||||
|
||||
func validateArity(arity int, cmdArgs [][]byte) bool {
|
||||
argNum := len(cmdArgs)
|
||||
if arity >= 0 {
|
||||
return argNum == arity
|
||||
}
|
||||
return argNum >= -arity
|
||||
}
|
||||
|
||||
/* ---- Data Access ----- */
|
||||
|
||||
// GetEntity returns DataEntity bind to given key
|
||||
func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
|
||||
raw, ok := db.data.Get(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if db.IsExpired(key) {
|
||||
return nil, false
|
||||
}
|
||||
entity, _ := raw.(*database.DataEntity)
|
||||
return entity, true
|
||||
}
|
||||
|
||||
// PutEntity a DataEntity into DB
|
||||
func (db *DB) PutEntity(key string, entity *database.DataEntity) int {
|
||||
return db.data.Put(key, entity)
|
||||
}
|
||||
|
||||
// PutIfExists edit an existing DataEntity
|
||||
func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
|
||||
return db.data.PutIfExists(key, entity)
|
||||
}
|
||||
|
||||
// PutIfAbsent insert an DataEntity only if the key not exists
|
||||
func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
|
||||
return db.data.PutIfAbsent(key, entity)
|
||||
}
|
||||
|
||||
// Remove the given key from db
|
||||
func (db *DB) Remove(key string) {
|
||||
db.data.Remove(key)
|
||||
db.ttlMap.Remove(key)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.Cancel(taskKey)
|
||||
}
|
||||
|
||||
// Removes the given keys from db
|
||||
func (db *DB) Removes(keys ...string) (deleted int) {
|
||||
deleted = 0
|
||||
for _, key := range keys {
|
||||
_, exists := db.data.Get(key)
|
||||
if exists {
|
||||
db.Remove(key)
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
// Flush clean database
|
||||
// deprecated
|
||||
// for test only
|
||||
func (db *DB) Flush() {
|
||||
db.data.Clear()
|
||||
db.ttlMap.Clear()
|
||||
db.locker = lock.Make(lockerSize)
|
||||
}
|
||||
|
||||
/* ---- Lock Function ----- */
|
||||
|
||||
// RWLocks lock keys for writing and reading
|
||||
func (db *DB) RWLocks(writeKeys []string, readKeys []string) {
|
||||
db.locker.RWLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
// RWUnLocks unlock keys for writing and reading
|
||||
func (db *DB) RWUnLocks(writeKeys []string, readKeys []string) {
|
||||
db.locker.RWUnLocks(writeKeys, readKeys)
|
||||
}
|
||||
|
||||
/* ---- TTL Functions ---- */
|
||||
|
||||
func genExpireTask(key string) string {
|
||||
return "expire:" + key
|
||||
}
|
||||
|
||||
// Expire sets ttlCmd of key
|
||||
func (db *DB) Expire(key string, expireTime time.Time) {
|
||||
db.ttlMap.Put(key, expireTime)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.At(expireTime, taskKey, func() {
|
||||
keys := []string{key}
|
||||
db.RWLocks(keys, nil)
|
||||
defer db.RWUnLocks(keys, nil)
|
||||
// check-lock-check, ttl may be updated during waiting lock
|
||||
logger.Info("expire " + key)
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expired := time.Now().After(expireTime)
|
||||
if expired {
|
||||
db.Remove(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Persist cancel ttlCmd of key
|
||||
func (db *DB) Persist(key string) {
|
||||
db.ttlMap.Remove(key)
|
||||
taskKey := genExpireTask(key)
|
||||
timewheel.Cancel(taskKey)
|
||||
}
|
||||
|
||||
// IsExpired check whether a key is expired
|
||||
func (db *DB) IsExpired(key string) bool {
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expired := time.Now().After(expireTime)
|
||||
if expired {
|
||||
db.Remove(key)
|
||||
}
|
||||
return expired
|
||||
}
|
||||
|
||||
/* --- add version --- */
|
||||
|
||||
func (db *DB) addVersion(keys ...string) {
|
||||
for _, key := range keys {
|
||||
versionCode := db.GetVersion(key)
|
||||
db.versionMap.Put(key, versionCode+1)
|
||||
}
|
||||
}
|
||||
|
||||
// GetVersion returns version code for given key
|
||||
func (db *DB) GetVersion(key string) uint32 {
|
||||
entity, ok := db.versionMap.Get(key)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return entity.(uint32)
|
||||
}
|
||||
|
||||
// ForEach traverses all the keys in the database
|
||||
func (db *DB) ForEach(cb func(key string, data *database.DataEntity, expiration *time.Time) bool) {
|
||||
db.data.ForEach(func(key string, raw interface{}) bool {
|
||||
entity, _ := raw.(*database.DataEntity)
|
||||
var expiration *time.Time
|
||||
rawExpireTime, ok := db.ttlMap.Get(key)
|
||||
if ok {
|
||||
expireTime, _ := rawExpireTime.(time.Time)
|
||||
expiration = &expireTime
|
||||
}
|
||||
return cb(key, entity, expiration)
|
||||
})
|
||||
}
|
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// Ping the server
|
||||
func Ping(db *DB, args [][]byte) redis.Reply {
|
||||
func Ping(c redis.Connection, args [][]byte) redis.Reply {
|
||||
if len(args) == 0 {
|
||||
return &protocol.PongReply{}
|
||||
} else if len(args) == 1 {
|
||||
@@ -39,7 +39,3 @@ func isAuthenticated(c redis.Connection) bool {
|
||||
}
|
||||
return c.GetPassword() == config.Properties.RequirePass
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterCommand("ping", Ping, noPrepare, nil, -1, flagReadOnly)
|
||||
}
|
@@ -9,12 +9,13 @@ import (
|
||||
)
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
actual := Ping(testDB, utils.ToCmdLine())
|
||||
c := connection.NewFakeConn()
|
||||
actual := Ping(c, utils.ToCmdLine())
|
||||
asserts.AssertStatusReply(t, actual, "PONG")
|
||||
val := utils.RandString(5)
|
||||
actual = Ping(testDB, utils.ToCmdLine(val))
|
||||
actual = Ping(c, utils.ToCmdLine(val))
|
||||
asserts.AssertStatusReply(t, actual, val)
|
||||
actual = Ping(testDB, utils.ToCmdLine(val, val))
|
||||
actual = Ping(c, utils.ToCmdLine(val, val))
|
||||
asserts.AssertErrReply(t, actual, "ERR wrong number of arguments for 'ping' command")
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ func TestAuth(t *testing.T) {
|
||||
}()
|
||||
ret = testServer.Exec(c, utils.ToCmdLine("AUTH", passwd+"wrong"))
|
||||
asserts.AssertErrReply(t, ret, "ERR invalid password")
|
||||
ret = testServer.Exec(c, utils.ToCmdLine("PING"))
|
||||
ret = testServer.Exec(c, utils.ToCmdLine("GET", "A"))
|
||||
asserts.AssertErrReply(t, ret, "NOAUTH Authentication required")
|
||||
ret = testServer.Exec(c, utils.ToCmdLine("AUTH", passwd))
|
||||
asserts.AssertStatusReply(t, ret, "OK")
|
@@ -15,8 +15,8 @@ type DB interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
// EmbedDB is the embedding storage engine exposing more methods for complex application
|
||||
type EmbedDB interface {
|
||||
// DBEngine is the embedding storage engine exposing more methods for complex application
|
||||
type DBEngine interface {
|
||||
DB
|
||||
ExecWithLock(conn redis.Connection, cmdLine [][]byte) redis.Reply
|
||||
ExecMulti(conn redis.Connection, watching map[string]uint32, cmdLines []CmdLine) redis.Reply
|
||||
|
Reference in New Issue
Block a user