add readonly support

This commit is contained in:
siddontang 2014-10-10 09:49:16 +08:00
parent 848fbf34ad
commit 855f0a3477
16 changed files with 58 additions and 45 deletions

View File

@ -1,4 +1,4 @@
//This file was generated by .tools/generate_commands.py on Wed Oct 08 2014 16:36:20 +0800
//This file was generated by .tools/generate_commands.py on Fri Oct 10 2014 09:08:54 +0800
package main
var helpCommands = [][]string{
@ -87,7 +87,7 @@ var helpCommands = [][]string{
{"SINTER", "key [key ...]", "Set"},
{"SINTERSTORE", "destination key [key ...]", "Set"},
{"SISMEMBER", "key member", "Set"},
{"SLAVEOF", "host port [restart]", "Replication"},
{"SLAVEOF", "host port [RESTART] [READONLY]", "Replication"},
{"SMCLEAR", "key [key ...]", "Set"},
{"SMEMBERS", "key", "Set"},
{"SPERSIST", "key", "Set"},

View File

@ -18,6 +18,8 @@ var configFile = flag.String("config", "", "ledisdb config file")
var dbName = flag.String("db_name", "", "select a db to use, it will overwrite the config's db name")
var usePprof = flag.Bool("pprof", false, "enable pprof")
var pprofPort = flag.Int("pprof_port", 6060, "pprof http port")
var slaveof = flag.String("slaveof", "", "make the server a slave of another instance")
var readonly = flag.Bool("readonly", false, "set readonly mode, salve server is always readonly")
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
@ -43,6 +45,13 @@ func main() {
cfg.DBName = *dbName
}
if len(*slaveof) > 0 {
cfg.SlaveOf = *slaveof
cfg.Readonly = true
} else {
cfg.Readonly = *readonly
}
var app *server.App
app, err = server.NewApp(cfg)
if err != nil {

View File

@ -54,6 +54,8 @@ type Config struct {
SlaveOf string `toml:"slaveof"`
Readonly bool `toml:readonly`
DataDir string `toml:"data_dir"`
DBName string `toml:"db_name"`
@ -106,6 +108,7 @@ func NewConfigDefault() *Config {
cfg.DBName = DefaultDBName
cfg.SlaveOf = ""
cfg.Readonly = false
// disable access log
cfg.AccessLog = ""

View File

@ -16,6 +16,10 @@ access_log = ""
# Any write operations except flushall and replication will be disabled in slave mode.
slaveof = ""
# Readonly mode, slave server is always readonly even readonly = false
# for readonly mode, only replication and flushall can write
readonly = false
# Choose which backend storage to use, now support:
#
# leveldb

View File

@ -301,7 +301,7 @@
"readonly": false
},
"SLAVEOF": {
"arguments": "host port [restart]",
"arguments": "host port [RESTART] [READONLY]",
"group": "Replication",
"readonly": false
},

View File

@ -122,7 +122,7 @@ Table of Contents
- [BPERSIST key](#bpersist-key)
- [BXSCAN key [MATCH match] [COUNT count]](#bxscan-key-match-match-count-count)
- [Replication](#replication)
- [SLAVEOF host port [restart]](#slaveof-host-port-restart)
- [SLAVEOF host port [RESTART] [READONLY]](#slaveof-host-port-restart-readonly)
- [FULLSYNC](#fullsync)
- [SYNC logid](#sync-logid)
- [Server](#server)
@ -2466,13 +2466,15 @@ See [XSCAN](#xscan-key-match-match-count-count) for more information.
## Replication
### SLAVEOF host port [restart]
### SLAVEOF host port [RESTART] [READONLY]
Changes the replication settings of a slave on the fly. If the server is already acting as slave, SLAVEOF NO ONE will turn off the replication.
Changes the replication settings of a slave on the fly. If the server is already acting as slave, `SLAVEOF NO ONE` will turn off the replication and turn the server into master. `SLAVEOF NO ONE READONLY` will turn the server into master with readonly mode.
SLAVEOF host port will make the server a slave of another server listening at the specified host and port.
If the server is already master, `SLAVEOF NO ONE READONLY` will force the server to readonly mode, and `SLAVEOF NO ONE` will disable readonly.
If a server is already a slave of a master, SLAVEOF host port will stop the replication against the old and start the synchronization against the new one, if restart is set, it will discard the old dataset, otherwise it will sync with LastLogID + 1.
`SLAVEOF host port` will make the server a slave of another server listening at the specified host and port.
If a server is already a slave of a master, `SLAVEOF host port` will stop the replication against the old and start the synchronization against the new one, if RESTART is set, it will discard the old dataset, otherwise it will sync with LastLogID + 1.
### FULLSYNC

View File

@ -16,6 +16,10 @@ access_log = ""
# Any write operations except flushall and replication will be disabled in slave mode.
slaveof = ""
# Readonly mode, slave server is always readonly even readonly = false
# for readonly mode, only replication and flushall can write
readonly = false
# Choose which backend storage to use, now support:
#
# leveldb

View File

@ -46,11 +46,6 @@ var (
}
)
const (
RDWRMode = 0
ROnlyMode = 1
)
const (
defaultScanCount int = 10
)

View File

@ -33,17 +33,10 @@ type Ledis struct {
wLock sync.RWMutex //allow one write at same time
commitLock sync.Mutex //allow one write commit at same time
// for readonly mode, only replication and flushall can write
readOnly bool
lock io.Closer
}
func Open(cfg *config.Config) (*Ledis, error) {
return Open2(cfg, RDWRMode)
}
func Open2(cfg *config.Config, flags int) (*Ledis, error) {
if len(cfg.DataDir) == 0 {
cfg.DataDir = config.DefaultDataDir
}
@ -53,13 +46,12 @@ func Open2(cfg *config.Config, flags int) (*Ledis, error) {
var err error
l := new(Ledis)
l.cfg = cfg
if l.lock, err = filelock.Lock(path.Join(cfg.DataDir, "LOCK")); err != nil {
return nil, err
}
l.readOnly = (flags&ROnlyMode > 0)
l.quit = make(chan struct{})
if l.ldb, err = store.Open(cfg); err != nil {
@ -163,7 +155,7 @@ func (l *Ledis) flushAll() error {
}
func (l *Ledis) IsReadOnly() bool {
if l.readOnly {
if l.cfg.Readonly {
return true
} else if l.r != nil {
if b, _ := l.r.CommitIDBehind(); b {
@ -173,10 +165,6 @@ func (l *Ledis) IsReadOnly() bool {
return false
}
func (l *Ledis) SetReadOnly(b bool) {
l.readOnly = b
}
func (l *Ledis) onDataExpired() {
defer l.wg.Done()

View File

@ -110,7 +110,7 @@ func (l *Ledis) WaitReplication() error {
func (l *Ledis) StoreLogsFromReader(rb io.Reader) error {
if !l.ReplicationUsed() {
return ErrRplNotSupport
} else if !l.readOnly {
} else if !l.cfg.Readonly {
return ErrRplInRDWR
}

View File

@ -46,10 +46,11 @@ func TestReplication(t *testing.T) {
cfgS := new(config.Config)
cfgS.DataDir = "/tmp/test_repl/slave"
cfgS.UseReplication = true
cfgS.Readonly = true
os.RemoveAll(cfgS.DataDir)
slave, err = Open2(cfgS, ROnlyMode)
slave, err = Open(cfgS)
if err != nil {
t.Fatal(err)
}

View File

@ -88,13 +88,12 @@ func NewApp(cfg *config.Config) (*App, error) {
}
}
flag := ledis.RDWRMode
if len(app.cfg.SlaveOf) > 0 {
//slave must readonly
flag = ledis.ROnlyMode
app.cfg.Readonly = true
}
if app.ldb, err = ledis.Open2(cfg, flag); err != nil {
if app.ldb, err = ledis.Open(cfg); err != nil {
return nil, err
}
@ -135,7 +134,7 @@ func (app *App) Close() {
func (app *App) Run() {
if len(app.cfg.SlaveOf) > 0 {
app.slaveof(app.cfg.SlaveOf, false)
app.slaveof(app.cfg.SlaveOf, false, app.cfg.Readonly)
}
go app.httpServe()

View File

@ -13,18 +13,19 @@ import (
func slaveofCommand(c *client) error {
args := c.args
if len(args) != 2 || len(args) != 3 {
if len(args) != 2 && len(args) != 3 {
return ErrCmdParams
}
masterAddr := ""
restart := false
readonly := false
if strings.ToLower(hack.String(args[0])) == "no" &&
strings.ToLower(hack.String(args[1])) == "one" {
//stop replication, use master = ""
if len(args) != 2 {
return ErrCmdParams
if len(args) == 3 && strings.ToLower(hack.String(args[2])) == "readonly" {
readonly = true
}
} else {
if _, err := strconv.ParseInt(hack.String(args[1]), 10, 16); err != nil {
@ -38,7 +39,7 @@ func slaveofCommand(c *client) error {
}
}
if err := c.app.slaveof(masterAddr, restart); err != nil {
if err := c.app.slaveof(masterAddr, restart, readonly); err != nil {
return err
}

View File

@ -96,7 +96,7 @@ func TestReplication(t *testing.T) {
t.Fatal(err)
}
slave.slaveof("", false)
slave.slaveof("", false, false)
db.Set([]byte("a2"), value)
db.Set([]byte("b2"), value)
@ -112,7 +112,7 @@ func TestReplication(t *testing.T) {
t.Fatal("must error")
}
slave.slaveof(masterCfg.Addr, false)
slave.slaveof(masterCfg.Addr, false, false)
time.Sleep(1 * time.Second)

View File

@ -115,7 +115,8 @@ func (i *info) dumpServer(buf *bytes.Buffer) {
i.dumpPairs(buf, infoPair{"os", i.Server.OS},
infoPair{"process_id", i.Server.ProceessId},
infoPair{"addr", i.app.cfg.Addr},
infoPair{"http_addr", i.app.cfg.HttpAddr})
infoPair{"http_addr", i.app.cfg.HttpAddr},
infoPair{"readonly", i.app.cfg.Readonly})
}
func (i *info) dumpClients(buf *bytes.Buffer) {
@ -155,10 +156,10 @@ func (i *info) dumpReplication(buf *bytes.Buffer) {
slaves = append(slaves, s.remoteAddr)
}
p = append(p, infoPair{"readonly", i.app.ldb.IsReadOnly()})
p = append(p, infoPair{"slaveof", i.app.cfg.SlaveOf})
if len(slaves) > 0 {
p = append(p, infoPair{"slave", strings.Join(slaves, ",")})
p = append(p, infoPair{"slaves", strings.Join(slaves, ",")})
}
if s, _ := i.app.ldb.ReplicationStat(); s != nil {

View File

@ -93,7 +93,7 @@ func (m *master) startReplication(masterAddr string, restart bool) error {
m.quit = make(chan struct{}, 1)
m.app.ldb.SetReadOnly(true)
m.app.cfg.Readonly = true
m.wg.Add(1)
go m.runReplication(restart)
@ -238,10 +238,16 @@ func (m *master) sync() error {
}
func (app *App) slaveof(masterAddr string, restart bool) error {
func (app *App) slaveof(masterAddr string, restart bool, readonly bool) error {
app.m.Lock()
defer app.m.Unlock()
//in master mode and no slaveof, only set readonly
if len(app.cfg.SlaveOf) == 0 && len(masterAddr) == 0 {
app.cfg.Readonly = readonly
return nil
}
if !app.ldb.ReplicationUsed() {
return fmt.Errorf("slaveof must enable replication")
}
@ -253,7 +259,7 @@ func (app *App) slaveof(masterAddr string, restart bool) error {
return err
}
app.ldb.SetReadOnly(false)
app.cfg.Readonly = readonly
} else {
return app.m.startReplication(masterAddr, restart)
}