From 855f0a3477a7a92caecabb770d40e72474029114 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 10 Oct 2014 09:49:16 +0800 Subject: [PATCH] add readonly support --- cmd/ledis-cli/const.go | 4 ++-- cmd/ledis-server/main.go | 9 +++++++++ config/config.go | 3 +++ config/config.toml | 4 ++++ doc/commands.json | 2 +- doc/commands.md | 12 +++++++----- etc/ledis.conf | 4 ++++ ledis/const.go | 5 ----- ledis/ledis.go | 16 ++-------------- ledis/replication.go | 2 +- ledis/replication_test.go | 3 ++- server/app.go | 7 +++---- server/cmd_replication.go | 9 +++++---- server/cmd_replication_test.go | 4 ++-- server/info.go | 7 ++++--- server/replication.go | 12 +++++++++--- 16 files changed, 58 insertions(+), 45 deletions(-) diff --git a/cmd/ledis-cli/const.go b/cmd/ledis-cli/const.go index f03b249..19c5ee9 100644 --- a/cmd/ledis-cli/const.go +++ b/cmd/ledis-cli/const.go @@ -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"}, diff --git a/cmd/ledis-server/main.go b/cmd/ledis-server/main.go index 448120c..c03bb84 100644 --- a/cmd/ledis-server/main.go +++ b/cmd/ledis-server/main.go @@ -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 { diff --git a/config/config.go b/config/config.go index 86e54b4..589a149 100644 --- a/config/config.go +++ b/config/config.go @@ -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 = "" diff --git a/config/config.toml b/config/config.toml index 0889933..1996303 100644 --- a/config/config.toml +++ b/config/config.toml @@ -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 diff --git a/doc/commands.json b/doc/commands.json index 8268ee8..7f354c9 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -301,7 +301,7 @@ "readonly": false }, "SLAVEOF": { - "arguments": "host port [restart]", + "arguments": "host port [RESTART] [READONLY]", "group": "Replication", "readonly": false }, diff --git a/doc/commands.md b/doc/commands.md index c01b67c..8337992 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -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 diff --git a/etc/ledis.conf b/etc/ledis.conf index 0889933..1996303 100644 --- a/etc/ledis.conf +++ b/etc/ledis.conf @@ -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 diff --git a/ledis/const.go b/ledis/const.go index 3b30123..3e17a95 100644 --- a/ledis/const.go +++ b/ledis/const.go @@ -46,11 +46,6 @@ var ( } ) -const ( - RDWRMode = 0 - ROnlyMode = 1 -) - const ( defaultScanCount int = 10 ) diff --git a/ledis/ledis.go b/ledis/ledis.go index 8893eee..75caedd 100644 --- a/ledis/ledis.go +++ b/ledis/ledis.go @@ -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() diff --git a/ledis/replication.go b/ledis/replication.go index b68a990..33a5e12 100644 --- a/ledis/replication.go +++ b/ledis/replication.go @@ -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 } diff --git a/ledis/replication_test.go b/ledis/replication_test.go index c300ef8..920ed63 100644 --- a/ledis/replication_test.go +++ b/ledis/replication_test.go @@ -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) } diff --git a/server/app.go b/server/app.go index dbf12e5..0a253ac 100644 --- a/server/app.go +++ b/server/app.go @@ -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() diff --git a/server/cmd_replication.go b/server/cmd_replication.go index aa6ede4..128e4af 100644 --- a/server/cmd_replication.go +++ b/server/cmd_replication.go @@ -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 } diff --git a/server/cmd_replication_test.go b/server/cmd_replication_test.go index 76bf2c2..1dfde6b 100644 --- a/server/cmd_replication_test.go +++ b/server/cmd_replication_test.go @@ -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) diff --git a/server/info.go b/server/info.go index b60db65..2babae8 100644 --- a/server/info.go +++ b/server/info.go @@ -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 { diff --git a/server/replication.go b/server/replication.go index 1f12b93..68f2bc9 100644 --- a/server/replication.go +++ b/server/replication.go @@ -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) }