github.com/hdt3213/godis@v1.2.9/database/server.go (about)

     1  package database
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime/debug"
     7  	"strconv"
     8  	"strings"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/hdt3213/godis/aof"
    13  	"github.com/hdt3213/godis/config"
    14  	"github.com/hdt3213/godis/interface/database"
    15  	"github.com/hdt3213/godis/interface/redis"
    16  	"github.com/hdt3213/godis/lib/logger"
    17  	"github.com/hdt3213/godis/lib/utils"
    18  	"github.com/hdt3213/godis/pubsub"
    19  	"github.com/hdt3213/godis/redis/protocol"
    20  )
    21  
    22  var godisVersion = "1.2.8" // do not modify
    23  
    24  // Server is a redis-server with full capabilities including multiple database, rdb loader, replication
    25  type Server struct {
    26  	dbSet []*atomic.Value // *DB
    27  
    28  	// handle publish/subscribe
    29  	hub *pubsub.Hub
    30  	// handle aof persistence
    31  	persister *aof.Persister
    32  
    33  	// for replication
    34  	role         int32
    35  	slaveStatus  *slaveStatus
    36  	masterStatus *masterStatus
    37  }
    38  
    39  func fileExists(filename string) bool {
    40  	info, err := os.Stat(filename)
    41  	return err == nil && !info.IsDir()
    42  }
    43  
    44  // NewStandaloneServer creates a standalone redis server, with multi database and all other funtions
    45  func NewStandaloneServer() *Server {
    46  	server := &Server{}
    47  	if config.Properties.Databases == 0 {
    48  		config.Properties.Databases = 16
    49  	}
    50  	// creat tmp dir
    51  	err := os.MkdirAll(config.GetTmpDir(), os.ModePerm)
    52  	if err != nil {
    53  		panic(fmt.Errorf("create tmp dir failed: %v", err))
    54  	}
    55  	// make db set
    56  	server.dbSet = make([]*atomic.Value, config.Properties.Databases)
    57  	for i := range server.dbSet {
    58  		singleDB := makeDB()
    59  		singleDB.index = i
    60  		holder := &atomic.Value{}
    61  		holder.Store(singleDB)
    62  		server.dbSet[i] = holder
    63  	}
    64  	server.hub = pubsub.MakeHub()
    65  	// record aof
    66  	validAof := false
    67  	if config.Properties.AppendOnly {
    68  		validAof = fileExists(config.Properties.AppendFilename)
    69  		aofHandler, err := NewPersister(server,
    70  			config.Properties.AppendFilename, true, config.Properties.AppendFsync)
    71  		if err != nil {
    72  			panic(err)
    73  		}
    74  		server.bindPersister(aofHandler)
    75  	}
    76  	if config.Properties.RDBFilename != "" && !validAof {
    77  		// load rdb
    78  		err := server.loadRdbFile()
    79  		if err != nil {
    80  			logger.Error(err)
    81  		}
    82  	}
    83  	server.slaveStatus = initReplSlaveStatus()
    84  	server.initMaster()
    85  	server.startReplCron()
    86  	server.role = masterRole // The initialization process does not require atomicity
    87  	return server
    88  }
    89  
    90  // Exec executes command
    91  // parameter `cmdLine` contains command and its arguments, for example: "set key value"
    92  func (server *Server) Exec(c redis.Connection, cmdLine [][]byte) (result redis.Reply) {
    93  	defer func() {
    94  		if err := recover(); err != nil {
    95  			logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
    96  			result = &protocol.UnknownErrReply{}
    97  		}
    98  	}()
    99  
   100  	cmdName := strings.ToLower(string(cmdLine[0]))
   101  	// ping
   102  	if cmdName == "ping" {
   103  		return Ping(c, cmdLine[1:])
   104  	}
   105  	// authenticate
   106  	if cmdName == "auth" {
   107  		return Auth(c, cmdLine[1:])
   108  	}
   109  	if !isAuthenticated(c) {
   110  		return protocol.MakeErrReply("NOAUTH Authentication required")
   111  	}
   112  	// info
   113  	if cmdName == "info" {
   114  		return Info(server, cmdLine[1:])
   115  	}
   116  	if cmdName == "slaveof" {
   117  		if c != nil && c.InMultiState() {
   118  			return protocol.MakeErrReply("cannot use slave of database within multi")
   119  		}
   120  		if len(cmdLine) != 3 {
   121  			return protocol.MakeArgNumErrReply("SLAVEOF")
   122  		}
   123  		return server.execSlaveOf(c, cmdLine[1:])
   124  	} else if cmdName == "command" {
   125  		return execCommand(cmdLine[1:])
   126  	}
   127  
   128  	// read only slave
   129  	role := atomic.LoadInt32(&server.role)
   130  	if role == slaveRole && !c.IsMaster() {
   131  		// only allow read only command, forbid all special commands except `auth` and `slaveof`
   132  		if !isReadOnlyCommand(cmdName) {
   133  			return protocol.MakeErrReply("READONLY You can't write against a read only slave.")
   134  		}
   135  	}
   136  
   137  	// special commands which cannot execute within transaction
   138  	if cmdName == "subscribe" {
   139  		if len(cmdLine) < 2 {
   140  			return protocol.MakeArgNumErrReply("subscribe")
   141  		}
   142  		return pubsub.Subscribe(server.hub, c, cmdLine[1:])
   143  	} else if cmdName == "publish" {
   144  		return pubsub.Publish(server.hub, cmdLine[1:])
   145  	} else if cmdName == "unsubscribe" {
   146  		return pubsub.UnSubscribe(server.hub, c, cmdLine[1:])
   147  	} else if cmdName == "bgrewriteaof" {
   148  		if !config.Properties.AppendOnly {
   149  			return protocol.MakeErrReply("AppendOnly is false, you can't rewrite aof file")
   150  		}
   151  		// aof.go imports router.go, router.go cannot import BGRewriteAOF from aof.go
   152  		return BGRewriteAOF(server, cmdLine[1:])
   153  	} else if cmdName == "rewriteaof" {
   154  		if !config.Properties.AppendOnly {
   155  			return protocol.MakeErrReply("AppendOnly is false, you can't rewrite aof file")
   156  		}
   157  		return RewriteAOF(server, cmdLine[1:])
   158  	} else if cmdName == "flushall" {
   159  		return server.flushAll()
   160  	} else if cmdName == "flushdb" {
   161  		if !validateArity(1, cmdLine) {
   162  			return protocol.MakeArgNumErrReply(cmdName)
   163  		}
   164  		if c.InMultiState() {
   165  			return protocol.MakeErrReply("ERR command 'FlushDB' cannot be used in MULTI")
   166  		}
   167  		return server.execFlushDB(c.GetDBIndex())
   168  	} else if cmdName == "save" {
   169  		return SaveRDB(server, cmdLine[1:])
   170  	} else if cmdName == "bgsave" {
   171  		return BGSaveRDB(server, cmdLine[1:])
   172  	} else if cmdName == "select" {
   173  		if c != nil && c.InMultiState() {
   174  			return protocol.MakeErrReply("cannot select database within multi")
   175  		}
   176  		if len(cmdLine) != 2 {
   177  			return protocol.MakeArgNumErrReply("select")
   178  		}
   179  		return execSelect(c, server, cmdLine[1:])
   180  	} else if cmdName == "copy" {
   181  		if len(cmdLine) < 3 {
   182  			return protocol.MakeArgNumErrReply("copy")
   183  		}
   184  		return execCopy(server, c, cmdLine[1:])
   185  	} else if cmdName == "replconf" {
   186  		return server.execReplConf(c, cmdLine[1:])
   187  	} else if cmdName == "psync" {
   188  		return server.execPSync(c, cmdLine[1:])
   189  	}
   190  	// todo: support multi database transaction
   191  
   192  	// normal commands
   193  	dbIndex := c.GetDBIndex()
   194  	selectedDB, errReply := server.selectDB(dbIndex)
   195  	if errReply != nil {
   196  		return errReply
   197  	}
   198  	return selectedDB.Exec(c, cmdLine)
   199  }
   200  
   201  // AfterClientClose does some clean after client close connection
   202  func (server *Server) AfterClientClose(c redis.Connection) {
   203  	pubsub.UnsubscribeAll(server.hub, c)
   204  }
   205  
   206  // Close graceful shutdown database
   207  func (server *Server) Close() {
   208  	// stop slaveStatus first
   209  	server.slaveStatus.close()
   210  	if server.persister != nil {
   211  		server.persister.Close()
   212  	}
   213  	server.stopMaster()
   214  }
   215  
   216  func execSelect(c redis.Connection, mdb *Server, args [][]byte) redis.Reply {
   217  	dbIndex, err := strconv.Atoi(string(args[0]))
   218  	if err != nil {
   219  		return protocol.MakeErrReply("ERR invalid DB index")
   220  	}
   221  	if dbIndex >= len(mdb.dbSet) || dbIndex < 0 {
   222  		return protocol.MakeErrReply("ERR DB index is out of range")
   223  	}
   224  	c.SelectDB(dbIndex)
   225  	return protocol.MakeOkReply()
   226  }
   227  
   228  func (server *Server) execFlushDB(dbIndex int) redis.Reply {
   229  	if server.persister != nil {
   230  		server.persister.SaveCmdLine(dbIndex, utils.ToCmdLine("FlushDB"))
   231  	}
   232  	return server.flushDB(dbIndex)
   233  }
   234  
   235  // flushDB flushes the selected database
   236  func (server *Server) flushDB(dbIndex int) redis.Reply {
   237  	if dbIndex >= len(server.dbSet) || dbIndex < 0 {
   238  		return protocol.MakeErrReply("ERR DB index is out of range")
   239  	}
   240  	newDB := makeDB()
   241  	server.loadDB(dbIndex, newDB)
   242  	return &protocol.OkReply{}
   243  }
   244  
   245  func (server *Server) loadDB(dbIndex int, newDB *DB) redis.Reply {
   246  	if dbIndex >= len(server.dbSet) || dbIndex < 0 {
   247  		return protocol.MakeErrReply("ERR DB index is out of range")
   248  	}
   249  	oldDB := server.mustSelectDB(dbIndex)
   250  	newDB.index = dbIndex
   251  	newDB.addAof = oldDB.addAof // inherit oldDB
   252  	server.dbSet[dbIndex].Store(newDB)
   253  	return &protocol.OkReply{}
   254  }
   255  
   256  // flushAll flushes all databases.
   257  func (server *Server) flushAll() redis.Reply {
   258  	for i := range server.dbSet {
   259  		server.flushDB(i)
   260  	}
   261  	if server.persister != nil {
   262  		server.persister.SaveCmdLine(0, utils.ToCmdLine("FlushAll"))
   263  	}
   264  	return &protocol.OkReply{}
   265  }
   266  
   267  // selectDB returns the database with the given index, or an error if the index is out of range.
   268  func (server *Server) selectDB(dbIndex int) (*DB, *protocol.StandardErrReply) {
   269  	if dbIndex >= len(server.dbSet) || dbIndex < 0 {
   270  		return nil, protocol.MakeErrReply("ERR DB index is out of range")
   271  	}
   272  	return server.dbSet[dbIndex].Load().(*DB), nil
   273  }
   274  
   275  // mustSelectDB is like selectDB, but panics if an error occurs.
   276  func (server *Server) mustSelectDB(dbIndex int) *DB {
   277  	selectedDB, err := server.selectDB(dbIndex)
   278  	if err != nil {
   279  		panic(err)
   280  	}
   281  	return selectedDB
   282  }
   283  
   284  // ForEach traverses all the keys in the given database
   285  func (server *Server) ForEach(dbIndex int, cb func(key string, data *database.DataEntity, expiration *time.Time) bool) {
   286  	server.mustSelectDB(dbIndex).ForEach(cb)
   287  }
   288  
   289  // ExecMulti executes multi commands transaction Atomically and Isolated
   290  func (server *Server) ExecMulti(conn redis.Connection, watching map[string]uint32, cmdLines []CmdLine) redis.Reply {
   291  	selectedDB, errReply := server.selectDB(conn.GetDBIndex())
   292  	if errReply != nil {
   293  		return errReply
   294  	}
   295  	return selectedDB.ExecMulti(conn, watching, cmdLines)
   296  }
   297  
   298  // RWLocks lock keys for writing and reading
   299  func (server *Server) RWLocks(dbIndex int, writeKeys []string, readKeys []string) {
   300  	server.mustSelectDB(dbIndex).RWLocks(writeKeys, readKeys)
   301  }
   302  
   303  // RWUnLocks unlock keys for writing and reading
   304  func (server *Server) RWUnLocks(dbIndex int, writeKeys []string, readKeys []string) {
   305  	server.mustSelectDB(dbIndex).RWUnLocks(writeKeys, readKeys)
   306  }
   307  
   308  // GetUndoLogs return rollback commands
   309  func (server *Server) GetUndoLogs(dbIndex int, cmdLine [][]byte) []CmdLine {
   310  	return server.mustSelectDB(dbIndex).GetUndoLogs(cmdLine)
   311  }
   312  
   313  // ExecWithLock executes normal commands, invoker should provide locks
   314  func (server *Server) ExecWithLock(conn redis.Connection, cmdLine [][]byte) redis.Reply {
   315  	db, errReply := server.selectDB(conn.GetDBIndex())
   316  	if errReply != nil {
   317  		return errReply
   318  	}
   319  	return db.execWithLock(cmdLine)
   320  }
   321  
   322  // BGRewriteAOF asynchronously rewrites Append-Only-File
   323  func BGRewriteAOF(db *Server, args [][]byte) redis.Reply {
   324  	go db.persister.Rewrite()
   325  	return protocol.MakeStatusReply("Background append only file rewriting started")
   326  }
   327  
   328  // RewriteAOF start Append-Only-File rewriting and blocked until it finished
   329  func RewriteAOF(db *Server, args [][]byte) redis.Reply {
   330  	err := db.persister.Rewrite()
   331  	if err != nil {
   332  		return protocol.MakeErrReply(err.Error())
   333  	}
   334  	return protocol.MakeOkReply()
   335  }
   336  
   337  // SaveRDB start RDB writing and blocked until it finished
   338  func SaveRDB(db *Server, args [][]byte) redis.Reply {
   339  	if db.persister == nil {
   340  		return protocol.MakeErrReply("please enable aof before using save")
   341  	}
   342  	rdbFilename := config.Properties.RDBFilename
   343  	if rdbFilename == "" {
   344  		rdbFilename = "dump.rdb"
   345  	}
   346  	err := db.persister.GenerateRDB(rdbFilename)
   347  	if err != nil {
   348  		return protocol.MakeErrReply(err.Error())
   349  	}
   350  	return protocol.MakeOkReply()
   351  }
   352  
   353  // BGSaveRDB asynchronously save RDB
   354  func BGSaveRDB(db *Server, args [][]byte) redis.Reply {
   355  	if db.persister == nil {
   356  		return protocol.MakeErrReply("please enable aof before using save")
   357  	}
   358  	go func() {
   359  		defer func() {
   360  			if err := recover(); err != nil {
   361  				logger.Error(err)
   362  			}
   363  		}()
   364  		rdbFilename := config.Properties.RDBFilename
   365  		if rdbFilename == "" {
   366  			rdbFilename = "dump.rdb"
   367  		}
   368  		err := db.persister.GenerateRDB(rdbFilename)
   369  		if err != nil {
   370  			logger.Error(err)
   371  		}
   372  	}()
   373  	return protocol.MakeStatusReply("Background saving started")
   374  }
   375  
   376  // GetDBSize returns keys count and ttl key count
   377  func (server *Server) GetDBSize(dbIndex int) (int, int) {
   378  	db := server.mustSelectDB(dbIndex)
   379  	return db.data.Len(), db.ttlMap.Len()
   380  }
   381  
   382  func (server *Server) startReplCron() {
   383  	go func(mdb *Server) {
   384  		ticker := time.Tick(time.Second * 10)
   385  		for range ticker {
   386  			mdb.slaveCron()
   387  			mdb.masterCron()
   388  		}
   389  	}(server)
   390  }
   391  
   392  // GetAvgTTL Calculate the average expiration time of keys
   393  func (server *Server) GetAvgTTL(dbIndex, randomKeyCount int) int64 {
   394  	var ttlCount int64
   395  	db := server.mustSelectDB(dbIndex)
   396  	keys := db.data.RandomKeys(randomKeyCount)
   397  	for _, k := range keys {
   398  		t := time.Now()
   399  		rawExpireTime, ok := db.ttlMap.Get(k)
   400  		if !ok {
   401  			continue
   402  		}
   403  		expireTime, _ := rawExpireTime.(time.Time)
   404  		// if the key has already reached its expiration time during calculation, ignore it
   405  		if expireTime.Sub(t).Microseconds() > 0 {
   406  			ttlCount += expireTime.Sub(t).Microseconds()
   407  		}
   408  	}
   409  	return ttlCount / int64(len(keys))
   410  }