├── LICENSE.md ├── README.md ├── backlog ├── backlog.go └── backlog_test.go ├── command ├── command.go ├── command_test.go └── types.go ├── conn.go ├── data ├── data.go ├── data_test.go ├── expiry.go ├── expiry_test.go ├── interface.go ├── interface_test.go ├── key.go └── key_test.go ├── example ├── config.go ├── consumer.go ├── main.go └── migration.go ├── interface.go ├── rdb ├── decode.go ├── doc.go ├── encode.go ├── entries.go ├── interface.go ├── intset.go ├── lzf.go └── reader.go ├── replica ├── config.go ├── decoder.go ├── interface.go └── replica.go ├── resp ├── command.go ├── conn.go ├── doc.go ├── dump.go ├── files.go ├── interface.go ├── reader.go ├── reader_test.go └── writer.go └── status └── status.go /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Avito 4 | 5 | Author 2017 Oleg Shevelev|mantyr@gmail.com 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Redis Replication 2 | 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) 4 | 5 | Это библиотека для подключения к redis-серверу в качестве slave и разбора всех данных репликации. 6 | 7 | В компании Avito использовалась для синронизации новой версии сервиса, с помощью неё было перелито порядка N ключей из старого кластера в новый сервис. 8 | 9 | Направление данных: 10 | 11 | users requests -> old service -> redis cluster 12 | 13 | redis cluster -> smart-redis-replication -> service -> redis cluster 14 | 15 | В процессе переноса были: 16 | 17 | 1. модифицированы ключи 18 | 19 | 2. отсеяны более не требующиеся данные 20 | 21 | 3. заново сгенерированы дополнительные данные (на уровне нового сервиса) 22 | 23 | Схема переноса данных не потребовала остановки обслуживания клиентов и позволила поддерживать два сервиса в синхронном состоянии достаточное количество времени, что бы провести тесты и подготовиться к переключению пользовательских запросов в новый сервис. 24 | 25 | Библиотека имеет встроенный Backlog, благодаря которому можно обновлять сервис будучи в синхронном состоянии без потери синхронизации. 26 | Данные накапливаются в backlog и отправляются в сервис как только тот будет перезапущен, а благодаря rolling update в kubernetes это происходит и вовсе незаметно для пользователей. 27 | 28 | ## Поддерживаются форматы: 29 | 30 | rdb - описание формата https://rdb.fnordig.de/file_format.html 31 | 32 | resp - описание формата https://redis.io/topics/protocol 33 | 34 | ## Поддерживаются типы ключей: 35 | 36 | Sorted Set 37 | 38 | Integer Set 39 | 40 | Set 41 | 42 | Map 43 | 44 | List 45 | 46 | String 47 | 48 | ## Поддерживаются типы данных: 49 | 50 | 0 = String Encoding 51 | 52 | 1 = List Encoding 53 | 54 | 2 = Set Encoding 55 | 56 | 3 = Sorted Set Encoding 57 | 58 | 4 = Hash Encoding 59 | 60 | 9 = Zipmap Encoding 61 | 62 | 10 = Ziplist Encoding 63 | 64 | 11 = Intset Encoding 65 | 66 | 12 = Sorted Set in Ziplist Encoding 67 | 68 | 13 = Hashmap in Ziplist Encoding (Introduced in RDB version 4) 69 | 70 | 14 = List in Quicklist encoding (Introduced in RDB version 7) 71 | 72 | ## Installation 73 | 74 | $ go get github.com/avito-tech/smart-redis-replication 75 | 76 | ## Examples 77 | 78 | $ ls ./example 79 | 80 | ## Author 81 | 82 | [Oleg Shevelev][mantyr] 83 | 84 | [mantyr]: https://github.com/mantyr 85 | 86 | -------------------------------------------------------------------------------- /backlog/backlog.go: -------------------------------------------------------------------------------- 1 | package backlog 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/avito-tech/smart-redis-replication/command" 8 | ) 9 | 10 | const ( 11 | // DefaultBacklogSize это размер backlog по умолчанию 12 | DefaultBacklogSize int = 50000000 13 | ) 14 | 15 | // Backlog это журнал отставания репликации 16 | type Backlog struct { 17 | sync.RWMutex 18 | 19 | // data это канал с командами 20 | data chan command.Command 21 | 22 | // size это максимальное количество элементов которые может хранить backlog 23 | size int 24 | } 25 | 26 | // New возвращает новый Backlog 27 | func New(size int) *Backlog { 28 | b := new(Backlog) 29 | b.data = make(chan command.Command, size) 30 | b.size = size 31 | return b 32 | } 33 | 34 | // Add добавляет в backlog команду 35 | func (b *Backlog) Add(command command.Command) error { 36 | b.Lock() 37 | defer b.Unlock() 38 | 39 | if len(b.data) == b.size { 40 | return errors.New("queue size exceeded") 41 | } 42 | 43 | b.data <- command 44 | return nil 45 | } 46 | 47 | // Get возвращает команду из backlog 48 | func (b *Backlog) Get() command.Command { 49 | return <-b.data 50 | } 51 | 52 | // Count возвращает количество команд в backlog 53 | func (b *Backlog) Count() int { 54 | return len(b.data) 55 | } 56 | -------------------------------------------------------------------------------- /backlog/backlog_test.go: -------------------------------------------------------------------------------- 1 | package backlog 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/avito-tech/smart-redis-replication/command" 9 | ) 10 | 11 | func TestBacklog(t *testing.T) { 12 | t.Run("Normal", func(t *testing.T) { 13 | testBacklog(t, 10, 10, 10, 0, true) 14 | }) 15 | t.Run("Count", func(t *testing.T) { 16 | testBacklog(t, 10, 10, 5, 5, true) 17 | }) 18 | t.Run("Error/Overflow", func(t *testing.T) { 19 | testBacklogOverflow(t) 20 | }) 21 | } 22 | 23 | // testBacklogOverflow проверяет наличии ошибки при переполнении backlog 24 | func testBacklogOverflow(t *testing.T) { 25 | backlog := New(2) 26 | for i := 0; i < 2; i++ { 27 | command := command.New([]string{fmt.Sprintf("command_%d", i)}) 28 | err := backlog.Add(command) 29 | if err != nil { 30 | t.Fatalf("backlog error: %q", err) 31 | } 32 | } 33 | 34 | command := command.New([]string{fmt.Sprintf("command_%d", 3)}) 35 | err := backlog.Add(command) 36 | if err == nil { 37 | t.Fatalf("expected error") 38 | } 39 | if err.Error() != "queue size exceeded" { 40 | t.Fatalf("expected error %q but actual %v", "queue size exceeded", err) 41 | } 42 | } 43 | 44 | // testBacklog проверяет работу backlog 45 | func testBacklog( 46 | t *testing.T, 47 | size int, 48 | add int, 49 | get int, 50 | expectedCount int, 51 | success bool, 52 | ) { 53 | if get > add { 54 | t.Fatalf("expected add > get") 55 | } 56 | backlog := New(size) 57 | 58 | stack := []command.Command{} 59 | for i := 0; i < add; i++ { 60 | command := command.New([]string{fmt.Sprintf("command_%d", i)}) 61 | stack = append(stack, command) 62 | err := backlog.Add(command) 63 | if err != nil { 64 | t.Fatalf("backlog error: %v", err) 65 | } 66 | } 67 | if len(stack) != add { 68 | t.Fatalf("expected stack size %d but actual %d", add, len(stack)) 69 | } 70 | 71 | for i := 0; i < get; i++ { 72 | command := backlog.Get() 73 | if !reflect.DeepEqual(stack[i], command) { 74 | t.Fatalf("expected command %q but actual %q", stack[i], command) 75 | } 76 | } 77 | count := backlog.Count() 78 | if count != expectedCount { 79 | t.Fatalf("expected count %d but actual %d", expectedCount, count) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/avito-tech/smart-redis-replication/data" 10 | ) 11 | 12 | // Command это структура содержащая команду в исходном виде 13 | type Command struct { 14 | data []string 15 | } 16 | 17 | // New возвращает новую команду 18 | func New(args []string) Command { 19 | return Command{ 20 | data: args, 21 | } 22 | } 23 | 24 | // Type возвращает тип команды 25 | func (c Command) Type() Type { 26 | if len(c.data) == 0 { 27 | return Empty 28 | } 29 | command := Type(strings.ToLower(strings.TrimSpace(c.data[0]))) 30 | if command == "" { 31 | return Empty 32 | } 33 | switch command { 34 | case Ping, Select, Zadd, Sadd, Zrem, Delete, RDB: 35 | return command 36 | } 37 | return Undefined 38 | } 39 | 40 | // KeyName возвращает название ключа если оно предусмотрено командой 41 | func (c Command) KeyName() (string, error) { 42 | if len(c.data) < 2 { 43 | return "", fmt.Errorf("expected count args >= 2 but actual %d", len(c.data)) 44 | } 45 | switch c.Type() { 46 | case Delete, Zrem, Zadd, Sadd: 47 | return c.data[1], nil 48 | } 49 | return "", fmt.Errorf("unexpected type %q", c.Type()) 50 | } 51 | 52 | // Values возвращает список значений если они предусмотрены командой 53 | func (c Command) Values() ([]string, error) { 54 | if len(c.data) < 3 { 55 | return []string{}, fmt.Errorf( 56 | "expected count args >= 2 but actual %d", 57 | len(c.data), 58 | ) 59 | } 60 | switch c.Type() { 61 | case Zrem: 62 | return c.data[2:], nil 63 | } 64 | return []string{}, fmt.Errorf("unexpected type %q", c.Type()) 65 | } 66 | 67 | // ConvertToSelectDB конвертирует команду в номер базы данных 68 | func (c Command) ConvertToSelectDB() (db int, err error) { 69 | if len(c.data) < 2 { 70 | return 0, fmt.Errorf("expected count args >=2 but actual %d", len(c.data)) 71 | } 72 | commandType := c.Type() 73 | if commandType != Select { 74 | return 0, fmt.Errorf("expected Select command but actual %s", commandType) 75 | } 76 | return strconv.Atoi(strings.TrimSpace(c.data[1])) 77 | } 78 | 79 | // ConvertToRDB конвертирует команду в размер RDB 80 | func (c Command) ConvertToRDB() (size int64, err error) { 81 | if len(c.data) < 2 { 82 | return 0, fmt.Errorf("expected count args >=2 but actual %d", len(c.data)) 83 | } 84 | commandType := c.Type() 85 | if commandType != RDB { 86 | return 0, fmt.Errorf("expected RDB command but actual %s", commandType) 87 | } 88 | return strconv.ParseInt(strings.TrimSpace(c.data[1]), 10, 64) 89 | } 90 | 91 | // ConvertToSortedSetKey конвертирует команду Zadd в ключ SortedSetKey 92 | func (c Command) ConvertToSortedSetKey(db int) (data.SortedSetKey, error) { 93 | if len(c.data) < 4 { 94 | return nil, fmt.Errorf("expected count args >=3 but actual %d", len(c.data)) 95 | } 96 | if len(c.data)%2 != 0 { 97 | return nil, errors.New("expected even count args but actual odd") 98 | } 99 | commandType := c.Type() 100 | if commandType != Zadd { 101 | return nil, fmt.Errorf("expected Zadd command but actual %s", commandType) 102 | } 103 | keyName := c.data[1] 104 | key := data.NewSortedSet(keyName) 105 | err := key.SetDB(db) 106 | if err != nil { 107 | return nil, err 108 | } 109 | count := len(c.data) 110 | for i := 2; i < count-1; i += 2 { 111 | score, err := strconv.ParseFloat(c.data[i], 64) 112 | if err != nil { 113 | return nil, err 114 | } 115 | value := c.data[i+1] 116 | err = key.Set(score, value) 117 | if err != nil { 118 | return nil, err 119 | } 120 | } 121 | return key, nil 122 | } 123 | 124 | // ConvertToSetKey конвертирует команду Sadd в ключ SetKey 125 | func (c Command) ConvertToSetKey(db int) (data.SetKey, error) { 126 | if len(c.data) < 3 { 127 | return nil, fmt.Errorf("expected count args >=4 but actual %d", len(c.data)) 128 | } 129 | commandType := c.Type() 130 | if commandType != Sadd { 131 | return nil, fmt.Errorf("expected Sadd command but actual %s", commandType) 132 | } 133 | keyName := c.data[1] 134 | key := data.NewSet(keyName) 135 | err := key.SetDB(db) 136 | if err != nil { 137 | return nil, err 138 | } 139 | count := len(c.data) 140 | for i := 2; i < count; i++ { 141 | err = key.Set(c.data[i]) 142 | if err != nil { 143 | return nil, err 144 | } 145 | } 146 | return key, nil 147 | } 148 | -------------------------------------------------------------------------------- /command/command_test.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/avito-tech/smart-redis-replication/data" 8 | ) 9 | 10 | // TestCommandType проверяет правильное определение типа команды 11 | func TestCommandType(t *testing.T) { 12 | t.Run("Ping", func(t *testing.T) { 13 | testCommandType(t, []string{"ping"}, Ping) 14 | testCommandType(t, []string{"PING"}, Ping) 15 | testCommandType(t, []string{"Ping"}, Ping) 16 | }) 17 | t.Run("Select", func(t *testing.T) { 18 | testCommandType(t, []string{"select"}, Select) 19 | testCommandType(t, []string{"SELECT"}, Select) 20 | testCommandType(t, []string{"Select"}, Select) 21 | }) 22 | t.Run("Zadd", func(t *testing.T) { 23 | testCommandType(t, []string{"zadd"}, Zadd) 24 | testCommandType(t, []string{"ZADD"}, Zadd) 25 | testCommandType(t, []string{"Zadd"}, Zadd) 26 | }) 27 | t.Run("Sadd", func(t *testing.T) { 28 | testCommandType(t, []string{"sadd"}, Sadd) 29 | testCommandType(t, []string{"SADD"}, Sadd) 30 | testCommandType(t, []string{"Sadd"}, Sadd) 31 | }) 32 | t.Run("Zrem", func(t *testing.T) { 33 | testCommandType(t, []string{"zrem"}, Zrem) 34 | testCommandType(t, []string{"ZREM"}, Zrem) 35 | testCommandType(t, []string{"Zrem"}, Zrem) 36 | }) 37 | t.Run("Delete", func(t *testing.T) { 38 | testCommandType(t, []string{"delete"}, Delete) 39 | testCommandType(t, []string{"DELETE"}, Delete) 40 | testCommandType(t, []string{"Delete"}, Delete) 41 | }) 42 | t.Run("Empty", func(t *testing.T) { 43 | testCommandType(t, []string{""}, Empty) 44 | testCommandType(t, []string{" "}, Empty) 45 | testCommandType(t, []string{}, Empty) 46 | }) 47 | t.Run("Undefined", func(t *testing.T) { 48 | testCommandType(t, []string{"TEST123"}, Undefined) 49 | testCommandType(t, []string{"test123"}, Undefined) 50 | testCommandType(t, []string{"_undefined_"}, Undefined) 51 | testCommandType(t, []string{"_other_"}, Undefined) 52 | }) 53 | } 54 | 55 | func testCommandType( 56 | t *testing.T, 57 | args []string, 58 | expected Type, 59 | ) { 60 | c := New(args) 61 | result := c.Type() 62 | if result != expected { 63 | t.Errorf("expected %q but actual %q, args: %q", expected, result, args) 64 | } 65 | } 66 | 67 | // TestCommandConvertToSelectDB проверяет правильное конвертирование команды 68 | func TestCommandConvertToSelectDB(t *testing.T) { 69 | t.Run("Normal", func(t *testing.T) { 70 | t.Run("0", func(t *testing.T) { 71 | testCommandConvertToSelectDB( 72 | t, 73 | []string{"select", "0"}, 74 | 0, 75 | true, 76 | ) 77 | }) 78 | t.Run("1", func(t *testing.T) { 79 | testCommandConvertToSelectDB( 80 | t, 81 | []string{"SELECT", "1"}, 82 | 1, 83 | true, 84 | ) 85 | }) 86 | }) 87 | t.Run("Error", func(t *testing.T) { 88 | t.Run("Empty", func(t *testing.T) { 89 | testCommandConvertToSelectDB( 90 | t, 91 | []string{}, 92 | 0, 93 | false, 94 | ) 95 | }) 96 | t.Run("NoDB", func(t *testing.T) { 97 | testCommandConvertToSelectDB( 98 | t, 99 | []string{"select"}, 100 | 0, 101 | false, 102 | ) 103 | }) 104 | t.Run("NoSelectType", func(t *testing.T) { 105 | testCommandConvertToSelectDB( 106 | t, 107 | []string{"PING", "1"}, 108 | 0, 109 | false, 110 | ) 111 | }) 112 | t.Run("IncorrectNumber", func(t *testing.T) { 113 | testCommandConvertToSelectDB( 114 | t, 115 | []string{"select", "incorrect_number"}, 116 | 0, 117 | false, 118 | ) 119 | }) 120 | }) 121 | } 122 | 123 | func testCommandConvertToSelectDB( 124 | t *testing.T, 125 | args []string, 126 | expected int, 127 | success bool, 128 | ) { 129 | c := New(args) 130 | result, err := c.ConvertToSelectDB() 131 | if success { 132 | if err != nil { 133 | t.Fatalf("conver error: %v", err) 134 | } 135 | if result != expected { 136 | t.Fatalf("expected %d db number but actual %d", expected, result) 137 | } 138 | } else { 139 | if err == nil { 140 | t.Fatalf("expected error") 141 | } 142 | } 143 | } 144 | 145 | // TestCommandConvertToSortedSetKey проверяет правильное конвертирование команды 146 | func TestCommandConvertToSortedSetKey(t *testing.T) { 147 | 148 | } 149 | 150 | func testCommandConvertToSortedSetKey( 151 | t *testing.T, 152 | args []string, 153 | db int, 154 | expected data.SortedSetKey, 155 | success bool, 156 | ) { 157 | c := New(args) 158 | result, err := c.ConvertToSortedSetKey(db) 159 | if success { 160 | if err != nil { 161 | t.Fatalf("conver error: %v", err) 162 | } 163 | if !reflect.DeepEqual(expected, result) { 164 | t.Fatalf("expected key %#v but actual %#v", expected, result) 165 | } 166 | } else { 167 | if err == nil { 168 | t.Fatalf("expected error") 169 | } 170 | } 171 | } 172 | 173 | // TestCommandConvertToSetKey проверяет правильное конвертирование команды 174 | func TestCommandConvertToSetKey(t *testing.T) { 175 | t.Run("Normal", func(t *testing.T) { 176 | 177 | }) 178 | t.Run("Error", func(t *testing.T) { 179 | t.Run("Empty", func(t *testing.T) { 180 | testCommandConvertToSetKey( 181 | t, 182 | []string{}, 183 | 0, 184 | nil, 185 | false, 186 | ) 187 | }) 188 | t.Run("NoSetType", func(t *testing.T) { 189 | testCommandConvertToSetKey( 190 | t, 191 | []string{"ping", "key", "value", "value"}, 192 | 0, 193 | nil, 194 | false, 195 | ) 196 | }) 197 | t.Run("NoKeyName", func(t *testing.T) { 198 | testCommandConvertToSetKey( 199 | t, 200 | []string{"sadd"}, 201 | 0, 202 | nil, 203 | false, 204 | ) 205 | }) 206 | }) 207 | } 208 | 209 | func testCommandConvertToSetKey( 210 | t *testing.T, 211 | args []string, 212 | db int, 213 | expected data.SetKey, 214 | success bool, 215 | ) { 216 | c := New(args) 217 | result, err := c.ConvertToSetKey(db) 218 | if success { 219 | if err != nil { 220 | t.Fatalf("conver error: %v", err) 221 | } 222 | if !reflect.DeepEqual(expected, result) { 223 | t.Fatalf("expected key %#v but actual %#v", expected, result) 224 | } 225 | } else { 226 | if err == nil { 227 | t.Fatalf("expected error") 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /command/types.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | // Это команды от сервера 4 | const ( 5 | RDB Type = "rdb" 6 | 7 | Ping Type = "ping" 8 | Select Type = "select" 9 | Zadd Type = "zadd" 10 | Sadd Type = "sadd" 11 | Zrem Type = "zrem" 12 | Delete Type = "delete" 13 | Empty Type = "empty" 14 | Undefined Type = "undefined" 15 | 16 | Lpush Type = "lpush" 17 | LpushX Type = "lpushx" 18 | Rpush Type = "rpush" 19 | RpushX Type = "rpushx" 20 | 21 | Rpop Type = "rpop" 22 | RpopLpush Type = "rpoplpush" 23 | Lpop Type = "lpop" 24 | 25 | ZremRangeByLex Type = "zremrangebylex" 26 | ZremRangeByRank Type = "zremrangebyrank" 27 | ZremRangeByScore Type = "zremrangebyscore" 28 | 29 | ZunionStore Type = "zunionstore" 30 | ZincrBy Type = "zincrby" 31 | SdiffStore Type = "sdiffstore" 32 | SinterStore Type = "sinterstore" 33 | Smove Type = "smove" 34 | SunionStore Type = "sunionstore" 35 | Srem Type = "srem" 36 | Set Type = "set" 37 | SetBit Type = "setbit" 38 | Append Type = "append" 39 | BitField Type = "bitfield" 40 | BitOp Type = "bitop" 41 | Decr Type = "decr" 42 | DecrBy Type = "decrby" 43 | Incr Type = "incr" 44 | IncrBy Type = "incrby" 45 | IncrByFloat Type = "incrbyfloat" 46 | GetSet Type = "getset" 47 | Mset Type = "mset" 48 | MsetNX Type = "msetnx" 49 | SetEX Type = "setex" 50 | SetNX Type = "setnx" 51 | SetRange Type = "setrange" 52 | BlPop Type = "blpop" 53 | BrPop Type = "brpop" 54 | BrPopLpush Type = "brpoplpush" 55 | Linsert Type = "linsert" 56 | Lrem Type = "lrem" 57 | Lset Type = "lset" 58 | Ltrim Type = "ltrim" 59 | Expire Type = "expire" 60 | ExpireAt Type = "expireat" 61 | Pexpire Type = "pexpire" 62 | PexpireAt Type = "pexpireat" 63 | Move Type = "move" 64 | Persist Type = "persist" 65 | Rename Type = "rename" 66 | Restore Type = "restore" 67 | Hset Type = "hset" 68 | HsetNx Type = "hset" 69 | HmSet Type = "hmset" 70 | HincrBy Type = "hincrby" 71 | HincrByFloat Type = "hincrby" 72 | PfAdd Type = "pfadd" 73 | PfMerge Type = "pfmerge" 74 | PsetX Type = "psetx" 75 | ) 76 | 77 | // Type это тип команды 78 | type Type string 79 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net" 8 | "strconv" 9 | "sync" 10 | 11 | "github.com/avito-tech/smart-redis-replication/replica" 12 | ) 13 | 14 | // Conn это постоянное соединение с redis сервером 15 | type Conn struct { 16 | sync.Mutex 17 | replication bool 18 | 19 | conn io.ReadWriteCloser 20 | } 21 | 22 | // NewConnect возвращает новый Conn 23 | func NewConnect(host string, port int, db int) (*Conn, error) { 24 | if host == "" { 25 | return nil, fmt.Errorf("expected host") 26 | } 27 | if port <= 0 { 28 | return nil, fmt.Errorf("expected port > 0") 29 | } 30 | if db < -1 { 31 | return nil, fmt.Errorf("expected db > -2") 32 | } 33 | 34 | conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | redisConn, err := NewConn(conn) 39 | if err != nil { 40 | return nil, err 41 | } 42 | if db > -1 { 43 | err = redisConn.Send("SELECT", db) 44 | if err != nil { 45 | err = redisConn.Close() 46 | return nil, err 47 | } 48 | } 49 | return redisConn, nil 50 | } 51 | 52 | // NewConn возвращает новый Conn 53 | func NewConn(conn io.ReadWriteCloser) (*Conn, error) { 54 | if conn == nil { 55 | return nil, fmt.Errorf("expected conn io.ReadWriteCloser") 56 | } 57 | return &Conn{ 58 | conn: conn, 59 | }, nil 60 | } 61 | 62 | func (c *Conn) replicationLock() { 63 | c.Lock() 64 | defer c.Unlock() 65 | c.replication = true 66 | } 67 | 68 | // NewReplica возвращает новый Replica, не переводит коннект в режим репликации 69 | func (c *Conn) NewReplica(config replica.Config) (replica.Replica, error) { 70 | c.replicationLock() 71 | return replica.NewReplica(c.conn, config), nil 72 | } 73 | 74 | // Send отправляет комманду и не читает ответ 75 | func (c *Conn) Send(commandName string, args ...interface{}) error { 76 | c.Lock() 77 | defer c.Unlock() 78 | if c.replication { 79 | return fmt.Errorf("error send: replication mode is enabled") 80 | } 81 | 82 | return c.send(commandName, args...) 83 | } 84 | 85 | // Close закрывает соединение 86 | func (c *Conn) Close() error { 87 | err := c.conn.Close() 88 | return err 89 | } 90 | 91 | // send отправляет комманду с аргументами в сокет 92 | func (c *Conn) send(commandName string, args ...interface{}) error { 93 | return c.writeCommand(commandName, args...) 94 | } 95 | 96 | // writeCommand формирует и записывает комманду непосредственно в сокет 97 | // nolint:gocyclo 98 | func (c *Conn) writeCommand( 99 | commandName string, 100 | args ...interface{}, 101 | ) ( 102 | err error, 103 | ) { 104 | err = c.writeLen('*', len(args)+1) 105 | if err != nil { 106 | return err 107 | } 108 | _, err = c.conn.Write([]byte(commandName)) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | for _, arg := range args { 114 | if err != nil { 115 | break 116 | } 117 | switch arg := arg.(type) { 118 | case string: 119 | err = c.writeString(arg) 120 | case []byte: 121 | err = c.writeBytes(arg) 122 | case int: 123 | err = c.writeInt64(int64(arg)) 124 | case int64: 125 | err = c.writeInt64(arg) 126 | case float64: 127 | err = c.writeFloat64(arg) 128 | case bool: 129 | if arg { 130 | err = c.writeString("1") 131 | } else { 132 | err = c.writeString("0") 133 | } 134 | case nil: 135 | err = c.writeString("") 136 | default: 137 | var buf bytes.Buffer 138 | fmt.Fprint(&buf, arg) 139 | err = c.writeBytes(buf.Bytes()) 140 | } 141 | } 142 | return err 143 | } 144 | 145 | func (c *Conn) writeLen(prefix byte, n int) error { 146 | _, err := c.conn.Write([]byte(fmt.Sprintf("%s%d\r\n", string(prefix), n))) 147 | return err 148 | } 149 | 150 | func (c *Conn) writeString(s string) error { 151 | err := c.writeLen('$', len(s)) 152 | if err != nil { 153 | return err 154 | } 155 | _, err = c.conn.Write([]byte(fmt.Sprintf("%s\r\n", s))) 156 | return err 157 | } 158 | 159 | func (c *Conn) writeInt64(n int64) error { 160 | return c.writeBytes(strconv.AppendInt([]byte{}, n, 10)) 161 | } 162 | 163 | func (c *Conn) writeFloat64(n float64) error { 164 | return c.writeBytes(strconv.AppendFloat([]byte{}, n, 'g', -1, 64)) 165 | } 166 | 167 | func (c *Conn) writeBytes(p []byte) error { 168 | err := c.writeLen('$', len(p)) 169 | if err != nil { 170 | return err 171 | } 172 | _, err = c.conn.Write(p) 173 | if err != nil { 174 | return err 175 | } 176 | _, err = c.conn.Write([]byte("\r\n")) 177 | return err 178 | } 179 | -------------------------------------------------------------------------------- /data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // SortedSet это упорядоченный набор данных 8 | type SortedSet struct { 9 | key 10 | data map[string]float64 11 | } 12 | 13 | // IntegerSet это отсортированный набор целых чисел 14 | type IntegerSet struct { 15 | key 16 | data map[uint64]struct{} 17 | } 18 | 19 | // Set это не упорядоченный набор данных 20 | type Set struct { 21 | key 22 | data map[string]struct{} 23 | } 24 | 25 | // Map это hash map 26 | type Map struct { 27 | key 28 | data map[string]string 29 | } 30 | 31 | // List это массив данных 32 | type List struct { 33 | key 34 | data []string 35 | } 36 | 37 | // String это строка данных 38 | type String struct { 39 | key 40 | value string 41 | } 42 | 43 | // NewSortedSet возвращает новый SortedSet 44 | func NewSortedSet(name string) *SortedSet { 45 | s := new(SortedSet) 46 | s.name = name 47 | s.data = make(map[string]float64) 48 | return s 49 | } 50 | 51 | // Set устанавливает значение с весом 52 | func (s *SortedSet) Set(weight float64, value string) error { 53 | s.data[value] = weight 54 | return nil 55 | } 56 | 57 | // SetData полностью меняет набор данных 58 | func (s *SortedSet) SetData(data map[string]float64) error { 59 | if data == nil { 60 | return errors.New("expected data") 61 | } 62 | s.data = data 63 | return nil 64 | } 65 | 66 | // Weight возвращает вес значения и был ли запрос успешным 67 | func (s *SortedSet) Weight(value string) (weight float64, ok bool) { 68 | weight, ok = s.data[value] 69 | return weight, ok 70 | } 71 | 72 | // Values возвращает набор данных 73 | func (s *SortedSet) Values() map[string]float64 { 74 | return s.data 75 | } 76 | 77 | // NewIntegerSet возвращает новый IntegerSet 78 | func NewIntegerSet(name string) *IntegerSet { 79 | s := new(IntegerSet) 80 | s.name = name 81 | s.data = make(map[uint64]struct{}) 82 | return s 83 | } 84 | 85 | // Set устанавливает значение в набор 86 | func (s *IntegerSet) Set(value uint64) error { 87 | s.data[value] = struct{}{} 88 | return nil 89 | } 90 | 91 | // SetData полностью меняет набор данных 92 | func (s *IntegerSet) SetData(data map[uint64]struct{}) error { 93 | if data == nil { 94 | return errors.New("expected data") 95 | } 96 | s.data = data 97 | return nil 98 | } 99 | 100 | // Values возвращает набор данных 101 | func (s *IntegerSet) Values() map[uint64]struct{} { 102 | return s.data 103 | } 104 | 105 | // Is возвращает true если значение есть, false если значения нет 106 | func (s *IntegerSet) Is(value uint64) (ok bool) { 107 | _, ok = s.data[value] 108 | return ok 109 | } 110 | 111 | // NewSet возвращает новый Set 112 | func NewSet(name string) *Set { 113 | s := new(Set) 114 | s.name = name 115 | s.data = make(map[string]struct{}) 116 | return s 117 | } 118 | 119 | // Set устанавливает значение 120 | func (s *Set) Set(value string) error { 121 | s.data[value] = struct{}{} 122 | return nil 123 | } 124 | 125 | // SetData полностью меняет набор данных 126 | func (s *Set) SetData(data map[string]struct{}) error { 127 | if data == nil { 128 | return errors.New("expected data") 129 | } 130 | s.data = data 131 | return nil 132 | } 133 | 134 | // Values возвращает набор данных 135 | func (s *Set) Values() map[string]struct{} { 136 | return s.data 137 | } 138 | 139 | // Is возвращает true если значение есть, false если значения нет 140 | func (s *Set) Is(value string) (ok bool) { 141 | _, ok = s.data[value] 142 | return ok 143 | } 144 | 145 | // NewMap возвращает новый Map 146 | func NewMap(name string) *Map { 147 | m := new(Map) 148 | m.name = name 149 | m.data = make(map[string]string) 150 | return m 151 | } 152 | 153 | // SetData полностью меняет набор данных 154 | func (m *Map) SetData(data map[string]string) error { 155 | if data == nil { 156 | return errors.New("expected data") 157 | } 158 | m.data = data 159 | return nil 160 | } 161 | 162 | // Set устанавливает ключ-значение 163 | func (m *Map) Set(key, value string) error { 164 | m.data[key] = value 165 | return nil 166 | } 167 | 168 | // Values возвращает набор данных 169 | func (m *Map) Values() map[string]string { 170 | return m.data 171 | } 172 | 173 | // Value возвращает значение и был ли запрос успешным 174 | func (m *Map) Value(key string) (value string, ok bool) { 175 | value, ok = m.data[key] 176 | return value, ok 177 | } 178 | 179 | // Is возвращает true если значение есть, false если значения нет 180 | func (m *Map) Is(key string) (ok bool) { 181 | _, ok = m.data[key] 182 | return ok 183 | } 184 | 185 | // NewList возвращает новый List 186 | func NewList(name string) *List { 187 | l := new(List) 188 | l.name = name 189 | return l 190 | } 191 | 192 | // SetData полностью меняет набор данных 193 | func (l *List) SetData(data []string) error { 194 | if data == nil { 195 | return errors.New("expected data") 196 | } 197 | l.data = data 198 | return nil 199 | } 200 | 201 | // Rpush добавляет значения в конец списка 202 | func (l *List) Rpush(values ...string) error { 203 | l.data = append(l.data, values...) 204 | return nil 205 | } 206 | 207 | // Lpush добавляет значения в начало списка 208 | func (l *List) Lpush(values ...string) error { 209 | l.data = append(values, l.data...) 210 | return nil 211 | } 212 | 213 | // Values возвращает список 214 | func (l *List) Values() []string { 215 | return l.data 216 | } 217 | 218 | // NewString возвращает новый String 219 | func NewString(name, value string) *String { 220 | s := new(String) 221 | s.name = name 222 | s.value = value 223 | return s 224 | } 225 | 226 | // Set устанавливает значение 227 | func (s *String) Set(value string) error { 228 | s.value = value 229 | return nil 230 | } 231 | 232 | // Value возвращает значение 233 | func (s *String) Value() string { 234 | return s.value 235 | } 236 | -------------------------------------------------------------------------------- /data/data_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // TestSortedSet проверяет функциональность SortedSet 9 | func TestSortedSet(t *testing.T) { 10 | t.Run("Data", func(t *testing.T) { 11 | t.Run("Normal", func(t *testing.T) { 12 | data := make(map[string]float64) 13 | data["value1"] = float64(1.001) 14 | data["value2"] = float64(2.002) 15 | testSortedSetData(t, data) 16 | }) 17 | t.Run("Error", testSortedSetDataError) 18 | }) 19 | t.Run("Set", func(t *testing.T) { 20 | weight := float64(123989) 21 | value := "value1" 22 | testSortedSetSet(t, weight, value) 23 | }) 24 | t.Run("Weight", func(t *testing.T) { 25 | t.Run("Normal", func(t *testing.T) { 26 | value := "value1" 27 | weight := float64(123456.2393) 28 | testSortedSetWeight(t, weight, value) 29 | }) 30 | t.Run("Undefined", func(t *testing.T) { 31 | value := "value1" 32 | testSortedSetWeightUndefined(t, value) 33 | }) 34 | }) 35 | } 36 | 37 | // testSortedSetData проверяет полную замену данных в SortedSet 38 | func testSortedSetData(t *testing.T, data map[string]float64) { 39 | s := NewSortedSet("") 40 | err := s.SetData(data) 41 | if err != nil { 42 | t.Fatalf("set data error: %q", err) 43 | } 44 | result := s.Values() 45 | if !reflect.DeepEqual(data, result) { 46 | t.Fatalf("expected data %#v but actual %#v", data, result) 47 | } 48 | } 49 | 50 | // testSortedSetDataError проверяет что SortedSet.SetData() не принимает nil 51 | func testSortedSetDataError(t *testing.T) { 52 | s := NewSortedSet("") 53 | err := s.SetData(nil) 54 | if err == nil { 55 | t.Fatalf("expected error") 56 | } 57 | } 58 | 59 | // testSortedSetSet проверяет установку значения 60 | func testSortedSetSet(t *testing.T, weight float64, value string) { 61 | s := NewSortedSet("") 62 | err := s.Set(weight, value) 63 | if err != nil { 64 | t.Fatalf("set error: %q", err) 65 | } 66 | result, ok := s.Weight(value) 67 | if !ok { 68 | t.Fatalf("expected weight") 69 | } 70 | if result != weight { 71 | t.Fatalf("expected weight %0.4f but actual %0.4f", weight, result) 72 | } 73 | } 74 | 75 | // testSortedSetWeight порверяет получение веса значения 76 | func testSortedSetWeight(t *testing.T, weight float64, value string) { 77 | data := make(map[string]float64) 78 | data[value] = weight 79 | 80 | s := NewSortedSet("") 81 | err := s.SetData(data) 82 | if err != nil { 83 | t.Fatalf("set data error: %q", err) 84 | } 85 | result, ok := s.Weight(value) 86 | if !ok { 87 | t.Fatalf("expected weight") 88 | } 89 | if result != weight { 90 | t.Fatalf("expected weight %0.4f but actual %0.4f", weight, result) 91 | } 92 | } 93 | 94 | // testSortedSetWeightUndefined проверяет что значения могут отсутствовать 95 | func testSortedSetWeightUndefined(t *testing.T, value string) { 96 | s := NewSortedSet("") 97 | _, ok := s.Weight(value) 98 | if ok { 99 | t.Fatalf("expected undefined value") 100 | } 101 | } 102 | 103 | // TestIntegerSet проверяет функциональность IntegerSet 104 | func TestIntegerSet(t *testing.T) { 105 | t.Run("Data", func(t *testing.T) { 106 | t.Run("Normal", func(t *testing.T) { 107 | data := make(map[uint64]struct{}) 108 | data[uint64(1)] = struct{}{} 109 | data[uint64(2)] = struct{}{} 110 | testIntegerSetData(t, data) 111 | }) 112 | t.Run("Error", testIntegerSetDataError) 113 | }) 114 | t.Run("Set", func(t *testing.T) { 115 | value := uint64(2938098) 116 | testIntegerSetSet(t, value) 117 | }) 118 | t.Run("Is", func(t *testing.T) { 119 | t.Run("Normal", func(t *testing.T) { 120 | value := uint64(8983984) 121 | testIntegerSetIs(t, value) 122 | }) 123 | t.Run("Undefined", func(t *testing.T) { 124 | value := uint64(23989823) 125 | testIntegerSetIsUndefined(t, value) 126 | }) 127 | }) 128 | } 129 | 130 | // testInsertSetData проверяет полную замену данных в InsertSet 131 | func testIntegerSetData(t *testing.T, data map[uint64]struct{}) { 132 | s := NewIntegerSet("") 133 | AssertData(t, s.SetData(data), s.Values(), data) 134 | } 135 | 136 | // testIntegerSetDataError проверяет что InsertSet.SetData() не принимает nil 137 | func testIntegerSetDataError(t *testing.T) { 138 | s := NewIntegerSet("") 139 | err := s.SetData(nil) 140 | if err == nil { 141 | t.Fatalf("expected error") 142 | } 143 | } 144 | 145 | // testIntegerSetSet проверяет установку значения 146 | func testIntegerSetSet(t *testing.T, value uint64) { 147 | s := NewIntegerSet("") 148 | AssertSetIs(t, s.Set(value), s.Is(value)) 149 | } 150 | 151 | // testIntegerSetIs проверяет наличие значения 152 | func testIntegerSetIs(t *testing.T, value uint64) { 153 | data := make(map[uint64]struct{}) 154 | data[value] = struct{}{} 155 | 156 | s := NewIntegerSet("") 157 | AssertIs(t, s.SetData(data), s.Is(value)) 158 | } 159 | 160 | // testIntegerSetIsUndefined проверяет что значения может и не быть 161 | func testIntegerSetIsUndefined(t *testing.T, value uint64) { 162 | s := NewIntegerSet("") 163 | ok := s.Is(value) 164 | if ok { 165 | t.Fatalf("expected undefined value") 166 | } 167 | } 168 | 169 | // TestSet проверяет функциональность Set 170 | func TestSet(t *testing.T) { 171 | t.Run("Data", func(t *testing.T) { 172 | t.Run("Normal", func(t *testing.T) { 173 | data := make(map[string]struct{}) 174 | data["value1"] = struct{}{} 175 | data["value2"] = struct{}{} 176 | testSetData(t, data) 177 | }) 178 | t.Run("Error", testSetDataError) 179 | }) 180 | t.Run("Set", func(t *testing.T) { 181 | value := "value1" 182 | testSetSet(t, value) 183 | }) 184 | t.Run("Is", func(t *testing.T) { 185 | t.Run("Normal", func(t *testing.T) { 186 | value := "value1" 187 | testSetIs(t, value) 188 | }) 189 | t.Run("Undefined", func(t *testing.T) { 190 | value := "value1" 191 | testSetIsUndefined(t, value) 192 | }) 193 | }) 194 | } 195 | 196 | // testSetData проверяет полную замену данных в Set 197 | func testSetData(t *testing.T, data map[string]struct{}) { 198 | s := NewSet("") 199 | AssertData(t, s.SetData(data), s.Values(), data) 200 | } 201 | 202 | // testSetDataError проверяет что Set.SetData() не принимает nil 203 | func testSetDataError(t *testing.T) { 204 | s := NewSet("") 205 | err := s.SetData(nil) 206 | if err == nil { 207 | t.Fatalf("expected error") 208 | } 209 | } 210 | 211 | // testSetSet проверяет установку значения 212 | func testSetSet(t *testing.T, value string) { 213 | s := NewSet("") 214 | AssertSetIs(t, s.Set(value), s.Is(value)) 215 | } 216 | 217 | // testSetIs проверяет наличие значения 218 | func testSetIs(t *testing.T, value string) { 219 | data := make(map[string]struct{}) 220 | data[value] = struct{}{} 221 | 222 | s := NewSet("") 223 | AssertIs(t, s.SetData(data), s.Is(value)) 224 | } 225 | 226 | // testSetIsUndefined проверяет что значения может и не быть 227 | func testSetIsUndefined(t *testing.T, value string) { 228 | s := NewSet("") 229 | ok := s.Is(value) 230 | if ok { 231 | t.Fatalf("expected undefined value") 232 | } 233 | } 234 | 235 | // TestMap проверяет функциональность Map 236 | func TestMap(t *testing.T) { 237 | t.Run("Data", func(t *testing.T) { 238 | t.Run("Normal", func(t *testing.T) { 239 | data := make(map[string]string) 240 | data["key1"] = "value1" 241 | data["key2"] = "value2" 242 | testMapData(t, data) 243 | }) 244 | t.Run("Error", testMapDataError) 245 | }) 246 | t.Run("Set", func(t *testing.T) { 247 | testMapSet(t, "key1", "value1") 248 | }) 249 | t.Run("Value", func(t *testing.T) { 250 | t.Run("Normal", func(t *testing.T) { 251 | testMapValue(t, "key1", "value1") 252 | }) 253 | t.Run("Undefined", func(t *testing.T) { 254 | testMapValueUndefined(t, "key1") 255 | }) 256 | }) 257 | t.Run("Is", func(t *testing.T) { 258 | t.Run("Normal", func(t *testing.T) { 259 | testMapIs(t, "key1", "value1") 260 | }) 261 | t.Run("Undefined", func(t *testing.T) { 262 | testMapIsUndefined(t, "key1") 263 | }) 264 | }) 265 | } 266 | 267 | // testMapData проверяет полную замену данных в Map 268 | func testMapData(t *testing.T, data map[string]string) { 269 | s := NewMap("") 270 | AssertData(t, s.SetData(data), s.Values(), data) 271 | } 272 | 273 | // testMapDataError проверяет что Map.SetData() не принимает nil 274 | func testMapDataError(t *testing.T) { 275 | s := NewMap("") 276 | err := s.SetData(nil) 277 | if err == nil { 278 | t.Fatalf("expected error") 279 | } 280 | } 281 | 282 | // testMapSet проверяет установку значения 283 | func testMapSet(t *testing.T, key, value string) { 284 | s := NewMap("") 285 | AssertSetIs(t, s.Set(key, value), s.Is(key)) 286 | } 287 | 288 | // testMapValue проверяет наличие ключа и значения 289 | func testMapValue(t *testing.T, key, value string) { 290 | data := make(map[string]string) 291 | data[key] = value 292 | 293 | s := NewMap("") 294 | AssertIs(t, s.SetData(data), s.Is(key)) 295 | result, ok := s.Value(key) 296 | if !ok { 297 | t.Fatalf("expected value") 298 | } 299 | if result != value { 300 | t.Fatalf("expected value %q but actual %q", value, result) 301 | } 302 | } 303 | 304 | // testMapValueUndefined проверяет что ключа может и не быть в данных 305 | func testMapValueUndefined(t *testing.T, key string) { 306 | s := NewMap("") 307 | _, ok := s.Value(key) 308 | if ok { 309 | t.Fatalf("expected undefined value") 310 | } 311 | } 312 | 313 | // testMapIs проверяет наличие значения 314 | func testMapIs(t *testing.T, key, value string) { 315 | data := make(map[string]string) 316 | data[key] = value 317 | 318 | s := NewMap("") 319 | AssertIs(t, s.SetData(data), s.Is(key)) 320 | } 321 | 322 | // testMapIsUndefined проверяет что ключа может и не быть в данных 323 | func testMapIsUndefined(t *testing.T, key string) { 324 | s := NewMap("") 325 | ok := s.Is(key) 326 | if ok { 327 | t.Fatalf("expected undefined value") 328 | } 329 | } 330 | 331 | // TestList проверяет функциональность List 332 | func TestList(t *testing.T) { 333 | t.Run("Data", func(t *testing.T) { 334 | t.Run("Normal", func(t *testing.T) { 335 | data := []string{ 336 | "value1", 337 | "value2", 338 | } 339 | testListData(t, data) 340 | }) 341 | t.Run("Error", testListDataError) 342 | }) 343 | t.Run("Rpush", func(t *testing.T) { 344 | testListPush(t, "right", []string{"a", "b", "c"}) 345 | }) 346 | t.Run("Lpush", func(t *testing.T) { 347 | testListPush(t, "left", []string{"a", "b", "c"}) 348 | }) 349 | } 350 | 351 | // testListData проверяет полную замену данных в List 352 | func testListData(t *testing.T, data []string) { 353 | s := NewList("") 354 | AssertData(t, s.SetData(data), s.Values(), data) 355 | } 356 | 357 | // testListDataError проверяет что List.SetData() не принимает nil 358 | func testListDataError(t *testing.T) { 359 | s := NewList("") 360 | err := s.SetData(nil) 361 | if err == nil { 362 | t.Fatalf("expected error") 363 | } 364 | } 365 | 366 | // testListPush проверяет добавление данных в List 367 | func testListPush(t *testing.T, direction string, data []string) { 368 | list := []string{"123", "234", "345"} 369 | var expected []string 370 | 371 | s := NewList("") 372 | err := s.SetData(list) 373 | if err != nil { 374 | t.Fatalf("set data error: %q", err) 375 | } 376 | switch direction { 377 | case "left": 378 | err = s.Lpush(data...) 379 | if err != nil { 380 | t.Fatalf("lpush error: %q", err) 381 | } 382 | expected = append(data, list...) 383 | case "right": 384 | err = s.Rpush(data...) 385 | if err != nil { 386 | t.Fatalf("lpush error: %q", err) 387 | } 388 | expected = append(list, data...) 389 | default: 390 | t.Fatalf("undefined direction") 391 | } 392 | result := s.Values() 393 | if !reflect.DeepEqual(expected, result) { 394 | t.Fatalf("expected data %#v but actual %#v", expected, result) 395 | } 396 | } 397 | 398 | // TestString проверяет функциональность String 399 | func TestString(t *testing.T) { 400 | t.Run("Name", func(t *testing.T) { 401 | name := "name" 402 | s := NewString(name, "value") 403 | result := s.Name() 404 | if result != name { 405 | t.Fatalf("expected name %q but actual %q", name, result) 406 | } 407 | }) 408 | t.Run("Value", func(t *testing.T) { 409 | value := "value" 410 | s := NewString("name", value) 411 | result := s.Value() 412 | if result != value { 413 | t.Fatalf("expected value %q but actual %q", value, result) 414 | } 415 | }) 416 | t.Run("Set", func(t *testing.T) { 417 | value := "value" 418 | s := NewString("", "") 419 | err := s.Set(value) 420 | if err != nil { 421 | t.Fatalf("set value error: %q", err) 422 | } 423 | result := s.Value() 424 | if result != value { 425 | t.Fatalf("expected value %q but actual %q", value, result) 426 | } 427 | }) 428 | } 429 | 430 | // AssertData проверяет полную замену данных 431 | func AssertData(t *testing.T, err error, result, expected interface{}) { 432 | if err != nil { 433 | t.Fatalf("set data error: %q", err) 434 | } 435 | if !reflect.DeepEqual(expected, result) { 436 | t.Fatalf("expected data %#v but actual %#v", expected, result) 437 | } 438 | } 439 | 440 | // AssertSetIs проверяет что значение удалось установить и проверить 441 | func AssertSetIs(t *testing.T, err error, ok bool) { 442 | if err != nil { 443 | t.Fatalf("set error: %q", err) 444 | } 445 | if !ok { 446 | t.Fatalf("expected value") 447 | } 448 | } 449 | 450 | // AssertIs проверяет наличие значения 451 | func AssertIs(t *testing.T, err error, ok bool) { 452 | if err != nil { 453 | t.Fatalf("set data error: %q", err) 454 | } 455 | if !ok { 456 | t.Fatalf("expected value") 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /data/expiry.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Expiry это время жизни объекта, например ключа 8 | type Expiry struct { 9 | t time.Duration 10 | } 11 | 12 | // NewExpiry возвращает новый Expiry 13 | func NewExpiry(milliseconds uint64) Expiry { 14 | return Expiry{ 15 | t: time.Duration(milliseconds*1000) * time.Microsecond, 16 | } 17 | } 18 | 19 | // Milliseconds возвращает время жизни в миллисекундах 20 | func (e Expiry) Milliseconds() uint64 { 21 | return uint64(e.t / (1000 * time.Microsecond)) 22 | } 23 | 24 | // Seconds возвращает время жизни в секундах 25 | func (e Expiry) Seconds() float64 { 26 | return e.t.Seconds() 27 | } 28 | -------------------------------------------------------------------------------- /data/expiry_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestExpiry(t *testing.T) { 8 | t.Run("Milliseconds", func(t *testing.T) { 9 | t.Run("1492780026", func(t *testing.T) { 10 | testExpiryMilliseconds(t, 1492780026) 11 | }) 12 | }) 13 | t.Run("Seconds", func(t *testing.T) { 14 | t.Run("1492780026", func(t *testing.T) { 15 | testExpirySeconds(t, 1492780026, 1492780.026) 16 | }) 17 | t.Run("1492780526", func(t *testing.T) { 18 | testExpirySeconds(t, 1492780526, 1492780.526) 19 | }) 20 | t.Run("1492780909", func(t *testing.T) { 21 | testExpirySeconds(t, 1492780909, 1492780.909) 22 | }) 23 | }) 24 | } 25 | 26 | // testExpiryMilliseconds проверяет значение времени жизни в миллисекундах 27 | func testExpiryMilliseconds(t *testing.T, milliseconds uint64) { 28 | e := NewExpiry(milliseconds) 29 | result := e.Milliseconds() 30 | if result != milliseconds { 31 | t.Fatalf("expected expiry %d but actual %d", milliseconds, result) 32 | } 33 | } 34 | 35 | // testExpiryMilliseconds проверяет значение времени жизни в секундах 36 | func testExpirySeconds(t *testing.T, milliseconds uint64, expected float64) { 37 | e := NewExpiry(milliseconds) 38 | result := e.Seconds() 39 | if result != expected { 40 | t.Fatalf("expected expiry %0.4f but actual %0.4f", expected, result) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /data/interface.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // Key это ключ с минимальным набором методов 8 | type Key interface { 9 | DB() int 10 | SetDB(int) error 11 | Name() string 12 | Expiry() Expiry 13 | SetName(string) error 14 | SetExpiry(Expiry) error 15 | ReplaceName(*regexp.Regexp, string) error 16 | } 17 | 18 | // SortedSetKey это интерфейс упорядоченного набора данных 19 | type SortedSetKey interface { 20 | Key 21 | Set(weight float64, value string) error 22 | SetData(data map[string]float64) error 23 | Values() map[string]float64 24 | Weight(value string) (float64, bool) 25 | } 26 | 27 | // IntegerSetKey это интерфейс отсортированного набора целых чисел 28 | // В RDB обозначается как IntSet представляет собой дерево бинарного поиска 29 | type IntegerSetKey interface { 30 | Key 31 | Set(value uint64) error 32 | SetData(data map[uint64]struct{}) error 33 | Values() map[uint64]struct{} 34 | Is(value uint64) bool 35 | } 36 | 37 | // SetKey это интерфейс не упорядоченного набора данных 38 | type SetKey interface { 39 | Key 40 | Set(value string) error 41 | SetData(data map[string]struct{}) error 42 | Values() map[string]struct{} 43 | Is(value string) (ok bool) 44 | } 45 | 46 | // MapKey это интерфейс классического hash map 47 | type MapKey interface { 48 | Key 49 | SetData(data map[string]string) error 50 | Set(key, value string) error 51 | Values() map[string]string 52 | Value(key string) (value string, ok bool) 53 | Is(key string) (ok bool) 54 | } 55 | 56 | // ListKey это интерфейс массива данных 57 | type ListKey interface { 58 | Key 59 | SetData(data []string) error 60 | Rpush(values ...string) error 61 | Lpush(values ...string) error 62 | Values() []string 63 | } 64 | 65 | // StringKey это интерфейс строкового значения 66 | type StringKey interface { 67 | Key 68 | Set(value string) error 69 | Value() string 70 | } 71 | -------------------------------------------------------------------------------- /data/interface_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestInteface проверяет соответствие структур интерфейсам 8 | func TestInterface(t *testing.T) { 9 | t.Run("Key", func(t *testing.T) { 10 | t.Run("SortedSet", func(t *testing.T) { 11 | testInterfaceKey(t, new(SortedSet)) 12 | }) 13 | t.Run("IntegerSet", func(t *testing.T) { 14 | testInterfaceKey(t, new(IntegerSet)) 15 | }) 16 | t.Run("Set", func(t *testing.T) { 17 | testInterfaceKey(t, new(Set)) 18 | }) 19 | t.Run("Map", func(t *testing.T) { 20 | testInterfaceKey(t, new(Map)) 21 | }) 22 | t.Run("List", func(t *testing.T) { 23 | testInterfaceKey(t, new(List)) 24 | }) 25 | t.Run("String", func(t *testing.T) { 26 | testInterfaceKey(t, new(String)) 27 | }) 28 | }) 29 | t.Run("SortedSetKey", func(t *testing.T) { 30 | testInterfaceSortedSetKey(t, new(SortedSet)) 31 | }) 32 | t.Run("IntegerSetKey", func(t *testing.T) { 33 | testInterfaceIntegerSetKey(t, new(IntegerSet)) 34 | }) 35 | t.Run("SetKey", func(t *testing.T) { 36 | testInterfaceSetKey(t, new(Set)) 37 | }) 38 | t.Run("MapKey", func(t *testing.T) { 39 | testInterfaceMapKey(t, new(Map)) 40 | }) 41 | t.Run("ListKey", func(t *testing.T) { 42 | testInterfaceListKey(t, new(List)) 43 | }) 44 | t.Run("StringKey", func(t *testing.T) { 45 | testInterfaceStringKey(t, new(String)) 46 | }) 47 | } 48 | 49 | // testInterfaceKey проверяет принадлежность к интерфейсу Key 50 | func testInterfaceKey(t *testing.T, key interface{}) { 51 | if _, ok := key.(Key); !ok { 52 | t.Fatalf("does not implement the interface") 53 | } 54 | } 55 | 56 | // testInterfaceSortedSetKey проверяет принадлежность к интерфейсу SortedSetKey 57 | func testInterfaceSortedSetKey(t *testing.T, key interface{}) { 58 | if _, ok := key.(SortedSetKey); !ok { 59 | t.Fatalf("does not implement the interface") 60 | } 61 | } 62 | 63 | // testInterfaceIntegerSetKey 64 | // проверяет принадлежность к интерфейсу IntegerSetKey 65 | func testInterfaceIntegerSetKey(t *testing.T, key interface{}) { 66 | if _, ok := key.(IntegerSetKey); !ok { 67 | t.Fatalf("does not implement the interface") 68 | } 69 | } 70 | 71 | // testInterfaceSetKey проверяет принадлежность к интерфейсу SetKey 72 | func testInterfaceSetKey(t *testing.T, key interface{}) { 73 | if _, ok := key.(SetKey); !ok { 74 | t.Fatalf("does not implement the interface") 75 | } 76 | } 77 | 78 | // testInterfaceMapKey проверяет принадлежность к интерфейсу MapKey 79 | func testInterfaceMapKey(t *testing.T, key interface{}) { 80 | if _, ok := key.(MapKey); !ok { 81 | t.Fatalf("does not implement the interface") 82 | } 83 | } 84 | 85 | // testInterfaceListKey проверяет принадлежность к интерфейсу ListKey 86 | func testInterfaceListKey(t *testing.T, key interface{}) { 87 | if _, ok := key.(ListKey); !ok { 88 | t.Fatalf("does not implement the interface") 89 | } 90 | } 91 | 92 | // testInterfaceStringKey проверяет принадлежность к интерфейсу StringKey 93 | func testInterfaceStringKey(t *testing.T, key interface{}) { 94 | if _, ok := key.(StringKey); !ok { 95 | t.Fatalf("does not implement the interface") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /data/key.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // key это общая часть для работы с ключами 8 | type key struct { 9 | db int 10 | name string 11 | expiry Expiry 12 | } 13 | 14 | // ReplaceName заменяет название ключа по регулярному выражению 15 | func (k *key) ReplaceName(srcRegexp *regexp.Regexp, repl string) error { 16 | k.name = string(srcRegexp.ReplaceAll([]byte(k.name), []byte(repl))) 17 | return nil 18 | } 19 | 20 | // DB возвращает номер базы данных 21 | func (k *key) DB() int { 22 | return k.db 23 | } 24 | 25 | // SetDB устанавливает номер базы данных 26 | func (k *key) SetDB(db int) error { 27 | k.db = db 28 | return nil 29 | } 30 | 31 | // GetKey возвращает название ключа 32 | func (k *key) Name() string { 33 | return k.name 34 | } 35 | 36 | // SetKey устанавливает новое название ключа 37 | func (k *key) SetName(name string) error { 38 | k.name = name 39 | return nil 40 | } 41 | 42 | // SetExpiry устанавливает время жизни ключа 43 | func (k *key) SetExpiry(expiry Expiry) error { 44 | k.expiry = expiry 45 | return nil 46 | } 47 | 48 | // Expiry возвращает время жизни ключа 49 | func (k *key) Expiry() Expiry { 50 | return k.expiry 51 | } 52 | -------------------------------------------------------------------------------- /data/key_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "testing" 7 | ) 8 | 9 | // newKey возвращает новый key 10 | func newKey() *key { 11 | return &key{} 12 | } 13 | 14 | // TestKey проверяет функциональность ключа 15 | func TestKey(t *testing.T) { 16 | // nolint:dupl 17 | t.Run("DB", func(t *testing.T) { 18 | t.Run("0", func(t *testing.T) { 19 | testKeyDB(t, 0) 20 | }) 21 | t.Run("1", func(t *testing.T) { 22 | testKeyDB(t, 1) 23 | }) 24 | }) 25 | // nolint:dupl 26 | t.Run("Name", func(t *testing.T) { 27 | t.Run("Normal", func(t *testing.T) { 28 | testKeyName(t, "name123") 29 | }) 30 | t.Run("Empty", func(t *testing.T) { 31 | testKeyName(t, "") 32 | }) 33 | }) 34 | // nolint:dupl 35 | t.Run("ReplaceName", func(t *testing.T) { 36 | t.Run("Normal", func(t *testing.T) { 37 | name := "a:b:c:d" 38 | src := "^a:b:(.*)" 39 | repl := "$1:e:f" 40 | expected := "c:d:e:f" 41 | testKeyReplaceName(t, name, src, repl, expected) 42 | }) 43 | }) 44 | // nolint:dupl 45 | t.Run("Expiry", func(t *testing.T) { 46 | t.Run("Normal", func(t *testing.T) { 47 | testKeyExpiry(t, NewExpiry(1234567)) 48 | }) 49 | t.Run("Empty", func(t *testing.T) { 50 | 51 | }) 52 | }) 53 | } 54 | 55 | // testKeyDB проверяет установку и получение номера базы данных 56 | // nolint:dupl 57 | func testKeyDB(t *testing.T, db int) { 58 | k := newKey() 59 | err := k.SetDB(db) 60 | if err != nil { 61 | t.Fatalf("set db error: %q", err) 62 | } 63 | result := k.DB() 64 | if result != db { 65 | t.Fatalf("expected db number %q but actual %q", db, result) 66 | } 67 | } 68 | 69 | // testKeyName проверяет установку и получение названия ключа 70 | // nolint:dupl 71 | func testKeyName(t *testing.T, name string) { 72 | k := newKey() 73 | err := k.SetName(name) 74 | if err != nil { 75 | t.Fatalf("set name error: %q", err) 76 | } 77 | result := k.Name() 78 | if result != name { 79 | t.Fatalf("expected name %q but actual %q", name, result) 80 | } 81 | } 82 | 83 | // testKeyReplaceName проверяет замену названия ключа 84 | func testKeyReplaceName(t *testing.T, name, src, repl, expected string) { 85 | reg, err := regexp.Compile(src) 86 | if err != nil { 87 | t.Fatalf("create regexp error: %q", err) 88 | } 89 | k := newKey() 90 | err = k.SetName(name) 91 | if err != nil { 92 | t.Fatalf("set name error: %q", err) 93 | } 94 | err = k.ReplaceName(reg, repl) 95 | if err != nil { 96 | t.Fatalf("replace name error :%q", err) 97 | } 98 | result := k.Name() 99 | if result != expected { 100 | t.Fatalf("expected name %q but actual %q", expected, result) 101 | } 102 | } 103 | 104 | // testKeyExpiry проверяет установку и получение времени жизни ключа 105 | func testKeyExpiry(t *testing.T, expiry Expiry) { 106 | k := newKey() 107 | err := k.SetExpiry(expiry) 108 | if err != nil { 109 | t.Fatalf("set expiry error: %q", err) 110 | } 111 | result := k.Expiry() 112 | if !reflect.DeepEqual(expiry, result) { 113 | t.Fatalf("expected expiry %#v but actual %#v", expiry, result) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/avito-tech/smart-redis-replication/replica" 7 | ) 8 | 9 | // Config это минимальный набор данных для запуска мигратора 10 | type Config struct { 11 | useStderr *bool 12 | useMetrics *bool 13 | redis struct { 14 | host *string 15 | port *int 16 | } 17 | service struct { 18 | address *string 19 | } 20 | replica replica.Config 21 | } 22 | 23 | // Check проверяет что в конфиге все необходимые данные 24 | func (c Config) Check() error { 25 | switch { 26 | case *c.redis.host == "": 27 | return errors.New("empty redis host") 28 | case *c.redis.port < 1: 29 | return errors.New("expected redis port > 0") 30 | case *c.service.address == "": 31 | return errors.New("empty service address") 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /example/consumer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "regexp" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/avito-tech/smart-redis-replication/command" 15 | "github.com/avito-tech/smart-redis-replication/data" 16 | "github.com/avito-tech/smart-redis-replication/status" 17 | ) 18 | 19 | // Consumer это получатель информации из репликации 20 | type Consumer struct { 21 | ctx context.Context 22 | cancel context.CancelFunc 23 | err error 24 | db int 25 | service struct { 26 | address string 27 | client *http.Client 28 | } 29 | regexp struct { 30 | keyName *regexp.Regexp 31 | repl string 32 | } 33 | Config Config 34 | } 35 | 36 | // NewConsumer возвращает новый Consumer 37 | func NewConsumer(address string) (c *Consumer, err error) { 38 | c = new(Consumer) 39 | c.regexp.repl = "$1" 40 | c.regexp.keyName, err = regexp.Compile("prefix:(.*)") 41 | if err != nil { 42 | return nil, err 43 | } 44 | c.service.address = address 45 | c.service.client = &http.Client{ 46 | Timeout: 6, 47 | Transport: &http.Transport{ 48 | MaxIdleConnsPerHost: 1000, 49 | }, 50 | } 51 | c.ctx, c.cancel = context.WithCancel(context.Background()) 52 | return c, nil 53 | } 54 | 55 | // Cancel останавливает обработку данных 56 | func (c *Consumer) Cancel(err *error) { 57 | c.err = *err 58 | c.cancel() 59 | } 60 | 61 | // Reset сбрасывает контекст 62 | func (c *Consumer) Reset() { 63 | c.ctx, c.cancel = context.WithCancel(context.Background()) 64 | } 65 | 66 | // Status возвращает true если репликация не прекратилась, иначе false 67 | func (c *Consumer) Status() bool { 68 | select { 69 | case <-c.ctx.Done(): 70 | return false 71 | default: 72 | return true 73 | } 74 | } 75 | 76 | // Err возвращает ошибку репликации 77 | func (c *Consumer) Err() error { 78 | return c.err 79 | } 80 | 81 | // CheckKeyName проверяет принадлежность ключа к регулярному выражению 82 | func (c *Consumer) CheckKeyName(keyName string) bool { 83 | return c.regexp.keyName.MatchString(keyName) 84 | } 85 | 86 | // ClearKeyName убирает префикс ключа 87 | func (c *Consumer) ClearKeyName(keyName string) string { 88 | return string(c.regexp.keyName.ReplaceAll( 89 | []byte(keyName), 90 | []byte(c.regexp.repl), 91 | )) 92 | } 93 | 94 | // Key принимает ключи с данными 95 | func (c *Consumer) Key(key data.Key) error { 96 | // проверяем префикс ключа 97 | if !c.CheckKeyName(key.Name()) { 98 | return nil 99 | } 100 | 101 | // проверяем в том ли формате мы ожидаем данные 102 | sortedSet, ok := key.(data.SortedSetKey) 103 | if !ok { 104 | return nil 105 | } 106 | 107 | data := struct { 108 | keyName string 109 | }{ 110 | keyName: sortedSet.Name(), 111 | } 112 | 113 | return c.Send("_PATH_", data) 114 | } 115 | 116 | // CheckCommand возвращает true если команда интересна получателю 117 | func (c *Consumer) CheckCommand(cmd command.Command) bool { 118 | switch cmd.Type() { 119 | case command.Select: 120 | return true 121 | case command.Delete, command.Zrem, command.Sadd, command.Zadd: 122 | keyName, err := cmd.KeyName() 123 | if err != nil { 124 | return false 125 | } 126 | // проверяем префикс ключа 127 | if !c.CheckKeyName(keyName) { 128 | return false 129 | } 130 | return true 131 | default: 132 | return false 133 | } 134 | } 135 | 136 | // Command принимает команды управления 137 | func (c *Consumer) Command(cmd command.Command) (err error) { 138 | switch cmd.Type() { 139 | case command.Select: 140 | c.db, err = cmd.ConvertToSelectDB() 141 | case command.Delete: 142 | err = c.DeleteKey(cmd) 143 | case command.Zrem: 144 | err = c.DeleteItemIDs(cmd) 145 | default: 146 | return nil 147 | } 148 | return err 149 | } 150 | 151 | // DeleteKey обрабатывает удаление ключа 152 | func (c *Consumer) DeleteKey(cmd command.Command) error { 153 | // c.Send() 154 | return nil 155 | } 156 | 157 | // DeleteItemIDs удаляет значения из SortedSet 158 | func (c *Consumer) DeleteItemIDs(cmd command.Command) error { 159 | // c.Send() 160 | return nil 161 | } 162 | 163 | // Send отправляет данные в сервис 164 | func (c *Consumer) Send(url string, data interface{}) error { 165 | body, err := json.Marshal(data) 166 | if err != nil { 167 | return fmt.Errorf("post body marshal error: %v", err) 168 | } 169 | for { 170 | select { 171 | case <-c.ctx.Done(): 172 | return c.ctx.Err() 173 | default: 174 | } 175 | 176 | r, err := http.NewRequest( 177 | "POST", 178 | c.service.address+url, 179 | bytes.NewBufferString(string(body)), 180 | ) 181 | if err != nil { 182 | return fmt.Errorf("create request error: %q", err) 183 | } 184 | r.Header.Set("HTTP_CONNECTION", "keep-alive") 185 | r.Header.Set("Content-Length", strconv.Itoa(len(string(body)))) 186 | 187 | var resp *http.Response 188 | resp, err = c.service.client.Do(r) 189 | if resp != nil && resp.Body != nil { 190 | resp.Body.Close() //nolint:errcheck 191 | } 192 | if err == nil && resp.StatusCode == 200 { 193 | return nil 194 | } 195 | time.Sleep(100 * time.Millisecond) 196 | } 197 | } 198 | 199 | // status отправляет отсечку на графики 200 | func (c *Consumer) status(port int, status string) { 201 | t := time.Now().Local().Unix() 202 | n := (port - 6300) * 10 203 | data := fmt.Sprintf("migration.status.%d.%s %d %d\n", port, status, n, t) 204 | 205 | for { 206 | conn, err := net.Dial("tcp", "graphite") 207 | if err == nil { 208 | _, err := conn.Write([]byte(data)) 209 | _ = conn.Close() 210 | if err == nil { 211 | break 212 | } 213 | } 214 | time.Sleep(1 * time.Second) 215 | } 216 | } 217 | 218 | // ReplicaStatus принимает статус репликации и отправляет его в лог 219 | func (c *Consumer) ReplicaStatus(status status.Status) error { 220 | go c.status(*c.Config.redis.port, string(status)) 221 | return nil 222 | } 223 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // Package main это пример приложения для бесшовной миграции данных 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "log" 7 | 8 | "github.com/avito-tech/smart-redis-replication/backlog" 9 | ) 10 | 11 | func main() { 12 | conf := Config{} 13 | conf.redis.host = flag.String("redis-host", "", "redis master hostname") 14 | conf.redis.port = flag.Int("redis-port", 0, "redis master port") 15 | conf.service.address = flag.String("service-address", "", "service address") 16 | conf.useStderr = flag.Bool("use-stderr", false, "logger useStderr") 17 | conf.useMetrics = flag.Bool("use-metrics", true, "enable metrics") 18 | conf.replica.CacheRDB = true 19 | conf.replica.CacheRDBFile = "/tmp/rdb.cache" 20 | conf.replica.BacklogSize = backlog.DefaultBacklogSize 21 | conf.replica.ReadRDB = true 22 | flag.Parse() 23 | 24 | err := conf.Check() 25 | if err != nil { 26 | log.Fatalf("config error: %v", err) 27 | } 28 | 29 | migration, err := NewMigration(conf) 30 | if err != nil { 31 | log.Fatalf("create migration error: %v", err) 32 | } 33 | err = migration.Start() 34 | if err != nil { 35 | log.Fatalf("migration error: %v", err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/migration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | srr "github.com/avito-tech/smart-redis-replication" 10 | "github.com/avito-tech/smart-redis-replication/status" 11 | ) 12 | 13 | // Migration это обёртка для запуска репликации 14 | type Migration struct { 15 | Config Config 16 | Consumer *Consumer 17 | } 18 | 19 | // NewMigration возвращает новый Migration 20 | func NewMigration( 21 | conf Config, 22 | ) ( 23 | m *Migration, 24 | err error, 25 | ) { 26 | m = new(Migration) 27 | m.Config = conf 28 | m.Consumer, err = NewConsumer(*conf.service.address) 29 | if err != nil { 30 | return nil, err 31 | } 32 | m.Config = conf 33 | m.Consumer.Config = conf 34 | return m, nil 35 | } 36 | 37 | // ReloadConfig могла бы перечитать конфиг, 38 | // но по факту нам нужно включить чтение RDB 39 | func (m *Migration) ReloadConfig() { 40 | m.Config.replica.ReadRDB = true 41 | } 42 | 43 | // Start запускает миграцию, перезапускает в случае потери соединения 44 | func (m *Migration) Start() error { 45 | for { 46 | m.Connect() // nolint:errcheck 47 | m.ReloadConfig() 48 | } 49 | } 50 | 51 | // SendStatus отправляет статус миграции в лог 52 | func (m *Migration) SendStatus(status status.Status, err error) { 53 | _ = err 54 | err = m.Consumer.ReplicaStatus(status) 55 | _ = err 56 | } 57 | 58 | // Connect подключается к редису и запускает миграцию 59 | func (m *Migration) Connect() error { 60 | conn, err := srr.NewConnect(*m.Config.redis.host, *m.Config.redis.port, -1) 61 | if err != nil { 62 | return fmt.Errorf("connect error: %v", err) 63 | } 64 | repl, err := conn.NewReplica(m.Config.replica) 65 | if err != nil { 66 | return fmt.Errorf("create replica error: %v", err) 67 | } 68 | 69 | go m.Statistics(repl) 70 | 71 | m.SendStatus(status.Connect, nil) 72 | err = repl.Do(m.Consumer) 73 | m.SendStatus(status.Reconnect, err) 74 | return err 75 | } 76 | 77 | // Statistics отправляет размер журнала отставания репликации в мониторинг 78 | func (m *Migration) Statistics(repl srr.Replica) { 79 | backlog := repl.Backlog() 80 | if backlog == nil { 81 | log.Println("statistics off") 82 | return 83 | } 84 | log.Println("statistics on") 85 | for { 86 | select { 87 | case <-repl.Done(): 88 | return 89 | case <-time.After(1 * time.Minute): 90 | } 91 | go m.backlogSize(*m.Config.redis.port, backlog.Count()) 92 | } 93 | } 94 | 95 | // backlogSize отправляет размер очереди в мониторинг 96 | func (m *Migration) backlogSize(port int, count int) { 97 | t := time.Now().Local().Unix() 98 | data := fmt.Sprintf("migration.backlogsize.%d %d %d\n", port, count, t) 99 | 100 | for { 101 | conn, err := net.Dial("tcp", "graphite") 102 | if err == nil { 103 | _, err := conn.Write([]byte(data)) 104 | _ = conn.Close() 105 | if err == nil { 106 | break 107 | } 108 | } 109 | time.Sleep(1 * time.Second) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | import ( 4 | "github.com/avito-tech/smart-redis-replication/replica" 5 | ) 6 | 7 | // Client это интерфейс соединения с redis сервером 8 | type Client interface { 9 | // NewReplica возвращает клиент для чтения логической репликации 10 | NewReplica() (replica.Replica, error) 11 | 12 | // Send отправляет простые команды на сервер 13 | Send(commandName string, args ...interface{}) error 14 | 15 | // Close закрывает сетевое соединение 16 | Close() error 17 | } 18 | 19 | // Replica это проброс интерфейса Replica из подпакета на уровень выше 20 | type Replica interface { 21 | replica.Replica 22 | } 23 | -------------------------------------------------------------------------------- /rdb/decode.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "github.com/avito-tech/smart-redis-replication/data" 9 | ) 10 | 11 | const ( 12 | tokenLevelStart int = iota 13 | tokenLevelInit 14 | tokenLevelDB 15 | ) 16 | 17 | // decoder реализует интерфейс Decoder 18 | type decoder struct { 19 | r Reader 20 | tokenLevelState int 21 | file *os.File 22 | } 23 | 24 | // NewDecoder возвращает новый Decoder 25 | func NewDecoder(r io.Reader) Decoder { 26 | return &decoder{ 27 | r: NewReader(r), 28 | } 29 | } 30 | 31 | // NewStringDecoder возвращает новый Decoder на основании строки 32 | func NewStringDecoder(data string) Decoder { 33 | return &decoder{ 34 | r: NewStringReader(data), 35 | } 36 | } 37 | 38 | // NewLimitDecoder возвращает новый Decoder ограниченный по размеру 39 | func NewLimitDecoder(r io.Reader, size int64) Decoder { 40 | return &decoder{ 41 | r: NewReader(io.LimitReader(r, size)), 42 | } 43 | } 44 | 45 | // NewFileDecoder возвращает новый Decoder на основании файла, 46 | // файл закрывается в конце Decode 47 | // Для возможности преждевременного закрытия файла воспользуйтесь NewDecoder 48 | // передав в него файл 49 | func NewFileDecoder(filename string) (Decoder, error) { 50 | file, err := os.Open(filename) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return &decoder{ 55 | r: NewReader(file), 56 | file: file, 57 | }, nil 58 | } 59 | 60 | // nolint:gocyclo 61 | func (d *decoder) DecodeKeys(consumer KeyConsumer) error { 62 | var db uint32 63 | for { 64 | token, err := d.Next() 65 | if err == io.EOF { 66 | _, ok := token.(EOF) 67 | if ok { 68 | err = nil 69 | } else { 70 | err = fmt.Errorf(`Unexpected io.EOF, actual token "%#v"`, token) 71 | } 72 | return err 73 | } 74 | if err != nil { 75 | return fmt.Errorf("error get next token: %q", err) 76 | } 77 | switch op := token.(type) { 78 | case Magic, AuxField, ResizeDB: 79 | case DBSelector: 80 | db = op.GetDBNumber() 81 | case data.Key: 82 | err = op.SetDB(int(db)) 83 | if err == nil { 84 | err = consumer.Key(op) 85 | } 86 | default: 87 | return fmt.Errorf("unexpected token %#v", op) 88 | } 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | } 94 | 95 | // nolint:gocyclo 96 | func (d *decoder) Decode(consumer Consumer) error { 97 | if d.file != nil { 98 | defer func() { 99 | _ = d.file.Close() 100 | }() 101 | } 102 | var db uint32 103 | for { 104 | token, err := d.Next() 105 | if err == io.EOF { 106 | eof, ok := token.(EOF) 107 | if ok { 108 | err = consumer.SetEOF(eof) 109 | } else { 110 | err = fmt.Errorf(`Unexpected io.EOF, actual token "%#v"`, token) 111 | } 112 | return err 113 | } 114 | if err != nil { 115 | return fmt.Errorf("error get next token: %q", err) 116 | } 117 | switch op := token.(type) { 118 | case Magic: 119 | err = consumer.SetMagic(op) 120 | case AuxField: 121 | err = consumer.SetAuxField(op) 122 | case DBSelector: 123 | db = op.GetDBNumber() 124 | case ResizeDB: 125 | err = consumer.SetResizeDB(db, op) 126 | case data.Key: 127 | err = op.SetDB(int(db)) 128 | if err == nil { 129 | err = consumer.Key(op) 130 | } 131 | default: 132 | return fmt.Errorf("unexpected token %#v", op) 133 | } 134 | if err != nil { 135 | return err 136 | } 137 | } 138 | } 139 | 140 | // checkTokenLevelState проверяет что токен находится в определённом уровне 141 | // вложенности в RDB файле 142 | func (d *decoder) checkTokenLevelState(tokenLevels ...int) error { 143 | for _, tokenLevel := range tokenLevels { 144 | if tokenLevel == d.tokenLevelState { 145 | return nil 146 | } 147 | } 148 | return fmt.Errorf( 149 | "expected token level %q but actual %d", 150 | tokenLevels, 151 | d.tokenLevelState, 152 | ) 153 | } 154 | 155 | // nolint:gocyclo 156 | func (d *decoder) Next() (interface{}, error) { 157 | if d.tokenLevelState == tokenLevelStart { 158 | d.tokenLevelState = tokenLevelInit 159 | return d.r.ReadMagic() 160 | } 161 | 162 | opcode, err := d.r.ReadOpcode() 163 | if err != nil { 164 | return nil, err 165 | } 166 | switch opcode { 167 | case AuxFieldOpcode: 168 | err = d.checkTokenLevelState(tokenLevelInit) 169 | if err != nil { 170 | return nil, err 171 | } 172 | return d.r.ReadAuxField() 173 | case DBSelectorOpcode: 174 | err = d.checkTokenLevelState(tokenLevelInit, tokenLevelDB) 175 | if err != nil { 176 | return nil, err 177 | } 178 | d.tokenLevelState = tokenLevelDB 179 | return d.r.ReadDBSelector() 180 | case ResizeDBOpcode: 181 | err = d.checkTokenLevelState(tokenLevelDB) 182 | if err != nil { 183 | return nil, err 184 | } 185 | return d.r.ReadResizeDB() 186 | case ExpirySecondsOpcode, ExpiryMillisecondsOpcode: 187 | err = d.checkTokenLevelState(tokenLevelDB) 188 | if err != nil { 189 | return nil, err 190 | } 191 | var expiry data.Expiry 192 | expiry, err = d.r.ReadExpiry(opcode) 193 | if err != nil { 194 | return nil, err 195 | } 196 | 197 | var opcodeNext byte 198 | opcodeNext, err = d.r.ReadByte() 199 | if err != nil { 200 | return nil, err 201 | } 202 | return d.readKey(opcodeNext, expiry) 203 | case EOFOpcode: 204 | err = d.checkTokenLevelState(tokenLevelInit, tokenLevelDB) 205 | if err != nil { 206 | return nil, err 207 | } 208 | return d.r.ReadEOF() 209 | } 210 | err = d.checkTokenLevelState(tokenLevelDB) 211 | if err != nil { 212 | return nil, err 213 | } 214 | return d.readKey(opcode, data.NewExpiry(0)) 215 | } 216 | 217 | // nolint:gocyclo 218 | func (d *decoder) readKey(opcode byte, expiry data.Expiry) (data.Key, error) { 219 | switch opcode { 220 | // SortedSet 221 | case ZipListSortedSetOpcode: 222 | return d.r.ReadZipListSortedSet(expiry) 223 | case SortedSetOpcode: 224 | return d.r.ReadSortedSet(expiry) 225 | 226 | // HashMap 227 | case ListHashMapOpcode: 228 | return d.r.ReadListHashMap(expiry) 229 | case ZipListHashMapOpcode: 230 | return d.r.ReadZipListHashMap(expiry) 231 | case ZipMapHashMapOpcode: 232 | return d.r.ReadZipMapHashMap(expiry) 233 | 234 | // List 235 | case ListOpcode: 236 | return d.r.ReadList(expiry) 237 | case ZipListOpcode: 238 | return d.r.ReadZipList(expiry) 239 | case QuickListOpcode: 240 | return d.r.ReadQuickList(expiry) 241 | 242 | case SetOpcode: 243 | return d.r.ReadSet(expiry) 244 | case IntSetOpcode: 245 | return d.r.ReadIntSet(expiry) 246 | case StringValueOpcode: 247 | return d.r.ReadStringValue(expiry) 248 | } 249 | return nil, fmt.Errorf("unsupported key opcode: %#v", opcode) 250 | } 251 | -------------------------------------------------------------------------------- /rdb/doc.go: -------------------------------------------------------------------------------- 1 | // Package rdb это пакет для decode/encode rdb файлов 2 | // 3 | // Описание формата http://rdb.fnordig.de/file_format.html 4 | // 5 | // Типы данных: 6 | // String 7 | // List 8 | // Set 9 | // Sorted Set 10 | // Hash 11 | // ZipMap 12 | // ZipList 13 | // IntSet 14 | // Sorted Set in Ziplist 15 | // HashMap in Ziplist (добавлен в RDB version 4) 16 | // List in QuickList (добавлен в RDB version 7) 17 | package rdb 18 | -------------------------------------------------------------------------------- /rdb/encode.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "math" 7 | "strconv" 8 | ) 9 | 10 | const ( 11 | encInt16 = 1 12 | encInt32 = 2 13 | ) 14 | 15 | var ( 16 | // ErrNotNumber это ошибка возникает в функции EncodeStringInt если строка 17 | // не содержит 32битное 10тиричное число 18 | ErrNotNumber = errors.New("string not number") 19 | ) 20 | 21 | // EncodeStringInt кодирует текст в число и 22 | // проверяет что при обратной конвертации будет тот же текст 23 | func EncodeStringInt(number string) (int, error) { 24 | i, err := strconv.ParseInt(number, 10, 32) 25 | if err != nil { 26 | return 0, ErrNotNumber 27 | } 28 | if number != strconv.FormatInt(i, 10) { 29 | return 0, ErrNotNumber 30 | } 31 | return int(i), nil 32 | } 33 | 34 | // EncodeInt кодирует 32битное число в байты 35 | func EncodeInt(i int) []byte { 36 | switch { 37 | case i >= math.MinInt8 && i <= math.MaxInt8: 38 | return []byte{lenEnc << 6, byte(int8(i))} 39 | case i >= math.MinInt16 && i <= math.MaxInt16: 40 | b := make([]byte, 3) 41 | b[0] = lenEnc<<6 | encInt16 42 | binary.LittleEndian.PutUint16(b[1:], uint16(int16(i))) 43 | return b 44 | case i >= math.MinInt32 && i <= math.MaxInt32: 45 | b := make([]byte, 5) 46 | b[0] = lenEnc<<6 | encInt32 47 | binary.LittleEndian.PutUint32(b[1:], uint32(int32(i))) 48 | return b 49 | } 50 | return []byte{} 51 | } 52 | 53 | // EncodeString кодирует строку в байты 54 | func EncodeString(st string) []byte { 55 | i, err := EncodeStringInt(st) 56 | if err == nil { 57 | return EncodeInt(i) 58 | } 59 | 60 | length := EncodeLength(uint32(len(st))) 61 | return append(length, []byte(st)...) 62 | } 63 | 64 | // EncodeLength кодирует длину в байты 65 | func EncodeLength(l uint32) []byte { 66 | switch { 67 | case l < 1<<6: 68 | return []byte{byte(l)} 69 | case l < 1<<14: 70 | return []byte{byte(l>>8) | len14Bit<<6, byte(l)} 71 | } 72 | 73 | b := make([]byte, 5) 74 | b[0] = len32Bit << 6 75 | binary.BigEndian.PutUint32(b[1:], l) 76 | return b 77 | } 78 | 79 | // EncodeFloat кодирует float64 в байты 80 | func EncodeFloat(f float64) []byte { 81 | switch { 82 | case math.IsNaN(f): 83 | return []byte{253} 84 | case math.IsInf(f, 1): 85 | return []byte{254} 86 | case math.IsInf(f, -1): 87 | return []byte{255} 88 | } 89 | b := []byte(strconv.FormatFloat(f, 'g', 17, 64)) 90 | return append([]byte{byte(len(b))}, b...) 91 | } 92 | -------------------------------------------------------------------------------- /rdb/entries.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "strconv" 10 | ) 11 | 12 | const ( 13 | zipListInt16 = 0xC0 14 | zipListInt32 = 0xD0 15 | zipListInt64 = 0xE0 16 | zipListInt24 = 0xF0 17 | zipListInt8 = 0xFE 18 | zipListInt4 = 15 19 | ) 20 | 21 | type entriesReader struct { 22 | *bufio.Reader 23 | } 24 | 25 | // NewEntriesReader возвращает новый EntriesReader 26 | func NewEntriesReader(r io.Reader) EntriesReader { 27 | return &entriesReader{ 28 | Reader: bufio.NewReaderSize(r, DefaultReaderSize), 29 | } 30 | } 31 | 32 | // NewEntriesStringReader возвращает новый EntriesReader 33 | func NewEntriesStringReader(st string) EntriesReader { 34 | r := bytes.NewBufferString(st) 35 | return &entriesReader{Reader: bufio.NewReaderSize(r, DefaultReaderSize)} 36 | } 37 | 38 | // SafeRead безопасно читает N байт 39 | func (r *entriesReader) SafeRead(n uint32) ([]byte, error) { 40 | result := make([]byte, n) 41 | _, err := io.ReadFull(r.Reader, result) 42 | return result, err 43 | } 44 | 45 | // ReadEntryLength читает размер элементов 46 | func (r *entriesReader) ReadEntryLength() (uint32, error) { 47 | bytesCount, err := r.SafeRead(4) 48 | if err != nil { 49 | return 0, err 50 | } 51 | return binary.LittleEndian.Uint32(bytesCount), nil 52 | } 53 | 54 | // ReadTail читает смещение последнего элемента 55 | func (r *entriesReader) ReadTail() (uint32, error) { 56 | tail, err := r.SafeRead(4) 57 | if err != nil { 58 | return 0, err 59 | } 60 | return binary.LittleEndian.Uint32(tail), nil 61 | } 62 | 63 | // ReadEntriesCount читает количество элементов 64 | func (r *entriesReader) ReadEntriesCount() (uint16, error) { 65 | lenBytes, err := r.SafeRead(2) 66 | if err != nil { 67 | return 0, err 68 | } 69 | return binary.LittleEndian.Uint16(lenBytes), nil 70 | } 71 | 72 | // ReadEntryLength читает размер предыдущего элемента 73 | // если первый байт меньше 254 то в нём размер, 74 | // если 254 то размер в следующих 4 байтах 75 | func (r *entriesReader) ReadPrevEntryLength() (uint32, error) { 76 | prevEntryLength, err := r.ReadByte() 77 | if err != nil { 78 | return 0, err 79 | } 80 | switch { 81 | case prevEntryLength < 254: 82 | return uint32(prevEntryLength), nil 83 | case prevEntryLength == 254: 84 | lenBytes, err := r.SafeRead(4) 85 | if err != nil { 86 | return 0, err 87 | } 88 | return binary.LittleEndian.Uint32(lenBytes), nil 89 | } 90 | return 0, fmt.Errorf("unexpected prevEntryLength %#v", prevEntryLength) 91 | } 92 | 93 | // ReadEntry читает элемент 94 | // nolint:gocyclo 95 | func (r *entriesReader) ReadEntry() ([]byte, error) { 96 | _, err := r.ReadPrevEntryLength() 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | header, err := r.ReadByte() 102 | if err != nil { 103 | return nil, err 104 | } 105 | switch { 106 | case header>>6 == len6Bit: 107 | return r.SafeRead(uint32(header & 0x3f)) 108 | case header>>6 == len14Bit: 109 | b, err := r.ReadByte() 110 | if err != nil { 111 | return nil, err 112 | } 113 | return r.SafeRead((uint32(header&0x3f) << 8) | uint32(b)) 114 | case header>>6 == len32Bit: 115 | lenBytes, err := r.SafeRead(4) 116 | if err != nil { 117 | return nil, err 118 | } 119 | return r.SafeRead(binary.BigEndian.Uint32(lenBytes)) 120 | case header == zipListInt16: 121 | intBytes, err := r.SafeRead(2) 122 | if err != nil { 123 | return nil, err 124 | } 125 | return []byte( 126 | strconv.FormatInt( 127 | int64(binary.LittleEndian.Uint16(intBytes)), 128 | 10, 129 | ), 130 | ), nil 131 | case header == zipListInt32: 132 | intBytes, err := r.SafeRead(4) 133 | if err != nil { 134 | return nil, err 135 | } 136 | return []byte( 137 | strconv.FormatInt( 138 | int64(binary.LittleEndian.Uint32(intBytes)), 139 | 10, 140 | ), 141 | ), nil 142 | case header == zipListInt64: 143 | intBytes, err := r.SafeRead(8) 144 | if err != nil { 145 | return nil, err 146 | } 147 | return []byte( 148 | strconv.FormatInt( 149 | int64(binary.LittleEndian.Uint64(intBytes)), 150 | 10, 151 | ), 152 | ), nil 153 | case header == zipListInt24: 154 | intBytes, err := r.SafeRead(3) 155 | if err != nil { 156 | return nil, err 157 | } 158 | intBytes = append([]byte{0x00}, intBytes...) 159 | return []byte( 160 | strconv.FormatInt( 161 | int64(binary.LittleEndian.Uint32(intBytes)>>8), 162 | 10, 163 | ), 164 | ), nil 165 | case header == zipListInt8: 166 | b, err := r.ReadByte() 167 | return []byte(strconv.FormatInt(int64(b), 10)), err 168 | case header>>4 == zipListInt4: 169 | return []byte(strconv.FormatInt(int64(header&0x0f)-1, 10)), nil 170 | } 171 | return nil, fmt.Errorf("rdb: unknown unsorted set header byte: %#v", header) 172 | } 173 | 174 | // ReadEnd читает закрывающий байт, 175 | // возвращает ошибку если он не найден или в нём неожиданные данные 176 | func (r *entriesReader) ReadEnd() error { 177 | end, err := r.ReadByte() 178 | if err != nil { 179 | return err 180 | } 181 | if end != 255 { 182 | return fmt.Errorf("expected end byte 255 but actual %d", end) 183 | } 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /rdb/interface.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/avito-tech/smart-redis-replication/data" 7 | ) 8 | 9 | // KeyConsumer это потребитель ключей с данными прочитанных из RDB файла 10 | type KeyConsumer interface { 11 | Key(data.Key) error 12 | } 13 | 14 | // Consumer это интерфейс потребителя всех данных прочитанных из RDB файла 15 | type Consumer interface { 16 | // SetMagic устанавливает Magic строку 17 | SetMagic(Magic) error 18 | 19 | // SetAuxField устанавливает дополнительный параметр 20 | SetAuxField(AuxField) error 21 | 22 | // SetResizeDB устанавливает размеры базы данных 23 | SetResizeDB(db uint32, resizeDB ResizeDB) error 24 | 25 | // SetEOF устанавливает конец передачи данных 26 | SetEOF(EOF) error 27 | 28 | // Key принимает ключ с данными 29 | Key(data.Key) error 30 | } 31 | 32 | // Decoder это интерфейс для декодирования RDB файла 33 | type Decoder interface { 34 | DecodeKeys(KeyConsumer) error 35 | Decode(Consumer) error 36 | Next() (interface{}, error) 37 | } 38 | 39 | // Reader это интерфейс для чтения специфичных для RDB форматов 40 | type Reader interface { 41 | io.Reader 42 | 43 | // ReadByte безопасно читает один байт 44 | ReadByte() (byte, error) 45 | 46 | // ReadOpcode читает код команды 47 | ReadOpcode() (byte, error) 48 | 49 | // ReadString читает закодированную строку 50 | ReadString() (line string, err error) 51 | 52 | // SafeRead безопасно читает N байт 53 | SafeRead(n uint32) ([]byte, error) 54 | 55 | // ReadLength читает длину или кодировку 56 | ReadLength() (length uint32, encoding int8, err error) 57 | 58 | // ReadUint8 читает закодированный uint8 59 | ReadUint8() (uint8, error) 60 | 61 | // ReadFloat64 читает закодированный float64 62 | ReadFloat64() (float64, error) 63 | 64 | ReadMagic() (Magic, error) 65 | ReadAuxField() (AuxField, error) 66 | ReadDBSelector() (DBSelector, error) 67 | ReadResizeDB() (ResizeDB, error) 68 | ReadEOF() (EOF, error) 69 | ReadExpiry(opcode byte) (data.Expiry, error) 70 | 71 | ReadSet(expiry data.Expiry) (data.SetKey, error) 72 | 73 | // SortedSet 74 | ReadSortedSet(expiry data.Expiry) (data.SortedSetKey, error) 75 | ReadZipListSortedSet(expiry data.Expiry) (data.SortedSetKey, error) 76 | 77 | // HashMap 78 | ReadZipListHashMap(expiry data.Expiry) (data.MapKey, error) 79 | ReadZipMapHashMap(expiry data.Expiry) (data.MapKey, error) 80 | ReadListHashMap(expiry data.Expiry) (data.MapKey, error) 81 | 82 | // List 83 | ReadZipList(expiry data.Expiry) (data.ListKey, error) 84 | ReadQuickList(expiry data.Expiry) (data.ListKey, error) 85 | ReadList(expiry data.Expiry) (data.ListKey, error) 86 | 87 | ReadIntSet(expiry data.Expiry) (data.IntegerSetKey, error) 88 | ReadStringValue(expiry data.Expiry) (data.StringKey, error) 89 | } 90 | 91 | // EntriesReader это интерфейс для чтения ZipList структур 92 | type EntriesReader interface { 93 | io.Reader 94 | 95 | // ReadByte безопасно читает один байт 96 | ReadByte() (byte, error) 97 | 98 | // ReadEntryLength читает размер элементов 99 | ReadEntryLength() (uint32, error) 100 | 101 | // ReadTail читает смещение последнего элемента 102 | ReadTail() (uint32, error) 103 | 104 | // ReadEntriesCount читает количество элементов 105 | ReadEntriesCount() (uint16, error) 106 | 107 | // ReadEntry читает элемент 108 | ReadEntry() ([]byte, error) 109 | } 110 | 111 | // IntSetReader это интерфейс для чтения бинарного дерева поиска целых чисел 112 | // Отличается от EntriesReader тем что все элементы это целые числа 113 | // одного размера: uint64, uint32, uint16 114 | type IntSetReader interface { 115 | // SafeRead безопасно читает N-байт 116 | SafeRead(n uint32) ([]byte, error) 117 | 118 | // ReadEntryLength читает размерность элементов (example: 2, 4, 8 bytes) 119 | ReadEntryLength() (uint32, error) 120 | 121 | // ReadEntriesCount читает количество элементов 122 | ReadEntriesCount() (uint32, error) 123 | 124 | // ReadEntry64 читает entry в формате uint64 125 | ReadEntry64() (uint64, error) 126 | 127 | // ReadEntry32 читает entry в формате uint32 128 | ReadEntry32() (uint32, error) 129 | 130 | // ReadEntry16 читает entry в формате uint16 131 | ReadEntry16() (uint16, error) 132 | } 133 | 134 | // ZipMapReader это интерфейс для чтения ZipMap структур 135 | type ZipMapReader interface { 136 | // SafeRead безопасно читает N-байт 137 | SafeRead(n uint32) ([]byte, error) 138 | 139 | // ReadCount читает количество элементов 140 | ReadCount() (uint8, error) 141 | 142 | // ReadLength читает длину строки 143 | ReadLength() (uint32, error) 144 | 145 | // ReadKey читает ключ 146 | ReadKey() ([]byte, error) 147 | 148 | // ReadValue читает значение ключа 149 | ReadValue() ([]byte, error) 150 | } 151 | -------------------------------------------------------------------------------- /rdb/intset.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | type intSetReader struct { 12 | *bufio.Reader 13 | } 14 | 15 | // NewIntSetReader возвращает новый IntSetReader 16 | func NewIntSetReader(r io.Reader) IntSetReader { 17 | return &intSetReader{ 18 | Reader: bufio.NewReaderSize(r, DefaultReaderSize), 19 | } 20 | } 21 | 22 | // NewIntSetStringReader возвращает новый IntSetReader 23 | func NewIntSetStringReader(st string) IntSetReader { 24 | r := bytes.NewBufferString(st) 25 | return &intSetReader{Reader: bufio.NewReaderSize(r, DefaultReaderSize)} 26 | } 27 | 28 | // SafeRead безопасно читает N байт 29 | func (r *intSetReader) SafeRead(n uint32) ([]byte, error) { 30 | result := make([]byte, n) 31 | _, err := io.ReadFull(r.Reader, result) 32 | return result, err 33 | } 34 | 35 | // ReadEntryLength читает размер элементов 36 | func (r *intSetReader) ReadEntryLength() (uint32, error) { 37 | bytesCount, err := r.SafeRead(4) 38 | if err != nil { 39 | return 0, err 40 | } 41 | length := binary.LittleEndian.Uint32(bytesCount) 42 | 43 | switch length { 44 | case 2, 4, 8: 45 | return length, nil 46 | } 47 | return 0, fmt.Errorf("unexpected intset encoding: %d", length) 48 | } 49 | 50 | // ReadEntriesCount читает количество элементов 51 | func (r *intSetReader) ReadEntriesCount() (uint32, error) { 52 | count, err := r.SafeRead(4) 53 | if err != nil { 54 | return 0, err 55 | } 56 | return binary.LittleEndian.Uint32(count), nil 57 | } 58 | 59 | // ReadEntry64 читает элемент длиной в 8 байт 60 | func (r *intSetReader) ReadEntry64() (uint64, error) { 61 | entry, err := r.SafeRead(8) 62 | if err != nil { 63 | return 0, err 64 | } 65 | return binary.LittleEndian.Uint64(entry), nil 66 | } 67 | 68 | // ReadEntry32 читает элемент длиной в 4 байта 69 | func (r *intSetReader) ReadEntry32() (uint32, error) { 70 | entry, err := r.SafeRead(4) 71 | if err != nil { 72 | return 0, err 73 | } 74 | return binary.LittleEndian.Uint32(entry), nil 75 | } 76 | 77 | // ReadEntry32 читает элемент длиной в 2 байта 78 | func (r *intSetReader) ReadEntry16() (uint16, error) { 79 | entry, err := r.SafeRead(2) 80 | if err != nil { 81 | return 0, err 82 | } 83 | return binary.LittleEndian.Uint16(entry), nil 84 | } 85 | -------------------------------------------------------------------------------- /rdb/lzf.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | // Taken from Golly: https://github.com/tav/golly/blob/master/lzf/lzf.go 4 | // Removed part that gets outputLength from data 5 | // nolint:gocyclo 6 | func lzfDecompress(input []byte, outputLength uint32) (output []byte) { 7 | inputLength := uint32(len(input)) 8 | 9 | var backref int64 10 | var ctrl, iidx, length, oidx uint32 11 | 12 | output = make([]byte, outputLength) 13 | iidx = 0 14 | 15 | for iidx < inputLength { 16 | // Get the control byte. 17 | ctrl = uint32(input[iidx]) 18 | iidx++ 19 | 20 | if ctrl < (1 << 5) { 21 | // The control byte indicates a literal reference. 22 | ctrl++ 23 | if oidx+ctrl > outputLength { 24 | return nil 25 | } 26 | 27 | // Safety check. 28 | if iidx+ctrl > inputLength { 29 | return nil 30 | } 31 | 32 | for { 33 | output[oidx] = input[iidx] 34 | iidx++ 35 | oidx++ 36 | ctrl-- 37 | if ctrl == 0 { 38 | break 39 | } 40 | } 41 | } else { 42 | // The control byte indicates a back reference. 43 | length = ctrl >> 5 44 | backref = int64(oidx - ((ctrl & 31) << 8) - 1) 45 | 46 | // Safety check. 47 | if iidx >= inputLength { 48 | return nil 49 | } 50 | 51 | // It's an extended back reference. Read the extended length before 52 | // reading the full back reference location. 53 | if length == 7 { 54 | length += uint32(input[iidx]) 55 | iidx++ 56 | // Safety check. 57 | if iidx >= inputLength { 58 | return nil 59 | } 60 | } 61 | 62 | // Put together the full back reference location. 63 | backref -= int64(input[iidx]) 64 | iidx++ 65 | 66 | if oidx+length+2 > outputLength { 67 | return nil 68 | } 69 | 70 | if backref < 0 { 71 | return nil 72 | } 73 | 74 | output[oidx] = output[backref] 75 | oidx++ 76 | backref++ 77 | output[oidx] = output[backref] 78 | oidx++ 79 | backref++ 80 | for { 81 | output[oidx] = output[backref] 82 | oidx++ 83 | backref++ 84 | length-- 85 | if length == 0 { 86 | break 87 | } 88 | } 89 | 90 | } 91 | } 92 | return output 93 | } 94 | -------------------------------------------------------------------------------- /rdb/reader.go: -------------------------------------------------------------------------------- 1 | package rdb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "math" 11 | "strconv" 12 | 13 | "github.com/avito-tech/smart-redis-replication/data" 14 | ) 15 | 16 | const ( 17 | // DBSelectorOpcode это индикатор номера базы данных 18 | DBSelectorOpcode = 0xFE 19 | 20 | // ResizeDBOpcode это индикатор изменения размеров базы (rdb version 7) 21 | ResizeDBOpcode = 0xFB 22 | 23 | // AuxFieldOpcode это индикатор дополнительного поля (rdb version 7) 24 | AuxFieldOpcode = 0xFA 25 | 26 | // ExpirySecondsOpcode это индикатор времени жизни ключа, 27 | // находится перед ключём к которому относится 28 | ExpirySecondsOpcode = 0xFD 29 | 30 | // ExpiryMillisecondsOpcode это индикатор времени жизни ключа, 31 | // находится перед ключём к которому относится 32 | ExpiryMillisecondsOpcode = 0xFC 33 | 34 | // EOFOpcode это индикатор конца файла, с версии 5 после него идёт 8 byte 35 | // контрольной суммы, 36 | // если эта опция на сервере отключена то 8 byte заполнены нулями 37 | EOFOpcode = 0xFF 38 | 39 | //StringValueOpcode это индикатор строки 40 | StringValueOpcode = 0x00 41 | 42 | // ListOpcode ... 43 | ListOpcode = 0x01 44 | 45 | // SetOpcode это индикатор 46 | SetOpcode = 0x02 47 | 48 | // SortedSetOpcode это индикатор SortedSet закодированного через List 49 | SortedSetOpcode = 0x03 50 | 51 | // ListHashMapOpcode это индикатор HashMap закодированного через List 52 | ListHashMapOpcode = 0x04 53 | 54 | // ZipMapHashMapOpcode это индикатор HashMap (собственный формат) 55 | ZipMapHashMapOpcode = 0x09 56 | 57 | // ZipListOpcode это индикатор сжатого неупорядоченного набора уникальных 58 | // значений 59 | ZipListOpcode = 0x0a 60 | 61 | // IntSetOpcode это индикатор двоичного дерева поиска целых чисел. 62 | // IntSet используется, когда все элементы набора являются целыми числами. 63 | // Номера в наборе всегда отсортированы 64 | IntSetOpcode = 0x0b 65 | 66 | // ZipListSortedSetOpcode это индикатор SortedSet закодированного через ZipList 67 | ZipListSortedSetOpcode = 0x0c 68 | 69 | // ZipListHashMapOpcode это индикатор HashMap закодированного черезе ZipList 70 | ZipListHashMapOpcode = 0x0d 71 | 72 | // QuickListOpcode это индикатор List закодированного через QuickList 73 | QuickListOpcode = 0x0e 74 | 75 | len6Bit = 0x0 76 | len14Bit = 0x1 77 | len32Bit = 0x2 78 | lenEnc = 0x3 79 | ) 80 | 81 | var ( 82 | // DefaultReaderSize это размер буфера чтения по умолчанию 83 | DefaultReaderSize = 16384 84 | 85 | // rdbSignature это обязательная часть файла []byte("REDIS") 86 | rdbSignature = []byte{0x52, 0x45, 0x44, 0x49, 0x53} 87 | 88 | // ErrValuesIsNotUsed эта ошибка означает что значение использовать не нужно 89 | ErrValuesIsNotUsed = errors.New("value is not used") 90 | ) 91 | 92 | // reader реализует интерфейс Reader 93 | type reader struct { 94 | *bufio.Reader 95 | } 96 | 97 | // NewReader возвращает новый Reader 98 | func NewReader(r io.Reader) Reader { 99 | return &reader{ 100 | Reader: bufio.NewReaderSize(r, DefaultReaderSize), 101 | } 102 | } 103 | 104 | // NewStringReader возвращает новый Reader 105 | func NewStringReader(st string) Reader { 106 | r := bytes.NewBufferString(st) 107 | return &reader{Reader: bufio.NewReaderSize(r, DefaultReaderSize)} 108 | } 109 | 110 | // SafeRead безопасно читает N байт 111 | func (r *reader) SafeRead(n uint32) ([]byte, error) { 112 | result := make([]byte, n) 113 | _, err := io.ReadFull(r.Reader, result) 114 | return result, err 115 | } 116 | 117 | // ReadOpcode читает код команды 118 | func (r *reader) ReadOpcode() (byte, error) { 119 | return r.Reader.ReadByte() 120 | } 121 | 122 | // ReadString читает строку RDB файла, поддерживается только несжатая версия 123 | // nolint:gocyclo 124 | func (r *reader) ReadString() (string, error) { 125 | length, encoding, err := r.ReadLength() 126 | if err != nil { 127 | return "", err 128 | } 129 | 130 | switch encoding { 131 | // length-prefixed string 132 | case -1: 133 | data, err := r.SafeRead(length) 134 | if err != nil { 135 | return "", err 136 | } 137 | return string(data), nil 138 | 139 | // integer as string 140 | case 0, 1, 2: 141 | data, err := r.SafeRead(1 << uint8(encoding)) 142 | if err != nil { 143 | return "", err 144 | } 145 | var num uint32 146 | 147 | if encoding == 0 { 148 | num = uint32(data[0]) 149 | } else if encoding == 1 { 150 | num = uint32(data[0]) | (uint32(data[1]) << 8) 151 | } else if encoding == 2 { 152 | num = uint32(data[0]) | (uint32(data[1]) << 8) | (uint32(data[2]) << 16) | (uint32(data[3]) << 24) //nolint:lll 153 | } 154 | return fmt.Sprintf("%d", num), nil 155 | 156 | // compressed string 157 | case 3: 158 | clength, _, err := r.ReadLength() 159 | if err != nil { 160 | return "", err 161 | } 162 | length, _, err := r.ReadLength() 163 | if err != nil { 164 | return "", err 165 | } 166 | data, err := r.SafeRead(clength) 167 | if err != nil { 168 | return "", err 169 | } 170 | result := string(lzfDecompress(data, length)) 171 | if len(result) != int(length) { 172 | return "", fmt.Errorf( 173 | "expected decompressed string length %d but actual %d", 174 | length, 175 | len(result), 176 | ) 177 | } 178 | return result, nil 179 | } 180 | return "", fmt.Errorf("unsupported string encoding") 181 | } 182 | 183 | // ReadLength читает префикс зашифрованной длины 184 | func (r *reader) ReadLength() (length uint32, encoding int8, err error) { 185 | prefix, err := r.ReadByte() 186 | if err != nil { 187 | return 0, 0, err 188 | } 189 | kind := (prefix & 0xC0) >> 6 190 | 191 | switch kind { 192 | case len6Bit: 193 | length = uint32(prefix & 0x3F) 194 | return length, -1, nil 195 | case len14Bit: 196 | data, err := r.ReadByte() 197 | if err != nil { 198 | return 0, 0, err 199 | } 200 | length = (uint32(prefix&0x3F) << 8) | uint32(data) 201 | return length, -1, nil 202 | case len32Bit: 203 | data, err := r.SafeRead(4) 204 | if err != nil { 205 | return 0, 0, err 206 | } 207 | length = binary.BigEndian.Uint32(data) 208 | return length, -1, nil 209 | case lenEnc: 210 | encoding = int8(prefix & 0x3F) 211 | return 0, encoding, nil 212 | } 213 | return 0, 0, fmt.Errorf("Undefined length") 214 | } 215 | 216 | // ReadUint8 читает один байт 217 | func (r *reader) ReadUint8() (uint8, error) { 218 | d, err := r.ReadByte() 219 | if err != nil { 220 | return 0, err 221 | } 222 | return d, nil 223 | } 224 | 225 | // ReadFloat64 читает значение с двойной точностью, 226 | // в том числе бесконечно положительное и бесконечно отрицательное число 227 | func (r *reader) ReadFloat64() (float64, error) { 228 | length, err := r.ReadUint8() 229 | if err != nil { 230 | return 0, err 231 | } 232 | switch length { 233 | case 253: 234 | return math.NaN(), nil 235 | case 254: 236 | return math.Inf(0), nil 237 | case 255: 238 | return math.Inf(-1), nil 239 | default: 240 | floatBytes, err := r.SafeRead(uint32(length)) 241 | if err != nil { 242 | return 0, err 243 | } 244 | f, err := strconv.ParseFloat(string(floatBytes), 64) 245 | if err != nil { 246 | return 0, err 247 | } 248 | return f, nil 249 | } 250 | } 251 | 252 | // Magic это сигнатура RDB файла 253 | type Magic struct { 254 | rdbVersion uint32 255 | } 256 | 257 | // NewMagic возвращает новый Magic 258 | func NewMagic(rdbVersion uint32) Magic { 259 | return Magic{rdbVersion: rdbVersion} 260 | } 261 | 262 | // GetRDBVersion возвращает номер rdb версии 263 | func (m Magic) GetRDBVersion() uint32 { 264 | return m.rdbVersion 265 | } 266 | 267 | // Bytes возвращает бинарное представление 268 | func (m Magic) Bytes() []byte { 269 | version := []byte(fmt.Sprintf("%04d", m.rdbVersion)) 270 | return append(rdbSignature, version...) 271 | } 272 | 273 | // ReadMagic читает Magic сигнатуру файла 274 | func (r *reader) ReadMagic() (Magic, error) { 275 | signature, err := r.SafeRead(5) 276 | if err != nil { 277 | return Magic{}, err 278 | } 279 | if !bytes.Equal(signature, rdbSignature) { 280 | return Magic{}, fmt.Errorf( 281 | "expected rdb signature %#v but actual %#v (%q)", 282 | rdbSignature, 283 | signature, 284 | string(signature), 285 | ) 286 | } 287 | version, err := r.SafeRead(4) 288 | if err != nil { 289 | return Magic{}, fmt.Errorf("error rdb version: %q", err) 290 | } 291 | return Magic{ 292 | rdbVersion: binary.LittleEndian.Uint32(version), 293 | }, nil 294 | } 295 | 296 | // AuxField это дополнительное поле относящееся к всему файлу 297 | type AuxField struct { 298 | key string 299 | value string 300 | } 301 | 302 | // NewAuxField возвращает новый AuxField 303 | func NewAuxField(key, value string) AuxField { 304 | return AuxField{ 305 | key: key, 306 | value: value, 307 | } 308 | } 309 | 310 | // GetKey возвращает название ключа 311 | func (a AuxField) GetKey() string { 312 | return a.key 313 | } 314 | 315 | // GetValue возвращает название ключа 316 | func (a AuxField) GetValue() string { 317 | return a.value 318 | } 319 | 320 | // Bytes возвращает бинарное представление 321 | // nolint:dupl 322 | func (a AuxField) Bytes() []byte { 323 | key := EncodeString(a.key) 324 | value := EncodeString(a.value) 325 | 326 | data := make([]byte, 0, len(key)+len(value)+1) 327 | 328 | buffer := bytes.NewBuffer(data) 329 | buffer.WriteByte(AuxFieldOpcode) 330 | buffer.Write(key) 331 | buffer.Write(value) 332 | 333 | return buffer.Bytes() 334 | } 335 | 336 | // ReadAuxField читает дополнительные поля относящиеся к файлу: 337 | // версия сервера, время создания и прочее 338 | func (r *reader) ReadAuxField() (AuxField, error) { 339 | key, err := r.ReadString() 340 | if err != nil { 341 | return AuxField{}, err 342 | } 343 | value, err := r.ReadString() 344 | if err != nil { 345 | return AuxField{}, err 346 | } 347 | return AuxField{ 348 | key: key, 349 | value: value, 350 | }, nil 351 | } 352 | 353 | // DBSelector это селектор номера базы данных, 354 | // после него идут данные предназначенные для базы данных с таким номером 355 | type DBSelector struct { 356 | dbNumber uint32 357 | } 358 | 359 | // NewDBSelector возвращает новый DBSelector 360 | func NewDBSelector(dbNumber uint32) DBSelector { 361 | return DBSelector{ 362 | dbNumber: dbNumber, 363 | } 364 | } 365 | 366 | // GetDBNumber возвращает номер базы данных 367 | func (db DBSelector) GetDBNumber() uint32 { 368 | return db.dbNumber 369 | } 370 | 371 | // Bytes возвращает бинарное представление 372 | func (db DBSelector) Bytes() []byte { 373 | number := EncodeLength(db.dbNumber) 374 | return append([]byte{DBSelectorOpcode}, number...) 375 | } 376 | 377 | // ReadDBSelector читает номер базы данных 378 | func (r *reader) ReadDBSelector() (DBSelector, error) { 379 | db, _, err := r.ReadLength() 380 | if err != nil { 381 | return DBSelector{}, err 382 | } 383 | return DBSelector{ 384 | dbNumber: db, 385 | }, nil 386 | } 387 | 388 | // ResizeDB это данные о размере базы данных 389 | type ResizeDB struct { 390 | hashTableSize uint32 391 | expiryHashTableSize uint32 392 | } 393 | 394 | // NewResizeDB возвращает новый ResizeDB 395 | func NewResizeDB(hashTableSize, expiryHashTableSize uint32) ResizeDB { 396 | return ResizeDB{ 397 | hashTableSize: hashTableSize, 398 | expiryHashTableSize: expiryHashTableSize, 399 | } 400 | } 401 | 402 | // GetHashTableSize возвращает размер HashTable 403 | func (r ResizeDB) GetHashTableSize() uint32 { 404 | return r.hashTableSize 405 | } 406 | 407 | // GetExpiryHashTableSize возвращает размер HashTable, 408 | // данные в котором устаревают 409 | func (r ResizeDB) GetExpiryHashTableSize() uint32 { 410 | return r.expiryHashTableSize 411 | } 412 | 413 | // Bytes возвращает бинарное представление 414 | // nolint:dupl 415 | func (r ResizeDB) Bytes() []byte { 416 | hashTableSize := EncodeLength(r.hashTableSize) 417 | expiryHashTableSize := EncodeLength(r.expiryHashTableSize) 418 | 419 | data := make([]byte, 0, len(hashTableSize)+len(expiryHashTableSize)+1) 420 | 421 | buffer := bytes.NewBuffer(data) 422 | buffer.WriteByte(ResizeDBOpcode) 423 | buffer.Write(hashTableSize) 424 | buffer.Write(expiryHashTableSize) 425 | 426 | return buffer.Bytes() 427 | } 428 | 429 | // parseResizeDB читает размер базы данных 430 | func (r *reader) ReadResizeDB() (ResizeDB, error) { 431 | hashTableSize, _, err := r.ReadLength() 432 | if err != nil { 433 | return ResizeDB{}, err 434 | } 435 | expiryHashTableSize, _, err := r.ReadLength() 436 | if err != nil { 437 | return ResizeDB{}, err 438 | } 439 | 440 | return ResizeDB{ 441 | hashTableSize: hashTableSize, 442 | expiryHashTableSize: expiryHashTableSize, 443 | }, nil 444 | } 445 | 446 | // EOF означает конец файла, доступен с RDB версии 5 447 | type EOF struct { 448 | checksum uint64 449 | } 450 | 451 | // NewEOF возвращает новый EOF 452 | func NewEOF() EOF { 453 | return EOF{} 454 | } 455 | 456 | // Bytes возвращает бинарное представление 457 | func (e EOF) Bytes() []byte { 458 | data := make([]byte, 9) 459 | data[0] = EOFOpcode 460 | binary.LittleEndian.PutUint64(data[1:], e.checksum) 461 | return data 462 | } 463 | 464 | // ReadEOF читает EOF 465 | // EOF доступен с RDB версии 5 466 | func (r *reader) ReadEOF() (EOF, error) { 467 | checksum, err := r.SafeRead(8) 468 | if err != nil { 469 | return EOF{}, fmt.Errorf("error checksum: %q", err) 470 | } 471 | return EOF{ 472 | checksum: binary.LittleEndian.Uint64(checksum), 473 | }, io.EOF 474 | } 475 | 476 | // ReadExpiry читает время жизни ключа 477 | func (r *reader) ReadExpiry(opcode byte) (data.Expiry, error) { 478 | var expiry uint64 479 | 480 | switch opcode { 481 | case ExpirySecondsOpcode: 482 | body, err := r.SafeRead(4) 483 | if err != nil { 484 | return data.NewExpiry(0), err 485 | } 486 | expiry = uint64(binary.LittleEndian.Uint32(body)) * 1000 487 | case ExpiryMillisecondsOpcode: 488 | body, err := r.SafeRead(8) 489 | if err != nil { 490 | return data.NewExpiry(0), err 491 | } 492 | expiry = binary.LittleEndian.Uint64(body) 493 | default: 494 | return data.NewExpiry(0), fmt.Errorf("unexpected expiry opcode %#v", opcode) 495 | } 496 | 497 | return data.NewExpiry(expiry), nil 498 | } 499 | 500 | // ReadSet читает неупорядоченный набор значений 501 | // nolint:dupl 502 | func (r *reader) ReadSet(expiry data.Expiry) (data.SetKey, error) { 503 | keyName, err := r.ReadString() 504 | if err != nil { 505 | return nil, err 506 | } 507 | 508 | key := data.NewSet(keyName) 509 | err = key.SetExpiry(expiry) 510 | if err != nil { 511 | return nil, err 512 | } 513 | err = r.DecodeSetList(key) 514 | if err != nil { 515 | return nil, err 516 | } 517 | return key, nil 518 | } 519 | 520 | // DecodeSetList декодирует Set закодированный через List 521 | // nolint:dupl 522 | func (r *reader) DecodeSetList(key data.SetKey) error { 523 | count, _, err := r.ReadLength() 524 | if err != nil { 525 | return err 526 | } 527 | for count > 0 { 528 | count-- 529 | value, err := r.ReadString() 530 | if err != nil { 531 | return err 532 | } 533 | err = key.Set(value) 534 | if err != nil { 535 | return err 536 | } 537 | } 538 | return nil 539 | } 540 | 541 | // ReadZipListSortedSet читает SortedSet закодированный с помощью ZipList 542 | // nolint:dupl 543 | func (r *reader) ReadZipListSortedSet( 544 | expiry data.Expiry, 545 | ) ( 546 | data.SortedSetKey, 547 | error, 548 | ) { 549 | keyName, err := r.ReadString() 550 | if err != nil { 551 | return nil, err 552 | } 553 | 554 | body, err := r.ReadString() 555 | if err != nil { 556 | return nil, err 557 | } 558 | 559 | key := data.NewSortedSet(keyName) 560 | err = key.SetExpiry(expiry) 561 | if err != nil { 562 | return nil, err 563 | } 564 | 565 | err = r.DecodeSortedSetZipList(key, body) 566 | if err != nil { 567 | return nil, err 568 | } 569 | return key, nil 570 | } 571 | 572 | // DecodeSortedSetZipList декодирует значения SortedSet закодированные через 573 | // ZipList 574 | // nolint:dupl 575 | func (r *reader) DecodeSortedSetZipList( 576 | key data.SortedSetKey, 577 | body string, 578 | ) error { 579 | dec := NewEntriesStringReader(body) 580 | 581 | _, err := dec.ReadEntryLength() 582 | if err != nil { 583 | return err 584 | } 585 | 586 | _, err = dec.ReadTail() 587 | if err != nil { 588 | return err 589 | } 590 | count, err := dec.ReadEntriesCount() 591 | if err != nil { 592 | return err 593 | } 594 | if count%2 == 1 { 595 | return fmt.Errorf( 596 | `expected "even" entries count bud actual "odd", count %d`, 597 | count, 598 | ) 599 | } 600 | 601 | for count > 1 { 602 | count -= 2 603 | value, err := dec.ReadEntry() 604 | if err != nil { 605 | return err 606 | } 607 | 608 | scoreBytes, err := dec.ReadEntry() 609 | if err != nil { 610 | return err 611 | } 612 | score, err := strconv.ParseFloat(string(scoreBytes), 64) 613 | if err != nil { 614 | return err 615 | } 616 | err = key.Set(score, string(value)) 617 | if err != nil { 618 | return err 619 | } 620 | } 621 | return nil 622 | } 623 | 624 | // ReadSortedSet читает SortedSet закодированный с помощью List 625 | // nolint:dupl 626 | func (r *reader) ReadSortedSet(expiry data.Expiry) (data.SortedSetKey, error) { 627 | keyName, err := r.ReadString() 628 | if err != nil { 629 | return nil, err 630 | } 631 | 632 | key := data.NewSortedSet(keyName) 633 | err = key.SetExpiry(expiry) 634 | if err != nil { 635 | return nil, err 636 | } 637 | 638 | err = r.DecodeSortedSetList(key) 639 | if err != nil { 640 | return nil, err 641 | } 642 | return key, nil 643 | } 644 | 645 | // DecodeSortedSetList декодирует значения SortedSet закодированные через List 646 | // nolint:dupl 647 | func (r *reader) DecodeSortedSetList(key data.SortedSetKey) error { 648 | count, _, err := r.ReadLength() 649 | if err != nil { 650 | return err 651 | } 652 | for count > 0 { 653 | count-- 654 | 655 | value, err := r.ReadString() 656 | if err != nil { 657 | return err 658 | } 659 | score, err := r.ReadFloat64() 660 | if err != nil { 661 | return err 662 | } 663 | err = key.Set(score, value) 664 | if err != nil { 665 | return err 666 | } 667 | } 668 | return nil 669 | } 670 | 671 | // ZipMapReader это Reader для чтения ZipMap формата 672 | type zipMapReader struct { 673 | *bufio.Reader 674 | } 675 | 676 | // NewZipMapReader возвращает новый ZipMapReader 677 | func NewZipMapReader(data string) ZipMapReader { 678 | return &zipMapReader{ 679 | Reader: bufio.NewReaderSize(bytes.NewBufferString(data), DefaultReaderSize), 680 | } 681 | } 682 | 683 | // SafeRead безопасно читает N байт 684 | func (r *zipMapReader) SafeRead(n uint32) ([]byte, error) { 685 | result := make([]byte, n) 686 | _, err := io.ReadFull(r.Reader, result) 687 | return result, err 688 | } 689 | 690 | // ReadCount читает количество элементов 691 | func (r *zipMapReader) ReadCount() (uint8, error) { 692 | count, err := r.ReadByte() 693 | if err != nil { 694 | return 0, err 695 | } 696 | if count < 254 { 697 | return count, nil 698 | } 699 | return 0, ErrValuesIsNotUsed 700 | } 701 | 702 | // ReadLength читает длину строки 703 | func (r *zipMapReader) ReadLength() (uint32, error) { 704 | lenByte, err := r.ReadByte() 705 | if err != nil { 706 | return 0, err 707 | } 708 | if lenByte < 253 { 709 | return uint32(lenByte), nil 710 | } else if lenByte == 253 { 711 | data, err := r.SafeRead(4) 712 | if err != nil { 713 | return 0, fmt.Errorf("error read length: %q", err) 714 | } 715 | return binary.LittleEndian.Uint32(data), nil 716 | } 717 | return 0, fmt.Errorf("unexpected length byte %#v", lenByte) 718 | } 719 | 720 | // ReadKey читает название ключа в zipMap hashMap 721 | func (r *zipMapReader) ReadKey() ([]byte, error) { 722 | length, err := r.ReadLength() 723 | if err != nil { 724 | return nil, err 725 | } 726 | data, err := r.SafeRead(length) 727 | if err != nil { 728 | return nil, fmt.Errorf("error read key: %q", err) 729 | } 730 | return data, nil 731 | } 732 | 733 | // ReadValue читает значение ключа в zipMap hashMap 734 | func (r *zipMapReader) ReadValue() ([]byte, error) { 735 | length, err := r.ReadLength() 736 | if err != nil { 737 | return nil, err 738 | } 739 | free, err := r.ReadByte() 740 | if err != nil { 741 | return nil, err 742 | } 743 | data, err := r.SafeRead(length) 744 | if err != nil { 745 | return nil, err 746 | } 747 | if free > 0 { 748 | if int(free) > len(data) { 749 | return nil, fmt.Errorf( 750 | "expected free < length but actual free:%d and length:%d", 751 | uint32(free), 752 | len(data), 753 | ) 754 | } 755 | return data[0 : len(data)-int(free)], nil 756 | } 757 | return data, nil 758 | } 759 | 760 | // ReadZipListHashMap читает HashMap закодированный с помощью ZipList 761 | // nolint:dupl 762 | func (r *reader) ReadZipListHashMap(expiry data.Expiry) (data.MapKey, error) { 763 | keyName, err := r.ReadString() 764 | if err != nil { 765 | return nil, err 766 | } 767 | 768 | body, err := r.ReadString() 769 | if err != nil { 770 | return nil, err 771 | } 772 | 773 | key := data.NewMap(keyName) 774 | err = key.SetExpiry(expiry) 775 | if err != nil { 776 | return nil, err 777 | } 778 | err = r.DecodeZipListHashMap(key, body) 779 | if err != nil { 780 | return nil, err 781 | } 782 | return key, nil 783 | } 784 | 785 | // DecodeZipList декодирует ZipList реализацию 786 | // nolint:dupl 787 | func (r *reader) DecodeZipListHashMap(key data.MapKey, body string) error { 788 | dec := NewEntriesStringReader(body) 789 | 790 | _, err := dec.ReadEntryLength() 791 | if err != nil { 792 | return err 793 | } 794 | 795 | _, err = dec.ReadTail() 796 | if err != nil { 797 | return err 798 | } 799 | count, err := dec.ReadEntriesCount() 800 | if err != nil { 801 | return err 802 | } 803 | if count%2 == 1 { 804 | return fmt.Errorf( 805 | `expected "even" entries count bud actual "odd", count %d`, 806 | count, 807 | ) 808 | } 809 | 810 | for count > 1 { 811 | count -= 2 812 | 813 | keyName, err := dec.ReadEntry() 814 | if err != nil { 815 | return err 816 | } 817 | 818 | keyValue, err := dec.ReadEntry() 819 | if err != nil { 820 | return err 821 | } 822 | 823 | err = key.Set(string(keyName), string(keyValue)) 824 | if err != nil { 825 | return err 826 | } 827 | } 828 | return nil 829 | } 830 | 831 | // ReadHashMap читает HashMap закодированный с помощью List 832 | // nolint:dupl 833 | func (r *reader) ReadListHashMap(expiry data.Expiry) (data.MapKey, error) { 834 | keyName, err := r.ReadString() 835 | if err != nil { 836 | return nil, err 837 | } 838 | 839 | key := data.NewMap(keyName) 840 | err = key.SetExpiry(expiry) 841 | if err != nil { 842 | return nil, err 843 | } 844 | err = r.DecodeHashMapList(key) 845 | if err != nil { 846 | return nil, err 847 | } 848 | return key, nil 849 | } 850 | 851 | // DecodeList декодирует List реализацию 852 | // nolint:dupl 853 | func (r *reader) DecodeHashMapList(key data.MapKey) error { 854 | count, _, err := r.ReadLength() 855 | if err != nil { 856 | return err 857 | } 858 | for count > 0 { 859 | count-- 860 | 861 | keyName, err := r.ReadString() 862 | if err != nil { 863 | return err 864 | } 865 | keyValue, err := r.ReadString() 866 | if err != nil { 867 | return err 868 | } 869 | err = key.Set(keyName, keyValue) 870 | if err != nil { 871 | return err 872 | } 873 | } 874 | return nil 875 | } 876 | 877 | // ReadZipMapHashMap читает HashMap закодированный в строку 878 | // nolint:dupl 879 | func (r *reader) ReadZipMapHashMap(expiry data.Expiry) (data.MapKey, error) { 880 | keyName, err := r.ReadString() 881 | if err != nil { 882 | return nil, err 883 | } 884 | body, err := r.ReadString() 885 | if err != nil { 886 | return nil, err 887 | } 888 | 889 | key := data.NewMap(keyName) 890 | err = key.SetExpiry(expiry) 891 | if err != nil { 892 | return nil, err 893 | } 894 | err = r.DecodeZipMapHashMap(key, body) 895 | if err != nil { 896 | return nil, err 897 | } 898 | return key, nil 899 | } 900 | 901 | // DecodeZipMap декодирует ZipMap реалиазацию 902 | // Структура строки: 903 | // 904 | // "foo" "bar" 905 | // "hello" "world" 906 | // 907 | // zmlen - 1 байт, который содержит размер zip-карты 908 | // - если значение больше или равно 254 то 909 | // такое значение не используется 910 | // (в этом случае длина вычисляется чтением всей карты) 911 | // len - длина строки, отличается от ReadLength() 912 | // free - 1 байт, хранит количество свободных байт в конце строки 913 | // Zmend - 1 байт, всегда 255, указывает на конец файла 914 | // nolint:dupl,gocyclo 915 | func (r *reader) DecodeZipMapHashMap(key data.MapKey, body string) error { 916 | dec := NewZipMapReader(body) 917 | 918 | count, err := dec.ReadCount() 919 | if err == ErrValuesIsNotUsed { 920 | for { 921 | keyName, err := dec.ReadKey() 922 | if err == io.EOF { 923 | return nil 924 | } 925 | if err != nil { 926 | return err 927 | } 928 | keyValue, err := dec.ReadValue() 929 | if err != nil { 930 | return err 931 | } 932 | err = key.Set(string(keyName), string(keyValue)) 933 | if err != nil { 934 | return err 935 | } 936 | } 937 | } 938 | for count > 0 { 939 | count-- 940 | keyName, err := dec.ReadKey() 941 | if err != nil { 942 | return err 943 | } 944 | keyValue, err := dec.ReadValue() 945 | if err != nil { 946 | return err 947 | } 948 | err = key.Set(string(keyName), string(keyValue)) 949 | if err != nil { 950 | return err 951 | } 952 | } 953 | return nil 954 | } 955 | 956 | // ReadZipList читает List закодированный как ZipList 957 | // nolint:dupl 958 | func (r *reader) ReadZipList(expiry data.Expiry) (data.ListKey, error) { 959 | keyName, err := r.ReadString() 960 | if err != nil { 961 | return nil, err 962 | } 963 | 964 | body, err := r.ReadString() 965 | if err != nil { 966 | return nil, err 967 | } 968 | 969 | key := data.NewList(keyName) 970 | err = key.SetExpiry(expiry) 971 | if err != nil { 972 | return nil, err 973 | } 974 | err = r.DecodeZipList(key, body) 975 | if err != nil { 976 | return nil, err 977 | } 978 | return key, nil 979 | } 980 | 981 | // DecodeZipList декодирует ZipList реализацию 982 | // nolint:dupl 983 | func (r *reader) DecodeZipList(key data.ListKey, body string) error { 984 | dec := NewEntriesStringReader(body) 985 | 986 | _, err := dec.ReadEntryLength() 987 | if err != nil { 988 | return err 989 | } 990 | 991 | _, err = dec.ReadTail() 992 | if err != nil { 993 | return err 994 | } 995 | count, err := dec.ReadEntriesCount() 996 | if err != nil { 997 | return err 998 | } 999 | 1000 | for count > 0 { 1001 | count-- 1002 | value, err := dec.ReadEntry() 1003 | if err != nil { 1004 | return err 1005 | } 1006 | 1007 | err = key.Rpush(string(value)) 1008 | if err != nil { 1009 | return err 1010 | } 1011 | } 1012 | return nil 1013 | } 1014 | 1015 | // ReadQuickList читает List закодированный как несколько ZipList, 1016 | // объединяя их в один List 1017 | // nolint:dupl 1018 | func (r *reader) ReadQuickList(expiry data.Expiry) (data.ListKey, error) { 1019 | keyName, err := r.ReadString() 1020 | if err != nil { 1021 | return nil, err 1022 | } 1023 | key := data.NewList(keyName) 1024 | err = key.SetExpiry(expiry) 1025 | if err != nil { 1026 | return nil, err 1027 | } 1028 | err = r.DecodeQuickList(key) 1029 | if err != nil { 1030 | return nil, err 1031 | } 1032 | return key, nil 1033 | } 1034 | 1035 | // DecodeQuickList декодирует QuickList реализацию, 1036 | // QuickList это список состоящий из ZipList 1037 | // nolint:dupl 1038 | func (r *reader) DecodeQuickList(key data.ListKey) error { 1039 | count, _, err := r.ReadLength() 1040 | if err != nil { 1041 | return err 1042 | } 1043 | 1044 | for count > 0 { 1045 | count-- 1046 | body, err := r.ReadString() 1047 | if err != nil { 1048 | return err 1049 | } 1050 | err = r.DecodeZipList(key, body) 1051 | if err != nil { 1052 | return err 1053 | } 1054 | } 1055 | return nil 1056 | } 1057 | 1058 | // ReadList читает List закодированный без сжатия 1059 | // nolint:dupl 1060 | func (r *reader) ReadList(expiry data.Expiry) (data.ListKey, error) { 1061 | keyName, err := r.ReadString() 1062 | if err != nil { 1063 | return nil, err 1064 | } 1065 | count, _, err := r.ReadLength() 1066 | if err != nil { 1067 | return nil, err 1068 | } 1069 | 1070 | key := data.NewList(keyName) 1071 | err = key.SetExpiry(expiry) 1072 | if err != nil { 1073 | return nil, err 1074 | } 1075 | for count > 0 { 1076 | count-- 1077 | value, err := r.ReadString() 1078 | if err != nil { 1079 | return nil, err 1080 | } 1081 | err = key.Rpush(value) 1082 | if err != nil { 1083 | return nil, err 1084 | } 1085 | } 1086 | return key, nil 1087 | } 1088 | 1089 | // ReadIntSet читает InegerSet 1090 | // nolint:dupl 1091 | func (r *reader) ReadIntSet(expiry data.Expiry) (data.IntegerSetKey, error) { 1092 | keyName, err := r.ReadString() 1093 | if err != nil { 1094 | return nil, err 1095 | } 1096 | 1097 | body, err := r.ReadString() 1098 | if err != nil { 1099 | return nil, err 1100 | } 1101 | 1102 | key := data.NewIntegerSet(keyName) 1103 | err = key.SetExpiry(expiry) 1104 | if err != nil { 1105 | return nil, err 1106 | } 1107 | err = r.DecodeIntegerSet(key, body) 1108 | if err != nil { 1109 | return nil, err 1110 | } 1111 | return key, nil 1112 | } 1113 | 1114 | // Decode декодирует IntSet из строки 1115 | // Структура строки: 1116 | // encoding - тип чисел, 2, 4, 8 байтовые (uint8, uint32, uint64) 1117 | // length-of-contents - количество элементов 1118 | // contents - перечень элементов кратные encoding 1119 | // nolint:gocyclo 1120 | func (r *reader) DecodeIntegerSet(key data.IntegerSetKey, body string) error { 1121 | dec := NewIntSetStringReader(body) 1122 | length, err := dec.ReadEntryLength() 1123 | if err != nil { 1124 | return err 1125 | } 1126 | count, err := dec.ReadEntriesCount() 1127 | if err != nil { 1128 | return err 1129 | } 1130 | switch length { 1131 | case 2: 1132 | for count > 0 { 1133 | count-- 1134 | value, err := dec.ReadEntry16() 1135 | if err != nil { 1136 | return err 1137 | } 1138 | err = key.Set(uint64(value)) 1139 | if err != nil { 1140 | return err 1141 | } 1142 | } 1143 | case 4: 1144 | for count > 0 { 1145 | count-- 1146 | value, err := dec.ReadEntry32() 1147 | if err != nil { 1148 | return err 1149 | } 1150 | err = key.Set(uint64(value)) 1151 | if err != nil { 1152 | return err 1153 | } 1154 | } 1155 | case 8: 1156 | for count > 0 { 1157 | count-- 1158 | value, err := dec.ReadEntry64() 1159 | if err != nil { 1160 | return err 1161 | } 1162 | err = key.Set(value) 1163 | if err != nil { 1164 | return err 1165 | } 1166 | } 1167 | default: 1168 | return fmt.Errorf("expected entry length 2, 4, 8 bytes but actual %d", length) 1169 | } 1170 | return nil 1171 | } 1172 | 1173 | // ReadStringValue читает ключ-значение 1174 | // nolint:dupl 1175 | func (r *reader) ReadStringValue(expiry data.Expiry) (data.StringKey, error) { 1176 | keyName, err := r.ReadString() 1177 | if err != nil { 1178 | return nil, err 1179 | } 1180 | 1181 | value, err := r.ReadString() 1182 | if err != nil { 1183 | return nil, err 1184 | } 1185 | 1186 | key := data.NewString(keyName, value) 1187 | err = key.SetExpiry(expiry) 1188 | if err != nil { 1189 | return nil, err 1190 | } 1191 | return key, nil 1192 | } 1193 | -------------------------------------------------------------------------------- /replica/config.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | // Config это минимальный набор данных для запуска репликации 4 | type Config struct { 5 | // ReadRDB означает будут ли обрабатываться данные из RDB или их нужно пропустить 6 | ReadRDB bool 7 | 8 | // CacheRDB означает требуется ли кешировать RDB 9 | CacheRDB bool 10 | 11 | // Debug включает запись отладки 12 | Debug bool 13 | 14 | // СacheRDBFile это адрес файла для хранения кеша RDB 15 | CacheRDBFile string 16 | 17 | // BacklogSize это размер Backlog 18 | BacklogSize int 19 | 20 | // DebugDumpDir это адрес каталога в который сохраняются дампы 21 | DebugDumpDir string 22 | } 23 | -------------------------------------------------------------------------------- /replica/decoder.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "os" 8 | 9 | "github.com/avito-tech/smart-redis-replication/backlog" 10 | "github.com/avito-tech/smart-redis-replication/command" 11 | "github.com/avito-tech/smart-redis-replication/data" 12 | "github.com/avito-tech/smart-redis-replication/rdb" 13 | "github.com/avito-tech/smart-redis-replication/status" 14 | ) 15 | 16 | type decoder struct { 17 | ctx context.Context 18 | cancel context.CancelFunc 19 | err error 20 | rdb rdb.Decoder 21 | file *os.File 22 | backlog *backlog.Backlog 23 | consumer Consumer 24 | config Config 25 | } 26 | 27 | // NewDecoder возвращает новый Decoder 28 | func NewDecoder( 29 | backlog *backlog.Backlog, 30 | config Config, 31 | ) ( 32 | Decoder, 33 | error, 34 | ) { 35 | if backlog == nil { 36 | return nil, errors.New("expected backlog") 37 | } 38 | d := &decoder{ 39 | backlog: backlog, 40 | config: config, 41 | } 42 | d.ctx, d.cancel = context.WithCancel(context.Background()) 43 | return d, nil 44 | } 45 | 46 | // Backlog возвращает журнал отставания репликации 47 | func (d *decoder) Backlog() *backlog.Backlog { 48 | return d.backlog 49 | } 50 | 51 | // SetRDB устанавливает источник RDB 52 | func (d *decoder) SetRDB(r io.Reader) error { 53 | if r == nil { 54 | return errors.New("expected io.Reader but actual nil") 55 | } 56 | d.rdb = rdb.NewDecoder(r) 57 | return nil 58 | } 59 | 60 | // SetFileRDB открывает файл в качестве источника RDB 61 | func (d *decoder) SetFileRDB(filename string) error { 62 | file, err := os.Open(filename) 63 | if err != nil { 64 | return nil 65 | } 66 | d.file = file 67 | return d.SetRDB(file) 68 | } 69 | 70 | // SetRDBDecoder устанавливает RDB decoder 71 | func (d *decoder) SetRDBDecoder(dec rdb.Decoder) error { 72 | if dec == nil { 73 | return errors.New("expected rdb.Decoder but actual nil") 74 | } 75 | d.rdb = dec 76 | return nil 77 | } 78 | 79 | // Done возвращает канал для ожидания завершения декодера 80 | func (d *decoder) Done() <-chan struct{} { 81 | return d.ctx.Done() 82 | } 83 | 84 | // Status возвращает true если декодирование выполняется и false если нет 85 | func (d *decoder) Status() bool { 86 | select { 87 | case <-d.ctx.Done(): 88 | return false 89 | default: 90 | return true 91 | } 92 | } 93 | 94 | // Err возвращает ошибку декодирования 95 | func (d *decoder) Err() error { 96 | return d.err 97 | } 98 | 99 | // Cancel останавливает декодирование 100 | func (d *decoder) Cancel(err *error) { 101 | d.err = *err 102 | if d.consumer != nil { 103 | d.consumer.Cancel(err) 104 | } 105 | d.cancel() 106 | if d.consumer != nil { 107 | d.consumer.ReplicaStatus(status.StopDecoder) // nolint:errcheck 108 | } 109 | } 110 | 111 | // Do запускает декодирование RDB и Backlog 112 | func (d *decoder) Decode(consumer Consumer) (err error) { 113 | defer d.Cancel(&err) 114 | if consumer == nil { 115 | return errors.New("expected consumer but actual nil") 116 | } 117 | d.consumer = consumer 118 | if d.rdb == nil { 119 | return errors.New("empty rdb.Decoder") 120 | } 121 | if d.backlog == nil { 122 | return errors.New("empty backlog") 123 | } 124 | 125 | if d.config.ReadRDB { 126 | consumer.ReplicaStatus(status.StartReadRDB) // nolint:errcheck 127 | err = d.decodeRDB(consumer) 128 | consumer.ReplicaStatus(status.StopReadRDB) // nolint:errcheck 129 | if err != nil { 130 | return err 131 | } 132 | } else { 133 | consumer.ReplicaStatus(status.SkipReadRDB) // nolint:errcheck 134 | } 135 | consumer.ReplicaStatus(status.StartReadBacklog) // nolint:errcheck 136 | err = d.decodeBacklog(consumer) 137 | return err 138 | } 139 | 140 | // decodeRDB декодирует RDB 141 | func (d *decoder) decodeRDB(consumer Consumer) error { 142 | if d.file != nil { 143 | defer d.file.Close() // nolint:errcheck 144 | } 145 | if consumer == nil { 146 | return errors.New("empty consumer") 147 | } 148 | // поидеи сюда тоже надо прокинуть контекст 149 | return d.rdb.DecodeKeys(consumer) 150 | } 151 | 152 | // decodeBacklog декодирует Backlog 153 | // nolint:gocyclo 154 | func (d *decoder) decodeBacklog(consumer Consumer) error { 155 | if d.rdb == nil { 156 | return errors.New("empty rdb.Decoder") 157 | } 158 | if d.backlog == nil { 159 | return errors.New("empty backlog") 160 | } 161 | if consumer == nil { 162 | return errors.New("empty consumer") 163 | } 164 | var db int 165 | var key data.Key 166 | var err error 167 | for { 168 | if !d.Status() { 169 | return d.Err() 170 | } 171 | cmd := d.backlog.Get() 172 | switch cmd.Type() { 173 | case command.Select: 174 | db, err = cmd.ConvertToSelectDB() 175 | case command.Zadd: 176 | key, err = cmd.ConvertToSortedSetKey(db) 177 | if err != nil { 178 | return err 179 | } 180 | err = consumer.Key(key) 181 | case command.Sadd: 182 | key, err = cmd.ConvertToSetKey(db) 183 | if err != nil { 184 | return err 185 | } 186 | err = consumer.Key(key) 187 | default: 188 | err = consumer.Command(cmd) 189 | } 190 | if err != nil { 191 | return err 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /replica/interface.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/avito-tech/smart-redis-replication/backlog" 7 | "github.com/avito-tech/smart-redis-replication/command" 8 | "github.com/avito-tech/smart-redis-replication/data" 9 | "github.com/avito-tech/smart-redis-replication/rdb" 10 | "github.com/avito-tech/smart-redis-replication/status" 11 | ) 12 | 13 | // Consumer это получатель информации из репликации 14 | type Consumer interface { 15 | // Key принимает ключи с данными 16 | Key(data.Key) error 17 | 18 | // Command принимает команды управления 19 | Command(command command.Command) error 20 | 21 | // CheckCommand возвращает true если команда интересна получателю 22 | CheckCommand(command command.Command) bool 23 | 24 | // Status принимает статус репликации 25 | ReplicaStatus(status.Status) error 26 | 27 | // Cancel останавливает обработку данных 28 | Cancel(err *error) 29 | } 30 | 31 | // Replica это интерфейс репликации 32 | type Replica interface { 33 | // Done возвращает канал для ожидания завершения репликации 34 | Done() <-chan struct{} 35 | 36 | // Status возвращает true если репликация выполняется и false если нет 37 | Status() bool 38 | 39 | // Err возвращает ошибку репликации 40 | Err() error 41 | 42 | // Возвращает журнал отставания репликации 43 | Backlog() *backlog.Backlog 44 | 45 | // Do запускает процесс репликации 46 | Do(consumer Consumer) error 47 | } 48 | 49 | // Decoder это интерфейс декодера который наполняет Consumer 50 | type Decoder interface { 51 | SetRDB(r io.Reader) error 52 | 53 | SetFileRDB(filename string) error 54 | 55 | SetRDBDecoder(rdb.Decoder) error 56 | 57 | // Возвращает журнал отставания репликации 58 | Backlog() *backlog.Backlog 59 | 60 | // Done возвращает канал для ожидания завершения декодера 61 | Done() <-chan struct{} 62 | 63 | // Status возвращает true если декодирование выполняется и false если нет 64 | Status() bool 65 | 66 | // Err возвращает ошибку выполнения 67 | Err() error 68 | 69 | // Decode запускает процес наполнения Consumer данными 70 | Decode(consumer Consumer) error 71 | 72 | // Cancel останавливает обработку данных 73 | Cancel(err *error) 74 | } 75 | -------------------------------------------------------------------------------- /replica/replica.go: -------------------------------------------------------------------------------- 1 | package replica 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path" 10 | 11 | "github.com/avito-tech/smart-redis-replication/backlog" 12 | "github.com/avito-tech/smart-redis-replication/command" 13 | "github.com/avito-tech/smart-redis-replication/resp" 14 | "github.com/avito-tech/smart-redis-replication/status" 15 | ) 16 | 17 | // replica это реализация Replica 18 | type replica struct { 19 | ctx context.Context 20 | cancel context.CancelFunc 21 | err error 22 | 23 | conn resp.Conn 24 | 25 | config Config 26 | status struct { 27 | // startDecodeRDB означает что декодирование RDB уже началось 28 | startDecodeRDB bool 29 | } 30 | 31 | // decoder это объект в котором происходит чтение rdb и backlog 32 | decoder Decoder 33 | 34 | // backlog это журнал отставания репликации 35 | backlog *backlog.Backlog 36 | 37 | // consumer это получатель информации из репликации 38 | consumer Consumer 39 | } 40 | 41 | // NewReplica возвращает новую Replica 42 | func NewReplica( 43 | r io.ReadWriteCloser, 44 | config Config, 45 | ) Replica { 46 | replica := &replica{ 47 | conn: resp.New(r), 48 | config: config, 49 | backlog: backlog.New(config.BacklogSize), 50 | } 51 | if config.Debug { 52 | _ = replica.conn.EnableDebug(config.DebugDumpDir) 53 | } 54 | replica.ctx, replica.cancel = context.WithCancel(context.Background()) 55 | return replica 56 | } 57 | 58 | // Backlog возвращает журнал отставания репликации 59 | func (r *replica) Backlog() *backlog.Backlog { 60 | return r.backlog 61 | } 62 | 63 | // SetCacheRDB устанавливает статус кеширования RDB на диск 64 | // status = true - кешировать 65 | func (r *replica) SetCacheRDB(status bool) { 66 | r.config.CacheRDB = status 67 | } 68 | 69 | // SendStatus отправляет статус репликации 70 | func (r *replica) SendStatus(status status.Status) { 71 | if r.consumer != nil { 72 | r.consumer.ReplicaStatus(status) // nolint:errcheck 73 | } 74 | } 75 | 76 | // sendSync отправляет сообщение SYNC необходимое для запуска репликации 77 | func (r *replica) sendSync() error { 78 | return r.sendRaw("SYNC") 79 | } 80 | 81 | // sendRaw отправляет сообщение в сокет и не читает ответ 82 | func (r *replica) sendRaw(data string) error { 83 | _, err := r.conn.Write([]byte(fmt.Sprintf("%s\r\n", data))) 84 | return err 85 | } 86 | 87 | // Do запускает процесс репликации, 88 | // возвращает ошибку в случае разрыва соединения с сервером, 89 | // метод синхронный 90 | func (r *replica) Do(consumer Consumer) (err error) { 91 | defer r.Cancel(&err) 92 | if consumer == nil { 93 | return errors.New("expected consumer but actual nil") 94 | } 95 | r.consumer = consumer 96 | 97 | r.decoder, err = NewDecoder( 98 | r.backlog, 99 | r.config, 100 | ) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | r.SendStatus(status.StartSync) 106 | err = r.sendSync() 107 | if err != nil { 108 | return err 109 | } 110 | err = r.decode() 111 | return err 112 | } 113 | 114 | // Done возвращает канал для ожидания завершения репликации 115 | func (r *replica) Done() <-chan struct{} { 116 | return r.ctx.Done() 117 | } 118 | 119 | // Status возвращает true если репликация работает и false если нет 120 | func (r *replica) Status() bool { 121 | select { 122 | case <-r.ctx.Done(): 123 | return false 124 | default: 125 | return true 126 | } 127 | } 128 | 129 | // Err возвращает ошибку декодирования 130 | func (r *replica) Err() error { 131 | return r.err 132 | } 133 | 134 | // Cancel прекращает процесс репликации 135 | func (r *replica) Cancel(err *error) { 136 | r.err = *err 137 | if r.decoder != nil { 138 | r.decoder.Cancel(err) 139 | } 140 | if r.consumer != nil { 141 | r.consumer.Cancel(err) 142 | } 143 | r.cancel() 144 | r.SendStatus(status.StopReplication) 145 | } 146 | 147 | func (r *replica) decode() (err error) { 148 | var cmd command.Command 149 | for { 150 | if !r.decoder.Status() { 151 | return r.decoder.Err() 152 | } 153 | cmd, err = r.conn.Command() 154 | 155 | if err != nil { 156 | return err 157 | } 158 | switch cmd.Type() { 159 | case command.Empty: 160 | continue 161 | case command.RDB: 162 | r.consumer.ReplicaStatus(status.StartCacheRDB) 163 | 164 | if r.status.startDecodeRDB { 165 | return errors.New("unexpected RDB command") 166 | } 167 | r.status.startDecodeRDB = true 168 | 169 | err = r.cacheRDB(cmd) 170 | r.consumer.ReplicaStatus(status.StopCacheRDB) 171 | if err == nil { 172 | r.decoder.SetFileRDB(r.config.CacheRDBFile) //nolint:errcheck 173 | r.startDecoder() 174 | } 175 | default: 176 | if r.consumer.CheckCommand(cmd) { 177 | err = r.backlog.Add(cmd) 178 | } 179 | } 180 | if err != nil { 181 | return err 182 | } 183 | } 184 | } 185 | 186 | // createRDBDir создаёт директорию для хренения RDB кеша 187 | func (r *replica) createRDBDir() error { 188 | dir := path.Dir(r.config.CacheRDBFile) 189 | if !resp.IsDir(dir) { 190 | err := os.MkdirAll(dir, os.ModeDir) 191 | if err != nil { 192 | return err 193 | } 194 | } 195 | return nil 196 | } 197 | 198 | // cacheRDB сохраняет RDB в файл, предварительно удаляя старый кеш 199 | func (r *replica) cacheRDB(cmd command.Command) error { 200 | size, err := cmd.ConvertToRDB() 201 | if err != nil { 202 | return err 203 | } 204 | err = r.createRDBDir() 205 | if err != nil { 206 | return err 207 | } 208 | err = r.deleteCacheRDB() 209 | if err != nil { 210 | return err 211 | } 212 | file, err := os.Create(r.config.CacheRDBFile) 213 | if err != nil { 214 | return err 215 | } 216 | defer file.Close() // nolint:errcheck 217 | 218 | n, err := io.CopyN(file, r.conn, size) 219 | if n != size { 220 | return fmt.Errorf( 221 | "save rdb cache error: expected %d byte but actual %d byte save", 222 | size, 223 | n, 224 | ) 225 | } 226 | return err 227 | } 228 | 229 | // deleteCacheRDB удаляет кеш RDB 230 | func (r *replica) deleteCacheRDB() error { 231 | if _, err := os.Stat(r.config.CacheRDBFile); err == nil { 232 | return os.Remove(r.config.CacheRDBFile) 233 | } 234 | return nil 235 | } 236 | 237 | // startDecoder запускает обработку RDB и Backlog 238 | func (r *replica) startDecoder() { 239 | go r.decoder.Decode(r.consumer) // nolint:errcheck 240 | } 241 | -------------------------------------------------------------------------------- /resp/command.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/avito-tech/smart-redis-replication/command" 8 | ) 9 | 10 | // Command читает и возвращает следующую команду 11 | func (r *reader) Command() (command.Command, error) { 12 | opcode, err := r.ReadOpcode() 13 | if err != nil { 14 | if err == io.EOF { 15 | err = nil 16 | } 17 | return command.Command{}, err 18 | } 19 | switch opcode { 20 | case LF: 21 | return r.ReadLFCommand() 22 | 23 | case BulkStringOpcode: 24 | return r.ReadRDBCommand() 25 | 26 | case ArrayOpcode: 27 | return r.ReadArrayCommand() 28 | 29 | case SimpleStringOpcode: 30 | return r.IgnoreSimpleStringCommand() 31 | 32 | case IntegerOpcode: 33 | return r.IgnoreIntegerCommand() 34 | 35 | case ErrorOpcode: 36 | return r.ReadErrorCommand() 37 | } 38 | return command.Command{}, fmt.Errorf("unexpected opcode %#v", opcode) 39 | } 40 | 41 | func (r *reader) ReadLFCommand() (command.Command, error) { 42 | return command.Command{}, nil 43 | } 44 | 45 | func (r *reader) ReadRDBCommand() (command.Command, error) { 46 | size, err := r.ReadInteger() 47 | if err != nil { 48 | return command.Command{}, err 49 | } 50 | return command.New([]string{ 51 | "rdb", 52 | fmt.Sprintf("%d", size), 53 | }), nil 54 | } 55 | 56 | func (r *reader) ReadArrayCommand() (cmd command.Command, err error) { 57 | r.StartDump("ArrayCommand") 58 | defer r.StopDump(&err) 59 | 60 | var args []string 61 | args, err = r.ReadArray() 62 | if err != nil { 63 | return command.Command{}, err 64 | } 65 | return command.New(args), nil 66 | } 67 | 68 | func (r *reader) ReadSimpleStringCommand() (command.Command, error) { 69 | s, err := r.ReadString('\n') 70 | return command.New([]string{s}), err 71 | } 72 | 73 | func (r *reader) IgnoreSimpleStringCommand() (command.Command, error) { 74 | _, err := r.ReadSimpleStringCommand() 75 | return command.Command{}, err 76 | } 77 | 78 | func (r *reader) IgnoreIntegerCommand() (command.Command, error) { 79 | _, err := r.ReadInteger() 80 | return command.Command{}, err 81 | } 82 | 83 | func (r *reader) ReadErrorCommand() (command.Command, error) { 84 | dataError, err := r.ReadError() 85 | if err == nil { 86 | err = fmt.Errorf("server error: %v", dataError) 87 | } 88 | return command.Command{}, err 89 | } 90 | -------------------------------------------------------------------------------- /resp/conn.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | ) 7 | 8 | // conn это коннект к redis по протоколу RESP 9 | type conn struct { 10 | close io.Closer 11 | Reader 12 | Writer 13 | } 14 | 15 | // New возвращает новый RESP коннект 16 | func New(r io.ReadWriteCloser) Conn { 17 | return &conn{ 18 | close: r, 19 | Reader: NewReader(bufio.NewReader(r)), 20 | Writer: NewWriter(r), 21 | } 22 | } 23 | 24 | // Close закрывает соединение 25 | func (c *conn) Close() error { 26 | return c.close.Close() 27 | } 28 | -------------------------------------------------------------------------------- /resp/doc.go: -------------------------------------------------------------------------------- 1 | // Package resp это пакет для реализации RESP (REdis Serialization Protocol) 2 | // https://redis.io/topics/protocol 3 | package resp 4 | -------------------------------------------------------------------------------- /resp/dump.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "time" 8 | ) 9 | 10 | // Read это обёртка над bufio.Read для записи дампа 11 | func (r *reader) Read(buf []byte) (int, error) { 12 | n, err := r.Reader.Read(buf) 13 | if r.debug && r.dump.start { 14 | r.dump.buf.Write(buf) 15 | } 16 | return n, err 17 | } 18 | 19 | // Read это обёртка над bufio.Read для записи дампа 20 | func (r *reader) ReadByte() (byte, error) { 21 | b, err := r.Reader.ReadByte() 22 | if r.debug && r.dump.start { 23 | r.dump.buf.WriteByte(b) 24 | } 25 | return b, err 26 | } 27 | 28 | // Read это обёртка над bufio.Read для записи дампа 29 | func (r *reader) ReadString(delim byte) (string, error) { 30 | s, err := r.Reader.ReadString(delim) 31 | if r.debug && r.dump.start { 32 | r.dump.buf.Write([]byte(s)) 33 | } 34 | return s, err 35 | } 36 | 37 | // EnableDebug включает логирование resp команд 38 | func (r *reader) EnableDebug(dir string) error { 39 | if !IsDir(dir) { 40 | err := os.MkdirAll(dir, os.ModeDir) 41 | if err != nil { 42 | return err 43 | } 44 | } 45 | r.debug = true 46 | r.dump.dir = dir 47 | 48 | buf := make([]byte, 0, 1024) 49 | r.dump.buf = bytes.NewBuffer(buf) 50 | return nil 51 | } 52 | 53 | // DisableDebug выключает логирование resp команд 54 | func (r *reader) DisableDebug() { 55 | r.debug = false 56 | } 57 | 58 | // StartDump включает запись чтения из потока 59 | func (r *reader) StartDump(name string) { 60 | if r.debug { 61 | r.dump.start = true 62 | r.dump.name = name 63 | r.dump.buf.Reset() 64 | } 65 | } 66 | 67 | // StopDump выключает запись чтения из потока 68 | func (r *reader) StopDump(cmdError *error) { 69 | if r.debug && *cmdError != nil { 70 | b := make([]byte, 20) 71 | r.Read(b) //nolint:errcheck 72 | 73 | filename := fmt.Sprintf( 74 | "%s/%s-%s.dump", 75 | r.dump.dir, 76 | time.Now().Local().Format("2006-01-02"), 77 | r.dump.name, 78 | ) 79 | r.saveDump(filename) //nolint:errcheck 80 | r.dump.start = false 81 | r.dump.buf.Reset() 82 | } 83 | } 84 | 85 | // saveDump сохраняет дамп в файл 86 | func (r *reader) saveDump(filename string) error { 87 | file, err := os.Create(filename) 88 | if err != nil { 89 | return err 90 | } 91 | _, err = file.Write(r.dump.buf.Bytes()) 92 | if err != nil { 93 | return err 94 | } 95 | err = file.Sync() 96 | if err != nil { 97 | return err 98 | } 99 | err = file.Close() 100 | return err 101 | } 102 | -------------------------------------------------------------------------------- /resp/files.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // IsDir возвращает true если path это каталог 8 | func IsDir(path string) bool { 9 | info, err := os.Stat(path) 10 | if err != nil { 11 | return false 12 | } 13 | return info.IsDir() 14 | } 15 | -------------------------------------------------------------------------------- /resp/interface.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/avito-tech/smart-redis-replication/command" 7 | ) 8 | 9 | const ( 10 | // SimpleStringOpcode это первый байт простого однострочного ответа ("+") 11 | SimpleStringOpcode = 0x2b 12 | 13 | // ErrorOpcode это первый байт ответа ошибки ("-") 14 | ErrorOpcode = 0x2d 15 | 16 | // IntegerOpcode это первый байт целого числа (":") 17 | IntegerOpcode = 0x3a 18 | 19 | // BulkStringOpcode это первый байт бинарных данных ("$") 20 | BulkStringOpcode = 0x24 21 | 22 | // ArrayOpcode это первый байт массива ("*") 23 | ArrayOpcode = 0x2a 24 | 25 | // CR это символ \r 26 | CR = 0xd 27 | 28 | // LF это символ \n 29 | LF = 0xa 30 | ) 31 | 32 | // Reader это интерфейс для чтения комманд из потока 33 | type Reader interface { 34 | io.Reader 35 | Command() (command.Command, error) 36 | 37 | // ReadByte() (byte, error) 38 | // ReadOpcode() (byte, error) 39 | ReadString(delim byte) (string, error) 40 | 41 | // ReadSimpleString() (string, error) 42 | // ReadError() (string, error) 43 | // ReadInteger() (int64, error) 44 | 45 | // ReadBulkString() (string, error) 46 | // ReadArray() ([]string, error) 47 | 48 | // EnableDebug включает отладку 49 | // В dir сохраняются команды в бинарном виде по одному файлу на команду 50 | EnableDebug(dir string) error 51 | 52 | // DisableDebug выключает отладку 53 | DisableDebug() 54 | } 55 | 56 | // Writer это интерфейс для записи комманд в поток 57 | type Writer interface { 58 | io.Writer 59 | WriteByte(byte) error 60 | WriteOpcode(byte) error 61 | WriteSimpleString(string) error 62 | WriteError(error) error 63 | WriteInteger(int64) error 64 | WriteBulkString([]byte) error 65 | WriteArray([]interface{}) error 66 | } 67 | 68 | // Closer это интерфейс для закрытия соединения 69 | type Closer interface { 70 | io.Closer 71 | } 72 | 73 | // Conn это интерфейс для чтения/записи в поток 74 | type Conn interface { 75 | Reader 76 | Writer 77 | Closer 78 | } 79 | -------------------------------------------------------------------------------- /resp/reader.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // reader это реализация Reader 14 | type reader struct { 15 | *bufio.Reader 16 | 17 | debug bool 18 | dump struct { 19 | name string 20 | start bool 21 | buf *bytes.Buffer 22 | dir string 23 | } 24 | } 25 | 26 | // NewReader возвращает новый Reader 27 | func NewReader(r *bufio.Reader) Reader { 28 | return newReader(r) 29 | } 30 | 31 | // newReader возвращает новый reader 32 | func newReader(r *bufio.Reader) *reader { 33 | return &reader{ 34 | Reader: r, 35 | } 36 | } 37 | 38 | // NewStringReader возвращает новый Reader на основе строки 39 | func NewStringReader(s string) Reader { 40 | return newStringReader(s) 41 | } 42 | 43 | // newStringReader возвращает новый reader на основе строки 44 | func newStringReader(s string) *reader { 45 | r := bufio.NewReader(strings.NewReader(s)) 46 | return newReader(r) 47 | } 48 | 49 | // NewFileReader возвращает новый Reader на основе файла 50 | func NewFileReader(filename string) (Reader, *os.File, error) { 51 | return newFileReader(filename) 52 | } 53 | 54 | // newFileReader возвращает новый reader на основе файла 55 | func newFileReader(filename string) (*reader, *os.File, error) { 56 | file, err := os.Open(filename) 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | r := newReader(bufio.NewReader(file)) 61 | return r, file, err 62 | } 63 | 64 | // SafeRead безопасно читает N байт 65 | func (r *reader) SafeRead(n uint32) (result []byte, err error) { 66 | result = make([]byte, n) 67 | _, err = io.ReadFull(r, result) 68 | return result, err 69 | } 70 | 71 | // ReadOpcode читает код команды 72 | func (r *reader) ReadOpcode() (byte, error) { 73 | return r.ReadByte() 74 | } 75 | 76 | // ReadSimpleString читает простую строку до появления символов \r\n 77 | func (r *reader) ReadSimpleString() (string, error) { 78 | st, err := r.ReadString('\n') 79 | if err != nil { 80 | return "", err 81 | } 82 | return strings.TrimSpace(st), nil 83 | } 84 | 85 | // ReadError читает строку с ошибкой 86 | func (r *reader) ReadError() (string, error) { 87 | st, err := r.ReadString('\n') 88 | if err != nil { 89 | return "", err 90 | } 91 | return strings.TrimSpace(st), nil 92 | } 93 | 94 | // ReadInteger читает целое число 95 | func (r *reader) ReadInteger() (int64, error) { 96 | st, err := r.ReadString('\n') 97 | if err != nil { 98 | return 0, err 99 | } 100 | return strconv.ParseInt(strings.TrimSpace(st), 10, 64) 101 | } 102 | 103 | // ReadBulkString читает бинарно-безопасную строку 104 | func (r *reader) ReadBulkString() (string, error) { 105 | length, err := r.ReadInteger() 106 | if err != nil { 107 | return "", err 108 | } 109 | data, err := r.SafeRead(uint32(length)) 110 | if err != nil { 111 | return "", err 112 | } 113 | err = r.ReadCRLF() 114 | if err != nil { 115 | return "", err 116 | } 117 | return string(data), nil 118 | } 119 | 120 | // ReadArray читает массив данных, в том числе вложенные 121 | // BulkString, SimpleString, Array, Integer, Error 122 | // nolint:gocyclo 123 | func (r *reader) ReadArray() ([]string, error) { 124 | length, err := r.ReadInteger() 125 | if err != nil { 126 | return nil, err 127 | } 128 | if length == 0 { 129 | return []string{}, nil 130 | } 131 | if length == -1 { 132 | return nil, nil 133 | } 134 | var result []string 135 | for length > 0 { 136 | length-- 137 | opcode, err := r.ReadOpcode() 138 | if err != nil { 139 | return nil, err 140 | } 141 | switch opcode { 142 | case ArrayOpcode: 143 | data, err := r.ReadArray() 144 | if err != nil { 145 | return nil, err 146 | } 147 | result = append(result, data...) 148 | case BulkStringOpcode: 149 | data, err := r.ReadBulkString() 150 | if err != nil { 151 | return nil, err 152 | } 153 | result = append(result, data) 154 | case IntegerOpcode: 155 | data, err := r.ReadInteger() 156 | if err != nil { 157 | return nil, err 158 | } 159 | result = append(result, fmt.Sprintf("%d", data)) 160 | case SimpleStringOpcode: 161 | data, err := r.ReadSimpleString() 162 | if err != nil { 163 | return nil, err 164 | } 165 | result = append(result, data) 166 | case ErrorOpcode: 167 | data, err := r.ReadError() 168 | if err != nil { 169 | return nil, err 170 | } 171 | result = append(result, data) 172 | default: 173 | return nil, fmt.Errorf("unexpected opcode %#v %#v", opcode, ArrayOpcode) 174 | } 175 | } 176 | return result, nil 177 | } 178 | 179 | // ReadCRLF читает два байта и проверяет что в них "\r\n" 180 | func (r *reader) ReadCRLF() error { 181 | crlf, err := r.SafeRead(2) 182 | if err != nil { 183 | return err 184 | } 185 | if crlf[0] != CR || crlf[1] != LF { 186 | return fmt.Errorf("expected CRLF but actual %#v", crlf) 187 | } 188 | return nil 189 | } 190 | -------------------------------------------------------------------------------- /resp/reader_test.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/avito-tech/smart-redis-replication/command" 8 | ) 9 | 10 | func TestReaderCommand(t *testing.T) { 11 | t.Run("Normal", func(t *testing.T) { 12 | t.Run("PING", func(t *testing.T) { 13 | testReadCommand( 14 | t, 15 | "*1\r\n$4\r\nPING\r\n", 16 | command.New([]string{"PING"}), 17 | true, 18 | ) 19 | }) 20 | t.Run("Select", func(t *testing.T) { 21 | testReadCommand( 22 | t, 23 | "*2\r\n$6\r\nSELECT\r\n:10\r\n", 24 | command.New([]string{"SELECT", "10"}), 25 | true, 26 | ) 27 | }) 28 | // nolint:dupl 29 | t.Run("Zadd", func(t *testing.T) { 30 | t.Run("StringValue", func(t *testing.T) { 31 | testReadCommand( 32 | t, 33 | "*6\r\n$4\r\nZADD\r\n$9\r\nkey:1:2:3\r\n:123456\r\n$5\r\nID123\r\n:23456\r\n$6\r\nID2345\r\n", // nolint:lll 34 | command.New([]string{"ZADD", "key:1:2:3", "123456", "ID123", "23456", "ID2345"}), // nolint:lll 35 | true, 36 | ) 37 | }) 38 | t.Run("IntegerValue", func(t *testing.T) { 39 | testReadCommand( 40 | t, 41 | "*6\r\n$4\r\nZADD\r\n$11\r\nkey:[1:2:]3\r\n:123456\r\n:123\r\n:23456\r\n:2345\r\n", // nolint:lll 42 | command.New([]string{"ZADD", "key:[1:2:]3", "123456", "123", "23456", "2345"}), // nolint:lll 43 | true, 44 | ) 45 | }) 46 | }) 47 | // nolint:dupl 48 | t.Run("Sadd", func(t *testing.T) { 49 | t.Run("StringValue", func(t *testing.T) { 50 | testReadCommand( 51 | t, 52 | "*6\r\n$4\r\nSADD\r\n$9\r\nkey:1:2:3\r\n:123456\r\n$5\r\nID123\r\n:23456\r\n$6\r\nID2345\r\n", // nolint:lll 53 | command.New([]string{"SADD", "key:1:2:3", "123456", "ID123", "23456", "ID2345"}), // nolint:lll 54 | true, 55 | ) 56 | }) 57 | t.Run("IntegerValue", func(t *testing.T) { 58 | testReadCommand( 59 | t, 60 | "*6\r\n$4\r\nSADD\r\n$11\r\nkey:[1:2:]3\r\n:123456\r\n:123\r\n:23456\r\n:2345\r\n", // nolint:lll 61 | command.New([]string{"SADD", "key:[1:2:]3", "123456", "123", "23456", "2345"}), // nolint:lll 62 | true, 63 | ) 64 | }) 65 | }) 66 | // nolint:dupl 67 | t.Run("Set", func(t *testing.T) { 68 | t.Run("StringValue", func(t *testing.T) { 69 | testReadCommand( 70 | t, 71 | "*3\r\n$3\r\nSET\r\n$9\r\nkey:1:2:3\r\n$8\r\nID123456\r\n", 72 | command.New([]string{"SET", "key:1:2:3", "ID123456"}), 73 | true, 74 | ) 75 | }) 76 | t.Run("IntegerValue", func(t *testing.T) { 77 | testReadCommand( 78 | t, 79 | "*3\r\n$3\r\nSET\r\n$9\r\nkey:1:2:3\r\n:123456\r\n", 80 | command.New([]string{"SET", "key:1:2:3", "123456"}), 81 | true, 82 | ) 83 | }) 84 | }) 85 | }) 86 | t.Run("Error", func(t *testing.T) { 87 | 88 | }) 89 | } 90 | 91 | // testReadCommand проверяет правильное чтение команды 92 | func testReadCommand( 93 | t *testing.T, 94 | s string, 95 | expectedCommand command.Command, 96 | success bool, 97 | ) { 98 | r := NewStringReader(s) 99 | cmd, err := r.Command() 100 | if success { 101 | if err != nil { 102 | t.Fatalf("read command error: %q", err) 103 | } 104 | if !reflect.DeepEqual(cmd, expectedCommand) { 105 | t.Fatalf("expected %q but actual %q", expectedCommand, cmd) 106 | } 107 | } else { 108 | if err == nil { 109 | t.Fatalf("expected error") 110 | } 111 | } 112 | } 113 | 114 | // testReadFileCommand проверяет правильное чтение команды из файла дампа 115 | // nolint:unused 116 | func testReadFileCommand( 117 | t *testing.T, 118 | filename string, 119 | expectedCommand command.Command, 120 | success bool, 121 | ) { 122 | r, file, err := NewFileReader(filename) 123 | if err != nil { 124 | t.Fatalf("open file error: %v", err) 125 | } 126 | defer file.Close() //nolint:errcheck 127 | cmd, err := r.Command() 128 | if success { 129 | if err != nil { 130 | t.Fatalf("read command error: %v", err) 131 | } 132 | if !reflect.DeepEqual(cmd, expectedCommand) { 133 | t.Fatalf("expected %q but actual %q", expectedCommand, cmd) 134 | } 135 | } else { 136 | if err == nil { 137 | t.Fatalf("expected error") 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /resp/writer.go: -------------------------------------------------------------------------------- 1 | package resp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | ) 8 | 9 | // Array это массив разноплановых данных 10 | type Array []interface{} 11 | 12 | // writer это реализация Writer 13 | type writer struct { 14 | io.Writer 15 | } 16 | 17 | // NewWriter возвращает новый Writer 18 | func NewWriter(w io.Writer) Writer { 19 | return &writer{ 20 | Writer: w, 21 | } 22 | } 23 | 24 | // WriteByte записывает один байт 25 | func (w *writer) WriteByte(b byte) error { 26 | _, err := w.Write([]byte{b}) 27 | if err != nil { 28 | return fmt.Errorf("error write byte: %v", err) 29 | } 30 | return nil 31 | } 32 | 33 | // WriteOpcode записывает opcode 34 | func (w *writer) WriteOpcode(opcode byte) error { 35 | _, err := w.Write([]byte{opcode}) 36 | if err != nil { 37 | return fmt.Errorf("error write opcode: %v", err) 38 | } 39 | return nil 40 | } 41 | 42 | // WriteSimpleString записывает простую строку вместе с opcode 43 | func (w *writer) WriteSimpleString(st string) error { 44 | err := w.WriteOpcode(SimpleStringOpcode) 45 | if err != nil { 46 | return err 47 | } 48 | _, err = w.Write([]byte(fmt.Sprintf("%s\r\n", strings.TrimSpace(st)))) 49 | return err 50 | } 51 | 52 | // WriteError записывает строку с ошибкой 53 | func (w *writer) WriteError(errData error) error { 54 | if errData == nil { 55 | return fmt.Errorf("expected error but actual nil") 56 | } 57 | err := w.WriteOpcode(ErrorOpcode) 58 | if err != nil { 59 | return err 60 | } 61 | _, err = w.Write( 62 | []byte(fmt.Sprintf( 63 | "%s\r\n", 64 | strings.TrimSpace(errData.Error()), 65 | )), 66 | ) 67 | return err 68 | } 69 | 70 | // WriteInteger записывает целое число 71 | func (w *writer) WriteInteger(n int64) error { 72 | err := w.WriteOpcode(IntegerOpcode) 73 | if err != nil { 74 | return err 75 | } 76 | _, err = w.Write([]byte(fmt.Sprintf("%d\r\n", n))) 77 | return err 78 | } 79 | 80 | // WriteBulkString записывает бинарно-безопасную строку 81 | func (w *writer) WriteBulkString(data []byte) error { 82 | err := w.WriteOpcode(BulkStringOpcode) 83 | if err != nil { 84 | return err 85 | } 86 | _, err = w.Write([]byte(fmt.Sprintf("%d\r\n", len(data)))) 87 | if err != nil { 88 | return err 89 | } 90 | _, err = w.Write(data) 91 | return err 92 | } 93 | 94 | // WriterArray записывает массив бинарно-безопасных строк 95 | // nolint:gocyclo 96 | func (w *writer) WriteArray(data []interface{}) error { 97 | err := w.WriteOpcode(ArrayOpcode) 98 | if err != nil { 99 | return err 100 | } 101 | _, err = w.Write([]byte(fmt.Sprintf("%d\r\n", len(data)))) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | for _, item := range data { 107 | switch op := item.(type) { 108 | case string: 109 | err = w.WriteSimpleString(op) 110 | case int64: 111 | err = w.WriteInteger(op) 112 | case Array: 113 | err = w.WriteArray(op) 114 | case []byte: 115 | err = w.WriteBulkString(op) 116 | case error: 117 | err = w.WriteError(op) 118 | default: 119 | return fmt.Errorf("unexpected type %#v", op) 120 | } 121 | if err != nil { 122 | return err 123 | } 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /status/status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | const ( 4 | // Connect означает что началось подключение к серверу 5 | Connect Status = "connect" 6 | 7 | // Reconnect означает что началось переподключение к серверу 8 | Reconnect Status = "reconnect" 9 | 10 | // Disconnect означает что произошло отключение от сервера 11 | Disconnect Status = "disconnect" 12 | 13 | // StartSync означает что инициировалась репликация 14 | StartSync Status = "start_sync" 15 | 16 | // RDB означает что началась секция с RDB 17 | RDB Status = "rdb" 18 | 19 | // StartCacheRDB означает что началось кеширование BulkString с RDB 20 | StartCacheRDB Status = "start_cache_rdb" 21 | 22 | // StopCacheRDB означает что закончилось кеширование BulkString с RDB 23 | StopCacheRDB Status = "stop_cache_rdb" 24 | 25 | // StartBacklog означает что началась запись в Backlog 26 | StartBacklog Status = "start_cache_real_time" 27 | 28 | // SkipReadRDB означает что файл rdb был скачан, но не разобран, перейдя сразу к чтению Backlog 29 | SkipReadRDB Status = "skip_read_rdb" 30 | 31 | // StartReadRDB означает что началось чтение из BulkString с RDB 32 | StartReadRDB Status = "start_read_rdb" 33 | 34 | // StopReadRDB означает что закончилось чтение из BulkString с RDB 35 | StopReadRDB Status = "stop_read_rdb" 36 | 37 | // StartReadBacklog означает что началось чтение из Backlog 38 | StartReadBacklog Status = "start_read_backlog" 39 | 40 | // StopDecoder означает что декодирование прекратилось 41 | StopDecoder Status = "stop_decoder" 42 | 43 | // StopReplication означает что репликация прекратилась 44 | StopReplication Status = "stop_replication" 45 | ) 46 | 47 | // Status это статус репликации 48 | type Status string 49 | --------------------------------------------------------------------------------