mirror of https://github.com/ledisdb/ledisdb.git
Use key lock when migration
https://github.com/siddontang/ledisdb/issues/141 Now migration is only safe for xcodis, but I think it does not matter.
This commit is contained in:
parent
acabb99368
commit
c6c82b979d
|
@ -46,6 +46,7 @@ type App struct {
|
||||||
|
|
||||||
migrateM sync.Mutex
|
migrateM sync.Mutex
|
||||||
migrateClients map[string]*goledis.Client
|
migrateClients map[string]*goledis.Client
|
||||||
|
migrateKeyLockers map[string]*migrateKeyLocker
|
||||||
}
|
}
|
||||||
|
|
||||||
func netType(s string) string {
|
func netType(s string) string {
|
||||||
|
@ -76,6 +77,7 @@ func NewApp(cfg *config.Config) (*App, error) {
|
||||||
app.rcs = make(map[*respClient]struct{})
|
app.rcs = make(map[*respClient]struct{})
|
||||||
|
|
||||||
app.migrateClients = make(map[string]*goledis.Client)
|
app.migrateClients = make(map[string]*goledis.Client)
|
||||||
|
app.newMigrateKeyLockers()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,13 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/siddontang/go/hack"
|
||||||
goledis "github.com/siddontang/ledisdb/client/goledis"
|
goledis "github.com/siddontang/ledisdb/client/goledis"
|
||||||
"github.com/siddontang/ledisdb/ledis"
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func dumpCommand(c *client) error {
|
func dumpCommand(c *client) error {
|
||||||
|
@ -110,15 +113,15 @@ func xdump(db *ledis.DB, tp string, key []byte) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
var data []byte
|
var data []byte
|
||||||
switch strings.ToUpper(tp) {
|
switch strings.ToUpper(tp) {
|
||||||
case "KV":
|
case KVName:
|
||||||
data, err = db.Dump(key)
|
data, err = db.Dump(key)
|
||||||
case "HASH":
|
case HashName:
|
||||||
data, err = db.HDump(key)
|
data, err = db.HDump(key)
|
||||||
case "LIST":
|
case ListName:
|
||||||
data, err = db.LDump(key)
|
data, err = db.LDump(key)
|
||||||
case "SET":
|
case SetName:
|
||||||
data, err = db.SDump(key)
|
data, err = db.SDump(key)
|
||||||
case "ZSET":
|
case ZSetName:
|
||||||
data, err = db.ZDump(key)
|
data, err = db.ZDump(key)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("invalid key type %s", tp)
|
err = fmt.Errorf("invalid key type %s", tp)
|
||||||
|
@ -129,15 +132,15 @@ func xdump(db *ledis.DB, tp string, key []byte) ([]byte, error) {
|
||||||
func xdel(db *ledis.DB, tp string, key []byte) error {
|
func xdel(db *ledis.DB, tp string, key []byte) error {
|
||||||
var err error
|
var err error
|
||||||
switch strings.ToUpper(tp) {
|
switch strings.ToUpper(tp) {
|
||||||
case "KV":
|
case KVName:
|
||||||
_, err = db.Del(key)
|
_, err = db.Del(key)
|
||||||
case "HASH":
|
case HashName:
|
||||||
_, err = db.HClear(key)
|
_, err = db.HClear(key)
|
||||||
case "LIST":
|
case ListName:
|
||||||
_, err = db.LClear(key)
|
_, err = db.LClear(key)
|
||||||
case "SET":
|
case SetName:
|
||||||
_, err = db.SClear(key)
|
_, err = db.SClear(key)
|
||||||
case "ZSET":
|
case ZSetName:
|
||||||
_, err = db.ZClear(key)
|
_, err = db.ZClear(key)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("invalid key type %s", tp)
|
err = fmt.Errorf("invalid key type %s", tp)
|
||||||
|
@ -147,15 +150,15 @@ func xdel(db *ledis.DB, tp string, key []byte) error {
|
||||||
|
|
||||||
func xttl(db *ledis.DB, tp string, key []byte) (int64, error) {
|
func xttl(db *ledis.DB, tp string, key []byte) (int64, error) {
|
||||||
switch strings.ToUpper(tp) {
|
switch strings.ToUpper(tp) {
|
||||||
case "KV":
|
case KVName:
|
||||||
return db.TTL(key)
|
return db.TTL(key)
|
||||||
case "HASH":
|
case HashName:
|
||||||
return db.HTTL(key)
|
return db.HTTL(key)
|
||||||
case "LIST":
|
case ListName:
|
||||||
return db.LTTL(key)
|
return db.LTTL(key)
|
||||||
case "SET":
|
case SetName:
|
||||||
return db.STTL(key)
|
return db.STTL(key)
|
||||||
case "ZSET":
|
case ZSetName:
|
||||||
return db.ZTTL(key)
|
return db.ZTTL(key)
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("invalid key type %s", tp)
|
return 0, fmt.Errorf("invalid key type %s", tp)
|
||||||
|
@ -164,15 +167,15 @@ func xttl(db *ledis.DB, tp string, key []byte) (int64, error) {
|
||||||
|
|
||||||
func xscan(db *ledis.DB, tp string, count int) ([][]byte, error) {
|
func xscan(db *ledis.DB, tp string, count int) ([][]byte, error) {
|
||||||
switch strings.ToUpper(tp) {
|
switch strings.ToUpper(tp) {
|
||||||
case "KV":
|
case KVName:
|
||||||
return db.Scan(KV, nil, count, false, "")
|
return db.Scan(KV, nil, count, false, "")
|
||||||
case "HASH":
|
case HashName:
|
||||||
return db.Scan(HASH, nil, count, false, "")
|
return db.Scan(HASH, nil, count, false, "")
|
||||||
case "LIST":
|
case ListName:
|
||||||
return db.Scan(LIST, nil, count, false, "")
|
return db.Scan(LIST, nil, count, false, "")
|
||||||
case "SET":
|
case SetName:
|
||||||
return db.Scan(SET, nil, count, false, "")
|
return db.Scan(SET, nil, count, false, "")
|
||||||
case "ZSET":
|
case ZSetName:
|
||||||
return db.Scan(ZSET, nil, count, false, "")
|
return db.Scan(ZSET, nil, count, false, "")
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid key type %s", tp)
|
return nil, fmt.Errorf("invalid key type %s", tp)
|
||||||
|
@ -185,7 +188,7 @@ func xdumpCommand(c *client) error {
|
||||||
return ErrCmdParams
|
return ErrCmdParams
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := string(args[0])
|
tp := strings.ToUpper(string(args[0]))
|
||||||
key := args[1]
|
key := args[1]
|
||||||
|
|
||||||
if data, err := xdump(c.db, tp, key); err != nil {
|
if data, err := xdump(c.db, tp, key); err != nil {
|
||||||
|
@ -211,6 +214,68 @@ func (app *App) getMigrateClient(addr string) *goledis.Client {
|
||||||
return mc
|
return mc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type migrateKeyLocker struct {
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
locks map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *migrateKeyLocker) Lock(key []byte) bool {
|
||||||
|
m.m.Lock()
|
||||||
|
defer m.m.Unlock()
|
||||||
|
|
||||||
|
k := hack.String(key)
|
||||||
|
_, ok := m.locks[k]
|
||||||
|
if ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m.locks[k] = struct{}{}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *migrateKeyLocker) Unlock(key []byte) {
|
||||||
|
m.m.Lock()
|
||||||
|
defer m.m.Unlock()
|
||||||
|
|
||||||
|
delete(m.locks, hack.String(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMigrateKeyLocker() *migrateKeyLocker {
|
||||||
|
m := new(migrateKeyLocker)
|
||||||
|
|
||||||
|
m.locks = make(map[string]struct{})
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) newMigrateKeyLockers() {
|
||||||
|
a.migrateKeyLockers = make(map[string]*migrateKeyLocker)
|
||||||
|
|
||||||
|
a.migrateKeyLockers[KVName] = newMigrateKeyLocker()
|
||||||
|
a.migrateKeyLockers[HashName] = newMigrateKeyLocker()
|
||||||
|
a.migrateKeyLockers[ListName] = newMigrateKeyLocker()
|
||||||
|
a.migrateKeyLockers[SetName] = newMigrateKeyLocker()
|
||||||
|
a.migrateKeyLockers[ZSetName] = newMigrateKeyLocker()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) migrateKeyLock(tp string, key []byte) bool {
|
||||||
|
l, ok := a.migrateKeyLockers[strings.ToUpper(tp)]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.Lock(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) migrateKeyUnlock(tp string, key []byte) {
|
||||||
|
l, ok := a.migrateKeyLockers[strings.ToUpper(tp)]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Unlock(key)
|
||||||
|
}
|
||||||
|
|
||||||
//XMIGRATEDB host port tp count db timeout
|
//XMIGRATEDB host port tp count db timeout
|
||||||
//select count tp type keys and migrate
|
//select count tp type keys and migrate
|
||||||
//will block any other write operations
|
//will block any other write operations
|
||||||
|
@ -227,7 +292,7 @@ func xmigratedbCommand(c *client) error {
|
||||||
return fmt.Errorf("migrate in same server is not allowed")
|
return fmt.Errorf("migrate in same server is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := string(args[2])
|
tp := strings.ToUpper(string(args[2]))
|
||||||
|
|
||||||
count, err := ledis.StrInt64(args[3], nil)
|
count, err := ledis.StrInt64(args[3], nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -250,13 +315,7 @@ func xmigratedbCommand(c *client) error {
|
||||||
return fmt.Errorf("invalid timeout %d", timeout)
|
return fmt.Errorf("invalid timeout %d", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := c.db.Multi()
|
keys, err := xscan(c.db, tp, int(count))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer m.Close()
|
|
||||||
|
|
||||||
keys, err := xscan(m.DB, tp, int(count))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(keys) == 0 {
|
} else if len(keys) == 0 {
|
||||||
|
@ -270,6 +329,7 @@ func xmigratedbCommand(c *client) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
//timeout is milliseconds
|
//timeout is milliseconds
|
||||||
t := time.Duration(timeout) * time.Millisecond
|
t := time.Duration(timeout) * time.Millisecond
|
||||||
|
@ -278,13 +338,24 @@ func xmigratedbCommand(c *client) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrateNum := int64(0)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
data, err := xdump(m.DB, tp, key)
|
if !c.app.migrateKeyLock(tp, key) {
|
||||||
if err != nil {
|
// other may also migrate this key, skip it
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, err := xttl(m.DB, tp, key)
|
defer c.app.migrateKeyUnlock(tp, key)
|
||||||
|
|
||||||
|
data, err := xdump(c.db, tp, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if data == nil {
|
||||||
|
// no key now, skip it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, err := xttl(c.db, tp, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -296,13 +367,14 @@ func xmigratedbCommand(c *client) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = xdel(m.DB, tp, key); err != nil {
|
if err = xdel(c.db, tp, key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
migrateNum++
|
||||||
}
|
}
|
||||||
|
|
||||||
c.resp.writeInteger(int64(len(keys)))
|
c.resp.writeInteger(migrateNum)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -323,7 +395,7 @@ func xmigrateCommand(c *client) error {
|
||||||
return fmt.Errorf("migrate in same server is not allowed")
|
return fmt.Errorf("migrate in same server is not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
tp := string(args[2])
|
tp := strings.ToUpper(string(args[2]))
|
||||||
key := args[3]
|
key := args[3]
|
||||||
db, err := ledis.StrUint64(args[4], nil)
|
db, err := ledis.StrUint64(args[4], nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -339,13 +411,14 @@ func xmigrateCommand(c *client) error {
|
||||||
return fmt.Errorf("invalid timeout %d", timeout)
|
return fmt.Errorf("invalid timeout %d", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := c.db.Multi()
|
if !c.app.migrateKeyLock(tp, key) {
|
||||||
if err != nil {
|
// other may also migrate this key, skip it
|
||||||
return err
|
return fmt.Errorf("%s %s is in migrating yet", tp, key)
|
||||||
}
|
}
|
||||||
defer m.Close()
|
|
||||||
|
|
||||||
data, err := xdump(m.DB, tp, key)
|
defer c.app.migrateKeyUnlock(tp, key)
|
||||||
|
|
||||||
|
data, err := xdump(c.db, tp, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if data == nil {
|
} else if data == nil {
|
||||||
|
@ -353,7 +426,7 @@ func xmigrateCommand(c *client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ttl, err := xttl(m.DB, tp, key)
|
ttl, err := xttl(c.db, tp, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -364,6 +437,7 @@ func xmigrateCommand(c *client) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
//timeout is milliseconds
|
//timeout is milliseconds
|
||||||
t := time.Duration(timeout) * time.Millisecond
|
t := time.Duration(timeout) * time.Millisecond
|
||||||
|
@ -379,7 +453,7 @@ func xmigrateCommand(c *client) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = xdel(m.DB, tp, key); err != nil {
|
if err = xdel(c.db, tp, key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,14 @@ const (
|
||||||
ZSET = ledis.ZSET
|
ZSET = ledis.ZSET
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KVName = ledis.KVName
|
||||||
|
ListName = ledis.ListName
|
||||||
|
HashName = ledis.HashName
|
||||||
|
SetName = ledis.SetName
|
||||||
|
ZSetName = ledis.ZSetName
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GB uint64 = 1024 * 1024 * 1024
|
GB uint64 = 1024 * 1024 * 1024
|
||||||
MB uint64 = 1024 * 1024
|
MB uint64 = 1024 * 1024
|
||||||
|
|
Loading…
Reference in New Issue