├── go.mod ├── .gitignore ├── Makefile ├── go.sum ├── api ├── c++ │ ├── Makefile │ ├── proto.cc │ ├── proto.h │ ├── slice.h │ ├── codec.h │ ├── example.cc │ ├── codec.cc │ └── gotable.h └── go │ └── table │ ├── pool.go │ ├── proto │ ├── proto.go │ └── codec.go │ ├── param.go │ └── client.go ├── LICENSE ├── store ├── db.cc ├── db_test.go ├── lock_test.go ├── access.go ├── lock.go └── db.go ├── util ├── sort.go ├── network.go ├── bitmap.go ├── network_test.go └── bitmap_test.go ├── ctrl ├── slot.go ├── ctrl.go └── inner.go ├── gotable.conf ├── cmd ├── gotable-server │ └── main.go ├── gotable-bench │ ├── histogram.go │ └── main.go ├── gotable-cli │ ├── main.go │ └── cli.go └── gotable-example │ └── example.go ├── config ├── config.go └── master.go ├── server ├── client.go └── replication.go ├── binlog ├── reader.go └── writer.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stevejiang/gotable 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.1 5 | github.com/GeertJohan/go.linenoise v0.0.0-20141120151038-1918ff89d613 6 | ) 7 | 8 | go 1.15 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | 3 | *.a 4 | *.arc 5 | *.d 6 | *.dylib* 7 | *.gcda 8 | *.gcno 9 | *.o 10 | *.so 11 | *.so.* 12 | *_test 13 | *_bench 14 | *_stress 15 | *.out 16 | *.class 17 | *.jar 18 | *.*jnilib* 19 | *.d-e 20 | *.o-* 21 | *.swp 22 | *.mk 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # please run build_rocksdb.sh first 2 | include make_config.mk 3 | 4 | export CGO_CFLAGS=-g -O2 -DNDEBUG -I$(ROCKSDB_DIR)/include 5 | export CGO_LDFLAGS=-L$(ROCKSDB_DIR) -lrocksdb $(PLATFORM_LDFLAGS) 6 | 7 | all: 8 | go install --ldflags '-extldflags "-static-libstdc++"' ./... 9 | 10 | test: 11 | go test -v --ldflags '-extldflags "-static-libstdc++"' ./... 12 | 13 | clean: 14 | go clean -i ./... 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/GeertJohan/go.linenoise v0.0.0-20141120151038-1918ff89d613 h1:6icnpgpfaYbNFPvsZHrIPhUqNbubaeikWdpMdj40kp0= 4 | github.com/GeertJohan/go.linenoise v0.0.0-20141120151038-1918ff89d613/go.mod h1:hjDAY2N/A6k4IInagcBqLG/q8SyECV6TDzEu5dY0Mwg= 5 | -------------------------------------------------------------------------------- /api/c++/Makefile: -------------------------------------------------------------------------------- 1 | HEADERS := $(wildcard *.h) 2 | 3 | CXX = g++ 4 | CFLAGS = -g -Wall 5 | 6 | LIB_OBJS= codec.o gotable.o proto.o 7 | 8 | all: libgotable.a 9 | 10 | libgotable.a:$(LIB_OBJS) $(HEADERS) 11 | ar -rs $@ $(LIB_OBJS) 12 | 13 | example: example.o libgotable.a 14 | $(CXX) $(CFLAGS) -Wno-strict-aliasing -o $@ $^ libgotable.a 15 | 16 | clean: 17 | rm -f *.o *.a example 18 | 19 | .cpp.o: 20 | $(CXX) $(CFLAGS) -c $^ 21 | .c.o: 22 | $(CXX) $(CFLAGS) -c $^ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 stevejiang. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /store/db.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //empty c++ file tells cgo use g++ as linker 16 | -------------------------------------------------------------------------------- /store/db_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package store 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestDB(t *testing.T) { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /util/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | type Uint64Slice []uint64 18 | 19 | func (p Uint64Slice) Len() int { return len(p) } 20 | func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] } 21 | func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 22 | -------------------------------------------------------------------------------- /ctrl/slot.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ctrl 16 | 17 | import ( 18 | "hash/crc32" 19 | ) 20 | 21 | const ( 22 | TotalSlotNum = 8192 23 | ) 24 | 25 | func GetSlotId(dbId, tableId uint8, rowKey []byte) uint16 { 26 | var a = crc32.Update(0, crc32.IEEETable, []byte{dbId, tableId}) 27 | return uint16(crc32.Update(a, crc32.IEEETable, rowKey) % TotalSlotNum) 28 | } 29 | -------------------------------------------------------------------------------- /gotable.conf: -------------------------------------------------------------------------------- 1 | # Example GoTable configuration file in TOML format. 2 | # TOML spec https://github.com/toml-lang/toml 3 | 4 | [database] 5 | # Server network: tcp, tcp4, tcp6, unix 6 | network = "tcp" 7 | # Server address ip:port 8 | address = "0.0.0.0:6688" 9 | 10 | # Data directory path 11 | data = "data" 12 | 13 | # Max cpu number GO uses (GOMAXPROCS) 14 | #max_cpu_num = 0 15 | 16 | # Default 64MB 17 | write_buffer_size = 67108864 18 | 19 | # Default 64MB, use a larger number if you have enough memory 20 | cache_size = 67108864 21 | 22 | # Compression Type: no, snappy, zlib, bzip2, lz4, lz4hc 23 | compression = "no" 24 | 25 | [auth] 26 | # Administrator password. The auth module is disabled when it is empty. 27 | #admin_password = "abcxyz" 28 | 29 | [binlog] 30 | # Memory binlog size (MB) 31 | memory_size = 8 32 | 33 | # Number of binlog files kept 34 | keep_num = 128 35 | 36 | [profile] 37 | # Memory profile file name 38 | #memory = "/tmp/memprofile" 39 | 40 | # Net HTTP profile host address ip:port 41 | #host = "0.0.0.0:8080" 42 | -------------------------------------------------------------------------------- /util/network.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | func GetRealAddr(writtenAddr, netAddr string) string { 22 | var nas = strings.Split(netAddr, ":") 23 | if len(nas) != 2 || len(nas[0]) == 0 { 24 | return writtenAddr 25 | } 26 | if strings.Index(writtenAddr, "127.0.0.1:") == 0 { 27 | var was = strings.Split(writtenAddr, ":") 28 | return nas[0] + ":" + was[1] 29 | } 30 | 31 | if strings.Index(writtenAddr, ":") == 0 { 32 | return nas[0] + writtenAddr 33 | } 34 | 35 | return writtenAddr 36 | } 37 | -------------------------------------------------------------------------------- /store/lock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package store 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestLock(t *testing.T) { 22 | tl := NewTableLock() 23 | key := []byte("key0") 24 | 25 | lck := tl.GetLock(key) 26 | lck.Lock() 27 | defer lck.Unlock() 28 | 29 | if lck.GetCas(key) != 0 { 30 | t.Fatalf("Cas should be 0 when not set") 31 | } 32 | 33 | cas := lck.NewCas(key) 34 | if cas == 0 { 35 | t.Fatalf("Cas should not be 0 after NewCas") 36 | } 37 | 38 | if cas != lck.GetCas(key) { 39 | t.Fatalf("Cas mismatch") 40 | } 41 | 42 | lck.ClearCas(key) 43 | 44 | if lck.GetCas(key) != 0 { 45 | t.Fatalf("Cas should be 0 after clear") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /util/bitmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | type BitMap struct { 18 | ab []uint8 19 | } 20 | 21 | // NewBitMap returns a new BitMap with size in bytes. 22 | func NewBitMap(size uint) *BitMap { 23 | bm := new(BitMap) 24 | bm.ab = make([]uint8, size) 25 | 26 | return bm 27 | } 28 | 29 | func (bm *BitMap) Get(index uint) bool { 30 | if len(bm.ab)*8 > int(index) { 31 | idx := index / 8 32 | bit := index % 8 33 | 34 | return bm.ab[idx]&(1< int(index) { 42 | idx := index / 8 43 | bit := index % 8 44 | 45 | bm.ab[idx] |= (1 << bit) 46 | } 47 | } 48 | 49 | func (bm *BitMap) Clear(index uint) { 50 | if len(bm.ab)*8 > int(index) { 51 | idx := index / 8 52 | bit := index % 8 53 | 54 | bm.ab[idx] &^= (1 << bit) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /util/network_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestGetRealAddr(t *testing.T) { 22 | if GetRealAddr(":6688", "127.0.0.1:12345") != "127.0.0.1:6688" { 23 | t.Fatalf("RealAddr not match") 24 | } 25 | 26 | if GetRealAddr(":6688", "10.1.0.25:12345") != "10.1.0.25:6688" { 27 | t.Fatalf("RealAddr not match") 28 | } 29 | 30 | if GetRealAddr("127.0.0.1:6688", "10.1.0.25:12345") != "10.1.0.25:6688" { 31 | t.Fatalf("RealAddr not match") 32 | } 33 | 34 | if GetRealAddr("127.0.0.1:6688", ":12345") != "127.0.0.1:6688" { 35 | t.Fatalf("RealAddr not match") 36 | } 37 | 38 | if GetRealAddr("10.1.0.25:6688", ":12345") != "10.1.0.25:6688" { 39 | t.Fatalf("RealAddr not match") 40 | } 41 | 42 | if GetRealAddr("10.1.0.25:6688", "10.1.0.33:12345") != "10.1.0.25:6688" { 43 | t.Fatalf("RealAddr not match") 44 | } 45 | 46 | if GetRealAddr("", "10.1.0.33:12345") != "" { 47 | t.Fatalf("RealAddr not match") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ctrl/ctrl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ctrl 16 | 17 | import ( 18 | "encoding/json" 19 | "github.com/stevejiang/gotable/api/go/table/proto" 20 | ) 21 | 22 | func Decode(pkg []byte, head *proto.PkgHead, v interface{}) error { 23 | if len(pkg) < proto.HeadSize { 24 | return proto.ErrPkgLen 25 | } 26 | 27 | var err error 28 | if head != nil { 29 | _, err = head.Decode(pkg) 30 | if err != nil { 31 | return err 32 | } 33 | } 34 | 35 | return json.Unmarshal(pkg[proto.HeadSize:], v) 36 | } 37 | 38 | func Encode(cmd, dbId uint8, seq uint64, v interface{}) ([]byte, error) { 39 | jsonPkg, err := json.Marshal(v) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | var pkgLen = proto.HeadSize + len(jsonPkg) 45 | var head proto.PkgHead 46 | head.Cmd = cmd 47 | head.DbId = dbId 48 | head.Seq = seq 49 | head.PkgLen = uint32(pkgLen) 50 | 51 | var pkg = make([]byte, pkgLen) 52 | _, err = head.Encode(pkg) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | copy(pkg[proto.HeadSize:], jsonPkg) 58 | return pkg, nil 59 | } 60 | -------------------------------------------------------------------------------- /util/bitmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestBitMap(t *testing.T) { 22 | var b = NewBitMap(256 / 8) 23 | 24 | if b.Get(1) { 25 | t.Fatalf("index 1 should be false") 26 | } 27 | 28 | b.Set(1) 29 | if !b.Get(1) { 30 | t.Fatalf("index 1 should be true") 31 | } 32 | 33 | b.Clear(1) 34 | if b.Get(1) { 35 | t.Fatalf("index 1 should be false") 36 | } 37 | 38 | if b.Get(255) { 39 | t.Fatalf("index 255 should be false") 40 | } 41 | if b.Get(256) { 42 | t.Fatalf("index 256 should be false") 43 | } 44 | if b.Get(260) { 45 | t.Fatalf("index 260 should be false") 46 | } 47 | 48 | b.Set(255) 49 | b.Set(256) 50 | b.Set(260) 51 | if !b.Get(255) { 52 | t.Fatalf("index 255 should be true") 53 | } 54 | if b.Get(256) { 55 | t.Fatalf("index 256 should be false") 56 | } 57 | if b.Get(260) { 58 | t.Fatalf("index 260 should be false") 59 | } 60 | 61 | b.Clear(255) 62 | b.Clear(256) 63 | b.Clear(260) 64 | if b.Get(255) { 65 | t.Fatalf("index 255 should be false") 66 | } 67 | if b.Get(256) { 68 | t.Fatalf("index 256 should be false") 69 | } 70 | if b.Get(260) { 71 | t.Fatalf("index 260 should be false") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmd/gotable-server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/stevejiang/gotable/config" 19 | "github.com/stevejiang/gotable/server" 20 | "log" 21 | "net/http" 22 | _ "net/http/pprof" 23 | "os" 24 | "os/signal" 25 | "runtime/pprof" 26 | "syscall" 27 | ) 28 | 29 | func main() { 30 | log.SetFlags(log.Flags() | log.Lshortfile) 31 | 32 | var configFile string 33 | if len(os.Args) > 1 { 34 | configFile = os.Args[1] 35 | } 36 | 37 | conf, err := config.Load(configFile) 38 | if err != nil { 39 | log.Fatalf("Failed to load config: %s", err) 40 | } 41 | 42 | var srv = server.NewServer(conf) 43 | if srv == nil { 44 | log.Fatalln("Failed to create new server!") 45 | } 46 | 47 | if len(conf.Profile.Host) > 0 { 48 | log.Printf("Start profile on http://%s/debug/pprof\n", conf.Profile.Host) 49 | go func() { 50 | http.ListenAndServe(conf.Profile.Host, nil) 51 | }() 52 | } 53 | 54 | go func() { 55 | var c = make(chan os.Signal, 1) 56 | signal.Notify(c, syscall.SIGUSR1, syscall.SIGINT) 57 | 58 | var s = <-c 59 | log.Println("Get signal:", s) 60 | 61 | if s == syscall.SIGUSR1 { 62 | if conf.Profile.Memory != "" { 63 | f, err := os.Create(conf.Profile.Memory) 64 | if err != nil { 65 | log.Println("create memory profile file failed:", err) 66 | return 67 | } 68 | 69 | pprof.WriteHeapProfile(f) 70 | f.Close() 71 | } 72 | } 73 | 74 | srv.Close() 75 | }() 76 | 77 | srv.Start() 78 | } 79 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "github.com/BurntSushi/toml" 19 | "log" 20 | ) 21 | 22 | type Config struct { 23 | Db database `toml:"database"` 24 | Bin binlog `toml:"binlog"` 25 | Auth auth 26 | Profile profile 27 | } 28 | 29 | type database struct { 30 | Network string 31 | Address string 32 | Data string 33 | MaxCpuNum int `toml:"max_cpu_num"` 34 | WriteBufSize int `toml:"write_buffer_size"` 35 | CacheSize int64 `toml:"cache_size"` 36 | Compression string 37 | } 38 | 39 | type binlog struct { 40 | MemSize int `toml:"memory_size"` 41 | KeepNum int `toml:"keep_num"` 42 | } 43 | 44 | type auth struct { 45 | AdminPwd string `toml:"admin_password"` 46 | } 47 | 48 | type profile struct { 49 | Memory string 50 | Host string 51 | } 52 | 53 | func Load(fileName string) (*Config, error) { 54 | var conf Config 55 | var err error 56 | if len(fileName) == 0 { 57 | log.Println("Use default configuration") 58 | _, err = toml.Decode(defaultConfig, &conf) 59 | } else { 60 | log.Printf("Use configuration file %s\n", fileName) 61 | _, err = toml.DecodeFile(fileName, &conf) 62 | } 63 | 64 | if err != nil { 65 | return nil, err 66 | } 67 | return &conf, nil 68 | } 69 | 70 | var defaultConfig = ` 71 | [database] 72 | network = "tcp" 73 | address = "0.0.0.0:6688" 74 | data = "data" 75 | write_buffer_size = 67108864 76 | cache_size = 67108864 77 | compression = "no" 78 | 79 | [binlog] 80 | memory_size = 8 81 | keep_num = 128 82 | 83 | ` 84 | -------------------------------------------------------------------------------- /store/access.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package store 16 | 17 | import ( 18 | "github.com/stevejiang/gotable/config" 19 | "github.com/stevejiang/gotable/ctrl" 20 | ) 21 | 22 | type WriteAccess struct { 23 | replication bool // Replication slave 24 | hasMaster bool 25 | migration bool 26 | slotId uint16 27 | } 28 | 29 | func NewWriteAccess(replication bool, mc *config.MasterConfig) *WriteAccess { 30 | hasMaster, migration, slotId := mc.GetMasterSlot() 31 | return &WriteAccess{replication, hasMaster, migration, slotId} 32 | } 33 | 34 | // Do we have right to write this key? 35 | func (m *WriteAccess) CheckKey(dbId, tableId uint8, rowKey []byte) bool { 36 | if m.replication { 37 | return true // Accept all replication data 38 | } 39 | 40 | if !m.hasMaster { 41 | return true 42 | } 43 | 44 | if m.migration { 45 | return m.slotId != ctrl.GetSlotId(dbId, tableId, rowKey) 46 | } else { 47 | return false 48 | } 49 | } 50 | 51 | // Do we have right to write this slot? 52 | func (m *WriteAccess) CheckSlot(slotId uint16) bool { 53 | if m.replication { 54 | return true // Accept all replication data 55 | } 56 | 57 | if !m.hasMaster { 58 | return true 59 | } 60 | 61 | if m.migration { 62 | return m.slotId != slotId 63 | } else { 64 | return false 65 | } 66 | } 67 | 68 | // return false: no right to write! 69 | // return true: may have right to write, need to CheckKey or CheckSlot again 70 | func (m *WriteAccess) Check() bool { 71 | if m.replication { 72 | return true // Accept all replication data 73 | } 74 | 75 | if m.hasMaster && !m.migration { 76 | return false 77 | } 78 | 79 | return true 80 | } 81 | -------------------------------------------------------------------------------- /ctrl/inner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ctrl 16 | 17 | // Slave/Migration status 18 | const ( 19 | NotSlave = iota // Not a normal slave (also not a migration slave) 20 | SlaveInit // Just receive slave or migration command 21 | SlaveNeedClear // Slave has old data, need to clear old data 22 | SlaveClear // Slave clearing(deleting) old data 23 | SlaveFullSync // Doing full sync right now 24 | SlaveIncrSync // Doing incremental sync right now 25 | SlaveReady // Slave is up to date with master 26 | ) 27 | 28 | // SlaveOf command pkg 29 | type PkgSlaveOf struct { 30 | ClientReq bool // true: from client api; false: from slave to master 31 | MasterAddr string // ip:host, no master if emtpy 32 | SlaveAddr string // ip:host 33 | LastSeq uint64 34 | ErrMsg string // error msg, nil means no error 35 | } 36 | 37 | // Migrate command pkg. 38 | // When stop migration: 39 | // If all data migrated, switch client requests to new servers. 40 | // If migration failed, wait for delete slot command. 41 | // Steps in cluster mode when migration succeeds: 42 | // 1. new servers switch to normal status (but still hold the master/slave connection) 43 | // 2. switch proxy and client requests routed to new servers 44 | // 3. wait for 10 seconds, and close the master/slave connection 45 | // 4. delete the slot data from old servers 46 | type PkgMigrate struct { 47 | ClientReq bool // true: from client api; false: from slave to master 48 | MasterAddr string // ip:host, stop migration if empty 49 | SlaveAddr string // ip:host 50 | SlotId uint16 // The slot to be migrated 51 | ErrMsg string // error msg, nil means no error 52 | } 53 | 54 | // Get migration/slave status 55 | type PkgSlaveStatus struct { 56 | Migration bool // true: Migration status; false: Normal slave status 57 | SlotId uint16 // The slot under migration 58 | Status int 59 | ErrMsg string // error msg, nil means no error 60 | } 61 | 62 | // Delete slot data 63 | type PkgDelSlot struct { 64 | SlotId uint16 // The slot to delete 65 | ErrMsg string // error msg, nil means no error 66 | } 67 | -------------------------------------------------------------------------------- /api/c++/proto.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include "proto.h" 18 | 19 | namespace gotable { 20 | 21 | static int8_t calHeadCrc(const char* pkg) { 22 | int8_t crc = 10; 23 | for (int i = 1; i < HeadSize; i++) { 24 | crc += pkg[i]; 25 | } 26 | return crc; 27 | } 28 | 29 | int PkgHead::length() { 30 | return HeadSize; 31 | } 32 | 33 | int PkgHead::decode(const char* pkg, int len) { 34 | if(len < HeadSize) { 35 | return -1; 36 | } 37 | 38 | crc = calHeadCrc(pkg); 39 | if (crc != pkg[0]) { 40 | return -2; 41 | } 42 | 43 | cmd = pkg[1]; 44 | dbId = pkg[2]; 45 | seq = getUint64(pkg+3); 46 | pkgLen = getUint32(pkg+11); 47 | 48 | return HeadSize; 49 | } 50 | 51 | int PkgHead::encode(char* pkg, int len) { 52 | if(len < HeadSize) { 53 | return -1; 54 | } 55 | 56 | pkg[1] = cmd; 57 | pkg[2] = dbId; 58 | putUint64(pkg+3, seq); 59 | putUint32(pkg+11, pkgLen); 60 | pkg[0] = calHeadCrc(pkg); 61 | 62 | return HeadSize; 63 | } 64 | 65 | void overWriteLen(char* pkg, int pkgLen) { 66 | putUint32(pkg+11, uint32_t(pkgLen)); 67 | pkg[0] = calHeadCrc(pkg); 68 | } 69 | 70 | void overWriteSeq(char* pkg, uint64_t seq) { 71 | putUint64(pkg+3, seq); 72 | pkg[0] = calHeadCrc(pkg); 73 | } 74 | 75 | int readPkg(int fd, char* buf, int bufLen, PkgHead* head, string& pkg) { 76 | pkg.clear(); 77 | 78 | if(buf == NULL || bufLen < HeadSize || head == NULL) { 79 | return -2; 80 | } 81 | 82 | int readLen = 0; 83 | while(true) { 84 | int n = read(fd, buf+readLen, HeadSize-readLen); 85 | if (n <= 0) { 86 | return n; 87 | } 88 | 89 | readLen += n; 90 | 91 | if(readLen < HeadSize) { 92 | continue; 93 | } 94 | 95 | int err = head->decode(buf, HeadSize); 96 | if(err < 0) { 97 | return err; 98 | } 99 | 100 | int pkgLen = int(head->pkgLen); 101 | if(pkgLen > MaxPkgLen) { 102 | return -3; 103 | } 104 | 105 | pkg.append(buf, HeadSize); 106 | 107 | while(readLen < pkgLen) { 108 | int curLen = pkgLen-readLen; 109 | if(curLen > bufLen) { 110 | curLen = bufLen; 111 | } 112 | n = read(fd, buf, curLen); 113 | if (n <= 0) { 114 | return n; 115 | } 116 | readLen += n; 117 | 118 | pkg.append(buf, n); 119 | } 120 | 121 | return pkgLen; 122 | } 123 | } 124 | 125 | } // namespace gotable 126 | -------------------------------------------------------------------------------- /store/lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package store 16 | 17 | import ( 18 | "hash/crc32" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | const ( 24 | dbLockSlotNum = 1024 25 | rollInterval = time.Second * 5 // 5 seconds 26 | minCasValue = uint32(6600) 27 | maxCasValue = uint32(0x80000000) 28 | ) 29 | 30 | var castagnoliTab = crc32.MakeTable(crc32.Castagnoli) 31 | 32 | type SlotLock struct { 33 | sync.Mutex 34 | curCas uint32 35 | curIdx int 36 | cas []map[string]uint32 // rawKey => cas 37 | } 38 | 39 | type TableLock struct { 40 | ul []SlotLock 41 | } 42 | 43 | func NewTableLock() *TableLock { 44 | var tl = new(TableLock) 45 | tl.ul = make([]SlotLock, dbLockSlotNum) 46 | 47 | go tl.goRollDeamon() 48 | return tl 49 | } 50 | 51 | func (tl *TableLock) GetLock(key []byte) *SlotLock { 52 | var idx = crc32.Checksum(key, castagnoliTab) % dbLockSlotNum 53 | return &tl.ul[idx] 54 | } 55 | 56 | func (tl *TableLock) goRollDeamon() { 57 | var tick = time.NewTicker(rollInterval) 58 | defer tick.Stop() 59 | for { 60 | select { 61 | case <-tick.C: 62 | for i := 0; i < len(tl.ul); i++ { 63 | tl.ul[i].roll() 64 | } 65 | } 66 | } 67 | } 68 | 69 | func (u *SlotLock) NewCas(key []byte) uint32 { 70 | var strKey = string(key) 71 | if u.cas == nil { 72 | u.curCas = minCasValue 73 | u.cas = make([]map[string]uint32, 2) 74 | } 75 | if u.cas[u.curIdx] == nil { 76 | u.cas[u.curIdx] = make(map[string]uint32) 77 | } 78 | 79 | if rc, ok := u.cas[u.curIdx][strKey]; ok { 80 | return rc 81 | } 82 | 83 | var idxBak = (u.curIdx + 1) % 2 84 | if u.cas[idxBak] != nil { 85 | if rc, ok := u.cas[idxBak][strKey]; ok { 86 | return rc 87 | } 88 | } 89 | 90 | u.curCas++ 91 | if u.curCas > maxCasValue { 92 | u.curCas = 1 93 | } 94 | 95 | u.cas[u.curIdx][strKey] = u.curCas 96 | 97 | return u.curCas 98 | } 99 | 100 | func (u *SlotLock) GetCas(key []byte) uint32 { 101 | var strKey = string(key) 102 | if u.cas == nil { 103 | return 0 104 | } 105 | if u.cas[u.curIdx] == nil { 106 | return 0 107 | } 108 | 109 | if rc, ok := u.cas[u.curIdx][strKey]; ok { 110 | return rc 111 | } 112 | 113 | var idxBak = (u.curIdx + 1) % 2 114 | if u.cas[idxBak] != nil { 115 | if rc, ok := u.cas[idxBak][strKey]; ok { 116 | return rc 117 | } 118 | } 119 | 120 | return 0 121 | } 122 | 123 | func (u *SlotLock) ClearCas(key []byte) { 124 | if u.cas != nil { 125 | var strKey = string(key) 126 | delete(u.cas[u.curIdx], strKey) 127 | var idxBak = (u.curIdx + 1) % 2 128 | delete(u.cas[idxBak], strKey) 129 | } 130 | } 131 | 132 | func (u *SlotLock) roll() { 133 | u.Lock() 134 | defer u.Unlock() 135 | 136 | if u.cas != nil { 137 | var idxBak = (u.curIdx + 1) % 2 138 | u.cas[idxBak] = nil 139 | u.curIdx = idxBak 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /api/c++/proto.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef _GO_TABLE_PROTO_H_ 16 | #define _GO_TABLE_PROTO_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #ifndef htonll 24 | #define htonll(x) gotable::switchll((x)) 25 | #endif 26 | #ifndef ntohll 27 | #define ntohll(x) gotable::switchll((x)) 28 | #endif 29 | 30 | namespace gotable { 31 | 32 | using std::string; 33 | 34 | 35 | enum { 36 | // Front CTRL 37 | CmdAuth = 0x9, 38 | 39 | // Front Read 40 | CmdPing = 0x10, 41 | CmdGet = 0x11, 42 | CmdMGet = 0x12, 43 | CmdScan = 0x13, 44 | CmdDump = 0x14, 45 | 46 | // Front Write 47 | CmdSet = 0x60, 48 | CmdMSet = 0x61, 49 | CmdDel = 0x62, 50 | CmdMDel = 0x63, 51 | CmdIncr = 0x64, 52 | CmdMIncr = 0x65, 53 | }; 54 | 55 | enum { 56 | AdminDbId = 255, 57 | HeadSize = 15, 58 | MaxUint8 = 255, 59 | MaxUint16 = 65535, 60 | MaxValueLen = 1024 * 1024, // 1MB 61 | MaxPkgLen = 1024 * 1024 * 2, // 2MB 62 | }; 63 | 64 | // cCrc+cCmd+cDbId+ddwSeq+dwPkgLen+sBody 65 | struct PkgHead { 66 | int8_t crc; //Head CRC 67 | uint8_t cmd; 68 | uint8_t dbId; 69 | uint64_t seq; //normal: request seq; replication: master binlog seq 70 | uint32_t pkgLen; 71 | 72 | virtual ~PkgHead() {} 73 | virtual int length(); 74 | virtual int decode(const char* pkg, int len); 75 | virtual int encode(char* pkg, int len); 76 | }; 77 | 78 | void overWriteLen(char* pkg, int pkgLen); 79 | void overWriteSeq(char* pkg, uint64_t seq); 80 | 81 | int readPkg(int fd, char* buf, int bufLen, PkgHead* head, string& pkg); 82 | 83 | 84 | inline uint64_t switchll(uint64_t x) 85 | { 86 | #if BYTE_ORDER == BIG_ENDIAN 87 | return x; 88 | #elif BYTE_ORDER == LITTLE_ENDIAN 89 | return ((((x) & 0xff00000000000000llu) >> 56) | 90 | (((x) & 0x00ff000000000000llu) >> 40) | 91 | (((x) & 0x0000ff0000000000llu) >> 24) | 92 | (((x) & 0x0000ff0000000000llu) >> 24) | 93 | (((x) & 0x000000ff00000000llu) >> 8) | 94 | (((x) & 0x00000000ff000000llu) << 8) | 95 | (((x) & 0x0000000000ff0000llu) << 24) | 96 | (((x) & 0x000000000000ff00llu) << 40) | 97 | (((x) & 0x00000000000000ffllu) << 56)); 98 | #else 99 | #error "What kind of system is this?" 100 | #endif 101 | } 102 | 103 | inline void putUint16(char* pkg, uint16_t a) { 104 | *(uint16_t*)pkg = htons(a); 105 | } 106 | 107 | inline uint16_t getUint16(const char* pkg) { 108 | return ntohs(*(uint16_t*)pkg); 109 | } 110 | 111 | inline void putUint32(char* pkg, uint32_t a) { 112 | *(uint32_t*)pkg = htonl(a); 113 | } 114 | 115 | inline uint32_t getUint32(const char* pkg) { 116 | return ntohl(*(uint32_t*)pkg); 117 | } 118 | 119 | inline void putUint64(char* pkg, uint64_t a) { 120 | *(uint64_t*)pkg = htonll(a); 121 | } 122 | 123 | inline uint64_t getUint64(const char* pkg) { 124 | return ntohll(*(uint64_t*)pkg); 125 | } 126 | 127 | } 128 | #endif 129 | -------------------------------------------------------------------------------- /api/go/table/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package table 16 | 17 | import ( 18 | "math/rand" 19 | "net" 20 | "sync" 21 | "time" 22 | ) 23 | 24 | const ( 25 | statusOk = iota 26 | statusErr 27 | ) 28 | 29 | type Addr struct { 30 | Network string 31 | Address string 32 | } 33 | 34 | type Pool struct { 35 | as []Addr // Server address list, never change once assigned 36 | connNum int 37 | 38 | mtx sync.Mutex 39 | status []int 40 | cs []*Client 41 | lastAddr int // Last as index 42 | lastCli int // Last cs index 43 | closed bool 44 | } 45 | 46 | func NewPool(as []Addr, connNum int) *Pool { 47 | var p = new(Pool) 48 | p.connNum = connNum 49 | p.as = as 50 | for i := 0; i < len(as); i++ { 51 | p.status = append(p.status, statusOk) 52 | } 53 | p.lastAddr = rand.Int() % len(as) 54 | 55 | go p.goPingDeamon() 56 | return p 57 | } 58 | 59 | func (p *Pool) Get() (*Client, error) { 60 | p.mtx.Lock() 61 | if p.closed { 62 | p.mtx.Unlock() 63 | return nil, ErrClosedPool 64 | } 65 | 66 | cliNum := len(p.cs) 67 | if cliNum >= p.connNum && cliNum > 0 { 68 | p.lastCli = (p.lastCli + 1) % cliNum 69 | var c = p.cs[p.lastCli] 70 | p.mtx.Unlock() 71 | return c, nil 72 | } 73 | 74 | defer p.mtx.Unlock() 75 | 76 | var lastAddr = p.lastAddr 77 | for i := 0; i < len(p.as); i++ { 78 | lastAddr = (lastAddr + 1) % len(p.as) 79 | if p.status[lastAddr] == statusOk { 80 | var addr = p.as[lastAddr] 81 | c, err := Dial(addr.Network, addr.Address) 82 | if err != nil { 83 | p.status[lastAddr] = statusErr 84 | continue 85 | } 86 | c.p = p 87 | p.cs = append(p.cs, c) 88 | p.lastAddr = lastAddr 89 | return c, nil 90 | } 91 | } 92 | 93 | return nil, ErrNoValidAddr 94 | } 95 | 96 | func (p *Pool) Close() { 97 | p.mtx.Lock() 98 | if p.closed { 99 | p.mtx.Unlock() 100 | return 101 | } 102 | p.closed = true 103 | var cs = make([]*Client, len(p.cs)) 104 | copy(cs, p.cs) 105 | p.cs = nil 106 | p.mtx.Unlock() 107 | 108 | for _, c := range cs { 109 | if c != nil { 110 | c.doClose() 111 | } 112 | } 113 | } 114 | 115 | func (p *Pool) remove(cli *Client) { 116 | p.mtx.Lock() 117 | defer p.mtx.Unlock() 118 | if p.closed { 119 | return 120 | } 121 | 122 | for i, c := range p.cs { 123 | if c == cli { 124 | copy(p.cs[i:], p.cs[i+1:]) 125 | p.cs = p.cs[:len(p.cs)-1] 126 | return 127 | } 128 | } 129 | } 130 | 131 | func (p *Pool) goPingDeamon() { 132 | for !p.closed { 133 | time.Sleep(time.Second) 134 | for i := 0; i < len(p.as); i++ { 135 | p.mtx.Lock() 136 | var st = p.status[i] 137 | p.mtx.Unlock() 138 | if st != statusOk { 139 | var addr = p.as[i] 140 | c, err := net.DialTimeout(addr.Network, addr.Address, time.Second) 141 | if err == nil { 142 | p.mtx.Lock() 143 | p.status[i] = statusOk 144 | p.mtx.Unlock() 145 | c.Close() 146 | continue 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /api/c++/slice.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef _GO_TABLE_SLICE_H_ 16 | #define _GO_TABLE_SLICE_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace gotable { 25 | 26 | class Slice { 27 | public: 28 | // Create an empty slice. 29 | Slice() : data_(""), size_(0) { } 30 | 31 | // Create a slice that refers to d[0,n-1]. 32 | Slice(const char* d, size_t n) : data_(d), size_(n) { } 33 | 34 | // Create a slice that refers to the contents of "s" 35 | /* implicit */ 36 | Slice(const std::string& s) : data_(s.data()), size_(s.size()) { } 37 | 38 | // Create a slice that refers to s[0,strlen(s)-1] 39 | /* implicit */ 40 | Slice(const char* s) : data_(s), size_(strlen(s)) { } 41 | 42 | // Return a pointer to the beginning of the referenced data 43 | const char* data() const { return data_; } 44 | 45 | // Return the length (in bytes) of the referenced data 46 | size_t size() const { return size_; } 47 | 48 | // Return true iff the length of the referenced data is zero 49 | bool empty() const { return size_ == 0; } 50 | 51 | // Return the ith byte in the referenced data. 52 | // REQUIRES: n < size() 53 | char operator[](size_t n) const { 54 | assert(n < size()); 55 | return data_[n]; 56 | } 57 | 58 | // Change this slice to refer to an empty array 59 | void clear() { data_ = ""; size_ = 0; } 60 | 61 | // Drop the first "n" bytes from this slice. 62 | void remove_prefix(size_t n) { 63 | assert(n <= size()); 64 | data_ += n; 65 | size_ -= n; 66 | } 67 | 68 | // Return a string that contains the copy of the referenced data. 69 | std::string ToString(bool hex = false) const { 70 | if (hex) { 71 | std::string result; 72 | char buf[10]; 73 | for (size_t i = 0; i < size_; i++) { 74 | snprintf(buf, 10, "%02X", (unsigned char)data_[i]); 75 | result += buf; 76 | } 77 | return result; 78 | } else { 79 | return std::string(data_, size_); 80 | } 81 | } 82 | 83 | // Three-way comparison. Returns value: 84 | // < 0 iff "*this" < "b", 85 | // == 0 iff "*this" == "b", 86 | // > 0 iff "*this" > "b" 87 | int compare(const Slice& b) const; 88 | 89 | // Return true iff "x" is a prefix of "*this" 90 | bool starts_with(const Slice& x) const { 91 | return ((size_ >= x.size_) && 92 | (memcmp(data_, x.data_, x.size_) == 0)); 93 | } 94 | 95 | // private: make these public for rocksdbjni access 96 | const char* data_; 97 | size_t size_; 98 | 99 | // Intentionally copyable 100 | }; 101 | 102 | // A set of Slices that are virtually concatenated together. 'parts' points 103 | // to an array of Slices. The number of elements in the array is 'num_parts'. 104 | struct SliceParts { 105 | SliceParts(const Slice* _parts, int _num_parts) : 106 | parts(_parts), num_parts(_num_parts) { } 107 | SliceParts() : parts(NULL), num_parts(0) {} 108 | 109 | const Slice* parts; 110 | int num_parts; 111 | }; 112 | 113 | inline bool operator==(const Slice& x, const Slice& y) { 114 | return ((x.size() == y.size()) && 115 | (memcmp(x.data(), y.data(), x.size()) == 0)); 116 | } 117 | 118 | inline bool operator!=(const Slice& x, const Slice& y) { 119 | return !(x == y); 120 | } 121 | 122 | inline int Slice::compare(const Slice& b) const { 123 | const size_t min_len = (size_ < b.size_) ? size_ : b.size_; 124 | int r = memcmp(data_, b.data_, min_len); 125 | if (r == 0) { 126 | if (size_ < b.size_) r = -1; 127 | else if (size_ > b.size_) r = +1; 128 | } 129 | return r; 130 | } 131 | 132 | } // namespace gotable 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /api/go/table/proto/proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "bufio" 19 | "encoding/binary" 20 | "errors" 21 | ) 22 | 23 | var ( 24 | ErrPkgLen = errors.New("pkg length out of range") 25 | ErrHeadCrc = errors.New("pkg head crc mismatch") 26 | ErrRowKeyLen = errors.New("row key length out of range") 27 | ErrColKeyLen = errors.New("col key length out of range") 28 | ErrKvArrayLen = errors.New("key/value array length out of range") 29 | ) 30 | 31 | const ( 32 | // Front CTRL 33 | CmdAuth = 0x9 34 | 35 | // Front Read 36 | CmdPing = 0x10 37 | CmdGet = 0x11 38 | CmdMGet = 0x12 39 | CmdScan = 0x13 40 | CmdDump = 0x14 41 | 42 | // Front Write 43 | CmdSet = 0x60 44 | CmdMSet = 0x61 45 | CmdDel = 0x62 46 | CmdMDel = 0x63 47 | CmdIncr = 0x64 48 | CmdMIncr = 0x65 49 | 50 | // Inner SYNC 51 | CmdSync = 0xB0 // Sync data 52 | CmdSyncSt = 0xB1 // Sync status 53 | 54 | // Inner CTRL 55 | CmdSlaveOf = 0xD0 56 | CmdMigrate = 0xD1 // Start/Stop migration 57 | CmdSlaveSt = 0xD2 // Get migration/slave status 58 | CmdDelSlot = 0xD3 // Delete slot data 59 | ) 60 | 61 | const ( 62 | AdminDbId = 255 63 | HeadSize = 15 64 | MaxUint8 = 255 65 | MaxUint16 = 65535 66 | MaxValueLen = 1024 * 1024 // 1MB 67 | MaxPkgLen = 1024 * 1024 * 2 // 2MB 68 | ) 69 | 70 | type PkgEncoding interface { 71 | Length() int 72 | Encode(pkg []byte) (int, error) 73 | Decode(pkg []byte) (int, error) 74 | } 75 | 76 | type PkgResponse interface { 77 | PkgEncoding 78 | SetErrCode(errCode int8) 79 | } 80 | 81 | // cCrc+cCmd+cDbId+ddwSeq+dwPkgLen+sBody 82 | type PkgHead struct { 83 | Crc uint8 // Head CRC 84 | Cmd uint8 85 | DbId uint8 86 | Seq uint64 // normal: request seq; replication: master binlog seq 87 | PkgLen uint32 88 | } 89 | 90 | func (head *PkgHead) Decode(pkg []byte) (int, error) { 91 | if len(pkg) < HeadSize { 92 | return 0, ErrPkgLen 93 | } 94 | 95 | head.Crc = CalHeadCrc(pkg) 96 | if head.Crc != pkg[0] { 97 | return 0, ErrHeadCrc 98 | } 99 | 100 | head.Cmd = pkg[1] 101 | head.DbId = pkg[2] 102 | head.Seq = binary.BigEndian.Uint64(pkg[3:]) 103 | head.PkgLen = binary.BigEndian.Uint32(pkg[11:]) 104 | 105 | return HeadSize, nil 106 | } 107 | 108 | func (head *PkgHead) Encode(pkg []byte) (int, error) { 109 | if len(pkg) < HeadSize { 110 | return 0, ErrPkgLen 111 | } 112 | 113 | pkg[1] = head.Cmd 114 | pkg[2] = head.DbId 115 | binary.BigEndian.PutUint64(pkg[3:], head.Seq) 116 | binary.BigEndian.PutUint32(pkg[11:], head.PkgLen) 117 | pkg[0] = CalHeadCrc(pkg) 118 | 119 | return HeadSize, nil 120 | } 121 | 122 | func CalHeadCrc(pkg []byte) uint8 { 123 | var crc int8 = 10 124 | for i := 1; i < HeadSize; i++ { 125 | crc += int8(pkg[i]) 126 | } 127 | return uint8(crc) 128 | } 129 | 130 | func OverWriteLen(pkg []byte, pkgLen int) { 131 | binary.BigEndian.PutUint32(pkg[11:], uint32(pkgLen)) 132 | pkg[0] = CalHeadCrc(pkg) 133 | } 134 | 135 | func OverWriteSeq(pkg []byte, seq uint64) { 136 | binary.BigEndian.PutUint64(pkg[3:], seq) 137 | pkg[0] = CalHeadCrc(pkg) 138 | } 139 | 140 | func ReadPkg(r *bufio.Reader, headBuf []byte, head *PkgHead, 141 | pkgBuf []byte) (pkg []byte, err error) { 142 | if len(headBuf) != HeadSize { 143 | headBuf = make([]byte, HeadSize) 144 | } 145 | 146 | if head == nil { 147 | head = new(PkgHead) 148 | } 149 | 150 | var readLen int 151 | for { 152 | n, err := r.Read(headBuf[readLen:]) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | readLen += n 158 | 159 | if readLen < HeadSize { 160 | continue 161 | } 162 | 163 | _, err = head.Decode(headBuf) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | var pkgLen = int(head.PkgLen) 169 | if pkgLen > MaxPkgLen { 170 | return nil, ErrPkgLen 171 | } 172 | 173 | if cap(pkgBuf) < pkgLen { 174 | pkg = make([]byte, pkgLen) 175 | } else { 176 | pkg = pkgBuf[:pkgLen] 177 | } 178 | 179 | copy(pkg, headBuf) 180 | 181 | for readLen < pkgLen { 182 | n, err = r.Read(pkg[readLen:]) 183 | if err != nil { 184 | return nil, err 185 | } 186 | readLen += n 187 | } 188 | 189 | return pkg, nil 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /api/go/table/param.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package table 16 | 17 | import ( 18 | "github.com/stevejiang/gotable/api/go/table/proto" 19 | ) 20 | 21 | type GetArgs struct { 22 | TableId uint8 23 | RowKey []byte 24 | ColKey []byte 25 | Cas uint32 26 | } 27 | 28 | type GetReply struct { 29 | ErrCode int8 30 | TableId uint8 31 | RowKey []byte 32 | ColKey []byte 33 | Value []byte 34 | Score int64 35 | Cas uint32 36 | } 37 | 38 | type SetArgs struct { 39 | TableId uint8 40 | RowKey []byte 41 | ColKey []byte 42 | Value []byte 43 | Score int64 44 | Cas uint32 45 | } 46 | 47 | type SetReply struct { 48 | ErrCode int8 49 | TableId uint8 50 | RowKey []byte 51 | ColKey []byte 52 | } 53 | 54 | type IncrArgs struct { 55 | TableId uint8 56 | RowKey []byte 57 | ColKey []byte 58 | Score int64 59 | Cas uint32 60 | } 61 | 62 | type IncrReply struct { 63 | ErrCode int8 64 | TableId uint8 65 | RowKey []byte 66 | ColKey []byte 67 | Value []byte 68 | Score int64 69 | } 70 | 71 | type DelArgs GetArgs 72 | type DelReply SetReply 73 | 74 | type MGetArgs []GetArgs 75 | type MSetArgs []SetArgs 76 | type MDelArgs []DelArgs 77 | type MIncrArgs []IncrArgs 78 | 79 | type multiArgs interface { 80 | length() int 81 | toKV(kv []proto.KeyValue) 82 | } 83 | 84 | func (a MGetArgs) length() int { 85 | return len(a) 86 | } 87 | 88 | func (a MGetArgs) toKV(kv []proto.KeyValue) { 89 | for i := 0; i < len(a); i++ { 90 | kv[i].TableId = a[i].TableId 91 | kv[i].RowKey = a[i].RowKey 92 | kv[i].ColKey = a[i].ColKey 93 | kv[i].SetCas(a[i].Cas) 94 | } 95 | } 96 | 97 | func (a MSetArgs) length() int { 98 | return len(a) 99 | } 100 | 101 | func (a MSetArgs) toKV(kv []proto.KeyValue) { 102 | for i := 0; i < len(a); i++ { 103 | kv[i].TableId = a[i].TableId 104 | kv[i].RowKey = a[i].RowKey 105 | kv[i].ColKey = a[i].ColKey 106 | kv[i].SetCas(a[i].Cas) 107 | kv[i].SetScore(a[i].Score) 108 | kv[i].SetValue(a[i].Value) 109 | } 110 | } 111 | 112 | func (a MDelArgs) length() int { 113 | return len(a) 114 | } 115 | 116 | func (a MDelArgs) toKV(kv []proto.KeyValue) { 117 | for i := 0; i < len(a); i++ { 118 | kv[i].TableId = a[i].TableId 119 | kv[i].RowKey = a[i].RowKey 120 | kv[i].ColKey = a[i].ColKey 121 | kv[i].SetCas(a[i].Cas) 122 | } 123 | } 124 | 125 | func (a MIncrArgs) length() int { 126 | return len(a) 127 | } 128 | 129 | func (a MIncrArgs) toKV(kv []proto.KeyValue) { 130 | for i := 0; i < len(a); i++ { 131 | kv[i].TableId = a[i].TableId 132 | kv[i].RowKey = a[i].RowKey 133 | kv[i].ColKey = a[i].ColKey 134 | kv[i].SetCas(a[i].Cas) 135 | kv[i].SetScore(a[i].Score) 136 | } 137 | } 138 | 139 | var emptyBytes = make([]byte, 0) 140 | 141 | func copyBytes(in []byte) []byte { 142 | if in == nil { 143 | return nil 144 | } 145 | if len(in) == 0 { 146 | return emptyBytes 147 | } 148 | var out = make([]byte, len(in)) 149 | copy(out, in) 150 | return out 151 | } 152 | 153 | func (a *MGetArgs) Add(tableId uint8, rowKey, colKey []byte, cas uint32) { 154 | *a = append(*a, GetArgs{tableId, rowKey, colKey, cas}) 155 | } 156 | 157 | func (a *MSetArgs) Add(tableId uint8, rowKey, colKey, value []byte, score int64, cas uint32) { 158 | *a = append(*a, SetArgs{tableId, rowKey, colKey, value, score, cas}) 159 | } 160 | 161 | func (a *MDelArgs) Add(tableId uint8, rowKey, colKey []byte, cas uint32) { 162 | *a = append(*a, DelArgs{tableId, rowKey, colKey, cas}) 163 | } 164 | 165 | func (a *MIncrArgs) Add(tableId uint8, rowKey, colKey []byte, score int64, cas uint32) { 166 | *a = append(*a, IncrArgs{tableId, rowKey, colKey, score, cas}) 167 | } 168 | 169 | type scanContext struct { 170 | tableId uint8 171 | rowKey []byte 172 | zop bool 173 | asc bool // true: Ascending order; false: Descending order 174 | orderByScore bool // true: Score+ColKey; false: ColKey 175 | num int // Max number of scan reply records 176 | } 177 | 178 | type ScanKV struct { 179 | ColKey []byte 180 | Value []byte 181 | Score int64 182 | } 183 | 184 | type ScanReply struct { 185 | TableId uint8 186 | RowKey []byte 187 | Kvs []ScanKV 188 | End bool // false: Not end yet; true: Scan to end, stop now 189 | 190 | ctx scanContext 191 | } 192 | 193 | type dumpContext struct { 194 | oneTable bool // Never change during dump 195 | tableId uint8 // Never change during dump 196 | startSlotId uint16 // Never change during dump 197 | endSlotId uint16 // Never change during dump 198 | lastSlotId uint16 // The last slot ID tried to dump 199 | slotStart bool // Next dump start from new SlotId 200 | } 201 | 202 | type DumpKV struct { 203 | TableId uint8 204 | ColSpace uint8 205 | RowKey []byte 206 | ColKey []byte 207 | Value []byte 208 | Score int64 209 | } 210 | 211 | type DumpReply struct { 212 | Kvs []DumpKV 213 | End bool // false: Not end yet; true: Has scan to end, stop now 214 | 215 | ctx dumpContext 216 | } 217 | -------------------------------------------------------------------------------- /api/c++/codec.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef _GO_TABLE_CODEC_H_ 16 | #define _GO_TABLE_CODEC_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "slice.h" 24 | #include "proto.h" 25 | 26 | namespace gotable { 27 | 28 | using std::string; 29 | using std::vector; 30 | 31 | // CtrlFlag 32 | enum { 33 | CtrlErrCode = 0x1, // Response Error Code 34 | CtrlCas = 0x2, // Compare And Switch 35 | CtrlColSpace = 0x4, 36 | CtrlValue = 0x8, 37 | CtrlScore = 0x10, 38 | }; 39 | 40 | enum { 41 | ColSpaceDefault = 0, // Default column space 42 | ColSpaceScore1 = 1, // rowKey+score+colKey => value 43 | ColSpaceScore2 = 2, // rowKey+colKey => value+score 44 | }; 45 | 46 | // KeyValue=cCtrlFlag+cTableId+[cErrCode]+[cColSpace] 47 | // +cRowKeyLen+sRowKey+wColKeyLen+sColKey 48 | // +[dwValueLen+sValue]+[ddwScore]+[dwCas] 49 | struct KeyValue { 50 | uint8_t ctrlFlag; 51 | int8_t errCode; // default: 0 if missing 52 | uint8_t colSpace; // default: 0 if missing 53 | uint8_t tableId; 54 | Slice rowKey; 55 | Slice colKey; 56 | Slice value; // default: empty if missing 57 | int64_t score; // default: 0 if missing 58 | uint32_t cas; // default: 0 if missing 59 | 60 | KeyValue() : ctrlFlag(0), errCode(0), colSpace(0), tableId(0), rowKey(), colKey(), 61 | value(), score(0), cas(0) {} 62 | 63 | int length(); 64 | int decode(const char* pkg, int len); 65 | int encode(char* pkg, int len); 66 | 67 | void setErrCode(int8_t errCode) { 68 | this->errCode = errCode; 69 | if(errCode != 0) { 70 | this->ctrlFlag |= CtrlErrCode; 71 | } else { 72 | this->ctrlFlag &= (~CtrlErrCode); 73 | } 74 | } 75 | 76 | void setColSpace(uint8_t colSpace) { 77 | this->colSpace = colSpace; 78 | if(colSpace != 0) { 79 | this->ctrlFlag |= CtrlColSpace; 80 | } else { 81 | this->ctrlFlag &= (~CtrlColSpace); 82 | } 83 | } 84 | 85 | void setCas(uint32_t cas) { 86 | this->cas = cas; 87 | if(cas != 0) { 88 | this->ctrlFlag |= CtrlCas; 89 | } else { 90 | this->ctrlFlag &= (~CtrlCas); 91 | } 92 | } 93 | 94 | void setValue(const string& value) { 95 | this->value = value; 96 | if(value.length() != 0) { 97 | this->ctrlFlag |= CtrlValue; 98 | } else { 99 | this->ctrlFlag &= (~CtrlValue); 100 | } 101 | } 102 | 103 | void setScore(int64_t score) { 104 | this->score = score; 105 | if(score != 0) { 106 | this->ctrlFlag |= CtrlScore; 107 | } else { 108 | this->ctrlFlag &= (~CtrlScore); 109 | } 110 | } 111 | }; 112 | 113 | // PkgFlag 114 | enum { 115 | // Common flags 116 | FlagZop = 0x1, // if set, it is a "Z" op 117 | 118 | // (Z)Scan flags 119 | FlagScanAsc = 0x4, // if set, Scan in ASC order, else DESC order 120 | FlagScanKeyStart = 0x8, // if set, Scan start from MIN/MAX key 121 | FlagScanEnd = 0x10, // if set, Scan finished, stop now 122 | 123 | // Dump flags 124 | FlagDumpTable = 0x4, // if set, Dump only one table, else Dump current DB(dbId) 125 | FlagDumpUnitStart = 0x8, // if set, Dump start from new UnitId, else from pivot record 126 | FlagDumpEnd = 0x10, // if set, Dump finished, stop now 127 | }; 128 | 129 | // Get, Set, Del, GetSet, GetDel, ZGet, ZSet, Sync 130 | // PKG=HEAD+cPkgFlag+KeyValue 131 | struct PkgOneOp : public PkgHead, KeyValue { 132 | uint8_t pkgFlag; 133 | 134 | PkgOneOp() : pkgFlag(0) {} 135 | 136 | int length(); 137 | int decode(const char* pkg, int len); 138 | int encode(char* pkg, int len); 139 | }; 140 | 141 | // MGet, MSet, MDel, MZGet, MZSet, MZDel 142 | // PKG=HEAD+cPkgFlag+cErrCode+wNum+KeyValue[wNum] 143 | struct PkgMultiOp : public PkgHead { 144 | uint8_t pkgFlag; 145 | int8_t errCode; 146 | vector kvs; 147 | 148 | PkgMultiOp() : pkgFlag(0), errCode(0) {} 149 | 150 | int length(); 151 | int decode(const char* pkg, int len); 152 | int encode(char* pkg, int len); 153 | }; 154 | 155 | // Scan, ZScan 156 | // PKG=PkgOneOp+wNum 157 | struct PkgScanReq : public PkgOneOp { 158 | uint16_t num; 159 | 160 | PkgScanReq() : num(0) {} 161 | 162 | int length(); 163 | int decode(const char* pkg, int len); 164 | int encode(char* pkg, int len); 165 | }; 166 | 167 | // Scan, ZScan 168 | typedef PkgMultiOp PkgScanResp; 169 | 170 | // Dump 171 | // PKG=PkgOneOp+wStartUnitId+wEndUnitId 172 | struct PkgDumpReq : public PkgOneOp { 173 | uint16_t startUnitId; // Dump start unit ID (included) 174 | uint16_t endUnitId; // Dump finish unit ID (included) 175 | 176 | PkgDumpReq() : startUnitId(0), endUnitId(0) {} 177 | 178 | int length(); 179 | int decode(const char* pkg, int len); 180 | int encode(char* pkg, int len); 181 | }; 182 | 183 | // Dump 184 | // PKG=PkgMultiOp+wStartUnitId+wEndUnitId+wLastUnitId 185 | struct PkgDumpResp : public PkgMultiOp { 186 | uint16_t startUnitId; 187 | uint16_t endUnitId; 188 | uint16_t lastUnitId; // Last Unit ID tried to dump 189 | 190 | PkgDumpResp() : startUnitId(0), endUnitId(0), lastUnitId(0) {} 191 | 192 | int length(); 193 | int decode(const char* pkg, int len); 194 | int encode(char* pkg, int len); 195 | }; 196 | 197 | } // namespace gotable 198 | #endif 199 | -------------------------------------------------------------------------------- /cmd/gotable-bench/histogram.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "math" 21 | ) 22 | 23 | const ( 24 | kNumBuckets = 154 25 | ) 26 | 27 | var kBucketLimit = [kNumBuckets]float64{ 28 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20, 25, 30, 35, 40, 45, 29 | 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 250, 300, 350, 400, 450, 30 | 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000, 2500, 3000, 31 | 3500, 4000, 4500, 5000, 6000, 7000, 8000, 9000, 10000, 12000, 14000, 32 | 16000, 18000, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 60000, 33 | 70000, 80000, 90000, 100000, 120000, 140000, 160000, 180000, 200000, 34 | 250000, 300000, 350000, 400000, 450000, 500000, 600000, 700000, 800000, 35 | 900000, 1000000, 1200000, 1400000, 1600000, 1800000, 2000000, 2500000, 36 | 3000000, 3500000, 4000000, 4500000, 5000000, 6000000, 7000000, 8000000, 37 | 9000000, 10000000, 12000000, 14000000, 16000000, 18000000, 20000000, 38 | 25000000, 30000000, 35000000, 40000000, 45000000, 50000000, 60000000, 39 | 70000000, 80000000, 90000000, 100000000, 120000000, 140000000, 160000000, 40 | 180000000, 200000000, 250000000, 300000000, 350000000, 400000000, 41 | 450000000, 500000000, 600000000, 700000000, 800000000, 900000000, 42 | 1000000000, 1200000000, 1400000000, 1600000000, 1800000000, 2000000000, 43 | 2500000000.0, 3000000000.0, 3500000000.0, 4000000000.0, 4500000000.0, 44 | 5000000000.0, 6000000000.0, 7000000000.0, 8000000000.0, 9000000000.0, 45 | 1e200, 46 | } 47 | 48 | type Histogram struct { 49 | min_ float64 50 | max_ float64 51 | num_ float64 52 | sum_ float64 53 | sum_squares_ float64 54 | 55 | buckets_ [kNumBuckets]float64 56 | } 57 | 58 | func (h *Histogram) Clear() { 59 | h.min_ = kBucketLimit[kNumBuckets-1] 60 | h.max_ = 0 61 | h.num_ = 0 62 | h.sum_ = 0 63 | h.sum_squares_ = 0 64 | for i := 0; i < kNumBuckets; i++ { 65 | h.buckets_[i] = 0 66 | } 67 | } 68 | 69 | func (h *Histogram) Add(value float64) { 70 | // Linear search is fast enough for our usage in db_bench 71 | var b = 0 72 | for b < kNumBuckets-1 && kBucketLimit[b] <= value { 73 | b++ 74 | } 75 | h.buckets_[b] += 1.0 76 | if h.min_ > value { 77 | h.min_ = value 78 | } 79 | if h.max_ < value { 80 | h.max_ = value 81 | } 82 | h.num_++ 83 | h.sum_ += value 84 | h.sum_squares_ += (value * value) 85 | } 86 | 87 | func (h *Histogram) Merge(other *Histogram) { 88 | if other.min_ < h.min_ { 89 | h.min_ = other.min_ 90 | } 91 | if other.max_ > h.max_ { 92 | h.max_ = other.max_ 93 | } 94 | h.num_ += other.num_ 95 | h.sum_ += other.sum_ 96 | h.sum_squares_ += other.sum_squares_ 97 | for b := 0; b < kNumBuckets; b++ { 98 | h.buckets_[b] += other.buckets_[b] 99 | } 100 | } 101 | 102 | func (h *Histogram) Median() float64 { 103 | return h.Percentile(50.0) 104 | } 105 | 106 | func (h *Histogram) Percentile(p float64) float64 { 107 | var threshold = h.num_ * (p / 100.0) 108 | var sum float64 = 0 109 | for b := 0; b < kNumBuckets; b++ { 110 | sum += h.buckets_[b] 111 | if sum >= threshold { 112 | // Scale linearly within this bucket 113 | var left_point float64 114 | if b != 0 { 115 | left_point = kBucketLimit[b-1] 116 | } 117 | 118 | var right_point = kBucketLimit[b] 119 | var left_sum = sum - h.buckets_[b] 120 | var right_sum = sum 121 | var pos = (threshold - left_sum) / (right_sum - left_sum) 122 | var r = left_point + (right_point-left_point)*pos 123 | if r < h.min_ { 124 | r = h.min_ 125 | } 126 | if r > h.max_ { 127 | r = h.max_ 128 | } 129 | return r 130 | } 131 | } 132 | return h.max_ 133 | } 134 | 135 | func (h *Histogram) Average() float64 { 136 | if h.num_ == 0.0 { 137 | return 0 138 | } 139 | return h.sum_ / h.num_ 140 | } 141 | 142 | func (h *Histogram) StandardDeviation() float64 { 143 | if h.num_ == 0.0 { 144 | return 0 145 | } 146 | var variance = (h.sum_squares_*h.num_ - h.sum_*h.sum_) / (h.num_ * h.num_) 147 | return math.Sqrt(variance) 148 | } 149 | 150 | func (h *Histogram) ToString() string { 151 | var s bytes.Buffer 152 | s.WriteString(fmt.Sprintf("Count: %.0f Average: %.4f StdDev: %.2f\n", 153 | h.num_, h.Average(), h.StandardDeviation())) 154 | 155 | var minRes float64 156 | if h.num_ != 0.0 { 157 | minRes = h.min_ 158 | } 159 | s.WriteString(fmt.Sprintf("Min: %.4f Median: %.4f Max: %.4f\n", 160 | minRes, h.Median(), h.max_)) 161 | s.WriteString("------------------------------------------------------\n") 162 | 163 | var mult = 100.0 / h.num_ 164 | var sum float64 165 | for b := 0; b < kNumBuckets; b++ { 166 | if h.buckets_[b] <= 0.0 { 167 | continue 168 | } 169 | sum += h.buckets_[b] 170 | var leftRes float64 171 | if b != 0 { 172 | leftRes = kBucketLimit[b-1] 173 | } 174 | 175 | s.WriteString(fmt.Sprintf("[ %7.0f, %7.0f ) %7.0f %7.3f%% %7.3f%% ", 176 | leftRes, // left 177 | kBucketLimit[b], // right 178 | h.buckets_[b], // count 179 | mult*h.buckets_[b], // percentage 180 | mult*sum)) 181 | 182 | // Add hash marks based on percentage; 20 marks for 100%. 183 | var marks = int(20*(h.buckets_[b]/h.num_) + 0.5) 184 | for i := 0; i < marks; i++ { 185 | s.WriteByte('#') 186 | } 187 | s.WriteByte('\n') 188 | } 189 | 190 | return s.String() 191 | } 192 | -------------------------------------------------------------------------------- /server/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server 16 | 17 | import ( 18 | "bufio" 19 | "io" 20 | "log" 21 | "net" 22 | "sync" 23 | "sync/atomic" 24 | 25 | "github.com/stevejiang/gotable/api/go/table/proto" 26 | "github.com/stevejiang/gotable/store" 27 | "github.com/stevejiang/gotable/util" 28 | ) 29 | 30 | const ( 31 | ClientTypeNormal = iota 32 | ClientTypeMaster 33 | ClientTypeSlave 34 | ) 35 | 36 | type Request struct { 37 | Cli *Client 38 | Slv *slave 39 | store.PkgArgs 40 | } 41 | 42 | type RequestChan struct { 43 | WriteReqChan chan *Request 44 | ReadReqChan chan *Request 45 | SyncReqChan chan *Request 46 | DumpReqChan chan *Request 47 | CtrlReqChan chan *Request 48 | } 49 | 50 | type Client struct { 51 | c net.Conn 52 | r *bufio.Reader 53 | respChan chan []byte 54 | authEnabled bool 55 | 56 | // atomic 57 | closed uint32 58 | cliType uint32 59 | 60 | // protects following 61 | mtx sync.RWMutex 62 | authBM *util.BitMap 63 | shutdown bool 64 | } 65 | 66 | func NewClient(conn net.Conn, authEnabled bool) *Client { 67 | var c = new(Client) 68 | c.c = conn 69 | c.r = bufio.NewReader(conn) 70 | c.respChan = make(chan []byte, 64) 71 | c.authEnabled = authEnabled 72 | atomic.StoreUint32(&c.cliType, ClientTypeNormal) 73 | return c 74 | } 75 | 76 | func (c *Client) AddResp(pkg []byte) { 77 | if !c.IsClosed() { 78 | defer func() { 79 | recover() 80 | }() 81 | c.respChan <- pkg 82 | } 83 | } 84 | 85 | func (c *Client) Close() { 86 | if !c.IsClosed() { 87 | atomic.AddUint32(&c.closed, 1) 88 | 89 | c.mtx.Lock() 90 | if !c.shutdown { 91 | c.shutdown = true 92 | c.mtx.Unlock() 93 | } else { 94 | c.mtx.Unlock() 95 | return 96 | } 97 | 98 | c.c.Close() 99 | close(c.respChan) 100 | 101 | //log.Printf("Close client %p\n", c) 102 | } 103 | } 104 | 105 | func (c *Client) LocalAddr() net.Addr { 106 | return c.c.LocalAddr() 107 | } 108 | 109 | func (c *Client) RemoteAddr() net.Addr { 110 | return c.c.RemoteAddr() 111 | } 112 | 113 | func (c *Client) IsClosed() bool { 114 | return atomic.LoadUint32(&c.closed) > 0 115 | } 116 | 117 | func (c *Client) SetClientType(cliType uint32) { 118 | atomic.StoreUint32(&c.cliType, cliType) 119 | } 120 | 121 | func (c *Client) ClientType() uint32 { 122 | return atomic.LoadUint32(&c.cliType) 123 | } 124 | 125 | // Check whether dbId is authencated. 126 | // If dbId is 255, it means admin privilege. 127 | func (c *Client) IsAuth(dbId uint8) bool { 128 | if c == nil || !c.authEnabled { 129 | return true 130 | } 131 | 132 | c.mtx.RLock() 133 | 134 | if c.authBM == nil { 135 | c.mtx.RUnlock() 136 | return false 137 | } 138 | 139 | if c.authBM.Get(proto.AdminDbId) { 140 | c.mtx.RUnlock() 141 | return true 142 | } 143 | 144 | if dbId != proto.AdminDbId && c.authBM.Get(uint(dbId)) { 145 | c.mtx.RUnlock() 146 | return true 147 | } 148 | 149 | c.mtx.RUnlock() 150 | return false 151 | } 152 | 153 | func (c *Client) SetAuth(dbId uint8) { 154 | if c != nil && c.authEnabled { 155 | c.mtx.Lock() 156 | if c.authBM == nil { 157 | c.authBM = util.NewBitMap(256 / 8) 158 | } 159 | 160 | c.authBM.Set(uint(dbId)) 161 | c.mtx.Unlock() 162 | } 163 | } 164 | 165 | func (c *Client) GoRecvRequest(ch *RequestChan, slv *slave) { 166 | var headBuf = make([]byte, proto.HeadSize) 167 | var head proto.PkgHead 168 | for { 169 | pkg, err := proto.ReadPkg(c.r, headBuf, &head, nil) 170 | if err != nil { 171 | if !c.IsClosed() && err != io.EOF && err != io.ErrUnexpectedEOF { 172 | log.Printf("ReadPkg failed: %s, close client!\n", err) 173 | } 174 | c.Close() 175 | return 176 | } 177 | 178 | //log.Printf("recv(%s): [0x%X\t%d\t%d]\n", 179 | // c.c.RemoteAddr(), head.Cmd, head.DbId, head.Seq) 180 | 181 | var req = Request{c, slv, store.PkgArgs{Cmd: head.Cmd, DbId: head.DbId, Seq: head.Seq, Pkg: pkg}} 182 | 183 | switch head.Cmd { 184 | case proto.CmdAuth: 185 | fallthrough 186 | case proto.CmdPing: 187 | fallthrough 188 | case proto.CmdScan: 189 | fallthrough 190 | case proto.CmdMGet: 191 | fallthrough 192 | case proto.CmdGet: 193 | ch.ReadReqChan <- &req 194 | case proto.CmdMIncr: 195 | fallthrough 196 | case proto.CmdMDel: 197 | fallthrough 198 | case proto.CmdMSet: 199 | fallthrough 200 | case proto.CmdIncr: 201 | fallthrough 202 | case proto.CmdDel: 203 | fallthrough 204 | case proto.CmdSet: 205 | if ClientTypeNormal == c.ClientType() { 206 | ch.WriteReqChan <- &req 207 | } else { 208 | ch.SyncReqChan <- &req 209 | } 210 | case proto.CmdSyncSt: 211 | fallthrough 212 | case proto.CmdSync: 213 | if ClientTypeNormal != c.ClientType() { 214 | ch.SyncReqChan <- &req 215 | } 216 | case proto.CmdDump: 217 | ch.DumpReqChan <- &req 218 | case proto.CmdDelSlot: 219 | fallthrough 220 | case proto.CmdSlaveSt: 221 | fallthrough 222 | case proto.CmdMigrate: 223 | fallthrough 224 | case proto.CmdSlaveOf: 225 | ch.CtrlReqChan <- &req 226 | default: 227 | log.Printf("Invalid cmd 0x%X\n", head.Cmd) 228 | c.Close() 229 | return 230 | } 231 | } 232 | } 233 | 234 | func (c *Client) GoSendResponse() { 235 | var err error 236 | for { 237 | select { 238 | case pkg, ok := <-c.respChan: 239 | if !ok { 240 | //log.Printf("channel closed %p\n", c) 241 | return 242 | } 243 | 244 | if err == nil && !c.IsClosed() { 245 | //log.Printf("send(%s): [0x%X\t%d\t%d]\n", 246 | // c.c.RemoteAddr(), resp.Cmd, resp.DbId, resp.Seq) 247 | _, err = c.c.Write(pkg) 248 | if err != nil { 249 | c.Close() 250 | } 251 | } 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /cmd/gotable-cli/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "github.com/GeertJohan/go.linenoise" 21 | "os" 22 | "regexp" 23 | "strings" 24 | ) 25 | 26 | var ( 27 | address = flag.String("h", "127.0.0.1:6688", "Server host address ip:port") 28 | network = flag.String("N", "tcp", "Server network: tcp, tcp4, tcp6, unix") 29 | ) 30 | 31 | func main() { 32 | flag.Parse() 33 | 34 | var cli = newClient() 35 | if cli == nil { 36 | writeln("Failed to create client!\n") 37 | flag.Usage() 38 | return 39 | } 40 | 41 | re, _ := regexp.Compile(`'[^\'\"]*'|[qQ]?"[^\'\"]*"|[^\s\'\"]+`) 42 | 43 | writeln("Welcome to GoTable.") 44 | //writeHelp() 45 | for { 46 | line, err := linenoise.Line(fmt.Sprintf("gotable@%d> ", cli.dbId)) 47 | if err != nil { 48 | if err == linenoise.KillSignalError { 49 | os.Exit(0) 50 | } 51 | writeln("Unexpected error: " + err.Error()) 52 | os.Exit(0) 53 | } 54 | fields := fullMatchString(re, line) 55 | 56 | if len(line) == 0 || !notWhiteSpace(line, 0, len(line)) { 57 | continue 58 | } 59 | 60 | linenoise.AddHistory(line) 61 | 62 | if len(fields) == 0 { 63 | writeUnrecognized() 64 | continue 65 | } 66 | 67 | var cmd = strings.ToLower(fields[0]) 68 | switch cmd { 69 | case "ping": 70 | checkError(cli.ping()) 71 | case "get": 72 | checkError(cli.get(false, fields[1:])) 73 | case "set": 74 | checkError(cli.set(false, fields[1:])) 75 | case "del": 76 | checkError(cli.del(false, fields[1:])) 77 | case "incr": 78 | checkError(cli.incr(false, fields[1:])) 79 | case "zget": 80 | checkError(cli.get(true, fields[1:])) 81 | case "zset": 82 | checkError(cli.set(true, fields[1:])) 83 | case "zdel": 84 | checkError(cli.del(true, fields[1:])) 85 | case "zincr": 86 | checkError(cli.incr(true, fields[1:])) 87 | case "scan": 88 | checkError(cli.scan(fields[1:])) 89 | case "zscan": 90 | checkError(cli.zscan(fields[1:])) 91 | case "auth": 92 | checkError(cli.auth(fields[1:])) 93 | case "select": 94 | checkError(cli.use(fields[1:])) 95 | case "slaveof": 96 | checkError(cli.slaveOf(fields[1:])) 97 | case "dump": 98 | checkError(cli.dump(fields[1:])) 99 | 100 | case "?": 101 | fallthrough 102 | case "help": 103 | writeHelp() 104 | case "clear": 105 | linenoise.Clear() 106 | case "exit": 107 | fallthrough 108 | case "q": 109 | fallthrough 110 | case "quit": 111 | quit() 112 | default: 113 | writeUnrecognized() 114 | } 115 | } 116 | } 117 | 118 | func quit() { 119 | writeln("Bye.") 120 | writeln("") 121 | os.Exit(0) 122 | } 123 | 124 | func writeln(msg string) { 125 | fmt.Fprintln(os.Stderr, msg) 126 | } 127 | 128 | func writeHelp() { 129 | writeln(" help print help message") 130 | writeln(" auth authenticate to the database") 131 | writeln("select select database [0 ~ 254] to use") 132 | writeln(" set [score]") 133 | writeln(" set key/value in selected database") 134 | writeln(" get ") 135 | writeln(" get key/value in selected database") 136 | writeln(" del ") 137 | writeln(" delete key in selected database") 138 | writeln(" incr [score]") 139 | writeln(" increase key score in selected database") 140 | writeln(" zset [score]") 141 | writeln(" zset key/value in selected database") 142 | writeln(" zget ") 143 | writeln(" zget key/value in selected database") 144 | writeln(" zdel ") 145 | writeln(" zdel key in selected database") 146 | writeln(" zincr [score]") 147 | writeln(" zincr key score in selected database") 148 | writeln(" scan [num]") 149 | writeln(" scan columns of rowKey in ASC order") 150 | writeln(" zscan [num]") 151 | writeln(" zscan columns of rowKey in ASC order by score") 152 | writeln(" dump [tableId] dump the selected database or the table. Fields are:") 153 | writeln(" tableId, rowKey, colSpace, colKey, value, score") 154 | writeln("slaveof [host] be slave of master host(ip:port)") 155 | writeln(" ping ping the server") 156 | writeln(" clear clear the screen") 157 | writeln(" quit exit") 158 | writeln("") 159 | writeln("Use the arrow up and down keys to walk through history.") 160 | writeln("") 161 | } 162 | 163 | func writeUnrecognized() { 164 | writeln("Unrecognized command line. Use 'help'.") 165 | } 166 | 167 | func checkError(err error) { 168 | if err != nil { 169 | writeln("Failed with error: " + err.Error()) 170 | } 171 | } 172 | 173 | func isWhiteSpace(c byte) bool { 174 | //\s whitespace (== [\t\n\f\r ]) 175 | if c != ' ' && c != '\t' && c != '\n' && c != '\f' && c != '\r' { 176 | return false 177 | } 178 | return true 179 | } 180 | 181 | func notWhiteSpace(s string, a, b int) bool { 182 | for j := a; j < b; j++ { 183 | if !isWhiteSpace(s[j]) { 184 | return true 185 | } 186 | } 187 | return false 188 | } 189 | 190 | func fullMatchString(re *regexp.Regexp, s string) []string { 191 | var rs = re.FindAllStringIndex(s, -1) 192 | var cur int 193 | for _, r := range rs { 194 | if notWhiteSpace(s, cur, r[0]) { 195 | return nil 196 | } 197 | if cur > 0 && cur == r[0] { 198 | return nil 199 | } 200 | cur = r[1] 201 | } 202 | 203 | if notWhiteSpace(s, cur, len(s)) { 204 | return nil 205 | } 206 | 207 | return re.FindAllString(s, -1) 208 | } 209 | -------------------------------------------------------------------------------- /config/master.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "github.com/stevejiang/gotable/ctrl" 21 | "log" 22 | "os" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | const ( 28 | masterConfigFile = "master.conf" 29 | ) 30 | 31 | type MasterInfo struct { 32 | MasterAddr string // Master address ip:host 33 | SlaveAddr string // This server address ip:host 34 | Migration bool // true: Migration; false: Normal master/slave 35 | SlotId uint16 // Only meaningful for migration 36 | Status int // Status of Slave/Migration 37 | } 38 | 39 | type MasterEncoding struct { 40 | HasMaster bool // true: Has master; false: No master/No migration 41 | MasterInfo 42 | LastTime time.Time // Last change time 43 | } 44 | 45 | type MasterConfig struct { 46 | dir string 47 | 48 | mtx sync.RWMutex // protects following 49 | m MasterEncoding 50 | } 51 | 52 | func NewMasterConfig(dir string) *MasterConfig { 53 | err := os.MkdirAll(dir, os.ModeDir|os.ModePerm) 54 | if err != nil { 55 | log.Printf("Invalid master config directory (%s): %s\n", dir, err) 56 | return nil 57 | } 58 | 59 | mc := new(MasterConfig) 60 | mc.dir = dir 61 | 62 | err = mc.load(fmt.Sprintf("%s/%s", mc.dir, masterConfigFile)) 63 | if err != nil { 64 | log.Printf("Load master config failed: %s\n", err) 65 | return nil 66 | } 67 | 68 | mc.mtx.Lock() 69 | if mc.m.HasMaster { 70 | if mc.m.Migration { 71 | // Need to clear old data first 72 | if mc.m.Status != ctrl.SlaveClear { 73 | mc.m.Status = ctrl.SlaveNeedClear 74 | } 75 | } else { 76 | if mc.m.Status != ctrl.SlaveNeedClear && 77 | mc.m.Status != ctrl.SlaveClear { 78 | mc.m.Status = ctrl.SlaveInit 79 | } 80 | } 81 | } 82 | mc.mtx.Unlock() 83 | 84 | return mc 85 | } 86 | 87 | func (mc *MasterConfig) load(confFile string) error { 88 | file, err := os.Open(confFile) 89 | if err != nil { 90 | if os.IsNotExist(err) { 91 | tmpFile := fmt.Sprintf("%s.tmp", confFile) 92 | file, err = os.Open(tmpFile) 93 | if err != nil { 94 | if os.IsNotExist(err) { 95 | return nil 96 | } 97 | } else { 98 | os.Rename(tmpFile, confFile) 99 | } 100 | } 101 | return err 102 | } 103 | 104 | de := json.NewDecoder(file) 105 | 106 | var m MasterEncoding 107 | err = de.Decode(&m) 108 | if err != nil { 109 | file.Close() 110 | return err 111 | } 112 | file.Close() 113 | 114 | // Migration shouldn't be interrupted. 115 | // If server restarted, remove migration config. 116 | // Administrator should send migrate command again. 117 | if m.HasMaster && m.Migration { 118 | m.HasMaster = false 119 | mc.save(&m) 120 | } 121 | 122 | mc.mtx.Lock() 123 | mc.m = m 124 | mc.mtx.Unlock() 125 | 126 | return nil 127 | } 128 | 129 | func (mc *MasterConfig) save(m *MasterEncoding) error { 130 | confFile := fmt.Sprintf("%s/%s", mc.dir, masterConfigFile) 131 | tmpFile := fmt.Sprintf("%s.tmp", confFile) 132 | file, err := os.Create(tmpFile) 133 | if err != nil { 134 | return err 135 | } 136 | defer file.Close() 137 | 138 | en := json.NewEncoder(file) 139 | 140 | err = en.Encode(m) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | err = file.Sync() 146 | if err != nil { 147 | return err 148 | } 149 | 150 | mc.mtx.Lock() 151 | mc.m = *m 152 | os.Rename(tmpFile, confFile) 153 | mc.mtx.Unlock() 154 | 155 | return nil 156 | } 157 | 158 | func (mc *MasterConfig) SetMaster(masterAddr, slaveAddr string) error { 159 | mc.mtx.RLock() 160 | var m = mc.m 161 | mc.mtx.RUnlock() 162 | 163 | if m.HasMaster && m.Migration { 164 | return fmt.Errorf("server is under migration and cannot change master/slave") 165 | } 166 | 167 | if len(masterAddr) > 0 { 168 | m.HasMaster = true 169 | m.LastTime = time.Now() 170 | m.MasterAddr = masterAddr 171 | m.SlaveAddr = slaveAddr 172 | m.Migration = false 173 | m.SlotId = ctrl.TotalSlotNum // Exceed 174 | m.Status = ctrl.SlaveInit 175 | } else { 176 | m.HasMaster = false 177 | m.LastTime = time.Now() 178 | m.Status = ctrl.NotSlave 179 | } 180 | 181 | return mc.save(&m) 182 | } 183 | 184 | func (mc *MasterConfig) SetMigration(masterAddr, slaveAddr string, slotId uint16) error { 185 | mc.mtx.RLock() 186 | var m = mc.m 187 | mc.mtx.RUnlock() 188 | 189 | if m.HasMaster { 190 | if !m.Migration { 191 | return fmt.Errorf("server is a slave and cannot start/stop migration") 192 | } else if m.Status == ctrl.SlaveClear { 193 | return fmt.Errorf("cannot update migration config when clearing data") 194 | } 195 | } 196 | 197 | if len(masterAddr) > 0 { 198 | if m.HasMaster && m.Migration && m.SlotId != slotId { 199 | return fmt.Errorf("cannot start more than 1 migration") 200 | } 201 | if slotId >= ctrl.TotalSlotNum { 202 | return fmt.Errorf("migrate slot id out of range") 203 | } 204 | 205 | m.HasMaster = true 206 | m.LastTime = time.Now() 207 | m.MasterAddr = masterAddr 208 | m.SlaveAddr = slaveAddr 209 | m.Migration = true 210 | m.SlotId = slotId 211 | m.Status = ctrl.SlaveInit 212 | } else { 213 | m.HasMaster = false 214 | m.LastTime = time.Now() 215 | m.Status = ctrl.NotSlave 216 | } 217 | 218 | return mc.save(&m) 219 | } 220 | 221 | func (mc *MasterConfig) SetStatus(status int) error { 222 | var changed bool 223 | mc.mtx.Lock() 224 | if mc.m.HasMaster { 225 | if status == ctrl.NotSlave { 226 | mc.m.HasMaster = false 227 | changed = true 228 | } 229 | if mc.m.Status != status { 230 | mc.m.Status = status 231 | changed = true 232 | } 233 | } 234 | var m = mc.m 235 | mc.mtx.Unlock() 236 | 237 | if changed { 238 | return mc.save(&m) 239 | } else { 240 | return nil 241 | } 242 | } 243 | 244 | func (mc *MasterConfig) Status() int { 245 | var st int = ctrl.NotSlave 246 | mc.mtx.RLock() 247 | if mc.m.HasMaster { 248 | st = mc.m.Status 249 | } 250 | mc.mtx.RUnlock() 251 | 252 | return st 253 | } 254 | 255 | func (mc *MasterConfig) GetMaster() MasterInfo { 256 | var m MasterInfo 257 | mc.mtx.RLock() 258 | if mc.m.HasMaster { 259 | m = mc.m.MasterInfo 260 | } 261 | mc.mtx.RUnlock() 262 | 263 | if len(m.MasterAddr) == 0 { 264 | m.Status = ctrl.NotSlave 265 | } 266 | 267 | return m 268 | } 269 | 270 | func (mc *MasterConfig) GetMasterSlot() (bool, bool, uint16) { 271 | var hasMaster, migration bool 272 | var slotId uint16 273 | mc.mtx.RLock() 274 | if mc.m.HasMaster { 275 | hasMaster = true 276 | migration = mc.m.Migration 277 | slotId = mc.m.SlotId 278 | } 279 | mc.mtx.RUnlock() 280 | 281 | return hasMaster, migration, slotId 282 | } 283 | -------------------------------------------------------------------------------- /binlog/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package binlog 16 | 17 | import ( 18 | "bufio" 19 | "errors" 20 | "log" 21 | "os" 22 | 23 | "github.com/stevejiang/gotable/api/go/table/proto" 24 | ) 25 | 26 | var ( 27 | ErrLogMissing = errors.New("log file missing") 28 | ErrUnexpected = errors.New("unexpected binlog reader error") 29 | ) 30 | 31 | type Reader struct { 32 | bin *BinLog 33 | curFile *os.File 34 | curBufR *bufio.Reader 35 | curMemPos int // -1: means read from file; >=0: means read from memory buffer 36 | curInfo fileInfo 37 | 38 | // temp variable 39 | headBuf []byte 40 | head proto.PkgHead 41 | 42 | // protected by bin.mtx 43 | rseq *readerSeq 44 | } 45 | 46 | func NewReader(bin *BinLog) *Reader { 47 | var r = new(Reader) 48 | r.bin = bin 49 | r.curMemPos = -1 50 | r.rseq = new(readerSeq) 51 | r.headBuf = make([]byte, proto.HeadSize) 52 | 53 | return r 54 | } 55 | 56 | func (r *Reader) Close() { 57 | if r.curFile != nil { 58 | r.curFile.Close() 59 | r.curFile = nil 60 | } 61 | 62 | r.bin.mtx.Lock() 63 | defer r.bin.mtx.Unlock() 64 | for index, tmp := range r.bin.rseqs { 65 | if tmp == r.rseq { 66 | copy(r.bin.rseqs[index:], r.bin.rseqs[index+1:]) 67 | r.bin.rseqs = r.bin.rseqs[:len(r.bin.rseqs)-1] 68 | break 69 | } 70 | } 71 | 72 | r.bin = nil 73 | r.curBufR = nil 74 | r.curMemPos = -1 75 | r.curInfo.Idx = 0 76 | r.rseq = nil 77 | } 78 | 79 | func (r *Reader) Init(lastSeq uint64) error { 80 | var err error 81 | r.bin.mtx.Lock() 82 | 83 | r.bin.rseqs = append(r.bin.rseqs, r.rseq) 84 | r.rseq.seq = lastSeq 85 | 86 | var index = -1 87 | for i, f := range r.bin.infos { 88 | index = i 89 | if lastSeq < f.MinSeq { 90 | break 91 | } 92 | 93 | if f.MaxSeq >= lastSeq || f.MaxSeq == 0 { 94 | break 95 | } 96 | } 97 | 98 | if index < 0 { 99 | r.bin.mtx.Unlock() // unlock immediately 100 | if lastSeq > 0 && lastSeq != MinNormalSeq { 101 | return ErrLogMissing 102 | } 103 | r.curMemPos = -1 104 | r.curInfo.Idx = 0 105 | return nil 106 | } else { 107 | r.curInfo = *r.bin.infos[index] 108 | if lastSeq > 0 && lastSeq != MinNormalSeq && r.curInfo.MinSeq > lastSeq+1 { 109 | r.bin.mtx.Unlock() // unlock immediately 110 | return ErrLogMissing 111 | } 112 | } 113 | 114 | r.rseq.seq = r.curInfo.MinSeq 115 | 116 | if r.curInfo.Done { 117 | r.bin.mtx.Unlock() // unlock immediately 118 | 119 | var name = r.bin.GetBinFileName(r.curInfo.Idx) 120 | r.curFile, err = os.Open(name) 121 | if err != nil { 122 | log.Printf("open file failed: (%s) %s\n", name, err) 123 | return err 124 | } 125 | 126 | if r.curBufR == nil { 127 | r.curBufR = bufio.NewReader(r.curFile) 128 | } else { 129 | r.curBufR.Reset(r.curFile) 130 | } 131 | 132 | if r.curInfo.MinSeq < lastSeq+1 { 133 | var pkgBuf = make([]byte, 4096) 134 | for { 135 | _, err = proto.ReadPkg(r.curBufR, r.headBuf, &r.head, pkgBuf) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | if r.head.Seq >= lastSeq { 141 | break 142 | } 143 | } 144 | } 145 | } else { 146 | defer r.bin.mtx.Unlock() // wait until func finished 147 | 148 | if r.curInfo.Idx != r.bin.fileIdx { 149 | log.Printf("invalid file index (%d, %d)\n", r.curInfo.Idx, r.bin.fileIdx) 150 | return ErrUnexpected 151 | } 152 | r.curMemPos = 0 153 | 154 | if r.curInfo.MinSeq < lastSeq+1 { 155 | for { 156 | if r.curMemPos+proto.HeadSize > r.bin.usedLen { 157 | return ErrUnexpected 158 | } 159 | 160 | _, err = r.head.Decode(r.bin.memlog[r.curMemPos:]) 161 | if err != nil { 162 | return ErrUnexpected 163 | } 164 | 165 | if r.curMemPos+int(r.head.PkgLen) > r.bin.usedLen { 166 | return ErrUnexpected 167 | } 168 | 169 | r.curMemPos += int(r.head.PkgLen) 170 | if r.head.Seq >= lastSeq { 171 | break 172 | } 173 | } 174 | } 175 | } 176 | 177 | return nil 178 | } 179 | 180 | func (r *Reader) nextFile() []byte { 181 | if r.curFile != nil { 182 | r.curFile.Close() 183 | r.curFile = nil 184 | } 185 | 186 | r.curMemPos = -1 187 | 188 | var find bool 189 | var curIdx = r.curInfo.Idx 190 | 191 | r.bin.mtx.Lock() 192 | var memFileIdx = r.bin.fileIdx 193 | var infos = r.bin.infos 194 | if curIdx > 0 { 195 | for i := len(infos) - 1; i >= 0; i-- { 196 | if infos[i].Idx <= curIdx { 197 | break 198 | } 199 | if infos[i].Idx == curIdx+1 || (i > 0 && infos[i-1].Idx == curIdx) { 200 | r.curInfo = *infos[i] 201 | find = true 202 | break 203 | } 204 | } 205 | } else if len(infos) > 0 { 206 | r.curInfo = *infos[0] 207 | find = true 208 | } 209 | if find { 210 | r.rseq.seq = r.curInfo.MinSeq 211 | } 212 | r.bin.mtx.Unlock() 213 | 214 | if !find { 215 | return nil 216 | } 217 | 218 | var err error 219 | if r.curInfo.Done { 220 | var name = r.bin.GetBinFileName(r.curInfo.Idx) 221 | r.curFile, err = os.Open(name) 222 | if err != nil { 223 | log.Printf("open file failed: (%s) %s\n", name, err) 224 | return nil 225 | } 226 | 227 | if r.curBufR == nil { 228 | r.curBufR = bufio.NewReader(r.curFile) 229 | } else { 230 | r.curBufR.Reset(r.curFile) 231 | } 232 | } else { 233 | if r.curInfo.Idx != memFileIdx { 234 | log.Printf("invalid file index (%d, %d)\n", r.curInfo.Idx, memFileIdx) 235 | return nil 236 | } 237 | r.curMemPos = 0 238 | } 239 | 240 | return r.next() 241 | } 242 | 243 | func (r *Reader) next() []byte { 244 | var err error 245 | if r.curFile != nil { 246 | pkg, err := proto.ReadPkg(r.curBufR, r.headBuf, &r.head, nil) 247 | if err != nil { 248 | return r.nextFile() 249 | } 250 | 251 | return pkg 252 | } else if r.curMemPos >= 0 { 253 | r.bin.mtx.Lock() 254 | if r.curInfo.Idx != r.bin.fileIdx { 255 | for _, f := range r.bin.infos { 256 | if f.Idx == r.curInfo.Idx { 257 | r.curInfo = *f 258 | break 259 | } 260 | } 261 | r.bin.mtx.Unlock() // unlock immediately 262 | 263 | var name = r.bin.GetBinFileName(r.curInfo.Idx) 264 | r.curFile, err = os.Open(name) 265 | if err != nil { 266 | log.Printf("open file failed: (%s) %s\n", name, err) 267 | return nil 268 | } 269 | 270 | if r.curBufR == nil { 271 | r.curBufR = bufio.NewReader(r.curFile) 272 | } else { 273 | r.curBufR.Reset(r.curFile) 274 | } 275 | 276 | r.curFile.Seek(int64(r.curMemPos), 0) 277 | r.curMemPos = -1 278 | return r.next() 279 | } 280 | 281 | defer r.bin.mtx.Unlock() // wait to finish 282 | 283 | if r.curMemPos+proto.HeadSize > r.bin.usedLen { 284 | return nil 285 | } 286 | _, err = r.head.Decode(r.bin.memlog[r.curMemPos:]) 287 | if err != nil { 288 | return nil 289 | } 290 | 291 | if r.curMemPos+int(r.head.PkgLen) > r.bin.usedLen { 292 | return nil 293 | } 294 | 295 | var pkg = make([]byte, int(r.head.PkgLen)) 296 | copy(pkg, r.bin.memlog[r.curMemPos:]) 297 | 298 | r.curMemPos += int(r.head.PkgLen) 299 | return pkg 300 | } else { 301 | return r.nextFile() 302 | } 303 | } 304 | 305 | func (r *Reader) Next() []byte { 306 | return r.next() 307 | } 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoTable 2 | 3 | GoTable is a high performance NoSQL database powered by [Go](http://golang.org/) and [RocksDB](http://rocksdb.org/). It's inspired by BigTable and Redis. 4 | 5 | ## Features 6 | 7 | + High performance and easy to scale. 8 | + Powerful set of APIs: GET, SET, DEL, MGET, MSET, MDEL, SCAN, INCR, DUMP and "Z" APIs. 9 | + Data storage is not limited by RAM. 10 | + Friendly with SSD. 11 | + Transaction support with [CAS](http://en.wikipedia.org/wiki/Compare-and-swap) (Compare-And-Swap). 12 | + Replication. 13 | 14 | ## Build and Install 15 | 16 | To build GoTable, you need to setup [Go](http://golang.org/) environment and gcc with c++11 support, please see the requirement part for details. 17 | 18 | #download GoTable source code 19 | git clone https://github.com/stevejiang/gotable.git 20 | 21 | cd gotable 22 | 23 | #build rocksdb, it will download rocksdb automatically if missing 24 | sh build_rocksdb.sh 25 | 26 | #build GoTable 27 | make 28 | 29 | The GoTable binary files are in $HOME/go/bin directory. 30 | 31 | ## Requirement 32 | 33 | + Linux or MacOS, 64 bit operating system is the best. 34 | + Go version >= 1.15 35 | + g++ version, supports c++11 36 | 37 | ## Running GoTable 38 | 39 | Please add $HOME/go/bin to PATH environment first. 40 | To run GoTable in default configuration just type: 41 | 42 | gotable-server 43 | 44 | If you want to provide your gotable.conf, you have to run it using an additional parameter (the path of the configuration file): 45 | 46 | gotable-server /path/to/gotable.conf 47 | 48 | ## Playing with GoTable 49 | 50 | You can use gotable-cli to play with GoTable. Start a gotable-server instance, then in another terminal try the following: 51 | 52 | % gotable-cli 53 | gotable@0> set 0 r1 c1 v1 54 | OK 55 | gotable@0> get 0 r1 c1 56 | [0 "v1"] 57 | gotable@0> incr 0 r1 c1 58 | [1 "v1"] 59 | gotable@0> incr 0 r1 c1 4 60 | [5 "v1"] 61 | gotable@0> zget 0 r1 c1 62 | 63 | gotable@0> zset 0 r1 c1 va 11 64 | OK 65 | gotable@0> zget 0 r1 c1 66 | [11 "va"] 67 | gotable@0> set 0 r1 c2 v2 2 68 | OK 69 | gotable@0> scan 0 r1 "" 70 | 0) ["r1" "c1"] [5 "v1"] 71 | 1) ["r1" "c2"] [2 "v2"] 72 | gotable@0> zscan 0 r1 0 "" 73 | 0) ["r1" 11 "c1"] ["va"] 74 | gotable@0> zset 0 r1 c2 vb 12 75 | OK 76 | gotable@0> zscan 0 r1 0 "" 77 | 0) ["r1" 11 "c1"] ["va"] 78 | 1) ["r1" 12 "c2"] ["vb"] 79 | gotable@0> select 1 80 | OK 81 | gotable@1> get 0 r1 c1 82 | 83 | gotable@1> select 0 84 | OK 85 | gotable@0> get 0 r1 c1 86 | [5 "v1"] 87 | 88 | ## Replication 89 | 90 | You can use gotable-cli with command SLAVEOF to change replication settings of a slave on the fly. If a GoTable server is already acting as slave, the command SLAVEOF NO ONE will turn off the replication, turning the GoTable server into a master. In the proper form SLAVEOF host will make the server a slave of another server listening at the specified host(ip:port). GoTable remembers the replication settings, it will reconnect to master automatically when restarted. 91 | 92 | % gotable-cli 93 | gotable@0> SLAVEOF 127.0.0.1:6689 94 | OK 95 | gotable@0> SLAVEOF 96 | OK 97 | 98 | If a server is already a slave of some master, SLAVEOF host will stop the replication against the old server and start the synchronization against the new one. Old dataset is kept and synchronization starts from the last binlog sequence. 99 | 100 | ## API Example 101 | 102 | + [Go Example](https://github.com/stevejiang/gotable/blob/master/cmd/gotable-example/example.go) 103 | + [C++ Example](https://github.com/stevejiang/gotable/blob/master/api/c++/example.cc) 104 | 105 | ## Data Model 106 | 107 | GoTable is constructed with up to 255 DBs, each DB is constructed with up to 256 Tables. The table structure is like the following chart: 108 | 109 | |-------------------------------------|-------------------------------------| 110 | | Default column space | "Z" sorted score column space | 111 | |---------------|---------------|-----|---------------|---------------|-----| 112 | | colKey1 | colKey2 | ... | colKey1 | colKey3 | ... | 113 | --------|---------------|---------------|-----|---------------|---------------|-----| 114 | rowKey1 |value11,score11|value12,score12| ... |value13,score13|value14,score14| ... | 115 | --------|---------------|---------------|-----|---------------|---------------|-----| 116 | rowKey2 |value21,score21|value22,score22| ... |value23,score23|value24,score24| ... | 117 | --------|---------------|---------------|-----|---------------|---------------|-----| 118 | ... | ... | ... | 119 | 120 | A table can hold unlimited number of rows(rowKey). Each row can have up to millions columns(colKey). 121 | Data sharding is based on rowKey, records with the same rowKey are stored in the same slot. So you should carefully construct rowKey to avoid hot spot issue. 122 | 123 | ### Default column space 124 | 125 | In default column space, all colKeys are stored in ASC order. The APIs GET/SET/DEL/INCR/SCAN take effect in this space. The SCAN API scans records order by colKey in ASC or DESC order for a rowKey. 126 | 127 | ### "Z" sorted score column space 128 | 129 | In "Z" sorted score column space, there are two lists for every rowKey. The first list is like the default column space, all colKeys are sorted in ASC order; the second list is order by score, all colKeys are sorted by "score+colKey" in ASC order. The APIs ZGET/ZSET/ZDEL/ZINCR/ZSCAN take effect in this space. The SCAN API can scan records on the two lists, order by colKey or "score+colKey". 130 | 131 | ## Performance Benchmark 132 | 133 | Benchmark command: 134 | 135 | gotable-bench -t set,zset,get,zget,scan,zscan,incr,zincr -range 10 -n 1000000 -c 100 136 | 137 | Benchmark result: 138 | 139 | SET : 136954.7 op/s 140 | ZSET : 105653.1 op/s 141 | GET : 244484.1 op/s 142 | ZGET : 264541.5 op/s 143 | SCAN 10 : 115589.9 op/s 144 | ZSCAN 10 : 115073.4 op/s 145 | INCR : 118933.3 op/s 146 | ZINCR : 86478.0 op/s 147 | 148 | If you want to see latency distribution, add "-histogram 1" to the command line: 149 | 150 | gotable-bench -t get -n 1000000 -c 100 -histogram 1 151 | 152 | Benchmark latency distribution: 153 | 154 | GET : 244271.3 op/s 155 | Microseconds per op: 156 | Count: 1000000 Average: 406.6848 StdDev: 189.40 157 | Min: 64.0000 Median: 384.1120 Max: 3220.0000 158 | ------------------------------------------------------ 159 | [ 60, 70 ) 5 0.001% 0.001% 160 | [ 70, 80 ) 18 0.002% 0.002% 161 | [ 80, 90 ) 74 0.007% 0.010% 162 | [ 90, 100 ) 249 0.025% 0.035% 163 | [ 100, 120 ) 1588 0.159% 0.193% 164 | [ 120, 140 ) 5490 0.549% 0.742% 165 | [ 140, 160 ) 11924 1.192% 1.935% 166 | [ 160, 180 ) 20065 2.006% 3.941% 167 | [ 180, 200 ) 27430 2.743% 6.684% # 168 | [ 200, 250 ) 96658 9.666% 16.350% ## 169 | [ 250, 300 ) 119100 11.910% 28.260% ## 170 | [ 300, 350 ) 125923 12.592% 40.852% ### 171 | [ 350, 400 ) 134082 13.408% 54.261% ### 172 | [ 400, 450 ) 141526 14.153% 68.413% ### 173 | [ 450, 500 ) 120417 12.042% 80.455% ## 174 | [ 500, 600 ) 115526 11.553% 92.008% ## 175 | [ 600, 700 ) 30308 3.031% 95.038% # 176 | [ 700, 800 ) 9663 0.966% 96.005% 177 | [ 800, 900 ) 6191 0.619% 96.624% 178 | [ 900, 1000 ) 7402 0.740% 97.364% 179 | [ 1000, 1200 ) 15571 1.557% 98.921% 180 | [ 1200, 1400 ) 8483 0.848% 99.769% 181 | [ 1400, 1600 ) 1912 0.191% 99.961% 182 | [ 1600, 1800 ) 286 0.029% 99.989% 183 | [ 1800, 2000 ) 62 0.006% 99.995% 184 | [ 2000, 2500 ) 43 0.004% 100.000% 185 | [ 2500, 3000 ) 3 0.000% 100.000% 186 | [ 3000, 3500 ) 1 0.000% 100.000% 187 | -------------------------------------------------------------------------------- /api/c++/example.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "gotable.h" 23 | 24 | using std::string; 25 | using std::vector; 26 | using namespace gotable; 27 | 28 | void testGet(Client* cli) { 29 | // set 30 | int err = cli->set(1, "row1", "col1", "v01", 10, 0); 31 | if(err < 0) { 32 | printf("set failed with error %d\n", err); 33 | return; 34 | } 35 | 36 | string value; 37 | int64_t score; 38 | err = cli->get(1, "row1", "col1", &value, &score, NULL); 39 | if(err < 0) { 40 | printf("get failed with error %d\n", err); 41 | return; 42 | } 43 | 44 | if(err > 0) { 45 | printf("get result1: Key not exist!\n"); 46 | } else { 47 | printf("get result1: %s\t%lld\n", value.c_str(), (long long)score); 48 | } 49 | 50 | // delete 51 | err = cli->del(1, "row1", "col1", 0); 52 | if(err < 0) { 53 | printf("del failed with error %d\n", err); 54 | return; 55 | } 56 | 57 | err = cli->get(1, "row1", "col1", &value, &score, NULL); 58 | if(err < 0) { 59 | printf("get failed with error %d\n", err); 60 | return; 61 | } 62 | 63 | if(err > 0) { 64 | printf("get result2: Key not exist!\n"); 65 | } else { 66 | printf("get result2: %s\t%lld\n", value.c_str(), (long long)score); 67 | } 68 | } 69 | 70 | void testMGet(Client* cli) { 71 | vector sa; 72 | sa.push_back(SetArgs(1, "row1", "col0", "v00", 10, 0)); 73 | sa.push_back(SetArgs(1, "row1", "col1", "v01", 9, 0)); 74 | sa.push_back(SetArgs(1, "row1", "col2", "v02", 8, 0)); 75 | sa.push_back(SetArgs(1, "row1", "col3", "v03", 7, 0)); 76 | sa.push_back(SetArgs(1, "row8", "col4", "v04", 6, 0)); 77 | int err = cli->mSet(sa, NULL); 78 | if(err < 0) { 79 | printf("mSet failed: %d\n", err); 80 | return; 81 | } 82 | 83 | vector ga; 84 | vector reply; 85 | ga.push_back(GetArgs(1, "row8", "col4", 0)); 86 | ga.push_back(GetArgs(1, "row1", "col2", 0)); 87 | ga.push_back(GetArgs(1, "row1", "col1", 0)); 88 | ga.push_back(GetArgs(1, "row1", "col3", 0)); 89 | ga.push_back(GetArgs(1, "row8", "not", 0)); 90 | err = cli->mGet(ga, &reply); 91 | if(err < 0) { 92 | printf("get failed with error %d\n", err); 93 | return; 94 | } 95 | 96 | printf("MGET result:\n"); 97 | for(unsigned i = 0; i < reply.size(); i++) { 98 | if(reply[i].errCode < 0) { 99 | printf("[%s\t%s]\tget failed with error %d!\n", 100 | reply[i].rowKey.c_str(), reply[i].colKey.c_str(), reply[i].errCode); 101 | } else if(reply[i].errCode > 0) { 102 | printf("[%s\t%s]\tkey not exist!\n", 103 | reply[i].rowKey.c_str(), reply[i].colKey.c_str()); 104 | } else { 105 | printf("[%s\t%s]\t[%lld\t%s]\n", 106 | reply[i].rowKey.c_str(), reply[i].colKey.c_str(), 107 | (long long)reply[i].score, reply[i].value.c_str()); 108 | } 109 | } 110 | } 111 | 112 | void testScan(Client* cli) { 113 | ScanReply reply; 114 | int err = cli->scan(1, "row1", true, 10, &reply); 115 | if(err < 0) { 116 | printf("scan failed: %d\n", err); 117 | return; 118 | } 119 | 120 | printf("SCAN result:\n"); 121 | for(unsigned i = 0; i < reply.kvs.size(); i++) { 122 | printf("[%s\t%s]\t[%lld\t%s]\n", 123 | reply.rowKey.c_str(), reply.kvs[i].colKey.c_str(), 124 | (long long)reply.kvs[i].score, reply.kvs[i].value.c_str()); 125 | } 126 | if(reply.end) { 127 | printf("SCAN finished!\n"); 128 | } else { 129 | printf("SCAN has more records!\n"); 130 | } 131 | } 132 | 133 | void testZScan(Client* cli) { 134 | int err = cli->zSet(1, "row2", "000", "v00", 10, 0); 135 | if(err < 0) { 136 | printf("zSet failed: %d\n", err); 137 | return; 138 | } 139 | 140 | vector sa; 141 | sa.push_back(SetArgs(1, "row2", "col1", "v01", 9, 0)); 142 | sa.push_back(SetArgs(1, "row2", "col2", "v02", 6, 0)); 143 | sa.push_back(SetArgs(1, "row2", "col3", "v03", 7, 0)); 144 | sa.push_back(SetArgs(1, "row2", "col4", "v04", 6, 0)); 145 | sa.push_back(SetArgs(1, "row2", "col5", "v05", -5, 0)); 146 | 147 | err = cli->zmSet(sa, NULL); 148 | if(err < 0) { 149 | printf("zmSet failed: %d\n", err); 150 | return; 151 | } 152 | 153 | ScanReply reply; 154 | err = cli->zScan(1, "row2", true, true, 4, &reply); 155 | if(err < 0) { 156 | printf("zScan failed: %d\n", err); 157 | return; 158 | } 159 | 160 | printf("ZSCAN result:\n"); 161 | while(true) { 162 | for(unsigned i = 0; i < reply.kvs.size(); i++) { 163 | printf("[%s\t%s]\t[%lld\t%s]\n", 164 | reply.rowKey.c_str(), reply.kvs[i].colKey.c_str(), 165 | (long long)reply.kvs[i].score, reply.kvs[i].value.c_str()); 166 | } 167 | 168 | if(reply.end) { 169 | printf("ZSCAN finished!\n"); 170 | break; 171 | } else { 172 | printf("ZSCAN has more records:\n"); 173 | } 174 | 175 | err = cli->scanMore(reply, &reply); 176 | if(err < 0) { 177 | printf("scanMore failed: %d\n", err); 178 | return; 179 | } 180 | } 181 | } 182 | 183 | void testCas(Client* cli) { 184 | string value; 185 | int64_t score; 186 | uint32_t cas = 0; 187 | int err = 0; 188 | // Try i < 11 for cas not match 189 | for(int i = 0; i < 1; i++) { 190 | uint32_t newCas = 2; 191 | err = cli->get(1, "row1", "col1", &value, &score, &newCas); 192 | if(err < 0) { 193 | printf("get failed: %d\n", err); 194 | return; 195 | } 196 | 197 | if(i > 0) { 198 | sleep(1); 199 | } else { 200 | cas = newCas; 201 | } 202 | 203 | printf("\tCas %02d: (%u %u)\t(%s, %lld)\n", i, newCas, cas, value.c_str(), (long long)score); 204 | } 205 | 206 | err = cli->set(1, "row1", "col1", value+"-cas", score+20, cas); 207 | if(err < 0) { 208 | printf("Set failed: %d\n", err); 209 | } 210 | 211 | err = cli->get(1, "row1", "col1", &value, &score, 0); 212 | if(err < 0) { 213 | printf("get failed: %d\n", err); 214 | return; 215 | } 216 | 217 | printf("CAS result: %s\t%lld\n", value.c_str(), (long long)score); 218 | } 219 | 220 | void testPing(Client* cli) { 221 | timeval start, end; 222 | gettimeofday(&start, NULL); 223 | int err = cli->ping(); 224 | if(err < 0) { 225 | printf("ping failed: %d\n", err); 226 | return; 227 | } 228 | 229 | gettimeofday(&end, NULL); 230 | float elapsed = (end.tv_sec-start.tv_sec)*1e6+(end.tv_usec-start.tv_usec); 231 | 232 | printf("Ping succeed: %.2f ms\n", elapsed/1e3); 233 | } 234 | 235 | void testDump(Client* cli) { 236 | DumpReply reply; 237 | int err = cli->dumpTable(1, &reply); 238 | if(err < 0) { 239 | printf("dump failed: %d\n", err); 240 | return; 241 | } 242 | 243 | printf("Dump result:\n"); 244 | int idx = 0; 245 | while(true) { 246 | for(unsigned i=0; i < reply.kvs.size(); i++) { 247 | printf("%02u) %d\t%s\t%d\t%s\t%lld\t%s\n", idx, 248 | reply.kvs[i].tableId, reply.kvs[i].rowKey.c_str(), reply.kvs[i].colSpace, 249 | reply.kvs[i].colKey.c_str(), (long long)reply.kvs[i].score, reply.kvs[i].value.c_str()); 250 | idx++; 251 | } 252 | 253 | if(reply.end) { 254 | break; 255 | } 256 | 257 | err = cli->dumpMore(reply, &reply); 258 | if(err < 0) { 259 | printf("dumpMore failed: %d\n", err); 260 | return; 261 | } 262 | } 263 | } 264 | 265 | 266 | int main(int argc, char** argv) { 267 | Client* cli = Client::Dial("127.0.0.1", 6688); 268 | if(cli == NULL) { 269 | printf("Failed to connect to gotable server!\n"); 270 | return 1; 271 | } 272 | cli->select(1); 273 | 274 | testGet(cli); 275 | testMGet(cli); 276 | testScan(cli); 277 | testZScan(cli); 278 | testCas(cli); 279 | testPing(cli); 280 | testDump(cli); 281 | 282 | delete cli; 283 | return 0; 284 | } 285 | -------------------------------------------------------------------------------- /store/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package store 16 | 17 | // #include 18 | // #include 19 | import "C" 20 | 21 | import ( 22 | "errors" 23 | "unsafe" 24 | ) 25 | 26 | type DB struct { 27 | db *C.rocksdb_t 28 | opt *C.rocksdb_options_t 29 | rOpt *C.rocksdb_readoptions_t // fill cache by default 30 | wOpt *C.rocksdb_writeoptions_t 31 | cache *C.rocksdb_cache_t 32 | fp *C.rocksdb_filterpolicy_t 33 | } 34 | 35 | type Iterator struct { 36 | it *C.rocksdb_iterator_t 37 | } 38 | 39 | type ReadOptions struct { 40 | rOpt *C.rocksdb_readoptions_t 41 | snap *C.rocksdb_snapshot_t 42 | db *C.rocksdb_t 43 | } 44 | 45 | type WriteBatch struct { 46 | batch *C.rocksdb_writebatch_t 47 | } 48 | 49 | func NewDB() *DB { 50 | db := new(DB) 51 | 52 | return db 53 | } 54 | 55 | func (db *DB) Close() { 56 | if db.db != nil { 57 | C.rocksdb_close(db.db) 58 | db.db = nil 59 | 60 | if db.opt != nil { 61 | C.rocksdb_options_destroy(db.opt) 62 | } 63 | if db.rOpt != nil { 64 | C.rocksdb_readoptions_destroy(db.rOpt) 65 | } 66 | if db.wOpt != nil { 67 | C.rocksdb_writeoptions_destroy(db.wOpt) 68 | } 69 | if db.cache != nil { 70 | C.rocksdb_cache_destroy(db.cache) 71 | } 72 | if db.fp != nil { 73 | C.rocksdb_filterpolicy_destroy(db.fp) 74 | } 75 | } 76 | } 77 | 78 | func (db *DB) Open(name string, createIfMissing bool, maxOpenFiles int, 79 | writeBufSize int, cacheSize int64, compression int) error { 80 | db.opt = C.rocksdb_options_create() 81 | C.rocksdb_options_set_create_if_missing(db.opt, boolToUchar(createIfMissing)) 82 | C.rocksdb_options_set_write_buffer_size(db.opt, C.size_t(writeBufSize)) 83 | C.rocksdb_options_set_max_open_files(db.opt, C.int(maxOpenFiles)) 84 | C.rocksdb_options_set_compression(db.opt, C.int(compression)) 85 | 86 | var block_options = C.rocksdb_block_based_options_create() 87 | if cacheSize > 0 { 88 | db.cache = C.rocksdb_cache_create_lru(C.size_t(cacheSize)) 89 | C.rocksdb_block_based_options_set_block_cache(block_options, db.cache) 90 | } else { 91 | C.rocksdb_block_based_options_set_no_block_cache(block_options, 1) 92 | } 93 | db.fp = C.rocksdb_filterpolicy_create_bloom(10) 94 | C.rocksdb_block_based_options_set_filter_policy(block_options, db.fp) 95 | 96 | C.rocksdb_options_set_block_based_table_factory(db.opt, block_options) 97 | 98 | cname := C.CString(name) 99 | defer C.free(unsafe.Pointer(cname)) 100 | 101 | var errStr *C.char 102 | db.db = C.rocksdb_open(db.opt, cname, &errStr) 103 | if errStr != nil { 104 | defer C.free(unsafe.Pointer(errStr)) 105 | return errors.New(C.GoString(errStr)) 106 | } 107 | 108 | db.rOpt = C.rocksdb_readoptions_create() 109 | db.wOpt = C.rocksdb_writeoptions_create() 110 | 111 | return nil 112 | } 113 | 114 | func (db *DB) Put(rawKey, value []byte, wb *WriteBatch) error { 115 | var ck, cv *C.char 116 | if len(rawKey) > 0 { 117 | ck = (*C.char)(unsafe.Pointer(&rawKey[0])) 118 | } 119 | if len(value) > 0 { 120 | cv = (*C.char)(unsafe.Pointer(&value[0])) 121 | } 122 | 123 | if wb == nil { 124 | var errStr *C.char 125 | C.rocksdb_put(db.db, db.wOpt, ck, C.size_t(len(rawKey)), cv, 126 | C.size_t(len(value)), &errStr) 127 | if errStr != nil { 128 | defer C.free(unsafe.Pointer(errStr)) 129 | return errors.New(C.GoString(errStr)) 130 | } 131 | } else { 132 | C.rocksdb_writebatch_put(wb.batch, ck, C.size_t(len(rawKey)), cv, 133 | C.size_t(len(value))) 134 | } 135 | 136 | return nil 137 | } 138 | 139 | func (db *DB) Get(opt *ReadOptions, rawKey []byte) ([]byte, error) { 140 | var ck = (*C.char)(unsafe.Pointer(&rawKey[0])) 141 | 142 | var rOpt = db.rOpt 143 | if opt != nil && opt.rOpt != nil { 144 | rOpt = opt.rOpt 145 | } 146 | 147 | var errStr *C.char 148 | var vallen C.size_t 149 | var cv = C.rocksdb_get(db.db, rOpt, ck, C.size_t(len(rawKey)), &vallen, &errStr) 150 | 151 | var err error 152 | if errStr != nil { 153 | defer C.free(unsafe.Pointer(errStr)) 154 | err = errors.New(C.GoString(errStr)) 155 | } 156 | 157 | if cv != nil { 158 | defer C.free(unsafe.Pointer(cv)) 159 | return C.GoBytes(unsafe.Pointer(cv), C.int(vallen)), err 160 | } 161 | 162 | return nil, err 163 | } 164 | 165 | func (db *DB) Del(rawKey []byte, wb *WriteBatch) error { 166 | var ck = (*C.char)(unsafe.Pointer(&rawKey[0])) 167 | 168 | if wb == nil { 169 | var errStr *C.char 170 | C.rocksdb_delete(db.db, db.wOpt, ck, C.size_t(len(rawKey)), &errStr) 171 | if errStr != nil { 172 | defer C.free(unsafe.Pointer(errStr)) 173 | return errors.New(C.GoString(errStr)) 174 | } 175 | } else { 176 | C.rocksdb_writebatch_delete(wb.batch, ck, C.size_t(len(rawKey))) 177 | } 178 | 179 | return nil 180 | } 181 | 182 | func (db *DB) NewReadOptions(createSnapshot bool) *ReadOptions { 183 | var opt = new(ReadOptions) 184 | opt.rOpt = C.rocksdb_readoptions_create() 185 | if createSnapshot { 186 | opt.snap = C.rocksdb_create_snapshot(db.db) 187 | C.rocksdb_readoptions_set_snapshot(opt.rOpt, opt.snap) 188 | opt.db = db.db 189 | } 190 | return opt 191 | } 192 | 193 | func (opt *ReadOptions) SetFillCache(fillCache bool) { 194 | C.rocksdb_readoptions_set_fill_cache(opt.rOpt, boolToUchar(fillCache)) 195 | } 196 | 197 | func (opt *ReadOptions) Destroy() { 198 | if opt.rOpt != nil { 199 | C.rocksdb_readoptions_destroy(opt.rOpt) 200 | opt.rOpt = nil 201 | } 202 | 203 | if opt.snap != nil { 204 | C.rocksdb_release_snapshot(opt.db, opt.snap) 205 | opt.snap = nil 206 | opt.db = nil 207 | } 208 | } 209 | 210 | func (db *DB) NewWriteBatch() *WriteBatch { 211 | return &WriteBatch{C.rocksdb_writebatch_create()} 212 | } 213 | 214 | func (db *DB) Commit(wb *WriteBatch) error { 215 | if wb != nil && wb.batch != nil { 216 | var errStr *C.char 217 | C.rocksdb_write(db.db, db.wOpt, wb.batch, &errStr) 218 | C.rocksdb_writebatch_clear(wb.batch) 219 | if errStr != nil { 220 | defer C.free(unsafe.Pointer(errStr)) 221 | return errors.New(C.GoString(errStr)) 222 | } 223 | } 224 | 225 | return nil 226 | } 227 | 228 | func (wb *WriteBatch) Destroy() { 229 | if wb.batch != nil { 230 | C.rocksdb_writebatch_destroy(wb.batch) 231 | wb.batch = nil 232 | } 233 | } 234 | 235 | func (db *DB) NewIterator(opt *ReadOptions) *Iterator { 236 | var rOpt = db.rOpt 237 | if opt != nil && opt.rOpt != nil { 238 | rOpt = opt.rOpt 239 | } 240 | 241 | var iter = new(Iterator) 242 | iter.it = C.rocksdb_create_iterator(db.db, rOpt) 243 | 244 | return iter 245 | } 246 | 247 | func (iter *Iterator) Destroy() { 248 | if iter.it != nil { 249 | C.rocksdb_iter_destroy(iter.it) 250 | iter.it = nil 251 | } 252 | } 253 | 254 | func (iter *Iterator) SeekToFirst() { 255 | C.rocksdb_iter_seek_to_first(iter.it) 256 | } 257 | 258 | func (iter *Iterator) SeekToLast() { 259 | C.rocksdb_iter_seek_to_last(iter.it) 260 | } 261 | 262 | func (iter *Iterator) Seek(key []byte) { 263 | var ck *C.char 264 | if len(key) > 0 { 265 | ck = (*C.char)(unsafe.Pointer(&key[0])) 266 | } 267 | C.rocksdb_iter_seek(iter.it, ck, C.size_t(len(key))) 268 | } 269 | 270 | func (iter *Iterator) Next() { 271 | C.rocksdb_iter_next(iter.it) 272 | } 273 | 274 | func (iter *Iterator) Prev() { 275 | C.rocksdb_iter_prev(iter.it) 276 | } 277 | 278 | func (iter *Iterator) Valid() bool { 279 | return C.rocksdb_iter_valid(iter.it) != 0 280 | } 281 | 282 | func (iter *Iterator) Key() []byte { 283 | var keyLen C.size_t 284 | var ck = C.rocksdb_iter_key(iter.it, &keyLen) 285 | return C.GoBytes(unsafe.Pointer(ck), C.int(keyLen)) 286 | } 287 | 288 | func (iter *Iterator) Value() []byte { 289 | var valueLen C.size_t 290 | var value = C.rocksdb_iter_value(iter.it, &valueLen) 291 | return C.GoBytes(unsafe.Pointer(value), C.int(valueLen)) 292 | } 293 | 294 | func boolToUchar(b bool) C.uchar { 295 | if b { 296 | return 1 297 | } else { 298 | return 0 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /api/c++/codec.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "proto.h" 17 | #include "codec.h" 18 | 19 | namespace gotable { 20 | 21 | 22 | int KeyValue::length() { 23 | // KeyValue=cCtrlFlag+cTableId+[cErrCode]+[cColSpace] 24 | // +cRowKeyLen+sRowKey+wColKeyLen+sColKey 25 | // +[dwValueLen+sValue]+[ddwScore]+[dwCas] 26 | int n = 2; 27 | if((ctrlFlag&CtrlErrCode) != 0) { 28 | n += 1; 29 | } 30 | if((ctrlFlag&CtrlColSpace) != 0) { 31 | n += 1; 32 | } 33 | n += 1 + rowKey.size() + 2 + colKey.size(); 34 | if((ctrlFlag&CtrlValue) != 0) { 35 | n += 4 + value.size(); 36 | } 37 | if((ctrlFlag&CtrlScore) != 0) { 38 | n += 8; 39 | } 40 | if((ctrlFlag&CtrlCas) != 0) { 41 | n += 4; 42 | } 43 | return n; 44 | } 45 | 46 | int KeyValue::decode(const char* pkg, int pkgLen) { 47 | int n = 0; 48 | if(n+2 > pkgLen) { 49 | return -2; 50 | } 51 | ctrlFlag = pkg[n]; 52 | n += 1; 53 | tableId = pkg[n]; 54 | n += 1; 55 | 56 | if((ctrlFlag&CtrlErrCode) != 0) { 57 | if(n+1 > pkgLen) { 58 | return -3; 59 | } 60 | errCode = pkg[n]; 61 | n += 1; 62 | } else { 63 | errCode = 0; 64 | } 65 | if((ctrlFlag&CtrlColSpace) != 0) { 66 | if(n+1 > pkgLen) { 67 | return -4; 68 | } 69 | colSpace = pkg[n]; 70 | n += 1; 71 | } else { 72 | colSpace = 0; 73 | } 74 | 75 | if(n+1 > pkgLen) { 76 | return -5; 77 | } 78 | int rowKeyLen = int(pkg[n]); 79 | n += 1; 80 | if(n+rowKeyLen+2 > pkgLen) { 81 | return -6; 82 | } 83 | rowKey = Slice(pkg+n, rowKeyLen); 84 | n += rowKeyLen; 85 | 86 | int colKeyLen = int(getUint16(pkg+n)); 87 | n += 2; 88 | if(n+colKeyLen > pkgLen) { 89 | return -7; 90 | } 91 | colKey = Slice(pkg+n, colKeyLen); 92 | n += colKeyLen; 93 | 94 | if((ctrlFlag&CtrlValue) != 0) { 95 | if(n+4 > pkgLen) { 96 | return -8; 97 | } 98 | int valueLen = int(getUint32(pkg+n)); 99 | n += 4; 100 | if(n+valueLen > pkgLen) { 101 | return -9; 102 | } 103 | value = Slice(pkg+n, valueLen); 104 | n += valueLen; 105 | } else { 106 | value.clear(); 107 | } 108 | if((ctrlFlag&CtrlScore) != 0) { 109 | if(n+8 > pkgLen) { 110 | return -10; 111 | } 112 | score = int64_t(getUint64(pkg+n)); 113 | n += 8; 114 | } else { 115 | score = 0; 116 | } 117 | if((ctrlFlag&CtrlCas) != 0) { 118 | if(n+4 > pkgLen) { 119 | return -11; 120 | } 121 | cas = getUint32(pkg+n); 122 | n += 4; 123 | } else { 124 | cas = 0; 125 | } 126 | return n; 127 | } 128 | 129 | int KeyValue::encode(char* pkg, int pkgLen) { 130 | if(pkgLen < length()) { 131 | return -2; 132 | } 133 | if(rowKey.size() > MaxUint8) { 134 | return -3; 135 | } 136 | if(colKey.size() > MaxUint16) { 137 | return -4; 138 | } 139 | 140 | int n = 0; 141 | pkg[n] = ctrlFlag; 142 | n += 1; 143 | pkg[n] = tableId; 144 | n += 1; 145 | if((ctrlFlag&CtrlErrCode) != 0) { 146 | pkg[n] = errCode; 147 | n += 1; 148 | } 149 | if((ctrlFlag&CtrlColSpace) != 0) { 150 | pkg[n] = colSpace; 151 | n += 1; 152 | } 153 | 154 | pkg[n] = uint8_t(rowKey.size()); 155 | n += 1; 156 | memcpy(pkg+n, rowKey.data(), rowKey.size()); 157 | n += rowKey.size(); 158 | 159 | putUint16(pkg+n, uint16_t(colKey.size())); 160 | n += 2; 161 | memcpy(pkg+n, colKey.data(), colKey.size()); 162 | n += colKey.size(); 163 | 164 | if((ctrlFlag&CtrlValue) != 0) { 165 | putUint32(pkg+n, uint32_t(value.size())); 166 | n += 4; 167 | memcpy(pkg+n, value.data(), value.size()); 168 | n += value.size(); 169 | } 170 | if((ctrlFlag&CtrlScore) != 0) { 171 | putUint64(pkg+n, uint64_t(score)); 172 | n += 8; 173 | } 174 | if((ctrlFlag&CtrlCas) != 0) { 175 | putUint32(pkg+n, cas); 176 | n += 4; 177 | } 178 | return n; 179 | } 180 | 181 | int PkgOneOp::length() { 182 | // PKG = HEAD+cPkgFlag+KeyValue 183 | return HeadSize + 1 + KeyValue::length(); 184 | } 185 | 186 | int PkgOneOp::decode(const char* pkg, int pkgLen) { 187 | int n= PkgHead::decode(pkg, pkgLen); 188 | if(n < 0){ 189 | return -2; 190 | } 191 | 192 | if(n+1 > pkgLen) { 193 | return -3; 194 | } 195 | pkgFlag = pkg[n]; 196 | n += 1; 197 | 198 | int m = KeyValue::decode(pkg+n, pkgLen-n); 199 | if(m < 0){ 200 | return -4; 201 | } 202 | n += m; 203 | 204 | return n; 205 | } 206 | 207 | int PkgOneOp::encode(char* pkg, int pkgLen) { 208 | int n = PkgHead::encode(pkg, pkgLen); 209 | if(n < 0) { 210 | return -2; 211 | } 212 | 213 | if(n+1 > pkgLen) { 214 | return -3; 215 | } 216 | pkg[n] = pkgFlag; 217 | n += 1; 218 | 219 | int m = KeyValue::encode(pkg+n, pkgLen-n); 220 | if(m < 0) { 221 | return -4; 222 | } 223 | n += m; 224 | 225 | overWriteLen(pkg, n); 226 | return n; 227 | } 228 | 229 | int PkgMultiOp::length() { 230 | // PKG = HEAD+cPkgFlag+cErrCode+wNum+KeyValue[wNum] 231 | int n = HeadSize + 4; 232 | for(int i = 0; i < kvs.size(); i++) { 233 | n += kvs[i].length(); 234 | } 235 | return n; 236 | } 237 | 238 | int PkgMultiOp::decode(const char* pkg, int pkgLen) { 239 | int n = PkgHead::decode(pkg, pkgLen); 240 | if(n < 0) { 241 | return -2; 242 | } 243 | 244 | if(n+4 > pkgLen) { 245 | return -3; 246 | } 247 | pkgFlag = pkg[n]; 248 | n += 1; 249 | errCode = pkg[n]; 250 | n += 1; 251 | int numKvs = int(getUint16(pkg+n)); 252 | n += 2; 253 | 254 | kvs.resize(numKvs); 255 | for(int i = 0; i < numKvs; i++) { 256 | int m = kvs[i].decode(pkg+n, pkgLen-n); 257 | if(m < 0) { 258 | return -4; 259 | } 260 | n += m; 261 | } 262 | 263 | return n; 264 | } 265 | 266 | int PkgMultiOp::encode(char* pkg, int pkgLen) { 267 | int numKvs = kvs.size(); 268 | if(numKvs > MaxUint16) { 269 | return -2; 270 | } 271 | 272 | int n = PkgHead::encode(pkg, pkgLen); 273 | if(n < 0) { 274 | return -3; 275 | } 276 | 277 | if(n+4 > pkgLen) { 278 | return -5; 279 | } 280 | pkg[n] = pkgFlag; 281 | n += 1; 282 | pkg[n] = errCode; 283 | n += 1; 284 | putUint16(pkg+n, uint16_t(numKvs)); 285 | n += 2; 286 | 287 | for(int i = 0; i < numKvs; i++) { 288 | int m = kvs[i].encode(pkg+n, pkgLen-n); 289 | if(m < 0) { 290 | return -6; 291 | } 292 | n += m; 293 | } 294 | 295 | overWriteLen(pkg, n); 296 | return n; 297 | } 298 | 299 | int PkgScanReq::length() { 300 | // PKG=PkgOneOp+wNum 301 | return PkgOneOp::length() + 2; 302 | } 303 | 304 | int PkgScanReq::decode(const char* pkg, int pkgLen) { 305 | int n = PkgOneOp::decode(pkg, pkgLen); 306 | if(n < 0) { 307 | return -2; 308 | } 309 | 310 | if(n+2 > pkgLen) { 311 | return -3; 312 | } 313 | num = getUint16(pkg+n); 314 | n += 2; 315 | 316 | return n; 317 | } 318 | 319 | int PkgScanReq::encode(char* pkg, int pkgLen) { 320 | int n= PkgOneOp::encode(pkg, pkgLen); 321 | if(n < 0) { 322 | return -2; 323 | } 324 | 325 | if(n+2 > pkgLen) { 326 | return -3; 327 | } 328 | putUint16(pkg+n, num); 329 | n += 2; 330 | 331 | overWriteLen(pkg, n); 332 | return n; 333 | } 334 | 335 | int PkgDumpReq::length() { 336 | // PKG=PkgOneOp+wStartUnitId+wEndUnitId 337 | return PkgOneOp::length() + 4; 338 | } 339 | 340 | int PkgDumpReq::decode(const char* pkg, int pkgLen) { 341 | int n = PkgOneOp::decode(pkg, pkgLen); 342 | if(n < 0) { 343 | return -2; 344 | } 345 | 346 | if(n+4 > pkgLen) { 347 | return -3; 348 | } 349 | startUnitId = getUint16(pkg+n); 350 | n += 2; 351 | endUnitId = getUint16(pkg+n); 352 | n += 2; 353 | 354 | return n; 355 | } 356 | 357 | int PkgDumpReq::encode(char* pkg, int pkgLen) { 358 | int n = PkgOneOp::encode(pkg, pkgLen); 359 | if(n < 0) { 360 | return -2; 361 | } 362 | 363 | if(n+4 > pkgLen) { 364 | return -3; 365 | } 366 | putUint16(pkg+n, startUnitId); 367 | n += 2; 368 | putUint16(pkg+n, endUnitId); 369 | n += 2; 370 | 371 | overWriteLen(pkg, n); 372 | return n; 373 | } 374 | 375 | int PkgDumpResp::length() { 376 | // PKG=PkgMultiOp+wStartUnitId+wEndUnitId+wLastUnitId 377 | return PkgMultiOp::length() + 6; 378 | } 379 | 380 | int PkgDumpResp::decode(const char* pkg, int pkgLen) { 381 | int n = PkgMultiOp::decode(pkg, pkgLen); 382 | if(n < 0) { 383 | return -2; 384 | } 385 | 386 | if(n+6 > pkgLen) { 387 | return -3; 388 | } 389 | startUnitId = getUint16(pkg+n); 390 | n += 2; 391 | endUnitId = getUint16(pkg+n); 392 | n += 2; 393 | lastUnitId = getUint16(pkg+n); 394 | n += 2; 395 | 396 | return n; 397 | } 398 | 399 | int PkgDumpResp::encode(char* pkg, int pkgLen) { 400 | int n = PkgMultiOp::encode(pkg, pkgLen); 401 | if(n < 0) { 402 | return -2; 403 | } 404 | 405 | if(n+6 > pkgLen) { 406 | return -3; 407 | } 408 | putUint16(pkg+n, startUnitId); 409 | n += 2; 410 | putUint16(pkg+n, endUnitId); 411 | n += 2; 412 | putUint16(pkg+n, lastUnitId); 413 | n += 2; 414 | 415 | overWriteLen(pkg, n); 416 | return n; 417 | } 418 | 419 | } // namespace gotable 420 | -------------------------------------------------------------------------------- /cmd/gotable-example/example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/binary" 19 | "flag" 20 | "fmt" 21 | "github.com/stevejiang/gotable/api/go/table" 22 | "time" 23 | ) 24 | 25 | var ( 26 | address = flag.String("h", "127.0.0.1:6688", "Server host address ip:port") 27 | network = flag.String("N", "tcp", "Server network: tcp, tcp4, tcp6, unix") 28 | ) 29 | 30 | func main() { 31 | flag.Parse() 32 | 33 | client, err := table.Dial(*network, *address) 34 | if err != nil { 35 | fmt.Printf("Dial failed: %s\n", err) 36 | return 37 | } 38 | defer client.Close() 39 | 40 | tc := client.NewContext(0) 41 | 42 | testGet(tc) 43 | testMGet(tc) 44 | testScan(tc) 45 | testZScan(tc) 46 | testCas(tc) 47 | testBinary(tc) 48 | testPing(tc) 49 | testAsync(tc) 50 | testDump(tc) 51 | } 52 | 53 | func testGet(tc *table.Context) { 54 | // SET 55 | err := tc.Set(1, []byte("row1"), []byte("col1"), []byte("v01"), 10, 0) 56 | if err != nil { 57 | fmt.Printf("Set failed: %s\n", err) 58 | return 59 | } 60 | 61 | // GET 62 | value, score, _, err := tc.Get(1, []byte("row1"), []byte("col1"), 0) 63 | if err != nil { 64 | fmt.Printf("Get failed: %s\n", err) 65 | return 66 | } 67 | 68 | if value == nil { 69 | fmt.Printf("GET result1: Key not exist!\n") 70 | } else { 71 | fmt.Printf("GET result1: %q\t%d\n", value, score) 72 | } 73 | 74 | // DEL 75 | err = tc.Del(1, []byte("row1"), []byte("col1"), 0) 76 | if err != nil { 77 | fmt.Printf("Del failed: %s\n", err) 78 | return 79 | } 80 | 81 | // GET 82 | value, score, _, err = tc.Get(1, []byte("row1"), []byte("col1"), 0) 83 | if err != nil { 84 | fmt.Printf("Get failed: %s\n", err) 85 | return 86 | } 87 | 88 | if value == nil { 89 | fmt.Printf("GET result2: Key not exist!\n") 90 | } else { 91 | fmt.Printf("GET result2: %q\t%d\n", value, score) 92 | } 93 | } 94 | 95 | func testMGet(tc *table.Context) { 96 | // MSET 97 | var ma table.MSetArgs 98 | ma.Add(1, []byte("row1"), []byte("col0"), []byte("v00"), 10, 0) 99 | ma.Add(1, []byte("row1"), []byte("col1"), []byte("v01"), 9, 0) 100 | ma.Add(1, []byte("row1"), []byte("col2"), []byte("v02"), 8, 0) 101 | ma.Add(1, []byte("row1"), []byte("col3"), []byte("v03"), 7, 0) 102 | ma.Add(1, []byte("row8"), []byte("col4"), []byte("v04"), 6, 0) 103 | _, err := tc.MSet(ma) 104 | if err != nil { 105 | fmt.Printf("Mset failed: %s\n", err) 106 | return 107 | } 108 | 109 | // MGET 110 | var mb table.MGetArgs 111 | mb.Add(1, []byte("row8"), []byte("col4"), 0) 112 | mb.Add(1, []byte("row1"), []byte("col2"), 0) 113 | mb.Add(1, []byte("row1"), []byte("col1"), 0) 114 | mb.Add(1, []byte("row1"), []byte("col3"), 0) 115 | mb.Add(1, []byte("row8"), []byte("not"), 0) 116 | r, err := tc.MGet(mb) 117 | if err != nil { 118 | fmt.Printf("Mget failed: %s\n", err) 119 | return 120 | } 121 | 122 | fmt.Println("MGET result:") 123 | for i := 0; i < len(r); i++ { 124 | if r[i].ErrCode < 0 { 125 | fmt.Printf("[%q\t%q]\tget failed with error %d!\n", 126 | r[i].RowKey, r[i].ColKey, r[i].ErrCode) 127 | } else if r[i].ErrCode > 0 { 128 | fmt.Printf("[%q\t%q]\tkey not exist!\n", 129 | r[i].RowKey, r[i].ColKey) 130 | } else { 131 | fmt.Printf("[%q\t%q]\t[%d\t%q]\n", 132 | r[i].RowKey, r[i].ColKey, 133 | r[i].Score, r[i].Value) 134 | } 135 | } 136 | } 137 | 138 | func printScanReply(r table.ScanReply) { 139 | for i := 0; i < len(r.Kvs); i++ { 140 | fmt.Printf("[%q\t%q]\t[%d\t%q]\n", 141 | r.RowKey, r.Kvs[i].ColKey, 142 | r.Kvs[i].Score, r.Kvs[i].Value) 143 | } 144 | } 145 | 146 | func testScan(tc *table.Context) { 147 | // SCAN 148 | r, err := tc.Scan(1, []byte("row1"), true, 10) 149 | if err != nil { 150 | fmt.Printf("Scan failed: %s\n", err) 151 | return 152 | } 153 | 154 | fmt.Println("SCAN result:") 155 | printScanReply(r) 156 | if r.End { 157 | fmt.Println("SCAN finished!") 158 | } else { 159 | fmt.Println("SCAN has more records!") 160 | } 161 | } 162 | 163 | func testZScan(tc *table.Context) { 164 | // ZSET 165 | err := tc.ZSet(1, []byte("row2"), []byte("000"), []byte("v00"), 10, 0) 166 | if err != nil { 167 | fmt.Printf("ZSet failed: %s\n", err) 168 | return 169 | } 170 | 171 | // ZMSET 172 | var ma table.MSetArgs 173 | ma.Add(1, []byte("row2"), []byte("001"), []byte("v01"), 9, 0) 174 | ma.Add(1, []byte("row2"), []byte("002"), []byte("v02"), 6, 0) 175 | ma.Add(1, []byte("row2"), []byte("003"), []byte("v03"), 7, 0) 176 | ma.Add(1, []byte("row2"), []byte("004"), []byte("v04"), 8, 0) 177 | ma.Add(1, []byte("row2"), []byte("005"), []byte("v05"), -5, 0) 178 | _, err = tc.ZmSet(ma) 179 | if err != nil { 180 | fmt.Printf("ZmSet failed: %s\n", err) 181 | return 182 | } 183 | 184 | // ZSCAN 185 | r, err := tc.ZScan(1, []byte("row2"), true, true, 4) 186 | if err != nil { 187 | fmt.Printf("ZScan failed: %s\n", err) 188 | return 189 | } 190 | 191 | fmt.Println("ZSCAN result:") 192 | for { 193 | printScanReply(r) 194 | 195 | if r.End { 196 | fmt.Println("ZSCAN finished!") 197 | break 198 | } else { 199 | fmt.Println("ZSCAN has more records:") 200 | } 201 | 202 | r, err = tc.ScanMore(r) 203 | if err != nil { 204 | fmt.Printf("ScanMore failed: %s\n", err) 205 | return 206 | } 207 | } 208 | } 209 | 210 | func testCas(tc *table.Context) { 211 | var value []byte 212 | var score int64 213 | var cas, newCas uint32 214 | var err error 215 | // Try i < 11 for cas not match 216 | for i := 0; i < 1; i++ { 217 | // GET with CAS=2 218 | value, score, newCas, err = tc.Get(1, []byte("row1"), []byte("col1"), 2) 219 | if err != nil { 220 | fmt.Printf("Get failed: %s\n", err) 221 | return 222 | } 223 | 224 | if i > 0 { 225 | time.Sleep(time.Second) 226 | } else { 227 | cas = newCas 228 | } 229 | 230 | fmt.Printf("\tCas %02d: (%d %d)\t(%s, %d)\n", i, newCas, cas, value, score) 231 | } 232 | 233 | // SET with CAS 234 | err = tc.Set(1, []byte("row1"), []byte("col1"), 235 | []byte(string(value)+"-cas"), score+20, cas) 236 | if err != nil { 237 | fmt.Printf("Set failed: %s\n", err) 238 | } 239 | 240 | // GET without CAS 241 | value, score, _, err = tc.Get(1, []byte("row1"), []byte("col1"), 0) 242 | if err != nil { 243 | fmt.Printf("Get failed: %s\n", err) 244 | return 245 | } 246 | 247 | fmt.Printf("CAS result: %q\t%d\n", value, score) 248 | } 249 | 250 | func testBinary(tc *table.Context) { 251 | var colKey = make([]byte, 4) 252 | var value = make([]byte, 8) 253 | binary.BigEndian.PutUint32(colKey, 998365) 254 | binary.BigEndian.PutUint64(value, 6000000000) 255 | err := tc.Set(1, []byte("row1"), colKey, value, 30, 0) 256 | if err != nil { 257 | fmt.Printf("Set failed: %s\n", err) 258 | } 259 | 260 | value, score, _, err := tc.Get(1, []byte("row1"), colKey, 0) 261 | if err != nil { 262 | fmt.Printf("Get failed: %s\n", err) 263 | return 264 | } 265 | 266 | fmt.Printf("Binary result: %q\t%d\t%d\n", 267 | value, binary.BigEndian.Uint64(value), score) 268 | } 269 | 270 | func testPing(tc *table.Context) { 271 | start := time.Now() 272 | 273 | err := tc.Ping() 274 | if err != nil { 275 | fmt.Printf("Ping failed: %s\n", err) 276 | return 277 | } 278 | 279 | elapsed := time.Since(start) 280 | 281 | fmt.Printf("Ping succeed: %.2fms\n", float64(elapsed)/1e6) 282 | } 283 | 284 | func testAsync(tc *table.Context) { 285 | //var done = make(chan *table.Call, 2) 286 | c1, err := tc.GoScan(1, []byte("row1"), true, 10, nil) 287 | if err != nil { 288 | fmt.Printf("GoScan failed: %s\n", err) 289 | return 290 | } 291 | 292 | c2, err := tc.GoZScan(1, []byte("row2"), true, true, 10, nil) 293 | if err != nil { 294 | fmt.Printf("GoZScan failed: %s\n", err) 295 | return 296 | } 297 | 298 | // Wait for reply 299 | <-c1.Done 300 | <-c2.Done 301 | 302 | r1, err := c1.Reply() 303 | if err != nil { 304 | fmt.Printf("Reply failed: %s\n", err) 305 | return 306 | } else { 307 | fmt.Println("ASYNC C1 result:") 308 | printScanReply(r1.(table.ScanReply)) 309 | } 310 | 311 | r2, err := c2.Reply() 312 | if err != nil { 313 | fmt.Printf("Reply failed: %s\n", err) 314 | return 315 | } else { 316 | fmt.Println("ASYNC C2 result:") 317 | printScanReply(r2.(table.ScanReply)) 318 | } 319 | } 320 | 321 | func testDump(tc *table.Context) { 322 | //r, err := tc.DumpDB() 323 | r, err := tc.DumpTable(1) 324 | if err != nil { 325 | fmt.Printf("Dump failed: %s\n", err) 326 | return 327 | } 328 | 329 | fmt.Println("Dump result:") 330 | var idx = 0 331 | for { 332 | for _, kv := range r.Kvs { 333 | fmt.Printf("%02d) %d\t%q\t%d\t%q\t%d\t%q\n", idx, 334 | kv.TableId, kv.RowKey, kv.ColSpace, kv.ColKey, kv.Score, kv.Value) 335 | idx++ 336 | } 337 | 338 | if r.End { 339 | break 340 | } 341 | 342 | r, err = tc.DumpMore(r) 343 | if err != nil { 344 | fmt.Printf("DumpMore failed: %s\n", err) 345 | return 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /api/go/table/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The official client API of GoTable. GoTable is a high performance 16 | // NoSQL database. 17 | package table 18 | 19 | import ( 20 | "bufio" 21 | "errors" 22 | "fmt" 23 | "github.com/stevejiang/gotable/api/go/table/proto" 24 | "github.com/stevejiang/gotable/util" 25 | "io" 26 | "log" 27 | "net" 28 | "strconv" 29 | "sync" 30 | ) 31 | 32 | const Version = "1.0.3" // GoTable version 33 | 34 | var ( 35 | ErrShutdown = errors.New("connection is shutdown") 36 | ErrCallNotReady = errors.New("call not ready to reply") 37 | ErrClosedPool = errors.New("connection pool is closed") 38 | ErrNoValidAddr = errors.New("no valid address") 39 | ) 40 | 41 | var ( 42 | ErrCasNotMatch = initErr(EcCasNotMatch, "cas not match") 43 | ErrTempFail = initErr(EcTempFail, "temporary failed") 44 | ErrUnknownCmd = initErr(EcUnknownCmd, "unknown cmd") 45 | ErrAuthFailed = initErr(EcAuthFailed, "authorize failed") 46 | ErrNoPrivilege = initErr(EcNoPrivilege, "no access privilege") 47 | ErrWriteSlave = initErr(EcWriteSlave, "can not write slave directly") 48 | ErrSlaveCas = initErr(EcSlaveCas, "invalid cas on slave") 49 | ErrReadFail = initErr(EcReadFail, "read failed") 50 | ErrWriteFail = initErr(EcWriteFail, "write failed") 51 | ErrDecodeFail = initErr(EcDecodeFail, "decode request pkg failed") 52 | ErrInvDbId = initErr(EcInvDbId, "can not use admin db") 53 | ErrInvRowKey = initErr(EcInvRowKey, "row key length out of range") 54 | ErrInvValue = initErr(EcInvValue, "value length out of range") 55 | ErrInvPkgLen = initErr(EcInvPkgLen, "pkg length out of range") 56 | ErrInvScanNum = initErr(EcInvScanNum, "scan request number out of range") 57 | ErrScanEnded = initErr(EcScanEnded, "already scan/dump to end") 58 | ) 59 | 60 | // GoTable Error Code List 61 | const ( 62 | EcNotExist = 1 // Key NOT exist 63 | EcOk = 0 // Success 64 | EcCasNotMatch = -1 // CAS not match, get new CAS and try again 65 | EcTempFail = -2 // Temporary failed, retry may fix this 66 | EcUnknownCmd = -10 // Unknown cmd 67 | EcAuthFailed = -11 // Authorize failed 68 | EcNoPrivilege = -12 // No access privilege 69 | EcWriteSlave = -13 // Can NOT write slave directly 70 | EcSlaveCas = -14 // Invalid CAS on slave for GET/MGET 71 | EcReadFail = -15 // Read failed 72 | EcWriteFail = -16 // Write failed 73 | EcDecodeFail = -17 // Decode request PKG failed 74 | EcInvDbId = -18 // Invalid DB ID (cannot be 255) 75 | EcInvRowKey = -19 // RowKey length should be [1 ~ 255] 76 | EcInvValue = -20 // Value length should be [0 ~ 1MB] 77 | EcInvPkgLen = -21 // Pkg length should be less than 2MB 78 | EcInvScanNum = -22 // Scan request number out of range 79 | EcScanEnded = -23 // Already scan/dump to end 80 | ) 81 | 82 | var tableErrors = make([]error, 256) 83 | 84 | func initErr(code int8, msg string) error { 85 | tableErrors[int(code)+128] = fmt.Errorf("%s (%d)", msg, code) 86 | return tableErrors[int(code)+128] 87 | } 88 | 89 | func getErr(code int8) error { 90 | if tableErrors[int(code)+128] != nil { 91 | return tableErrors[int(code)+128] 92 | } else { 93 | return errors.New("error code " + strconv.Itoa(int(code))) 94 | } 95 | } 96 | 97 | // A Client is a connection to GoTable server. 98 | // It's safe to use in multiple goroutines. 99 | type Client struct { 100 | p *Pool 101 | c net.Conn 102 | r *bufio.Reader 103 | sending chan *Call 104 | 105 | mtx sync.Mutex // protects following 106 | authBM *util.BitMap 107 | seq uint64 108 | pending map[uint64]*Call 109 | closing bool // user has called Close 110 | shutdown bool // server has told us to stop 111 | } 112 | 113 | // Create a new connection Client to GoTable server. 114 | func NewClient(conn net.Conn) *Client { 115 | var c = new(Client) 116 | c.c = conn 117 | c.r = bufio.NewReader(conn) 118 | c.sending = make(chan *Call, 128) 119 | c.pending = make(map[uint64]*Call) 120 | 121 | go c.recv() 122 | go c.send() 123 | 124 | return c 125 | } 126 | 127 | func newPoolClient(network, address string, pool *Pool) *Client { 128 | c, err := Dial(network, address) 129 | if err != nil { 130 | return nil 131 | } 132 | 133 | c.p = pool 134 | return c 135 | } 136 | 137 | // Dial connects to the address on the named network of GoTable server. 138 | // 139 | // Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), 140 | // and "unix". 141 | // For TCP networks, addresses have the form host:port. 142 | // For Unix networks, the address must be a file system path. 143 | // 144 | // It returns a connection Client to GoTable server. 145 | func Dial(network, address string) (*Client, error) { 146 | conn, err := net.Dial(network, address) 147 | if err != nil { 148 | return nil, err 149 | } 150 | return NewClient(conn), nil 151 | } 152 | 153 | // Create a new client Context with selected dbId. 154 | // All operations on the Context use the selected dbId. 155 | func (c *Client) NewContext(dbId uint8) *Context { 156 | return &Context{c, dbId} 157 | } 158 | 159 | // Close the connection. 160 | func (c *Client) Close() error { 161 | if c.p == nil { 162 | return c.doClose() 163 | } else { 164 | c.mtx.Lock() 165 | if c.shutdown { 166 | c.mtx.Unlock() 167 | return c.doClose() 168 | } 169 | c.mtx.Unlock() 170 | return nil 171 | } 172 | } 173 | 174 | func (c *Client) doClose() error { 175 | c.mtx.Lock() 176 | if c.closing { 177 | c.mtx.Unlock() 178 | return ErrShutdown 179 | } 180 | c.closing = true 181 | c.mtx.Unlock() 182 | 183 | close(c.sending) 184 | var err = c.c.Close() 185 | 186 | if c.p != nil { 187 | c.p.remove(c) 188 | } 189 | 190 | return err 191 | } 192 | 193 | // Test whether already authorized. 194 | func (c *Client) isAuthorized(dbId uint8) bool { 195 | c.mtx.Lock() 196 | defer c.mtx.Unlock() 197 | 198 | if c.authBM == nil { 199 | return false 200 | } 201 | 202 | if c.authBM.Get(proto.AdminDbId) { 203 | return true 204 | } 205 | 206 | return dbId != proto.AdminDbId && c.authBM.Get(uint(dbId)) 207 | } 208 | 209 | // Cache authorize result. When authorizing again, return directly. 210 | func (c *Client) cachAuth(pkg []byte) { 211 | var one proto.PkgOneOp 212 | _, err := one.Decode(pkg) 213 | if err == nil && one.ErrCode == 0 { 214 | c.mtx.Lock() 215 | if c.authBM == nil { 216 | c.authBM = util.NewBitMap(256 / 8) 217 | } 218 | c.authBM.Set(uint(one.DbId)) 219 | c.mtx.Unlock() 220 | } 221 | } 222 | 223 | func (c *Client) recv() { 224 | var headBuf = make([]byte, proto.HeadSize) 225 | var head proto.PkgHead 226 | 227 | var pkg []byte 228 | var err error 229 | for err == nil { 230 | pkg, err = proto.ReadPkg(c.r, headBuf, &head, nil) 231 | if err != nil { 232 | break 233 | } 234 | 235 | var call *Call 236 | var ok bool 237 | 238 | c.mtx.Lock() 239 | if call, ok = c.pending[head.Seq]; ok { 240 | delete(c.pending, head.Seq) 241 | } 242 | c.mtx.Unlock() 243 | 244 | if proto.CmdAuth == head.Cmd { 245 | c.cachAuth(pkg) 246 | } 247 | 248 | if call != nil { 249 | call.pkg = pkg 250 | call.ready = true 251 | call.done() 252 | } 253 | } 254 | 255 | // Terminate pending calls. 256 | c.mtx.Lock() 257 | c.shutdown = true 258 | if err == io.EOF { 259 | if c.closing { 260 | err = ErrShutdown 261 | } else { 262 | err = io.ErrUnexpectedEOF 263 | } 264 | } 265 | for _, call := range c.pending { 266 | call.err = err 267 | call.ready = true 268 | call.done() 269 | } 270 | c.mtx.Unlock() 271 | 272 | c.doClose() 273 | } 274 | 275 | func (c *Client) send() { 276 | var err error 277 | for { 278 | select { 279 | case call, ok := <-c.sending: 280 | if !ok { 281 | return 282 | } 283 | 284 | if err == nil { 285 | _, err = c.c.Write(call.pkg) 286 | if err != nil { 287 | c.mtx.Lock() 288 | c.shutdown = true 289 | c.mtx.Unlock() 290 | } 291 | } 292 | } 293 | } 294 | } 295 | 296 | func (call *Call) done() { 297 | if call.ready { 298 | select { 299 | case call.Done <- call: 300 | // ok 301 | default: 302 | // We don't want to block here. It is the caller's responsibility to make 303 | // sure the channel has enough buffer space. See comment in Go(). 304 | log.Println("discarding reply due to insufficient Done chan capacity") 305 | } 306 | } 307 | } 308 | 309 | func (c *Client) newCall(cmd uint8, done chan *Call) *Call { 310 | var call = new(Call) 311 | call.cmd = cmd 312 | if done == nil { 313 | done = make(chan *Call, 4) 314 | } else { 315 | if cap(done) == 0 { 316 | log.Panic("gotable: done channel is unbuffered") 317 | } 318 | } 319 | call.Done = done 320 | 321 | c.mtx.Lock() 322 | if c.shutdown || c.closing { 323 | c.mtx.Unlock() 324 | c.errCall(call, ErrShutdown) 325 | return call 326 | } 327 | c.seq += 1 328 | call.seq = c.seq 329 | c.pending[call.seq] = call 330 | c.mtx.Unlock() 331 | 332 | return call 333 | } 334 | 335 | func (c *Client) errCall(call *Call, err error) { 336 | call.err = err 337 | 338 | if call.seq > 0 { 339 | c.mtx.Lock() 340 | delete(c.pending, call.seq) 341 | c.mtx.Unlock() 342 | } 343 | 344 | call.done() 345 | } 346 | -------------------------------------------------------------------------------- /binlog/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package binlog 16 | 17 | import ( 18 | "bufio" 19 | "encoding/json" 20 | "fmt" 21 | "log" 22 | "os" 23 | "sort" 24 | "sync" 25 | "time" 26 | 27 | "github.com/stevejiang/gotable/api/go/table/proto" 28 | "github.com/stevejiang/gotable/util" 29 | ) 30 | 31 | // Monitor is a BinLog monitor. 32 | type Monitor interface { 33 | // NewLogComming tells the monitor there is new binlog written. 34 | NewLogComming() 35 | } 36 | 37 | const ( 38 | MinNormalSeq = uint64(1000000000000000000) 39 | ) 40 | 41 | type fileInfo struct { 42 | Idx uint64 43 | MinSeq uint64 44 | MaxSeq uint64 45 | Done bool 46 | } 47 | 48 | type readerSeq struct { 49 | seq uint64 50 | } 51 | 52 | type Request struct { 53 | MasterSeq uint64 54 | Pkg []byte 55 | } 56 | 57 | type BinLog struct { 58 | dir string 59 | memSize int 60 | keepNum int 61 | reqChan chan *Request 62 | 63 | binFile *os.File 64 | binBufW *bufio.Writer 65 | 66 | mtx sync.Mutex // The following variables are protected by mtx 67 | hasMaster bool // Has master or not, ONLY for normal master/slave 68 | msChanged bool // Whether monitors changed 69 | monitors []Monitor 70 | 71 | fileIdx uint64 72 | logSeq uint64 73 | 74 | memlog []byte 75 | usedLen int 76 | 77 | infos []*fileInfo // All binlog files 78 | rseqs []*readerSeq // Most recent reader seq 79 | } 80 | 81 | func NewBinLog(dir string, memSize, keepNum int) *BinLog { 82 | var bin = new(BinLog) 83 | bin.dir = dir 84 | bin.memSize = memSize 85 | bin.keepNum = keepNum 86 | bin.reqChan = make(chan *Request, 10000) 87 | 88 | bin.hasMaster = false // No master 89 | bin.msChanged = true 90 | bin.logSeq = MinNormalSeq // Master server seq start from here 91 | bin.memlog = make([]byte, memSize) 92 | 93 | var err = bin.init() 94 | if err != nil { 95 | log.Printf("BinLog init failed: %s\n", err) 96 | return nil 97 | } 98 | 99 | go bin.goWriteBinLog() 100 | 101 | log.Printf("BinLog fileIdx %d, logSeq %d, memSize %dMB, keepNum %d\n", 102 | bin.fileIdx, bin.logSeq, memSize/1024/1024, keepNum) 103 | 104 | return bin 105 | } 106 | 107 | func (bin *BinLog) init() error { 108 | err := os.MkdirAll(bin.dir, os.ModeDir|os.ModePerm) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | idxs, err := bin.loadAllFilesIndex() 114 | if err != nil { 115 | return err 116 | } 117 | 118 | err = bin.loadAndFixSeqFiles(idxs) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | if len(bin.infos) > 0 { 124 | bin.fileIdx = bin.infos[len(bin.infos)-1].Idx 125 | bin.logSeq = bin.infos[len(bin.infos)-1].MaxSeq 126 | } 127 | 128 | return nil 129 | } 130 | 131 | func (bin *BinLog) Close() { 132 | bin.Flush() 133 | 134 | if bin.binFile != nil { 135 | bin.binFile.Close() 136 | bin.binFile = nil 137 | } 138 | 139 | if bin.reqChan != nil { 140 | close(bin.reqChan) 141 | } 142 | 143 | bin.infos = nil 144 | bin.rseqs = nil 145 | } 146 | 147 | func (bin *BinLog) Flush() { 148 | if bin.binBufW != nil { 149 | bin.binBufW.Flush() 150 | } 151 | } 152 | 153 | func (bin *BinLog) RegisterMonitor(m Monitor) { 154 | bin.mtx.Lock() 155 | bin.monitors = append(bin.monitors, m) 156 | bin.msChanged = true 157 | bin.mtx.Unlock() 158 | } 159 | 160 | func (bin *BinLog) RemoveMonitor(m Monitor) { 161 | bin.mtx.Lock() 162 | for index, tmp := range bin.monitors { 163 | if tmp == m { 164 | copy(bin.monitors[index:], bin.monitors[index+1:]) 165 | bin.monitors = bin.monitors[:len(bin.monitors)-1] 166 | break 167 | } 168 | } 169 | bin.msChanged = true 170 | bin.mtx.Unlock() 171 | } 172 | 173 | func (bin *BinLog) AsMaster() { 174 | bin.mtx.Lock() 175 | bin.hasMaster = false 176 | if bin.logSeq < MinNormalSeq { 177 | bin.logSeq = MinNormalSeq 178 | } 179 | bin.mtx.Unlock() 180 | } 181 | 182 | func (bin *BinLog) AsSlave() { 183 | bin.mtx.Lock() 184 | bin.hasMaster = true 185 | bin.logSeq = 0 186 | bin.mtx.Unlock() 187 | } 188 | 189 | func (bin *BinLog) GetLogSeqChanLen() (uint64, int) { 190 | bin.mtx.Lock() 191 | seq := bin.logSeq 192 | chanLen := len(bin.reqChan) 193 | bin.mtx.Unlock() 194 | return seq, chanLen 195 | } 196 | 197 | // Only for master/slave mode 198 | func (bin *BinLog) GetMasterSeq() (masterSeq uint64, valid bool) { 199 | masterSeq = 0 200 | bin.mtx.Lock() 201 | if len(bin.infos) > 0 { 202 | masterSeq = bin.infos[len(bin.infos)-1].MaxSeq 203 | } 204 | bin.mtx.Unlock() 205 | 206 | valid = true 207 | if masterSeq > 0 && masterSeq < MinNormalSeq { 208 | valid = false 209 | } 210 | return 211 | } 212 | 213 | func (bin *BinLog) AddRequest(req *Request) { 214 | bin.reqChan <- req 215 | } 216 | 217 | func (bin *BinLog) GetBinFileName(fileIdx uint64) string { 218 | return fmt.Sprintf("%s/%06d.bin", bin.dir, fileIdx) 219 | } 220 | 221 | func (bin *BinLog) GetSeqFileName(fileIdx uint64) string { 222 | return fmt.Sprintf("%s/%06d.seq", bin.dir, fileIdx) 223 | } 224 | 225 | func (bin *BinLog) goWriteBinLog() { 226 | var ms []Monitor 227 | var last1, last2 *Request 228 | var tick = time.NewTicker(time.Second) 229 | defer tick.Stop() 230 | for { 231 | select { 232 | case req, ok := <-bin.reqChan: 233 | if !ok { 234 | log.Printf("write binlog channel closed: %s\n", bin.dir) 235 | return 236 | } 237 | 238 | bin.mtx.Lock() 239 | if bin.hasMaster && req.MasterSeq > 0 { 240 | bin.logSeq = req.MasterSeq 241 | } else { 242 | bin.logSeq++ 243 | } 244 | if bin.msChanged { 245 | bin.msChanged = false 246 | ms = make([]Monitor, len(bin.monitors)) 247 | copy(ms, bin.monitors) 248 | } 249 | bin.mtx.Unlock() 250 | 251 | proto.OverWriteSeq(req.Pkg, bin.logSeq) 252 | bin.doWrite(req, bin.logSeq) 253 | 254 | for _, ms := range ms { 255 | ms.NewLogComming() 256 | } 257 | 258 | last1 = req 259 | 260 | case <-tick.C: 261 | bin.Flush() 262 | 263 | if last1 == nil { 264 | if last2 != nil { 265 | log.Printf("Write binlog: seq=%d, masterSeq=%d\n", 266 | bin.logSeq, last2.MasterSeq) 267 | last2 = nil 268 | } 269 | } else { 270 | last2 = last1 271 | last1 = nil 272 | } 273 | } 274 | } 275 | } 276 | 277 | func (bin *BinLog) doWrite(req *Request, logSeq uint64) error { 278 | if bin.usedLen+len(req.Pkg) > len(bin.memlog) { 279 | if bin.binFile != nil { 280 | bin.binBufW.Flush() 281 | bin.binBufW = nil 282 | bin.binFile.Close() 283 | bin.binFile = nil 284 | 285 | bin.mtx.Lock() 286 | bin.usedLen = 0 287 | bin.infos[len(bin.infos)-1].Done = true 288 | var fi = *bin.infos[len(bin.infos)-1] 289 | var delIdxs = bin.selectDelBinLogFiles() 290 | bin.mtx.Unlock() 291 | 292 | bin.writeSeqFile(&fi) 293 | bin.deleteOldBinLogFiles(delIdxs) 294 | } 295 | } 296 | 297 | var err error 298 | if bin.binFile == nil { 299 | bin.mtx.Lock() 300 | bin.fileIdx++ 301 | bin.mtx.Unlock() 302 | 303 | var name = bin.GetBinFileName(bin.fileIdx) 304 | bin.binFile, err = os.Create(name) 305 | if err != nil { 306 | log.Printf("create file failed: (%s) %s\n", name, err) 307 | return err 308 | } 309 | 310 | bin.binBufW = bufio.NewWriter(bin.binFile) 311 | 312 | bin.mtx.Lock() 313 | bin.infos = append(bin.infos, &fileInfo{bin.fileIdx, logSeq, 0, false}) 314 | bin.mtx.Unlock() 315 | } 316 | 317 | copy(bin.memlog[bin.usedLen:], req.Pkg) 318 | bin.binBufW.Write(req.Pkg) 319 | 320 | bin.mtx.Lock() 321 | bin.usedLen += len(req.Pkg) 322 | bin.infos[len(bin.infos)-1].MaxSeq = logSeq 323 | bin.mtx.Unlock() 324 | 325 | return nil 326 | } 327 | 328 | func (bin *BinLog) selectDelBinLogFiles() []uint64 { 329 | var delIdxs []uint64 330 | 331 | var minSeq = bin.logSeq 332 | for _, rs := range bin.rseqs { 333 | if minSeq > rs.seq { 334 | minSeq = rs.seq 335 | } 336 | } 337 | var delPivot = -1 338 | for i := 0; i < len(bin.infos)-bin.keepNum; i++ { 339 | if bin.infos[i].MaxSeq < minSeq { 340 | delPivot = i 341 | } 342 | } 343 | for i := 0; i <= delPivot; i++ { 344 | delIdxs = append(delIdxs, bin.infos[i].Idx) 345 | } 346 | if delPivot > -1 { 347 | bin.infos = bin.infos[delPivot+1:] 348 | } 349 | 350 | return delIdxs 351 | } 352 | 353 | func (bin *BinLog) deleteOldBinLogFiles(idxs []uint64) { 354 | for _, idx := range idxs { 355 | os.Remove(bin.GetBinFileName(idx)) 356 | os.Remove(bin.GetSeqFileName(idx)) 357 | } 358 | } 359 | 360 | func (bin *BinLog) loadAllFilesIndex() ([]uint64, error) { 361 | dir, err := os.Open(bin.dir) 362 | if err != nil { 363 | return nil, err 364 | } 365 | 366 | fi, err := dir.Readdir(-1) 367 | if err != nil { 368 | return nil, err 369 | } 370 | 371 | var idxs []uint64 372 | for _, f := range fi { 373 | if f.IsDir() { 374 | continue 375 | } 376 | 377 | var idx uint64 378 | n, err := fmt.Sscanf(f.Name(), "%d.bin", &idx) 379 | if err != nil || n != 1 { 380 | continue 381 | } 382 | 383 | idxs = append(idxs, idx) 384 | } 385 | 386 | sort.Sort(util.Uint64Slice(idxs)) 387 | 388 | return idxs, nil 389 | } 390 | 391 | func (bin *BinLog) writeSeqFile(fi *fileInfo) error { 392 | var name = bin.GetSeqFileName(fi.Idx) 393 | file, err := os.Create(name) 394 | if err != nil { 395 | return err 396 | } 397 | defer file.Close() 398 | 399 | en := json.NewEncoder(file) 400 | 401 | err = en.Encode(fi) 402 | if err != nil { 403 | return err 404 | } 405 | 406 | return nil 407 | } 408 | 409 | func (bin *BinLog) fixSeqFile(idx uint64) (fileInfo, error) { 410 | var fi fileInfo 411 | var name = bin.GetBinFileName(idx) 412 | file, err := os.Open(name) 413 | if err != nil { 414 | return fi, err 415 | } 416 | 417 | var r = bufio.NewReader(file) 418 | var headBuf = make([]byte, proto.HeadSize) 419 | var head proto.PkgHead 420 | for { 421 | _, err := proto.ReadPkg(r, headBuf, &head, nil) 422 | if err != nil { 423 | break 424 | } 425 | if fi.MinSeq == 0 { 426 | fi.MinSeq = head.Seq 427 | fi.Idx = idx 428 | fi.Done = true 429 | } 430 | if fi.MaxSeq < head.Seq { 431 | fi.MaxSeq = head.Seq 432 | } 433 | } 434 | 435 | file.Close() 436 | 437 | if fi.Idx == 0 { 438 | os.Remove(bin.GetBinFileName(idx)) 439 | os.Remove(bin.GetSeqFileName(idx)) 440 | return fi, fmt.Errorf("no record in bin file id %d", idx) 441 | } 442 | 443 | return fi, bin.writeSeqFile(&fi) 444 | } 445 | 446 | func (bin *BinLog) loadAndFixSeqFiles(idxs []uint64) error { 447 | for _, idx := range idxs { 448 | var needFix bool 449 | var name = bin.GetSeqFileName(idx) 450 | file, err := os.Open(name) 451 | if err != nil { 452 | if os.IsNotExist(err) { 453 | needFix = true 454 | } else { 455 | return err 456 | } 457 | } 458 | 459 | var fi fileInfo 460 | if !needFix { 461 | de := json.NewDecoder(file) 462 | err = de.Decode(&fi) 463 | if err != nil { 464 | needFix = true 465 | } 466 | file.Close() 467 | } 468 | 469 | if needFix { 470 | fi, err = bin.fixSeqFile(idx) 471 | } 472 | 473 | if err == nil && fi.Idx > 0 { 474 | fi.Done = true 475 | bin.infos = append(bin.infos, &fi) 476 | } 477 | } 478 | 479 | return nil 480 | } 481 | -------------------------------------------------------------------------------- /cmd/gotable-cli/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "github.com/stevejiang/gotable/api/go/table" 20 | "github.com/stevejiang/gotable/api/go/table/proto" 21 | "strconv" 22 | "time" 23 | ) 24 | 25 | type client struct { 26 | c *table.Context 27 | dbId uint8 28 | } 29 | 30 | func newClient() *client { 31 | var c = new(client) 32 | cli, err := table.Dial(*network, *address) 33 | if err != nil { 34 | fmt.Println("Dial failed: ", err) 35 | return nil 36 | } 37 | 38 | c.dbId = 0 39 | c.c = cli.NewContext(c.dbId) 40 | 41 | return c 42 | } 43 | 44 | func (c *client) auth(args []string) error { 45 | //auth 46 | if len(args) != 2 { 47 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 48 | } 49 | 50 | dbId, err := getDatabaseId(args[0]) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | password, err := extractString(args[1]) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | ctx := c.c.Client().NewContext(dbId) 61 | err = ctx.Auth(password) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if dbId != proto.AdminDbId && dbId != c.dbId { 67 | c.c = ctx 68 | c.dbId = dbId 69 | } 70 | 71 | fmt.Println("OK") 72 | return nil 73 | } 74 | 75 | func (c *client) use(args []string) error { 76 | //select 77 | if len(args) != 1 { 78 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 79 | } 80 | 81 | dbId, err := getDatabaseId(args[0]) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | c.dbId = dbId 87 | c.c = c.c.Client().NewContext(dbId) 88 | 89 | fmt.Println("OK") 90 | return nil 91 | } 92 | 93 | func (c *client) slaveOf(args []string) error { 94 | //slaveof [host] 95 | //Examples: 96 | //slaveof 97 | //slaveof 127.0.0.1:6688 98 | if len(args) > 1 { 99 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 100 | } 101 | 102 | var host string 103 | var err error 104 | if len(args) > 0 { 105 | host, err = extractString(args[0]) 106 | if err != nil { 107 | return err 108 | } 109 | } 110 | 111 | var cc = table.CtrlContext(*c.c) 112 | err = cc.SlaveOf(host) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | fmt.Println("OK") 118 | return nil 119 | } 120 | 121 | func (c *client) ping() error { 122 | // ping 123 | start := time.Now() 124 | 125 | err := c.c.Ping() 126 | if err != nil { 127 | return err 128 | } 129 | 130 | elapsed := time.Since(start) 131 | 132 | fmt.Printf("PONG: %.2fms\n", float64(elapsed)/1e6) 133 | 134 | return nil 135 | } 136 | 137 | func (c *client) get(zop bool, args []string) error { 138 | // get 139 | //zget 140 | if len(args) != 3 { 141 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 142 | } 143 | 144 | tableId, err := getTableId(args[0]) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | rowKey, err := extractString(args[1]) 150 | if err != nil { 151 | return err 152 | } 153 | colKey, err := extractString(args[2]) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | var value []byte 159 | var score int64 160 | if zop { 161 | value, score, _, err = c.c.ZGet(tableId, []byte(rowKey), []byte(colKey), 0) 162 | } else { 163 | value, score, _, err = c.c.Get(tableId, []byte(rowKey), []byte(colKey), 0) 164 | } 165 | if err != nil { 166 | return err 167 | } 168 | 169 | if value == nil { 170 | fmt.Println("") 171 | } else { 172 | fmt.Printf("[%d\t%q]\n", score, value) 173 | } 174 | 175 | return nil 176 | } 177 | 178 | func (c *client) set(zop bool, args []string) error { 179 | // set [score] 180 | //zset [score] 181 | if len(args) < 4 || len(args) > 5 { 182 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 183 | } 184 | 185 | tableId, err := getTableId(args[0]) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | rowKey, err := extractString(args[1]) 191 | if err != nil { 192 | return err 193 | } 194 | colKey, err := extractString(args[2]) 195 | if err != nil { 196 | return err 197 | } 198 | value, err := extractString(args[3]) 199 | if err != nil { 200 | return err 201 | } 202 | var score int64 203 | if len(args) >= 5 { 204 | score, err = strconv.ParseInt(args[4], 10, 64) 205 | if err != nil { 206 | return err 207 | } 208 | } 209 | 210 | if zop { 211 | err = c.c.ZSet(tableId, []byte(rowKey), []byte(colKey), []byte(value), score, 0) 212 | } else { 213 | err = c.c.Set(tableId, []byte(rowKey), []byte(colKey), []byte(value), score, 0) 214 | } 215 | if err != nil { 216 | return err 217 | } 218 | 219 | fmt.Println("OK") 220 | return nil 221 | } 222 | 223 | func (c *client) del(zop bool, args []string) error { 224 | // del 225 | //zdel 226 | if len(args) != 3 { 227 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 228 | } 229 | 230 | tableId, err := getTableId(args[0]) 231 | if err != nil { 232 | return err 233 | } 234 | 235 | rowKey, err := extractString(args[1]) 236 | if err != nil { 237 | return err 238 | } 239 | colKey, err := extractString(args[2]) 240 | if err != nil { 241 | return err 242 | } 243 | 244 | if zop { 245 | err = c.c.ZDel(tableId, []byte(rowKey), []byte(colKey), 0) 246 | } else { 247 | err = c.c.Del(tableId, []byte(rowKey), []byte(colKey), 0) 248 | } 249 | if err != nil { 250 | return err 251 | } 252 | 253 | fmt.Println("OK") 254 | return nil 255 | } 256 | 257 | func (c *client) incr(zop bool, args []string) error { 258 | // incr [score] 259 | //zincr [score] 260 | if len(args) < 3 || len(args) > 4 { 261 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 262 | } 263 | 264 | tableId, err := getTableId(args[0]) 265 | if err != nil { 266 | return err 267 | } 268 | 269 | rowKey, err := extractString(args[1]) 270 | if err != nil { 271 | return err 272 | } 273 | colKey, err := extractString(args[2]) 274 | if err != nil { 275 | return err 276 | } 277 | var score int64 = 1 278 | if len(args) >= 4 { 279 | score, err = strconv.ParseInt(args[3], 10, 64) 280 | if err != nil { 281 | return err 282 | } 283 | } 284 | 285 | var value []byte 286 | if zop { 287 | value, score, err = c.c.ZIncr(tableId, []byte(rowKey), []byte(colKey), score, 0) 288 | } else { 289 | value, score, err = c.c.Incr(tableId, []byte(rowKey), []byte(colKey), score, 0) 290 | } 291 | if err != nil { 292 | return err 293 | } 294 | 295 | fmt.Printf("[%d\t%q]\n", score, value) 296 | return nil 297 | } 298 | 299 | func (c *client) scan(args []string) error { 300 | //scan [num] 301 | if len(args) < 3 || len(args) > 4 { 302 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 303 | } 304 | 305 | tableId, err := getTableId(args[0]) 306 | if err != nil { 307 | return err 308 | } 309 | 310 | rowKey, err := extractString(args[1]) 311 | if err != nil { 312 | return err 313 | } 314 | colKey, err := extractString(args[2]) 315 | if err != nil { 316 | return err 317 | } 318 | 319 | var num int64 = 10 320 | if len(args) >= 4 { 321 | num, err = strconv.ParseInt(args[3], 10, 16) 322 | if err != nil { 323 | return err 324 | } 325 | } 326 | 327 | r, err := c.c.ScanPivot(tableId, []byte(rowKey), []byte(colKey), true, int(num)) 328 | if err != nil { 329 | return err 330 | } 331 | 332 | if len(r.Kvs) == 0 { 333 | fmt.Println("No record!") 334 | } else { 335 | for i := 0; i < len(r.Kvs); i++ { 336 | var kv = r.Kvs[i] 337 | fmt.Printf("%2d) [%q\t%d\t%q]\n", i, kv.ColKey, kv.Score, kv.Value) 338 | } 339 | } 340 | 341 | return nil 342 | } 343 | 344 | func (c *client) zscan(args []string) error { 345 | //zscan [num] 346 | if len(args) < 4 || len(args) > 5 { 347 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 348 | } 349 | 350 | tableId, err := getTableId(args[0]) 351 | if err != nil { 352 | return err 353 | } 354 | 355 | rowKey, err := extractString(args[1]) 356 | if err != nil { 357 | return err 358 | } 359 | 360 | score, err := strconv.ParseInt(args[2], 10, 64) 361 | if err != nil { 362 | return err 363 | } 364 | colKey, err := extractString(args[3]) 365 | if err != nil { 366 | return err 367 | } 368 | 369 | var num int64 = 10 370 | if len(args) >= 5 { 371 | num, err = strconv.ParseInt(args[4], 10, 16) 372 | if err != nil { 373 | return err 374 | } 375 | } 376 | 377 | r, err := c.c.ZScanPivot(tableId, []byte(rowKey), []byte(colKey), score, 378 | true, true, int(num)) 379 | if err != nil { 380 | return err 381 | } 382 | 383 | if len(r.Kvs) == 0 { 384 | fmt.Println("No record!") 385 | } else { 386 | for i := 0; i < len(r.Kvs); i++ { 387 | var kv = r.Kvs[i] 388 | fmt.Printf("%2d) [%d\t%q\t%q]\n", i, kv.Score, kv.ColKey, kv.Value) 389 | } 390 | } 391 | 392 | return nil 393 | } 394 | 395 | func (c *client) dump(args []string) error { 396 | //dump [tableId] 397 | if len(args) < 1 || len(args) > 2 { 398 | return fmt.Errorf("invalid number of arguments (%d)", len(args)) 399 | } 400 | 401 | dbId, err := getDatabaseId(args[0]) 402 | if err != nil { 403 | return err 404 | } 405 | 406 | // select new databse 407 | c.dbId = dbId 408 | c.c = c.c.Client().NewContext(dbId) 409 | 410 | var r table.DumpReply 411 | if len(args) > 1 { 412 | tableId, err := getTableId(args[1]) 413 | if err != nil { 414 | return err 415 | } 416 | 417 | r, err = c.c.DumpTable(tableId) 418 | if err != nil { 419 | return err 420 | } 421 | } else { 422 | r, err = c.c.DumpDB() 423 | if err != nil { 424 | return err 425 | } 426 | } 427 | 428 | for { 429 | for _, kv := range r.Kvs { 430 | fmt.Printf("%d\t%q\t%d\t%q\t%q\t%d\n", 431 | kv.TableId, kv.RowKey, kv.ColSpace, kv.ColKey, kv.Value, kv.Score) 432 | } 433 | 434 | if r.End { 435 | break 436 | } 437 | 438 | r, err = c.c.DumpMore(r) 439 | if err != nil { 440 | return err 441 | } 442 | } 443 | 444 | return nil 445 | } 446 | 447 | func errCodeMsg(errCode int8) string { 448 | switch errCode { 449 | case table.EcCasNotMatch: 450 | return "cas not match" 451 | case table.EcNoPrivilege: 452 | return "no access privilege" 453 | default: 454 | return fmt.Sprintf("error code %d", errCode) 455 | } 456 | } 457 | 458 | func getTableId(arg string) (uint8, error) { 459 | tableId, err := strconv.Atoi(arg) 460 | if err != nil { 461 | return 0, fmt.Errorf(" %s is not a number", arg) 462 | } 463 | 464 | if tableId < 0 || tableId > 255 { 465 | return 0, fmt.Errorf(" %s is out of range [0 ~ 255]", arg) 466 | } 467 | 468 | return uint8(tableId), nil 469 | } 470 | 471 | func getDatabaseId(arg string) (uint8, error) { 472 | dbId, err := strconv.Atoi(arg) 473 | if err != nil { 474 | return 0, fmt.Errorf(" %s is not a number", arg) 475 | } 476 | 477 | if dbId == proto.AdminDbId { 478 | return 0, fmt.Errorf(" %d is reserved for internal use only", 479 | proto.AdminDbId) 480 | } 481 | 482 | if dbId < 0 || dbId > 255 { 483 | return 0, fmt.Errorf(" %s is out of range [0 ~ 255]", arg) 484 | } 485 | 486 | return uint8(dbId), nil 487 | } 488 | 489 | func extractString(arg string) (string, error) { 490 | if arg[0] == '\'' || arg[0] == '"' { 491 | if len(arg) < 2 { 492 | return "", fmt.Errorf("invalid string (%s)", arg) 493 | } 494 | 495 | if arg[0] != arg[len(arg)-1] { 496 | return "", fmt.Errorf("invalid string (%s)", arg) 497 | } 498 | 499 | return arg[1 : len(arg)-1], nil 500 | } 501 | 502 | if len(arg) >= 3 && arg[1] == '"' && arg[len(arg)-1] == '"' { 503 | if arg[0] != 'q' && arg[0] != 'Q' { 504 | return "", fmt.Errorf("invalid string (%s)", arg) 505 | } 506 | 507 | var s string 508 | _, err := fmt.Sscanf(arg[1:], "%q", &s) 509 | if err != nil { 510 | return "", err 511 | } 512 | return s, nil 513 | } 514 | 515 | return arg, nil 516 | } 517 | -------------------------------------------------------------------------------- /server/replication.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server 16 | 17 | import ( 18 | "errors" 19 | "log" 20 | "net" 21 | "sync" 22 | "sync/atomic" 23 | "time" 24 | 25 | "github.com/stevejiang/gotable/api/go/table/proto" 26 | "github.com/stevejiang/gotable/binlog" 27 | "github.com/stevejiang/gotable/config" 28 | "github.com/stevejiang/gotable/ctrl" 29 | "github.com/stevejiang/gotable/store" 30 | "github.com/stevejiang/gotable/util" 31 | ) 32 | 33 | type slave struct { 34 | reqChan *RequestChan 35 | bin *binlog.BinLog 36 | mc *config.MasterConfig 37 | adminPwd string 38 | 39 | mtx sync.Mutex // protects following 40 | mi config.MasterInfo 41 | cli *Client 42 | closed bool 43 | } 44 | 45 | func NewSlave(reqChan *RequestChan, bin *binlog.BinLog, 46 | mc *config.MasterConfig, adminPwd string) *slave { 47 | var slv = new(slave) 48 | slv.reqChan = reqChan 49 | slv.bin = bin 50 | slv.mc = mc 51 | slv.mi = mc.GetMaster() 52 | slv.adminPwd = adminPwd 53 | 54 | return slv 55 | } 56 | 57 | func (slv *slave) Close() { 58 | var cli *Client 59 | slv.mtx.Lock() 60 | if !slv.closed { 61 | slv.closed = true 62 | cli = slv.cli 63 | } 64 | slv.mtx.Unlock() 65 | 66 | if cli != nil { 67 | cli.Close() 68 | } 69 | } 70 | 71 | func (slv *slave) DelayClose() { 72 | // Any better solution? 73 | log.Printf("Delay close slave %p\n", slv) 74 | time.Sleep(time.Second * 2) 75 | slv.Close() 76 | } 77 | 78 | func (slv *slave) IsClosed() bool { 79 | slv.mtx.Lock() 80 | closed := slv.closed 81 | slv.mtx.Unlock() 82 | return closed 83 | } 84 | 85 | func (slv *slave) GoConnectToMaster() { 86 | slv.doConnectToMaster() 87 | 88 | slv.cli = nil 89 | slv.bin = nil 90 | slv.reqChan = nil 91 | slv.mc = nil 92 | } 93 | 94 | func (slv *slave) doConnectToMaster() { 95 | slv.mtx.Lock() 96 | var mi = slv.mi 97 | slv.mtx.Unlock() 98 | 99 | for { 100 | if slv.IsClosed() { 101 | return 102 | } 103 | 104 | c, err := net.Dial("tcp", mi.MasterAddr) 105 | if err != nil { 106 | log.Printf("Connect to master %s failed, sleep 1 second and try again.\n", 107 | mi.MasterAddr) 108 | time.Sleep(time.Second) 109 | continue 110 | } 111 | 112 | mi.MasterAddr = util.GetRealAddr(mi.MasterAddr, c.RemoteAddr().String()) 113 | mi.SlaveAddr = util.GetRealAddr(mi.SlaveAddr, c.LocalAddr().String()) 114 | 115 | if mi.MasterAddr == mi.SlaveAddr { 116 | log.Printf("Master and slave addresses are the same!\n") 117 | c.Close() 118 | slv.Close() 119 | return 120 | } 121 | 122 | cli := NewClient(c, false) 123 | slv.mtx.Lock() 124 | slv.mi = mi 125 | slv.cli = cli 126 | slv.mtx.Unlock() 127 | if slv.IsClosed() { 128 | return 129 | } 130 | 131 | cli.SetClientType(ClientTypeSlave) 132 | 133 | go cli.GoRecvRequest(slv.reqChan, slv) 134 | go cli.GoSendResponse() 135 | 136 | if len(slv.adminPwd) == 0 { 137 | err = slv.SendSlaveOfToMaster() 138 | if err != nil { 139 | log.Printf("SendSlaveOfToMaster failed(%s), close slave!", err) 140 | slv.Close() 141 | return 142 | } 143 | } else { 144 | err = slv.SendAuthToMaster() 145 | if err != nil { 146 | log.Printf("SendAuthToMaster failed(%s), close slave!", err) 147 | slv.Close() 148 | return 149 | } 150 | } 151 | 152 | for !cli.IsClosed() { 153 | time.Sleep(time.Second) 154 | } 155 | } 156 | } 157 | 158 | func (slv *slave) SendSlaveOfToMaster() error { 159 | slv.mtx.Lock() 160 | var mi = slv.mi 161 | var cli = slv.cli 162 | slv.mtx.Unlock() 163 | 164 | if cli == nil { 165 | return nil 166 | } 167 | 168 | var err error 169 | var pkg []byte 170 | if mi.Migration { 171 | var p ctrl.PkgMigrate 172 | p.ClientReq = false 173 | p.MasterAddr = mi.MasterAddr 174 | p.SlaveAddr = mi.SlaveAddr 175 | p.SlotId = mi.SlotId 176 | 177 | pkg, err = ctrl.Encode(proto.CmdMigrate, 0, 0, &p) 178 | if err != nil { 179 | return err 180 | } 181 | } else { 182 | lastSeq, valid := slv.bin.GetMasterSeq() 183 | if !valid { 184 | slv.mc.SetStatus(ctrl.SlaveNeedClear) 185 | // Any better solution? 186 | log.Fatalf("Slave lastSeq %d is out of sync, please clear old data! "+ 187 | "(Restart may fix this issue)", lastSeq) 188 | } 189 | 190 | var p ctrl.PkgSlaveOf 191 | p.ClientReq = false 192 | p.MasterAddr = mi.MasterAddr 193 | p.SlaveAddr = mi.SlaveAddr 194 | p.LastSeq = lastSeq 195 | log.Printf("Connect to master %s with lastSeq %d\n", 196 | mi.MasterAddr, p.LastSeq) 197 | 198 | pkg, err = ctrl.Encode(proto.CmdSlaveOf, 0, 0, &p) 199 | if err != nil { 200 | return err 201 | } 202 | } 203 | 204 | cli.AddResp(pkg) 205 | return slv.mc.SetStatus(ctrl.SlaveFullSync) 206 | } 207 | 208 | func (slv *slave) SendAuthToMaster() error { 209 | slv.mtx.Lock() 210 | var cli = slv.cli 211 | slv.mtx.Unlock() 212 | if cli == nil { 213 | return nil 214 | } 215 | if len(slv.adminPwd) == 0 { 216 | return nil 217 | } 218 | 219 | var p proto.PkgOneOp 220 | p.DbId = proto.AdminDbId 221 | p.Cmd = proto.CmdAuth 222 | p.RowKey = []byte(slv.adminPwd) 223 | 224 | var pkg = make([]byte, p.Length()) 225 | _, err := p.Encode(pkg) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | cli.AddResp(pkg) 231 | return nil 232 | } 233 | 234 | type master struct { 235 | syncChan chan struct{} 236 | cli *Client 237 | bin *binlog.BinLog 238 | reader *binlog.Reader 239 | slaveAddr string 240 | lastSeq uint64 241 | migration bool // true: Migration; false: Normal master/slave 242 | slotId uint16 // Only meaningful for migration 243 | 244 | // atomic 245 | closed uint32 246 | } 247 | 248 | func NewMaster(slaveAddr string, lastSeq uint64, migration bool, slotId uint16, 249 | cli *Client, bin *binlog.BinLog) *master { 250 | var ms = new(master) 251 | ms.syncChan = make(chan struct{}, 20) 252 | ms.cli = cli 253 | ms.bin = bin 254 | ms.reader = nil 255 | ms.slaveAddr = slaveAddr 256 | ms.lastSeq = lastSeq 257 | ms.migration = migration 258 | if migration { 259 | ms.slotId = slotId 260 | } else { 261 | ms.slotId = ctrl.TotalSlotNum 262 | } 263 | ms.bin.RegisterMonitor(ms) 264 | 265 | return ms 266 | } 267 | 268 | func (ms *master) doClose() { 269 | atomic.AddUint32(&ms.closed, 1) 270 | 271 | cli := ms.cli 272 | if cli != nil { 273 | cli.Close() 274 | } 275 | 276 | bin := ms.bin 277 | if bin != nil { 278 | bin.RemoveMonitor(ms) 279 | } 280 | 281 | reader := ms.reader 282 | if reader != nil { 283 | reader.Close() 284 | } 285 | 286 | ms.cli = nil 287 | ms.bin = nil 288 | ms.reader = nil 289 | 290 | log.Printf("Master sync to slave %s is closed\n", ms.slaveAddr) 291 | } 292 | 293 | func (ms *master) isClosed() bool { 294 | return atomic.LoadUint32(&ms.closed) > 0 295 | } 296 | 297 | func (ms *master) NewLogComming() { 298 | if len(ms.syncChan)*2 < cap(ms.syncChan) { 299 | ms.syncChan <- struct{}{} 300 | } 301 | } 302 | 303 | func (ms *master) syncStatus(key string, lastSeq uint64) { 304 | var p proto.PkgOneOp 305 | p.Cmd = proto.CmdSyncSt 306 | p.DbId = proto.AdminDbId 307 | p.Seq = lastSeq 308 | p.RowKey = []byte(key) 309 | var pkg = make([]byte, p.Length()) 310 | p.Encode(pkg) 311 | ms.cli.AddResp(pkg) 312 | } 313 | 314 | func (ms *master) openReader(lastSeq uint64) error { 315 | ms.reader = binlog.NewReader(ms.bin) 316 | var err = ms.reader.Init(lastSeq) 317 | if err == binlog.ErrLogMissing { 318 | ms.syncStatus(store.KeySyncLogMissing, 0) 319 | 320 | // Any better solution? 321 | log.Printf("Wait for sync status log missing to slave %s\n", ms.slaveAddr) 322 | time.Sleep(time.Second * 2) 323 | } 324 | 325 | return err 326 | } 327 | 328 | func (ms *master) fullSync(tbl *store.Table) (uint64, error) { 329 | var lastSeq uint64 330 | if ms.lastSeq > 0 { 331 | lastSeq = ms.lastSeq 332 | if ms.migration { 333 | log.Printf("Migration lastSeq is not 0, close now!\n") 334 | return lastSeq, errors.New("migration lastSeq is not 0") 335 | } else { 336 | log.Printf("Already full synced to %s\n", ms.slaveAddr) 337 | return lastSeq, nil 338 | } 339 | } 340 | 341 | // Stop write globally 342 | rwMtx := tbl.GetRWMutex() 343 | rwMtx.Lock() 344 | var chanLen int 345 | for lastSeq, chanLen = ms.bin.GetLogSeqChanLen(); chanLen != 0; { 346 | log.Println("Stop write globally for 1ms") 347 | time.Sleep(time.Millisecond) 348 | lastSeq, chanLen = ms.bin.GetLogSeqChanLen() 349 | } 350 | var it = tbl.NewIterator(false) 351 | rwMtx.Unlock() 352 | 353 | defer it.Destroy() 354 | 355 | // Open BinLog reader to keep log files from deleting 356 | var err = ms.openReader(lastSeq) 357 | if err != nil { 358 | return lastSeq, err 359 | } 360 | 361 | // Full sync 362 | var p proto.PkgMultiOp 363 | p.Cmd = proto.CmdSync 364 | for it.SeekToFirst(); it.Valid(); { 365 | ok := store.SeekAndCopySyncPkg(it, &p, ms.migration, ms.slotId) 366 | 367 | if ms.cli.IsClosed() { 368 | return lastSeq, nil 369 | } 370 | 371 | if len(p.Kvs) > 0 { 372 | p.Seq = 0 373 | var pkg = make([]byte, p.Length()) 374 | p.Encode(pkg) 375 | ms.cli.AddResp(pkg) 376 | } 377 | 378 | if !ok { 379 | break 380 | } 381 | } 382 | 383 | // Tell slave full sync finished 384 | if ms.migration { 385 | ms.syncStatus(store.KeyFullSyncEnd, 0) 386 | log.Printf("Full migration to %s slotId %d finished\n", 387 | ms.slaveAddr, ms.slotId) 388 | } else { 389 | ms.syncStatus(store.KeyFullSyncEnd, lastSeq) 390 | log.Printf("Full sync to %s finished\n", ms.slaveAddr) 391 | } 392 | 393 | return lastSeq, nil 394 | } 395 | 396 | func (ms *master) GoAsync(tbl *store.Table) { 397 | defer ms.doClose() 398 | 399 | lastSeq, err := ms.fullSync(tbl) 400 | if err != nil || ms.isClosed() || ms.cli.IsClosed() { 401 | return 402 | } 403 | 404 | if ms.migration { 405 | log.Printf("Start incremental migration to %s slotId %d, lastSeq=%d\n", 406 | ms.slaveAddr, ms.slotId, lastSeq) 407 | } else { 408 | log.Printf("Start incremental sync to %s, lastSeq=%d", 409 | ms.slaveAddr, lastSeq) 410 | } 411 | 412 | if ms.reader == nil && ms.openReader(lastSeq) != nil { 413 | log.Printf("Invalid BinLog reader, lastSeq=%d, stop sync!\n", lastSeq) 414 | return 415 | } 416 | 417 | ms.NewLogComming() 418 | 419 | var readyCount int64 420 | var head proto.PkgHead 421 | var tick = time.NewTicker(time.Second) 422 | defer tick.Stop() 423 | for { 424 | select { 425 | case _, ok := <-ms.syncChan: 426 | if !ok || ms.isClosed() || ms.cli.IsClosed() { 427 | return 428 | } 429 | 430 | for !ms.isClosed() && !ms.cli.IsClosed() { 431 | var pkg = ms.reader.Next() 432 | if pkg == nil { 433 | if readyCount%61 == 0 { 434 | ms.syncStatus(store.KeyIncrSyncEnd, 0) 435 | readyCount++ 436 | } 437 | break 438 | } 439 | 440 | pkg, err := ms.convertMigPkg(pkg, &head) 441 | if err != nil { 442 | break 443 | } 444 | if pkg == nil { 445 | continue 446 | } 447 | 448 | ms.cli.AddResp(pkg) 449 | } 450 | 451 | case <-tick.C: 452 | if ms.isClosed() || ms.cli.IsClosed() { 453 | return 454 | } 455 | 456 | if readyCount > 0 { 457 | readyCount++ 458 | } 459 | 460 | ms.NewLogComming() 461 | } 462 | } 463 | } 464 | 465 | func (ms *master) convertMigPkg(pkg []byte, head *proto.PkgHead) ([]byte, error) { 466 | _, err := head.Decode(pkg) 467 | if err != nil { 468 | return nil, err 469 | } 470 | 471 | if !ms.migration { 472 | return pkg, nil 473 | } 474 | 475 | switch head.Cmd { 476 | case proto.CmdIncr: 477 | fallthrough 478 | case proto.CmdDel: 479 | fallthrough 480 | case proto.CmdSet: 481 | var p proto.PkgOneOp 482 | _, err = p.Decode(pkg) 483 | if err != nil { 484 | return nil, err 485 | } 486 | if ms.slotId == ctrl.GetSlotId(p.DbId, p.TableId, p.RowKey) { 487 | return pkg, nil 488 | } else { 489 | return nil, nil 490 | } 491 | case proto.CmdSync: 492 | fallthrough 493 | case proto.CmdMIncr: 494 | fallthrough 495 | case proto.CmdMDel: 496 | fallthrough 497 | case proto.CmdMSet: 498 | var p proto.PkgMultiOp 499 | _, err = p.Decode(pkg) 500 | if err != nil { 501 | return nil, err 502 | } 503 | var kvs []proto.KeyValue 504 | for i := 0; i < len(p.Kvs); i++ { 505 | if ms.slotId == ctrl.GetSlotId(p.DbId, p.Kvs[i].TableId, p.Kvs[i].RowKey) { 506 | kvs = append(kvs, p.Kvs[i]) 507 | } 508 | } 509 | if len(kvs) == 0 { 510 | return nil, nil 511 | } else { 512 | p.Kvs = kvs 513 | pkg = make([]byte, p.Length()) 514 | _, err = p.Encode(pkg) 515 | if err != nil { 516 | return nil, err 517 | } 518 | return pkg, nil 519 | } 520 | } 521 | 522 | return nil, nil 523 | } 524 | -------------------------------------------------------------------------------- /api/go/table/proto/codec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proto 16 | 17 | import ( 18 | "encoding/binary" 19 | ) 20 | 21 | // CtrlFlag 22 | const ( 23 | CtrlErrCode = 0x1 // Response Error Code 24 | CtrlCas = 0x2 // Compare And Switch 25 | CtrlColSpace = 0x4 26 | CtrlValue = 0x8 27 | CtrlScore = 0x10 28 | ) 29 | 30 | const ( 31 | ColSpaceDefault = 0 // Default column space 32 | ColSpaceScore1 = 1 // rowKey+score+colKey => value 33 | ColSpaceScore2 = 2 // rowKey+colKey => value+score 34 | ) 35 | 36 | // KeyValue=cCtrlFlag+cTableId+[cErrCode]+[cColSpace] 37 | // +cRowKeyLen+sRowKey+wColKeyLen+sColKey 38 | // +[dwValueLen+sValue]+[ddwScore]+[dwCas] 39 | type KeyValue struct { 40 | CtrlFlag uint8 41 | ErrCode int8 // default: 0 if missing 42 | ColSpace uint8 // default: 0 if missing 43 | TableId uint8 44 | RowKey []byte 45 | ColKey []byte 46 | Value []byte // default: nil if missing 47 | Score int64 // default: 0 if missing 48 | Cas uint32 // default: 0 if missing 49 | } 50 | 51 | func (kv *KeyValue) SetErrCode(errCode int8) { 52 | kv.ErrCode = errCode 53 | if errCode != 0 { 54 | kv.CtrlFlag |= CtrlErrCode 55 | } else { 56 | kv.CtrlFlag &^= CtrlErrCode 57 | } 58 | } 59 | 60 | func (kv *KeyValue) SetColSpace(colSpace uint8) { 61 | kv.ColSpace = colSpace 62 | if colSpace != 0 { 63 | kv.CtrlFlag |= CtrlColSpace 64 | } else { 65 | kv.CtrlFlag &^= CtrlColSpace 66 | } 67 | } 68 | 69 | func (kv *KeyValue) SetCas(cas uint32) { 70 | kv.Cas = cas 71 | if cas != 0 { 72 | kv.CtrlFlag |= CtrlCas 73 | } else { 74 | kv.CtrlFlag &^= CtrlCas 75 | } 76 | } 77 | 78 | func (kv *KeyValue) SetScore(score int64) { 79 | kv.Score = score 80 | if score != 0 { 81 | kv.CtrlFlag |= CtrlScore 82 | } else { 83 | kv.CtrlFlag &^= CtrlScore 84 | } 85 | } 86 | 87 | func (kv *KeyValue) SetValue(value []byte) { 88 | kv.Value = value 89 | if len(value) != 0 { 90 | kv.CtrlFlag |= CtrlValue 91 | } else { 92 | kv.CtrlFlag &^= CtrlValue 93 | } 94 | } 95 | 96 | // PkgFlag 97 | const ( 98 | // Common flags 99 | FlagZop = 0x1 // if set, it is a "Z" op 100 | 101 | // (Z)Scan flags 102 | FlagScanAsc = 0x4 // if set, Scan in ASC order, else DESC order 103 | FlagScanKeyStart = 0x8 // if set, Scan start from MIN/MAX key 104 | FlagScanEnd = 0x10 // if set, Scan finished, stop now 105 | 106 | // Dump flags 107 | FlagDumpTable = 0x4 // if set, Dump only one table, else Dump current DB(dbId) 108 | FlagDumpSlotStart = 0x8 // if set, Dump start from new SlotId, else from pivot record 109 | FlagDumpEnd = 0x10 // if set, Dump finished, stop now 110 | ) 111 | 112 | // Get, Set, Del, GetSet, GetDel, ZGet, ZSet, Sync 113 | // PKG=HEAD+cPkgFlag+KeyValue 114 | type PkgOneOp struct { 115 | PkgHead 116 | PkgFlag uint8 117 | KeyValue 118 | } 119 | 120 | // MGet, MSet, MDel, MZGet, MZSet, MZDel 121 | // PKG=HEAD+cPkgFlag+cErrCode+wNum+KeyValue[wNum] 122 | type PkgMultiOp struct { 123 | PkgFlag uint8 124 | ErrCode int8 125 | PkgHead 126 | Kvs []KeyValue 127 | } 128 | 129 | // Scan, ZScan 130 | // PKG=PkgOneOp+wNum 131 | type PkgScanReq struct { 132 | Num uint16 133 | PkgOneOp 134 | } 135 | 136 | // Scan, ZScan 137 | type PkgScanResp struct { 138 | PkgMultiOp 139 | } 140 | 141 | // Dump 142 | // PKG=PkgOneOp+wStartSlotId+wEndSlotId 143 | type PkgDumpReq struct { 144 | StartSlotId uint16 // Dump start slot ID (included) 145 | EndSlotId uint16 // Dump finish slot ID (included) 146 | PkgOneOp 147 | } 148 | 149 | // Dump 150 | // PKG=PkgMultiOp+wStartSlotId+wEndSlotId+wLastSlotId 151 | type PkgDumpResp struct { 152 | StartSlotId uint16 153 | EndSlotId uint16 154 | LastSlotId uint16 // Last Slot ID tried to dump 155 | PkgMultiOp 156 | } 157 | 158 | func (kv *KeyValue) Length() int { 159 | // KeyValue=cCtrlFlag+cTableId+[cErrCode]+[cColSpace] 160 | // +cRowKeyLen+sRowKey+wColKeyLen+sColKey 161 | // +[dwValueLen+sValue]+[ddwScore]+[dwCas] 162 | var n = 2 163 | if kv.CtrlFlag&CtrlErrCode != 0 { 164 | n += 1 165 | } 166 | if kv.CtrlFlag&CtrlColSpace != 0 { 167 | n += 1 168 | } 169 | n += 1 + len(kv.RowKey) + 2 + len(kv.ColKey) 170 | if kv.CtrlFlag&CtrlValue != 0 { 171 | n += 4 + len(kv.Value) 172 | } 173 | if kv.CtrlFlag&CtrlScore != 0 { 174 | n += 8 175 | } 176 | if kv.CtrlFlag&CtrlCas != 0 { 177 | n += 4 178 | } 179 | return n 180 | } 181 | 182 | func (kv *KeyValue) Encode(pkg []byte) (int, error) { 183 | if len(pkg) < kv.Length() { 184 | return 0, ErrPkgLen 185 | } 186 | if len(kv.RowKey) > MaxUint8 { 187 | return 0, ErrRowKeyLen 188 | } 189 | if len(kv.ColKey) > MaxUint16 { 190 | return 0, ErrColKeyLen 191 | } 192 | 193 | var n int 194 | pkg[n] = kv.CtrlFlag 195 | n += 1 196 | pkg[n] = kv.TableId 197 | n += 1 198 | if kv.CtrlFlag&CtrlErrCode != 0 { 199 | pkg[n] = uint8(kv.ErrCode) 200 | n += 1 201 | } 202 | if kv.CtrlFlag&CtrlColSpace != 0 { 203 | pkg[n] = kv.ColSpace 204 | n += 1 205 | } 206 | 207 | pkg[n] = uint8(len(kv.RowKey)) 208 | n += 1 209 | copy(pkg[n:], kv.RowKey) 210 | n += len(kv.RowKey) 211 | 212 | binary.BigEndian.PutUint16(pkg[n:], uint16(len(kv.ColKey))) 213 | n += 2 214 | copy(pkg[n:], kv.ColKey) 215 | n += len(kv.ColKey) 216 | 217 | if kv.CtrlFlag&CtrlValue != 0 { 218 | binary.BigEndian.PutUint32(pkg[n:], uint32(len(kv.Value))) 219 | n += 4 220 | copy(pkg[n:], kv.Value) 221 | n += len(kv.Value) 222 | } 223 | if kv.CtrlFlag&CtrlScore != 0 { 224 | binary.BigEndian.PutUint64(pkg[n:], uint64(kv.Score)) 225 | n += 8 226 | } 227 | if kv.CtrlFlag&CtrlCas != 0 { 228 | binary.BigEndian.PutUint32(pkg[n:], kv.Cas) 229 | n += 4 230 | } 231 | return n, nil 232 | } 233 | 234 | func (kv *KeyValue) Decode(pkg []byte) (int, error) { 235 | var pkgLen = len(pkg) 236 | var n = 0 237 | if n+2 > pkgLen { 238 | return n, ErrPkgLen 239 | } 240 | kv.CtrlFlag = pkg[n] 241 | n += 1 242 | kv.TableId = pkg[n] 243 | n += 1 244 | 245 | if kv.CtrlFlag&CtrlErrCode != 0 { 246 | if n+1 > pkgLen { 247 | return n, ErrPkgLen 248 | } 249 | kv.ErrCode = int8(pkg[n]) 250 | n += 1 251 | } else { 252 | kv.ErrCode = 0 253 | } 254 | if kv.CtrlFlag&CtrlColSpace != 0 { 255 | if n+1 > pkgLen { 256 | return n, ErrPkgLen 257 | } 258 | kv.ColSpace = pkg[n] 259 | n += 1 260 | } else { 261 | kv.ColSpace = 0 262 | } 263 | 264 | if n+1 > pkgLen { 265 | return n, ErrPkgLen 266 | } 267 | var rowKeyLen = int(pkg[n]) 268 | n += 1 269 | if n+rowKeyLen+2 > pkgLen { 270 | return n, ErrPkgLen 271 | } 272 | kv.RowKey = pkg[n : n+rowKeyLen] 273 | n += rowKeyLen 274 | 275 | var colKeyLen = int(binary.BigEndian.Uint16(pkg[n:])) 276 | n += 2 277 | if n+colKeyLen > pkgLen { 278 | return n, ErrPkgLen 279 | } 280 | kv.ColKey = pkg[n : n+colKeyLen] 281 | n += colKeyLen 282 | 283 | if kv.CtrlFlag&CtrlValue != 0 { 284 | if n+4 > pkgLen { 285 | return n, ErrPkgLen 286 | } 287 | var valueLen = int(binary.BigEndian.Uint32(pkg[n:])) 288 | n += 4 289 | if n+valueLen > pkgLen { 290 | return n, ErrPkgLen 291 | } 292 | kv.Value = pkg[n : n+valueLen] 293 | n += valueLen 294 | } else { 295 | if kv.ErrCode != 0 { 296 | kv.Value = nil // Key not exist 297 | } else { 298 | kv.Value = pkg[0:0] 299 | } 300 | } 301 | if kv.CtrlFlag&CtrlScore != 0 { 302 | if n+8 > pkgLen { 303 | return n, ErrPkgLen 304 | } 305 | kv.Score = int64(binary.BigEndian.Uint64(pkg[n:])) 306 | n += 8 307 | } else { 308 | kv.Score = 0 309 | } 310 | if kv.CtrlFlag&CtrlCas != 0 { 311 | if n+4 > pkgLen { 312 | return n, ErrPkgLen 313 | } 314 | kv.Cas = binary.BigEndian.Uint32(pkg[n:]) 315 | n += 4 316 | } else { 317 | kv.Cas = 0 318 | } 319 | return n, nil 320 | } 321 | 322 | func (p *PkgOneOp) Length() int { 323 | // PKG = HEAD+cPkgFlag+KeyValue 324 | return HeadSize + 1 + p.KeyValue.Length() 325 | } 326 | 327 | func (p *PkgOneOp) Encode(pkg []byte) (int, error) { 328 | n, err := p.PkgHead.Encode(pkg) 329 | if err != nil { 330 | return n, err 331 | } 332 | 333 | if n+1 > len(pkg) { 334 | return 0, ErrPkgLen 335 | } 336 | pkg[n] = p.PkgFlag 337 | n += 1 338 | 339 | m, err := p.KeyValue.Encode(pkg[n:]) 340 | if err != nil { 341 | return n, err 342 | } 343 | n += m 344 | 345 | OverWriteLen(pkg, n) 346 | return n, nil 347 | } 348 | 349 | func (p *PkgOneOp) Decode(pkg []byte) (int, error) { 350 | n, err := p.PkgHead.Decode(pkg) 351 | if err != nil { 352 | return 0, err 353 | } 354 | 355 | if n+1 > len(pkg) { 356 | return n, ErrPkgLen 357 | } 358 | p.PkgFlag = pkg[n] 359 | n += 1 360 | 361 | m, err := p.KeyValue.Decode(pkg[n:]) 362 | if err != nil { 363 | return n, err 364 | } 365 | n += m 366 | 367 | return n, nil 368 | } 369 | 370 | func (p *PkgMultiOp) Length() int { 371 | // PKG = HEAD+cPkgFlag+cErrCode+wNum+KeyValue[wNum] 372 | var n = HeadSize + 4 373 | for i := 0; i < len(p.Kvs); i++ { 374 | n += p.Kvs[i].Length() 375 | } 376 | return n 377 | } 378 | 379 | func (p *PkgMultiOp) SetErrCode(errCode int8) { 380 | p.ErrCode = errCode 381 | } 382 | 383 | func (p *PkgMultiOp) Encode(pkg []byte) (int, error) { 384 | var numKvs = len(p.Kvs) 385 | if numKvs > MaxUint16 { 386 | return 0, ErrKvArrayLen 387 | } 388 | 389 | n, err := p.PkgHead.Encode(pkg) 390 | if err != nil { 391 | return n, err 392 | } 393 | 394 | if n+4 > len(pkg) { 395 | return 0, ErrPkgLen 396 | } 397 | pkg[n] = p.PkgFlag 398 | n += 1 399 | pkg[n] = uint8(p.ErrCode) 400 | n += 1 401 | binary.BigEndian.PutUint16(pkg[n:], uint16(numKvs)) 402 | n += 2 403 | 404 | for i := 0; i < numKvs; i++ { 405 | m, err := p.Kvs[i].Encode(pkg[n:]) 406 | if err != nil { 407 | return n, err 408 | } 409 | n += m 410 | } 411 | 412 | OverWriteLen(pkg, n) 413 | return n, nil 414 | } 415 | 416 | func (p *PkgMultiOp) Decode(pkg []byte) (int, error) { 417 | n, err := p.PkgHead.Decode(pkg) 418 | if err != nil { 419 | return 0, err 420 | } 421 | 422 | if n+4 > len(pkg) { 423 | return n, ErrPkgLen 424 | } 425 | p.PkgFlag = pkg[n] 426 | n += 1 427 | p.ErrCode = int8(pkg[n]) 428 | n += 1 429 | var numKvs = int(binary.BigEndian.Uint16(pkg[n:])) 430 | n += 2 431 | 432 | p.Kvs = make([]KeyValue, numKvs) 433 | for i := 0; i < numKvs; i++ { 434 | m, err := p.Kvs[i].Decode(pkg[n:]) 435 | if err != nil { 436 | return n, err 437 | } 438 | n += m 439 | } 440 | 441 | return n, nil 442 | } 443 | 444 | func (p *PkgScanReq) Length() int { 445 | // PKG=PkgOneOp+wNum 446 | return p.PkgOneOp.Length() + 2 447 | } 448 | 449 | func (p *PkgScanReq) Encode(pkg []byte) (int, error) { 450 | n, err := p.PkgOneOp.Encode(pkg) 451 | if err != nil { 452 | return n, err 453 | } 454 | 455 | if n+2 > len(pkg) { 456 | return 0, ErrPkgLen 457 | } 458 | binary.BigEndian.PutUint16(pkg[n:], p.Num) 459 | n += 2 460 | 461 | OverWriteLen(pkg, n) 462 | return n, nil 463 | } 464 | 465 | func (p *PkgScanReq) Decode(pkg []byte) (int, error) { 466 | n, err := p.PkgOneOp.Decode(pkg) 467 | if err != nil { 468 | return n, err 469 | } 470 | 471 | if n+2 > len(pkg) { 472 | return n, ErrPkgLen 473 | } 474 | p.Num = binary.BigEndian.Uint16(pkg[n:]) 475 | n += 2 476 | 477 | return n, nil 478 | } 479 | 480 | func (p *PkgDumpReq) Length() int { 481 | // PKG=PkgOneOp+wStartSlotId+wEndSlotId 482 | return p.PkgOneOp.Length() + 4 483 | } 484 | 485 | func (p *PkgDumpReq) Encode(pkg []byte) (int, error) { 486 | n, err := p.PkgOneOp.Encode(pkg) 487 | if err != nil { 488 | return n, err 489 | } 490 | 491 | if n+4 > len(pkg) { 492 | return n, ErrPkgLen 493 | } 494 | binary.BigEndian.PutUint16(pkg[n:], p.StartSlotId) 495 | n += 2 496 | binary.BigEndian.PutUint16(pkg[n:], p.EndSlotId) 497 | n += 2 498 | 499 | OverWriteLen(pkg, n) 500 | return n, nil 501 | } 502 | 503 | func (p *PkgDumpReq) Decode(pkg []byte) (int, error) { 504 | n, err := p.PkgOneOp.Decode(pkg) 505 | if err != nil { 506 | return n, err 507 | } 508 | 509 | if n+4 > len(pkg) { 510 | return n, ErrPkgLen 511 | } 512 | p.StartSlotId = binary.BigEndian.Uint16(pkg[n:]) 513 | n += 2 514 | p.EndSlotId = binary.BigEndian.Uint16(pkg[n:]) 515 | n += 2 516 | 517 | return n, nil 518 | } 519 | 520 | func (p *PkgDumpResp) Length() int { 521 | // PKG=PkgMultiOp+wStartSlotId+wEndSlotId+wLastSlotId 522 | return p.PkgMultiOp.Length() + 6 523 | } 524 | 525 | func (p *PkgDumpResp) Encode(pkg []byte) (int, error) { 526 | n, err := p.PkgMultiOp.Encode(pkg) 527 | if err != nil { 528 | return n, err 529 | } 530 | 531 | if n+6 > len(pkg) { 532 | return n, ErrPkgLen 533 | } 534 | binary.BigEndian.PutUint16(pkg[n:], p.StartSlotId) 535 | n += 2 536 | binary.BigEndian.PutUint16(pkg[n:], p.EndSlotId) 537 | n += 2 538 | binary.BigEndian.PutUint16(pkg[n:], p.LastSlotId) 539 | n += 2 540 | 541 | OverWriteLen(pkg, n) 542 | return n, nil 543 | } 544 | 545 | func (p *PkgDumpResp) Decode(pkg []byte) (int, error) { 546 | n, err := p.PkgMultiOp.Decode(pkg) 547 | if err != nil { 548 | return n, err 549 | } 550 | 551 | if n+6 > len(pkg) { 552 | return n, ErrPkgLen 553 | } 554 | p.StartSlotId = binary.BigEndian.Uint16(pkg[n:]) 555 | n += 2 556 | p.EndSlotId = binary.BigEndian.Uint16(pkg[n:]) 557 | n += 2 558 | p.LastSlotId = binary.BigEndian.Uint16(pkg[n:]) 559 | n += 2 560 | 561 | return n, nil 562 | } 563 | -------------------------------------------------------------------------------- /cmd/gotable-bench/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "github.com/stevejiang/gotable/api/go/table" 21 | "os" 22 | "runtime" 23 | "strconv" 24 | "strings" 25 | "sync" 26 | "time" 27 | ) 28 | 29 | var ( 30 | address = flag.String("h", "127.0.0.1:6688", "Server host address ip:port") 31 | network = flag.String("N", "tcp", "Server network: tcp, tcp4, tcp6, unix") 32 | cliNum = flag.Int("c", 50, "Number of parallel clients") 33 | reqNum = flag.Int("n", 100000, "Total number of requests") 34 | dataSize = flag.Int("d", 8, "Data size of SET/MSET value in bytes") 35 | testCase = flag.String("t", "set,get", "Test cases: "+ 36 | "set,get,zset,zget,scan,zscan,incr,zincr,mset,zmset,mget,zmget") 37 | rangeNum = flag.Int("range", 10, "Scan/MGet/Mset range number") 38 | histogram = flag.Int("histogram", 0, "Print histogram of operation timings") 39 | pipeline = flag.Int("P", 0, "Pipeline number") 40 | verbose = flag.Int("v", 0, "Verbose mode, if enabled it will slow down the test") 41 | poolNum = flag.Int("pool", 10, "Max number of pool connections") 42 | maxProcs = flag.Int("cpu", runtime.NumCPU(), "Go Max Procs") 43 | profileport = flag.String("profileport", "", "Profile port, such as 8080") 44 | ) 45 | 46 | func main() { 47 | flag.Parse() 48 | if *maxProcs > 0 { 49 | runtime.GOMAXPROCS(*maxProcs) 50 | } 51 | 52 | var cliPool = table.NewPool([]table.Addr{{*network, *address}}, *poolNum) 53 | client, err := cliPool.Get() 54 | if err != nil { 55 | fmt.Printf("Get connection client to host %s://%s failed!\n\n", 56 | *network, *address) 57 | cliPool.Close() 58 | flag.Usage() 59 | os.Exit(1) 60 | } 61 | client.Close() 62 | 63 | var tests = strings.Split(*testCase, ",") 64 | for _, t := range tests { 65 | switch t { 66 | case "get": 67 | benchGet(cliPool, false) 68 | case "zget": 69 | benchGet(cliPool, true) 70 | case "set": 71 | benchSet(cliPool, false) 72 | case "zset": 73 | benchSet(cliPool, true) 74 | case "incr": 75 | benchIncr(cliPool, false) 76 | case "zincr": 77 | benchIncr(cliPool, true) 78 | case "scan": 79 | benchScan(cliPool, false) 80 | case "zscan": 81 | benchScan(cliPool, true) 82 | case "mset": 83 | benchMSet(cliPool, false) 84 | case "zmset": 85 | benchMSet(cliPool, true) 86 | case "mget": 87 | benchMGet(cliPool, false) 88 | case "zmget": 89 | benchMGet(cliPool, true) 90 | default: 91 | fmt.Printf("Unknown test case: %s\n", t) 92 | } 93 | } 94 | } 95 | 96 | type OpParam struct { 97 | c *table.Context 98 | done chan *table.Call 99 | keyBuf []byte 100 | value []byte 101 | } 102 | 103 | func NewOpParam(id int, cliPool *table.Pool) *OpParam { 104 | var p = new(OpParam) 105 | client, _ := cliPool.Get() 106 | p.c = client.NewContext(0) 107 | 108 | if *pipeline > 0 { 109 | p.done = make(chan *table.Call, *pipeline) 110 | } 111 | 112 | p.keyBuf = make([]byte, 0, 64) 113 | p.value = make([]byte, *dataSize) 114 | for i := 0; i < *dataSize; i++ { 115 | p.value[i] = 'x' 116 | } 117 | 118 | return p 119 | } 120 | 121 | func (p *OpParam) ResetValue(cutLen int) { 122 | for i := 0; i < cutLen && i < len(p.value); i++ { 123 | p.value[i] = 'x' 124 | } 125 | } 126 | 127 | func benchmark(cliPool *table.Pool, name string, op func(v int, p *OpParam)) { 128 | var numChan = make(chan int, 100000) 129 | go func() { 130 | var n = *reqNum + 1000 131 | for i := 1000; i < n; i++ { 132 | numChan <- i 133 | } 134 | 135 | close(numChan) 136 | }() 137 | 138 | var hists []Histogram 139 | if *histogram != 0 && *pipeline <= 0 { 140 | hists = make([]Histogram, *cliNum) 141 | } 142 | 143 | var g sync.WaitGroup 144 | g.Add(*cliNum) 145 | 146 | start := time.Now() 147 | for i := 0; i < *cliNum; i++ { 148 | go benchClient(i, cliPool, name, &g, numChan, start, hists, op) 149 | } 150 | 151 | g.Wait() 152 | 153 | elapsed := time.Since(start) 154 | speed := float64(*reqNum) * 1e9 / float64(elapsed) 155 | fmt.Printf("%-10s : %9.1f op/s\n", name, speed) 156 | 157 | if hists != nil { 158 | var hist = hists[0] 159 | for i := 1; i < len(hists); i++ { 160 | hist.Merge(&hists[i]) 161 | } 162 | fmt.Printf("Microseconds per op:\n%s\n", hist.ToString()) 163 | } 164 | } 165 | 166 | func benchClient(id int, cliPool *table.Pool, name string, g *sync.WaitGroup, 167 | numChan <-chan int, start time.Time, hists []Histogram, op func(v int, p *OpParam)) { 168 | defer g.Done() 169 | 170 | var hist *Histogram 171 | if hists != nil { 172 | hist = &hists[id] 173 | hist.Clear() 174 | } 175 | 176 | var p = NewOpParam(id, cliPool) 177 | var pipeReqNum int 178 | var opStart time.Time 179 | for { 180 | select { 181 | case v, ok := <-numChan: 182 | if !ok { 183 | if p.done != nil { 184 | for i := 0; i < pipeReqNum; i++ { 185 | <-p.done 186 | } 187 | } 188 | p.c.Client().Close() 189 | return 190 | } 191 | 192 | if hist != nil { 193 | opStart = time.Now() 194 | } 195 | 196 | op(v, p) // Handle OP 197 | 198 | if p.done != nil { 199 | pipeReqNum++ 200 | if pipeReqNum >= *pipeline { 201 | for i := 0; i < pipeReqNum; i++ { 202 | <-p.done 203 | } 204 | pipeReqNum = 0 205 | } 206 | } 207 | 208 | if hist != nil { 209 | d := time.Since(opStart) 210 | hist.Add(float64(d / 1000)) 211 | } 212 | 213 | if v%10000 == 0 && v > 0 { 214 | elapsed := time.Since(start) 215 | speed := float64(v+1) * 1e9 / float64(elapsed) 216 | fmt.Printf("%-10s : %9.1f op/s \r", name, speed) 217 | } 218 | } 219 | } 220 | } 221 | 222 | func benchSet(cliPool *table.Pool, zop bool) { 223 | var op = func(v int, p *OpParam) { 224 | key := strconv.AppendInt(p.keyBuf, int64(v), 10) 225 | var rowKey = key[0 : len(key)-3] 226 | var colKey = key[len(key)-3:] 227 | copy(p.value, key) 228 | 229 | set(p.c, p.done, zop, rowKey, colKey, p.value, int64(-v)) 230 | 231 | p.ResetValue(len(key)) 232 | } 233 | 234 | if zop { 235 | benchmark(cliPool, "ZSET", op) 236 | } else { 237 | benchmark(cliPool, "SET", op) 238 | } 239 | } 240 | 241 | func benchGet(cliPool *table.Pool, zop bool) { 242 | var op = func(v int, p *OpParam) { 243 | key := strconv.AppendInt(p.keyBuf, int64(v), 10) 244 | var rowKey = key[0 : len(key)-3] 245 | var colKey = key[len(key)-3:] 246 | 247 | get(p.c, p.done, zop, rowKey, colKey) 248 | } 249 | 250 | if zop { 251 | benchmark(cliPool, "ZGET", op) 252 | } else { 253 | benchmark(cliPool, "GET", op) 254 | } 255 | } 256 | 257 | func benchIncr(cliPool *table.Pool, zop bool) { 258 | var op = func(v int, p *OpParam) { 259 | key := strconv.AppendInt(p.keyBuf, int64(v), 10) 260 | var rowKey = key[0 : len(key)-3] 261 | var colKey = key[len(key)-3:] 262 | 263 | incr(p.c, p.done, zop, rowKey, colKey, 1) 264 | } 265 | 266 | if zop { 267 | benchmark(cliPool, "ZINCR", op) 268 | } else { 269 | benchmark(cliPool, "INCR", op) 270 | } 271 | } 272 | 273 | func benchScan(cliPool *table.Pool, zop bool) { 274 | var startScore int64 275 | if zop { 276 | startScore = -500000000 277 | } 278 | 279 | var op = func(v int, p *OpParam) { 280 | key := strconv.AppendInt(p.keyBuf, int64(v), 10) 281 | var rowKey = key[0 : len(key)-3] 282 | 283 | scan(p.c, p.done, zop, rowKey, nil, startScore, *rangeNum) 284 | } 285 | 286 | if zop { 287 | benchmark(cliPool, fmt.Sprintf("ZSCAN %d", *rangeNum), op) 288 | } else { 289 | benchmark(cliPool, fmt.Sprintf("SCAN %d", *rangeNum), op) 290 | } 291 | } 292 | 293 | func benchMSet(cliPool *table.Pool, zop bool) { 294 | var colKeys = make([][]byte, *rangeNum) 295 | for i := 0; i < *rangeNum; i++ { 296 | colKeys[i] = []byte(fmt.Sprintf("%03d", i)) 297 | } 298 | 299 | var op = func(v int, p *OpParam) { 300 | var rowKey = strconv.AppendInt(p.keyBuf, int64(v), 10) 301 | copy(p.value, rowKey) 302 | 303 | var ma table.MSetArgs 304 | for i := 0; i < len(colKeys); i++ { 305 | ma.Add(0, rowKey, colKeys[i], p.value, int64(v*1000+i), 0) 306 | } 307 | 308 | mSet(p.c, p.done, zop, ma) 309 | 310 | p.ResetValue(len(rowKey)) 311 | } 312 | 313 | if zop { 314 | benchmark(cliPool, fmt.Sprintf("ZMSET %d", *rangeNum), op) 315 | } else { 316 | benchmark(cliPool, fmt.Sprintf("MSET %d", *rangeNum), op) 317 | } 318 | } 319 | 320 | func benchMGet(cliPool *table.Pool, zop bool) { 321 | var colKeys = make([][]byte, *rangeNum) 322 | for i := 0; i < *rangeNum; i++ { 323 | colKeys[i] = []byte(fmt.Sprintf("%03d", i)) 324 | } 325 | 326 | var op = func(v int, p *OpParam) { 327 | var rowKey = strconv.AppendInt(p.keyBuf, int64(v), 10) 328 | 329 | var ma table.MGetArgs 330 | for i := 0; i < len(colKeys); i++ { 331 | ma.Add(0, rowKey, colKeys[i], 0) 332 | } 333 | 334 | mGet(p.c, p.done, zop, ma) 335 | } 336 | 337 | if zop { 338 | benchmark(cliPool, fmt.Sprintf("ZMGET %d", *rangeNum), op) 339 | } else { 340 | benchmark(cliPool, fmt.Sprintf("MGET %d", *rangeNum), op) 341 | } 342 | } 343 | 344 | func set(c *table.Context, done chan *table.Call, zop bool, 345 | rowKey, colKey, value []byte, score int64) { 346 | var err error 347 | if zop { 348 | if done == nil { 349 | err = c.ZSet(0, rowKey, colKey, value, score, 0) 350 | } else { 351 | _, err = c.GoZSet(0, rowKey, colKey, value, score, 0, done) 352 | } 353 | } else { 354 | if done == nil { 355 | err = c.Set(0, rowKey, colKey, value, score, 0) 356 | } else { 357 | _, err = c.GoSet(0, rowKey, colKey, value, score, 0, done) 358 | } 359 | } 360 | if err != nil { 361 | fmt.Printf("Set failed: %s\n", err) 362 | os.Exit(1) 363 | } 364 | 365 | if *verbose != 0 && done == nil { 366 | fmt.Printf("rowKey: %2s, colKey: %s\n", string(rowKey), string(colKey)) 367 | } 368 | } 369 | 370 | func get(c *table.Context, done chan *table.Call, zop bool, rowKey, colKey []byte) { 371 | var err error 372 | var value []byte 373 | var score int64 374 | if zop { 375 | if done == nil { 376 | value, score, _, err = c.ZGet(0, rowKey, colKey, 0) 377 | } else { 378 | _, err = c.GoZGet(0, rowKey, colKey, 0, done) 379 | } 380 | } else { 381 | if done == nil { 382 | value, score, _, err = c.Get(0, rowKey, colKey, 0) 383 | } else { 384 | _, err = c.GoGet(0, rowKey, colKey, 0, done) 385 | } 386 | } 387 | if err != nil { 388 | fmt.Printf("Get failed: %s\n", err) 389 | os.Exit(1) 390 | } 391 | 392 | if *verbose != 0 && done == nil { 393 | fmt.Printf("rowKey: %2s, colKey: %s, value: %s, score:%d\n", 394 | string(rowKey), string(colKey), string(value), score) 395 | } 396 | } 397 | 398 | func incr(c *table.Context, done chan *table.Call, zop bool, 399 | rowKey, colKey []byte, score int64) { 400 | var err error 401 | var value []byte 402 | if zop { 403 | if done == nil { 404 | value, score, err = c.ZIncr(0, rowKey, colKey, score, 0) 405 | } else { 406 | _, err = c.GoZIncr(0, rowKey, colKey, score, 0, done) 407 | } 408 | } else { 409 | if done == nil { 410 | value, score, err = c.Incr(0, rowKey, colKey, score, 0) 411 | } else { 412 | _, err = c.GoIncr(0, rowKey, colKey, score, 0, done) 413 | } 414 | } 415 | if err != nil { 416 | fmt.Printf("Incr failed: %s\n", err) 417 | os.Exit(1) 418 | } 419 | 420 | if *verbose != 0 && done == nil { 421 | fmt.Printf("rowKey: %2s, colKey: %s, value: %s, score:%d\n", 422 | string(rowKey), string(colKey), string(value), score) 423 | } 424 | } 425 | 426 | func scan(c *table.Context, done chan *table.Call, zop bool, 427 | rowKey, colKey []byte, score int64, num int) { 428 | var err error 429 | var r table.ScanReply 430 | if zop { 431 | if done == nil { 432 | r, err = c.ZScanPivot(0, rowKey, colKey, score, true, true, num) 433 | } else { 434 | _, err = c.GoZScanPivot(0, rowKey, colKey, score, true, true, num, done) 435 | } 436 | } else { 437 | if done == nil { 438 | r, err = c.ScanPivot(0, rowKey, colKey, true, num) 439 | } else { 440 | _, err = c.GoScanPivot(0, rowKey, colKey, true, num, done) 441 | } 442 | } 443 | if err != nil { 444 | fmt.Printf("Scan failed: %s\n", err) 445 | os.Exit(1) 446 | } 447 | 448 | if *verbose != 0 && done == nil { 449 | for i := 0; i < len(r.Kvs); i++ { 450 | var one = r.Kvs[i] 451 | fmt.Printf("%02d) [%q\t%q]\t[%d\t%q]\n", i, 452 | r.RowKey, one.ColKey, one.Score, one.Value) 453 | } 454 | } 455 | } 456 | 457 | func mSet(c *table.Context, done chan *table.Call, zop bool, ma table.MSetArgs) { 458 | var err error 459 | if zop { 460 | if done == nil { 461 | _, err = c.ZmSet(ma) 462 | } else { 463 | _, err = c.GoZmSet(ma, done) 464 | } 465 | } else { 466 | if done == nil { 467 | _, err = c.MSet(ma) 468 | } else { 469 | _, err = c.GoMSet(ma, done) 470 | } 471 | } 472 | if err != nil { 473 | fmt.Printf("MSet failed: %s\n", err) 474 | os.Exit(1) 475 | } 476 | 477 | if *verbose != 0 && done == nil { 478 | for i := 0; i < len(ma); i++ { 479 | var one = ma[i] 480 | fmt.Printf("%02d) [%q\t%q]\t[%d\t%q]\n", i, 481 | one.RowKey, one.ColKey, one.Score, one.Value) 482 | } 483 | } 484 | } 485 | 486 | func mGet(c *table.Context, done chan *table.Call, zop bool, ma table.MGetArgs) { 487 | var err error 488 | var r []table.GetReply 489 | if zop { 490 | if done == nil { 491 | r, err = c.ZmGet(ma) 492 | } else { 493 | _, err = c.GoZmGet(ma, done) 494 | } 495 | } else { 496 | if done == nil { 497 | r, err = c.MGet(ma) 498 | } else { 499 | _, err = c.GoMGet(ma, done) 500 | } 501 | } 502 | if err != nil { 503 | fmt.Printf("MGet failed: %s\n", err) 504 | os.Exit(1) 505 | } 506 | 507 | if *verbose != 0 && done == nil { 508 | for i := 0; i < len(r); i++ { 509 | var one = r[i] 510 | fmt.Printf("%02d) [%q\t%q]\t[%d\t%q]\n", i, 511 | one.RowKey, one.ColKey, one.Score, one.Value) 512 | } 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /api/c++/gotable.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 stevejiang. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef _GO_TABLE_H_ 16 | #define _GO_TABLE_H_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace gotable { 25 | 26 | using std::string; 27 | using std::vector; 28 | 29 | // GoTable Error Code List 30 | enum { 31 | EcNotExist = 1, // Key NOT exist 32 | EcOk = 0, // Success 33 | EcCasNotMatch = -1, // CAS not match, get new CAS and try again 34 | EcTempFail = -2, // Temporary failed, retry may fix this 35 | EcUnknownCmd = -10, // Unknown cmd 36 | EcAuthFailed = -11, // Authorize failed 37 | EcNoPrivilege = -12, // No access privilege 38 | EcWriteSlave = -13, // Can NOT write slave directly 39 | EcSlaveCas = -14, // Invalid CAS on slave for GET/MGET 40 | EcReadFail = -15, // Read failed 41 | EcWriteFail = -16, // Write failed 42 | EcDecodeFail = -17, // Decode request PKG failed 43 | EcInvDbId = -18, // Invalid DB ID (cannot be 255) 44 | EcInvRowKey = -19, // RowKey length should be [1 ~ 255] 45 | EcInvValue = -20, // Value length should be [0 ~ 1MB] 46 | EcInvPkgLen = -21, // Pkg length should be less than 2MB 47 | EcInvScanNum = -22, // Scan request number out of range 48 | EcScanEnded = -23, // Already scan/dump to end 49 | }; 50 | 51 | struct GetArgs { 52 | uint8_t tableId; 53 | string rowKey; 54 | string colKey; 55 | uint32_t cas; 56 | 57 | GetArgs() : tableId(0), cas(0) {} 58 | 59 | GetArgs(uint8_t tableId, const string& rowKey, const string& colKey, uint32_t cas) : 60 | tableId(tableId), rowKey(rowKey), colKey(colKey), cas(cas) {} 61 | }; 62 | 63 | struct GetReply { 64 | int8_t errCode; // Error Code Replied 65 | uint8_t tableId; 66 | string rowKey; 67 | string colKey; 68 | string value; 69 | int64_t score; 70 | uint32_t cas; 71 | 72 | GetReply() : errCode(0), tableId(0), score(0), cas(0) {} 73 | }; 74 | 75 | struct SetArgs { 76 | uint8_t tableId; 77 | string rowKey; 78 | string colKey; 79 | string value; 80 | int64_t score; 81 | uint32_t cas; 82 | 83 | SetArgs() : tableId(0), score(0), cas(0) {} 84 | 85 | SetArgs(uint8_t tableId, const string& rowKey, const string& colKey, 86 | const string& value, int64_t score, uint32_t cas) : 87 | tableId(tableId), rowKey(rowKey), colKey(colKey), 88 | value(value), score(score), cas(cas) {} 89 | }; 90 | 91 | struct SetReply { 92 | int8_t errCode; // Error Code Replied 93 | uint8_t tableId; 94 | string rowKey; 95 | string colKey; 96 | 97 | SetReply() : errCode(0), tableId(0) {} 98 | }; 99 | 100 | struct IncrArgs { 101 | uint8_t tableId; 102 | string rowKey; 103 | string colKey; 104 | int64_t score; 105 | uint32_t cas; 106 | 107 | IncrArgs() : tableId(0), score(0), cas(0) {} 108 | 109 | IncrArgs(uint8_t tableId, const string& rowKey, const string& colKey, 110 | int64_t score, uint32_t cas) : 111 | tableId(tableId), rowKey(rowKey), colKey(colKey), score(score), cas(cas) {} 112 | }; 113 | 114 | struct IncrReply { 115 | int8_t errCode; // Error Code Replied 116 | uint8_t tableId; 117 | string rowKey; 118 | string colKey; 119 | string value; 120 | int64_t score; 121 | 122 | IncrReply() : errCode(0), tableId(0), score(0) {} 123 | }; 124 | 125 | typedef GetArgs DelArgs; 126 | typedef SetReply DelReply; 127 | 128 | struct ScanKV { 129 | string colKey; 130 | string value; 131 | int64_t score; 132 | 133 | ScanKV() : score(0) {} 134 | }; 135 | 136 | struct ScanReply { 137 | uint8_t tableId; 138 | string rowKey; 139 | vector kvs; 140 | bool end; // true: Scan to end, stop now 141 | 142 | ScanReply() : tableId(0), kvs(), end(false) {} 143 | 144 | private: 145 | struct ScanContext { 146 | bool zop; 147 | bool asc; // true: Ascending order; false: Descending order 148 | bool orderByScore; // true: Score+ColKey; false: ColKey 149 | int num; // Max number of scan reply records 150 | }; 151 | ScanContext ctx; 152 | friend class Client; 153 | }; 154 | 155 | struct DumpKV { 156 | uint8_t tableId; 157 | uint8_t colSpace; 158 | string rowKey; 159 | string colKey; 160 | string value; 161 | int64_t score; 162 | 163 | DumpKV() : tableId(0), colSpace(0), score(0) {} 164 | }; 165 | 166 | struct DumpReply { 167 | vector kvs; 168 | bool end; // true: Dump to end, stop now 169 | 170 | private: 171 | struct DumpContext { 172 | bool oneTable; // Never change during dump 173 | uint8_t tableId; // Never change during dump 174 | uint16_t startUnitId; // Never change during dump 175 | uint16_t endUnitId; // Never change during dump 176 | uint16_t lastUnitId; // The last unit ID tried to dump 177 | bool unitStart; // Next dump start from new UnitId 178 | }; 179 | DumpContext ctx; 180 | friend class Client; 181 | }; 182 | 183 | struct PkgOneOp; 184 | struct PkgMultiOp; 185 | struct PkgDumpResp; 186 | 187 | class Client { 188 | public: 189 | Client(int fd); 190 | ~Client(); 191 | 192 | // Dial connects to the address of GoTable server. 193 | // Upon success it returns a connection Client to GoTable server. 194 | // Otherwise NULL is returned. 195 | static Client* Dial(const char* ip, int port); 196 | 197 | // Close the connection. 198 | void close(); 199 | 200 | // Change the selected database for the current connection. 201 | // Database 0 is selected by default. 202 | void select(uint8_t dbId); 203 | 204 | // Get the selected database ID for the current connection. 205 | uint8_t databaseId(); 206 | 207 | // Authenticate to the server. 208 | // Return value <0 means failed, 0 means succeed. 209 | int auth(const char* password); 210 | 211 | // Ping the server. 212 | // Return value <0 means failed, 0 means succeed. 213 | int ping(); 214 | 215 | // Get value&score of the key in default column space. 216 | // Parameter CAS is Compare-And-Swap, 2 means read data on master and 217 | // return a new CAS, 1 means read data on master machine but without a 218 | // new CAS, 0(NULL) means read data on any machine without a new CAS. 219 | // On cluster mode, routing to master machine is automatically, but on a 220 | // normal master/slave mode it should be done manually. 221 | // If CAS 1&2 sent to a slave machine, error will be returned. 222 | // Return value <0 means failed, 0 means succeed, 1 means key not exist. 223 | int get(uint8_t tableId, const string& rowKey, const string& colKey, 224 | string* value, int64_t* score, uint32_t* cas=NULL); 225 | 226 | // Get value&score of the key in "Z" sorted score column space. 227 | // Request and return parameters have the same meaning as the Get API. 228 | int zGet(uint8_t tableId, const string& rowKey, const string& colKey, 229 | string* value, int64_t* score, uint32_t* cas=NULL); 230 | 231 | // Set key/value in default column space. CAS is 0 for normal cases. 232 | // Use the CAS returned by GET if you want to "lock" the record. 233 | // Return value <0 means failed, 0 means succeed. 234 | int set(uint8_t tableId, const string& rowKey, const string& colKey, 235 | const string& value, int64_t score, uint32_t cas=0); 236 | 237 | // Set key/value in "Z" sorted score column space. CAS is 0 for normal cases. 238 | // Use the CAS returned by GET if you want to "lock" the record. 239 | // Return value <0 means failed, 0 means succeed. 240 | int zSet(uint8_t tableId, const string& rowKey, const string& colKey, 241 | const string& value, int64_t score, uint32_t cas=0); 242 | 243 | // Delete the key in default column space. CAS is 0 for normal cases. 244 | // Use the CAS returned by GET if you want to "lock" the record. 245 | // Return value <0 means failed, 0 means succeed. 246 | int del(uint8_t tableId, const string& rowKey, const string& colKey, 247 | uint32_t cas=0); 248 | 249 | // Delete the key in "Z" sorted score column space. CAS is 0 for normal cases. 250 | // Use the CAS returned by GET if you want to "lock" the record. 251 | // Return value <0 means failed, 0 means succeed. 252 | int zDel(uint8_t tableId, const string& rowKey, const string& colKey, 253 | uint32_t cas=0); 254 | 255 | // Increase key/score in default column space. CAS is 0 for normal cases. 256 | // Use the CAS returned by GET if you want to "lock" the record. 257 | // Return value <0 means failed, 0 means succeed. 258 | int incr(uint8_t tableId, const string& rowKey, const string& colKey, 259 | string* value, int64_t* score, uint32_t cas=0); 260 | 261 | // Increase key/score in "Z" sorted score column space. CAS is 0 for normal cases. 262 | // Use the CAS returned by GET if you want to "lock" the record. 263 | // Return value <0 means failed, 0 means succeed. 264 | int zIncr(uint8_t tableId, const string& rowKey, const string& colKey, 265 | string* value, int64_t* score, uint32_t cas=0); 266 | 267 | // Get values&scores of multiple keys in default column space. 268 | // Return value <0 means failed, 0 means succeed. 269 | int mGet(const vector& args, vector* reply); 270 | 271 | // Get values&scores of multiple keys in "Z" sorted score column space. 272 | // Return value <0 means failed, 0 means succeed. 273 | int zmGet(const vector& args, vector* reply); 274 | 275 | // Set multiple keys/values in default column space. 276 | // Return value <0 means failed, 0 means succeed. 277 | int mSet(const vector& args, vector* reply); 278 | 279 | // Set multiple keys/values in "Z" sorted score column space. 280 | // Return value <0 means failed, 0 means succeed. 281 | int zmSet(const vector& args, vector* reply); 282 | 283 | // Delete multiple keys in default column space. 284 | // Return value <0 means failed, 0 means succeed. 285 | int mDel(const vector& args, vector* reply); 286 | 287 | // Delete multiple keys in "Z" sorted score column space. 288 | // Return value <0 means failed, 0 means succeed. 289 | int zmDel(const vector& args, vector* reply); 290 | 291 | // Increase multiple keys/scores in default column space. 292 | // Return value <0 means failed, 0 means succeed. 293 | int mIncr(const vector& args, vector* reply); 294 | 295 | // Increase multiple keys/scores in "Z" sorted score column space. 296 | // Return value <0 means failed, 0 means succeed. 297 | int zmIncr(const vector& args, vector* reply); 298 | 299 | // Scan columns of rowKey in default column space from MIN/MAX colKey. 300 | // If asc is true SCAN start from the MIN colKey, else SCAN from the MAX colKey. 301 | // It replies at most num records. 302 | // Return value <0 means failed, 0 means succeed. 303 | int scan(uint8_t tableId, const string& rowKey, 304 | bool asc, int num, ScanReply* reply); 305 | 306 | // Scan columns of rowKey in default column space from pivot record. 307 | // The colKey is the pivot record where scan starts. 308 | // If asc is true SCAN in ASC order, else SCAN in DESC order. 309 | // It replies at most num records. The pivot record is excluded from the reply. 310 | // Return value <0 means failed, 0 means succeed. 311 | int scanPivot(uint8_t tableId, const string& rowKey, const string& colKey, 312 | bool asc, int num, ScanReply* reply); 313 | 314 | // Scan columns of rowKey in "Z" sorted score space from MIN/MAX colKey and score. 315 | // If asc is true ZSCAN start from the MIN colKey and score, 316 | // else ZSCAN from the MAX colKey and score. 317 | // If orderByScore is true ZSCAN order by score+colKey, else ZSCAN order by colKey. 318 | // It replies at most num records. 319 | // Return value <0 means failed, 0 means succeed. 320 | int zScan(uint8_t tableId, const string& rowKey, 321 | bool asc, bool orderByScore, int num, ScanReply* reply); 322 | 323 | // Scan columns of rowKey in "Z" sorted score space from pivot record. 324 | // The colKey and score is the pivot record where scan starts. 325 | // If asc is true ZSCAN in ASC order, else ZSCAN in DESC order. 326 | // If orderByScore is true ZSCAN order by score+colKey, else ZSCAN order by colKey. 327 | // It replies at most num records. The pivot record is excluded from the reply. 328 | // Return value <0 means failed, 0 means succeed. 329 | int zScanPivot(uint8_t tableId, const string& rowKey, const string& colKey, int64_t score, 330 | bool asc, bool orderByScore, int num, ScanReply* reply); 331 | 332 | // Scan/ZScan more records. 333 | // Return value <0 means failed, 0 means succeed. 334 | int scanMore(const ScanReply& last, ScanReply* reply); 335 | 336 | // Dump records from the pivot record. 337 | // If oneTable is true, only dump the selected table. 338 | // If oneTable is false, dump all tables in current DB(dbId). 339 | // The pivot record is excluded from the reply. 340 | // Return value <0 means failed, 0 means succeed. 341 | int dumpPivot(bool oneTable, uint8_t tableId, uint8_t colSpace, 342 | const string& rowKey, const string& colKey, int64_t score, 343 | uint16_t startUnitId, uint16_t endUnitId, DumpReply* reply); 344 | 345 | // Dump all tables in current DB(database selected). 346 | // Return value <0 means failed, 0 means succeed. 347 | int dumpDB(DumpReply* reply); 348 | 349 | // Dump the selected Table. 350 | // Return value <0 means failed, 0 means succeed. 351 | int dumpTable(uint8_t tableId, DumpReply* reply); 352 | 353 | // Dump more records. 354 | // Return value <0 means failed, 0 means succeed. 355 | int dumpMore(const DumpReply& last, DumpReply* reply); 356 | 357 | private: 358 | int doOneOp(bool zop, uint8_t cmd, uint8_t tableId, 359 | const string& rowKey, const string& colKey, 360 | const string& value, int64_t score, uint32_t cas, 361 | PkgOneOp* reply, string& pkg); 362 | 363 | template 364 | int doMultiOp(bool zop, uint8_t cmd, const vector& args, 365 | PkgMultiOp* reply, string& pkg); 366 | 367 | int doScan(bool zop, uint8_t tableId, const string& rowKey, const string& colKey, 368 | int64_t score, bool start, bool asc, bool orderByScore, int num, 369 | ScanReply* reply, PkgMultiOp* resp, string& pkg); 370 | 371 | int doDump(bool oneTable, uint8_t tableId, uint8_t colSpace, 372 | const string& rowKey, const string& colKey, int64_t score, 373 | uint16_t startUnitId, uint16_t endUnitId, 374 | DumpReply* reply, PkgDumpResp* resp, string& pkg); 375 | 376 | private: //disable 377 | Client(const Client&); 378 | void operator=(const Client&); 379 | 380 | private: 381 | bool closed; 382 | int fd; 383 | uint8_t dbId; 384 | uint64_t seq; 385 | bool authAdmin; 386 | std::set setAuth; 387 | char buf[4096]; 388 | }; 389 | 390 | } // namespace gotable 391 | #endif 392 | --------------------------------------------------------------------------------