bugfix: Fixed minor issues

v4
suxb201 12 months ago committed by suxb201
parent 6cfb386eca
commit d9d431158c
  1. 6
      docs/.vitepress/config.ts
  2. 90
      docs/src/zh/function/best_practices.md
  3. 3
      docs/src/zh/function/introduction.md
  4. 5
      docs/src/zh/guide/getting-started.md
  5. 31
      docs/src/zh/others/modules.md
  6. 8
      docs/src/zh/reader/sync_reader.md
  7. 12
      docs/src/zh/writer/redis_writer.md
  8. 2
      go.mod
  9. 4
      go.sum
  10. 22
      internal/reader/sync_standalone_reader.go
  11. 2
      shake.toml

@ -43,16 +43,16 @@ export default defineConfig({
]
},
{
text: 'function',
text: 'Function',
items: [
{ text: '什么是 function', link: '/zh/function/introduction' },
{ text: '最佳实践', link: '/zh/function/best_practices' }
]
},
{
text: 'others',
text: 'Others',
items: [
{ text: 'redis modle 支持', link: '/zh/others/module-supported' },
{ text: 'Redis Modules', link: '/zh/others/modules' },
]
},
// {

@ -4,8 +4,96 @@ outline: deep
# 最佳实践
## 过滤
### 过滤 Key
```lua
local prefix = "user:"
local prefix_len = #prefix
if string.sub(KEYS[1], 1, prefix_len) ~= prefix then
return
end
shake.call(DB, ARGV)
```
效果是只将 key 以 `user:` 开头的源数据写入到目标端。没有考虑 `mset` 等多 key 命令的情况。
### 过滤 DB
```lua
shake.log(DB)
if DB == 0
then
return
end
shake.call(DB, ARGV)
```
效果是丢弃源端 `db` 0 的数据,将其他 `db` 的数据写入到目标端。
### 过滤某类数据结构
可以通过 `GROUP` 变量来判断数据结构类型,支持的数据结构类型有:`STRING`、`LIST`、`SET`、`ZSET`、`HASH`、`SCRIPTING` 等。
#### 过滤 Hash 类型数据
```lua
if GROUP == "HASH" then
return
end
shake.call(DB, ARGV)
```
效果是丢弃源端的 `hash` 类型数据,将其他数据写入到目标端。
#### 过滤 [LUA 脚本](https://redis.io/docs/interact/programmability/eval-intro/)
```lua
if GROUP == "SCRIPTING" then
return
end
shake.call(DB, ARGV)
```
效果是丢弃源端的 `lua` 脚本,将其他数据写入到目标端。常见于主从同步至集群时,存在集群不支持的 LUA 脚本。
## 修改
### 修改 Key 的前缀
TODO
```lua
local prefix_old = "prefix_old_"
local prefix_new = "prefix_new_"
shake.log("old=" .. table.concat(ARGV, " "))
for i, index in ipairs(KEY_INDEXES) do
local key = ARGV[index]
if string.sub(key, 1, #prefix_old) == prefix_old then
ARGV[index] = prefix_new .. string.sub(key, #prefix_old + 1)
end
end
shake.log("new=" .. table.concat(ARGV, " "))
shake.call(DB, ARGV)
```
效果是将源端的 key `prefix_old_key` 写入到目标端的 key `prefix_new_key`
### 交换 DB
```lua
local db1 = 1
local db2 = 2
if DB == db1 then
DB = db2
elseif DB == db2 then
DB = db1
end
shake.call(DB, ARGV)
```
效果是将源端的 `db 1` 写入到目标端的 `db 2`,将源端的 `db 2` 写入到目标端的 `db 1`, 其他 `db` 不变。

@ -38,6 +38,9 @@ address = "127.0.0.1:6380"
## function API
### 变量
因为有些命令中含有多个 key,比如 `mset` 等命令。所以,`KEYS`、`KEY_INDEXES`、`SLOTS` 这三个变量都是数组类型。如果确认命令只有一个 key,可以直接使用 `KEYS[1]`、`KEY_INDEXES[1]`、`SLOTS[1]`。
| 变量 | 类型 | 示例 | 描述 |
|-|-|-|-----|
| DB | number | 1 | 命令所属的 `db` |

@ -38,3 +38,8 @@ address = "127.0.0.1:6380"
```shell
./redis-shake shake.toml
```
## 注意事项
1. 不要在同一个目录运行两个 RedisShake 进程,因为运行时产生的临时文件可能会被覆盖,导致异常行为。
2. 不要降低 Redis 版本,比如从 6.0 降到 5.0,因为 RedisShake 每个大版本都会引入一些新的命令和新的编码方式,如果降低版本,可能会导致不兼容。

@ -2,14 +2,23 @@
outline: deep
---
# Redis Modules
# 介绍
可以为 RedisShake 贡献代码支持其它自定义 module 类型
Redis Modules 是 Redis 4.0 版本引入的一个新特性,它允许开发者扩展 Redis 的功能。通过创建模块,开发者可以定义新的命令,数据类型,甚至改变 Redis 的行为。因此,Redis Modules 可以极大地增强 Redis 的灵活性和可扩展性。
由于 Redis Modules 可以定义新的数据类型和命令,RedisShake 需要对这些新的数据类型和命令进行专门的处理,才能正确地迁移或同步这些数据。否则,如果 RedisShake 不理解这些新的数据类型和命令,它可能会无法正确地处理这些数据,或者在处理过程中出错。因此,对于使用了 Redis Modules 的 Redis 实例,一般需要 RedisShake 为其使用的 Module 提供相应的适配器,以便正确地处理这些自定义的数据类型和命令。
# 核心流程
相关代码在`internal\rdb`目录下,如需要支持其它 redis module 类型,可分解为以下三个步骤——
## 已支持的 Redis Modules 列表
- [TairHash](https://github.com/tair-opensource/TairHash):支持 field 级别设置过期和版本的 Hash 数据结构。
- [TairString](https://github.com/tair-opensource/TairString):支持版本的 String 结构,可以实现分布式锁/乐观锁。
- [TairZset](https://github.com/tair-opensource/TairZset):支持最多 256 维的 double 排序,可以实现多维排行榜。
## 如何支持新的 Redis Modules
### 核心流程
相关代码在`internal\rdb`目录下,如需要支持其它 Redis Modules 类型,可分解为以下三个步骤:
- 从rdb文件中正确读入
- RedisShake 中已经 对 redis module 自定的几种类型进行了封装,从 rdb 文件进行读取时,可直接借助于已经封装好的函数进行读取(`internal\rdb\structure\module2_struct.go`)
- 构建一个合适的中间数据结构类型,用于存储相应数据(key + value)
@ -21,20 +30,14 @@ outline: deep
![module-supported.jpg](/public/module-supported.jpg)
# 其它
## 补充命令测试
### 补充命令测试
为了确保正常,需要在` tests\helpers\commands` 里面添加对应 module 的命令,来测试相关命令可以在 rdb、sync、scan 三个模式下工作正常。测试框架具体见[pybbt](https://pypi.org/project/pybbt/),具体思想——借助于redis-py 包,对其进行封装,模拟客户端发送命令,然后比对实际的返回值与已有的返回值。
## 补充命令列表
### 补充命令列表
RedisShake 在针对大 key 进行传输时,会查命令表格`RedisShake\internal\commands\table.go`,检查命令的合规性,因此在添加新 module 时,需要将对应的命令加入表格,具体可参照`RedisShake\scripts`部分代码
## 补充 ci
在 ci 测试中,需要添加对自定义 module 的编译,具体可见` ci.yml` 内容
### 补充 ci
在 ci 测试中,需要添加对自定义 module 的编译,具体可见` ci.yml` 内容
# 已支持的 redis module 列表
- [TairHash](https://github.com/tair-opensource/TairHash)
- [TairString](https://github.com/tair-opensource/TairString)
- [TairZset](https://github.com/tair-opensource/TairZset)

@ -11,6 +11,8 @@
优势:数据一致性最佳,对源库影响小,可以实现不停机的切换
原理:RedisShake 模拟 Slave 连接到 Master 节点,Master 会向 RedisShake 发送数据,数据包含全量与增量两部分。全量是一个 RDB 文件,增量是 AOF 数据流,RedisShake 会接受全量与增量将其暂存到硬盘上。全量同步阶段:RedisShake 首先会将 RDB 文件解析为一条条的 Redis 命令,然后将这些命令发送至目的端。增量同步阶段:RedisShake 会持续将 AOF 数据流同步至目的端。
## 配置
```toml
@ -20,6 +22,8 @@ address = "127.0.0.1:6379" # when cluster is true, set address to one of the clu
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
tls = false
sync_rdb = true # set to false if you don't want to sync rdb
sync_aof = true # set to false if you don't want to sync aof
```
* `cluster`:源端是否为集群
@ -28,4 +32,6 @@ tls = false
* 当源端使用 ACL 账号时,配置 `username``password`
* 当源端使用传统账号时,仅配置 `password`
* 当源端无鉴权时,不配置 `username``password`
* `tls`:源端是否开启 TLS/SSL,不需要配置证书因为 RedisShake 没有校验服务器证书
* `tls`:源端是否开启 TLS/SSL,不需要配置证书因为 RedisShake 没有校验服务器证书
* `sync_rdb`:是否同步 RDB,设置为 false 时,RedisShake 会跳过全量同步阶段
* `sync_aof`:是否同步 AOF,设置为 false 时,RedisShake 会跳过增量同步阶段,此时 RedisShake 会在全量同步阶段结束后退出

@ -15,13 +15,13 @@ password = "" # keep empty if no authentication is required
tls = false
```
* `cluster`源端是否为集群
* `address`源端地址, 当源端为集群时,`address` 为集群中的任意一个节点即可
* `cluster`:是否为集群
* `address`连接地址。当目的端为集群时,`address` 填写集群中的任意一个节点即可
* 鉴权:
* 当源端使用 ACL 账号时,配置 `username``password`
* 当源端使用传统账号时,仅配置 `password`
* 当源端无鉴权时,不配置 `username``password`
* `tls`源端是否开启 TLS/SSL,不需要配置证书因为 RedisShake 没有校验服务器证书
* 当使用 ACL 账号体系时,配置 `username``password`
* 当使用传统账号体系时,仅配置 `password`
* 当无鉴权时,不配置 `username``password`
* `tls`:是否开启 TLS/SSL,不需要配置证书因为 RedisShake 没有校验服务器证书
注意事项:
1. 当目的端为集群时,应保证源端发过来的命令满足 [Key 的哈希值属于同一个 slot](https://redis.io/docs/reference/cluster-spec/#implemented-subset)。

@ -26,7 +26,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

@ -327,8 +327,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

@ -25,6 +25,8 @@ type SyncReaderOptions struct {
Username string `mapstructure:"username" default:""`
Password string `mapstructure:"password" default:""`
Tls bool `mapstructure:"tls" default:"false"`
SyncRdb bool `mapstructure:"sync_rdb" default:"true"`
SyncAof bool `mapstructure:"sync_aof" default:"true"`
}
type State string
@ -38,6 +40,7 @@ const (
)
type syncStandaloneReader struct {
opts *SyncReaderOptions
client *client.Redis
ch chan *entry.Entry
@ -72,6 +75,7 @@ type syncStandaloneReader struct {
func NewSyncStandaloneReader(opts *SyncReaderOptions) Reader {
r := new(syncStandaloneReader)
r.opts = opts
r.client = client.NewRedisClient(opts.Address, opts.Username, opts.Password, opts.Tls)
r.rd = r.client.BufioReader()
r.stat.Name = "reader_" + strings.Replace(opts.Address, ":", "_", -1)
@ -91,9 +95,14 @@ func (r *syncStandaloneReader) StartRead() chan *entry.Entry {
r.receiveRDB()
startOffset := r.stat.AofReceivedOffset
go r.receiveAOF(r.rd)
r.sendRDB()
r.stat.Status = kSyncAof
r.sendAOF(startOffset)
if r.opts.SyncRdb {
r.sendRDB()
}
if r.opts.SyncAof {
r.stat.Status = kSyncAof
r.sendAOF(startOffset)
}
close(r.ch)
}()
return r.ch
@ -103,12 +112,9 @@ func (r *syncStandaloneReader) sendReplconfListenPort() {
// use status_port as redis-shake port
argv := []string{"replconf", "listening-port", strconv.Itoa(config.Opt.Advanced.StatusPort)}
r.client.Send(argv...)
reply, err := r.client.Receive()
_, err := r.client.Receive()
if err != nil {
log.Warnf("[%s] send replconf command to redis server failed. reply=[%s], error=[%v]", r.stat.Name, reply, err)
}
if reply != "OK" {
log.Warnf("[%s] send replconf command to redis server failed. reply=[%s]", r.stat.Name, reply)
log.Warnf("[%s] send replconf command to redis server failed. error=[%v]", r.stat.Name, err)
}
}

@ -7,6 +7,8 @@ address = "127.0.0.1:6379" # when cluster is true, set address to one of the clu
username = "" # keep empty if not using ACL
password = "" # keep empty if no authentication is required
tls = false
sync_rdb = true # set to false if you don't want to sync rdb
sync_aof = true # set to false if you don't want to sync aof
# [scan_reader]
# cluster = false # set to true if source is a redis cluster

Loading…
Cancel
Save