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 }