├── .gitignore ├── LICENSE ├── README.md ├── README_cn.md ├── check └── checkenv.go ├── cmd ├── compare.go └── result.go ├── commons ├── fileutil.go ├── interfaceutil.go ├── randutil.go ├── randutil_test.go ├── redisutil.go ├── report.go ├── stringutil.go └── yamlutile.go ├── compare ├── compare.go ├── compareenvironment.go ├── compareenvironment_test.go ├── comparesingle2cluster.go ├── comparesingle2cluster_test.go ├── comparesingle2single.go └── comparesingle2single_test.go ├── docs └── images │ └── use.gif ├── execyamlexample ├── cluster2cluster.yml ├── multisingle2cluster.yml ├── multisingle2single.yml ├── single2cluster.yml └── single2single.yml ├── globalzap ├── globlezap.go └── golobezap_test.go ├── go.mod ├── go.sum ├── interact └── cli.go ├── main.go ├── makefile └── redisconfigparameters ├── all ├── all.bak ├── parameters_all ├── parameters_v2 ├── parameters_v3 ├── parameters_v4 ├── parameters_v5 ├── parameters_v6 ├── v2 ├── v3 ├── v4 ├── v5 └── v6 /.gitignore: -------------------------------------------------------------------------------- 1 | #java ignore 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | **/log 8 | 9 | # example yaml files dir 10 | **/execyaml 11 | 12 | # result or report file 13 | compare_* 14 | 15 | # BlueJ files 16 | *.ctxt 17 | 18 | # Mobile Tools for Java (J2ME) 19 | .mtj.tmp/ 20 | 21 | # Package Files # 22 | *.jar 23 | *.war 24 | *.nar 25 | *.ear 26 | *.zip 27 | *.tar.gz 28 | *.rar 29 | # go ignore 30 | **/vendor 31 | *.exe 32 | *.exe~ 33 | *.dll 34 | *.so 35 | *.dylib 36 | 37 | 38 | #idea ignore 39 | **/.idea 40 | 41 | # vscode 42 | **/.vscode 43 | # rust ignore 44 | Cargo.lock 45 | **/target 46 | **/*.rs.bk 47 | 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 JiaShiwen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rediscompare 2 | 3 | [简体中文](README_cn.md) 4 | 5 | rediscompare is a command line tool used to compare redis database data consistency 6 | 7 | ![showuse](./docs/images/use.gif) 8 | 9 | ## Verification mechanism 10 | 11 | rediscompare scans the data in the source database and compares it with the target database through the scan command, and checks it from dimensions such as value length, value, and ttl. Finally, a result file is generated, which contains the key of the data inconsistency and the reason for the inconsistency. 12 | 13 | In actual scenarios, some keys may be inconsistent in the first comparison due to transmission delay issues. Rediscompare supports multiple comparisons in a cycle, which is based on the inconsistent keys in the last comparison and generates a result file. The number of cycles can be passed "--comparetimes" "Parameter specification 14 | 15 | ## Scenario 16 | 17 | rediscompare provides a comparison plan for the following scenarios according to the different types of targets and sources 18 | 19 | * compare parameters 20 | To compare diff of parameters between two redis insance 21 | 22 | * single2single 23 | Comparison of redis single instance to single instance. Used to compare the data consistency of a single instance single database 24 | 25 | * single2cluster 26 | Used to compare the data consistency between a db in a single instance and the redis native cluster 27 | 28 | * cluster2cluster 29 | Used to compare the data consistency between redis native cluster and native cluster 30 | 31 | * multisingle2single 32 | Used to compare the data consistency between a collection of multiple databases in multiple single instances and a database in a single instance 33 | 34 | * multisingle2cluster 35 | Used to compare the data consistency between multiple databases collections in multiple single instances and a single instance database 36 | 37 | ## quick start 38 | 39 | ### build execute file 40 | 41 | ```shell 42 | git clone https://github.com/TraceNature/rediscompare.git 43 | cd rediscompare 44 | go mod tidy 45 | go mod vendor 46 | go build -o rediscompare 47 | ``` 48 | 49 | ### Usage example 50 | 51 | rediscompare supports command line mode and interactive mode, interactive mode supports command prompt. The comparison command supports direct command input and yaml customization. Let's introduce the basic usage of each scene according to the scene. 52 | 53 | Use the -i parameter to enter the interactive mode "rediscompare -i" 54 | 55 | #### compare subcommand 56 | 57 | * parameters 58 | 59 | ```shell 60 | rediscompare compare parameters --saddr 10.0.0.1:6379 --spassword "redistest0102" --taddr 10.0.0.2:6379 --tpassword "testredis0102" 61 | ``` 62 | 63 | * single2single 64 | * 命令模式 Command mode 65 | 66 | ``` shell 67 | rediscompare compare single2single --saddr "10.0.0.1:6379" --spassword "redistest0102" --taddr "10.0.0.2:6379" --tpassword "redistest0102" --comparetimes 3 68 | ``` 69 | 70 | * single2cluster 71 | * Command mode 72 | 73 | ```shell 74 | rediscompare compare single2cluster --saddr "10.0.0.1:6379" --spassword "redistest0102" --taddr "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" --tpassword "testredis0102" --comparetimes 3 75 | ``` 76 | 77 | * cluster2cluster 78 | * Command mode 79 | 80 | ```shell 81 | rediscompare compare cluster2cluster --saddr "10.0.0.1:36379,10.0.0.2:36379,10.0.0.3:36379" --spassword "testredis0102" --taddr "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" --tpassword "testredis0102" --comparetimes 3 82 | ``` 83 | 84 | * multisingle2single 85 | * Execute yaml file 86 | 87 | ```yaml 88 | # multisingle2single yaml file 89 | saddr: 90 | - addr: "10.0.0.1:6379" 91 | password: "redistest0102" 92 | dbs: 93 | - 0 94 | - 2 95 | - 3 96 | - addr: "10.0.0.2:6379" 97 | password: "redistest0102" 98 | dbs: 99 | - 1 100 | - 5 101 | - 9 102 | taddr: "10.0.0.3:6379" 103 | tpassword: "redistest0102" 104 | batchsize: 30 105 | threads: 2 106 | ttldiff: 10000 107 | comparetimes: 3 108 | report: true 109 | scenario: "multisingle2single" 110 | ``` 111 | 112 | ```shell 113 | rediscompare compare exec path/miltisingle2single.yml 114 | ``` 115 | 116 | * multisingle2cluster 117 | * Execute yaml file 118 | 119 | ```yaml 120 | # multisingle2cluster yaml file 121 | saddr: 122 | - addr: "10.0.0.1:6379" 123 | password: "redistest0102" 124 | dbs: 125 | - 0 126 | - 2 127 | - 3 128 | - addr: "10.0.0.2:6379" 129 | password: "redistest0102" 130 | dbs: 131 | - 1 132 | - 5 133 | - 9 134 | taddr: "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" 135 | tpassword: "testredis0102" 136 | batchsize: 30 137 | threads: 2 138 | ttldiff: 10000 139 | comparetimes: 3 140 | report: true 141 | scenario: "multisingle2cluster" 142 | ``` 143 | 144 | ```shell 145 | rediscompare compare exec path/miltisingle2cluster.yml 146 | ``` 147 | 148 | The two scenarios of multisingle2single and multisingle2cluster are difficult to express due to the complex mapping relationship of the databases. Currently, only yaml file execution is supported; single2single, single2cluster, cluster2cluster support command line and yaml file mode. 149 | 150 | #### yaml example 151 | 152 | For yaml example files, please refer to the .yml file in the execyamlexample directory 153 | 154 | #### result subcommand 155 | 156 | The result subcommand is used to format a .result or .rep file. The file is the comparison result, json plaintext. The result command converts the file into a two-dimensional table to increase readability 157 | 158 | ```shell 159 | rediscompare result parse compare_xxxxxxxx.rep 160 | ``` 161 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # rediscompare 2 | 3 | [English](README.md) 4 | 5 | rediscompare 是用来对比redis 数据库数据一致性的命令行工具 6 | 7 | ![showuse](./docs/images/use.gif) 8 | 9 | ## 校验机制 10 | 11 | rediscompare 通过scan 命令扫描源库中的左右数据依次与目标数据库进行比较,从value长度、value值、ttl等维度进行核对。最后生成result文件,文件中包含数据不一致的key以及不一致原因。 12 | 在实际场景中,某些key可能在首次比较中由于传输延迟问题不一致,rediscompare 支持循环多次对比,既根据上次对比中不一致的key重新对比并生成result文件,循环次数可以通过"--comparetimes" 参数指定 13 | 14 | ## 场景 15 | 16 | rediscompare 根据目标以及源的不同类型提供一下场景的对比方案 17 | 18 | * compare parameters 19 | 用于对比redis实例的参数差异 20 | 21 | * single2single 22 | redis单实例到单实例的比较。用于比较单实例单库的数据一致性 23 | 24 | * single2cluster 25 | 用于比较单实例中某库与redis原生cluster的数据一致性 26 | 27 | * cluster2cluster 28 | 用于对比redis原生cluster与原生cluster的数据一致性 29 | 30 | * multisingle2single 31 | 用于对比多个单实例中多个库的集合与某单实例中某库的数据一致性 32 | 33 | * multisingle2cluster 34 | 用于对比多个单实例中多个库集合与单实例某库的数据一致性 35 | 36 | ## quick start 37 | 38 | ### build execute file 39 | 40 | ```shell 41 | git clone https://github.com/TraceNature/rediscompare.git 42 | cd rediscompare 43 | go mod tidy 44 | go mod vendor 45 | go build -o rediscompare 46 | ``` 47 | 48 | ### 使用范例 49 | 50 | rediscompare 支持命令行模式和交互模式,交互模式支持命令提示。对比指令支持直接命令输入和yaml定制。一下按照场景分别介绍各个场景下的基本使用。 51 | 使用 -i 参数进入交互模式 "rediscompare -i" 52 | 53 | #### compare 子命令 54 | 55 | * parameters 56 | 57 | ```shell 58 | rediscompare compare parameters --saddr 10.0.0.1:6379 --spassword "redistest0102" --taddr 10.0.0.2:6379 --tpassword "testredis0102" 59 | ``` 60 | 61 | * single2single 62 | * 命令模式 63 | 64 | ```shell 65 | rediscompare compare single2single --saddr "10.0.0.1:6379" --spassword "redistest0102" --taddr "10.0.0.2:6379" --tpassword "redistest0102" --comparetimes 3 66 | ``` 67 | 68 | * single2cluster 69 | * 命令模式 70 | 71 | ```shell 72 | rediscompare compare single2cluster --saddr "10.0.0.1:6379" --spassword "redistest0102" --taddr "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" --tpassword "testredis0102" --comparetimes 3 73 | ``` 74 | 75 | * cluster2cluster 76 | * 命令模式 77 | 78 | ```shell 79 | rediscompare compare cluster2cluster --saddr "10.0.0.1:36379,10.0.0.2:36379,10.0.0.3:36379" --spassword "testredis0102" --taddr "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" --tpassword "testredis0102" --comparetimes 3 80 | ``` 81 | 82 | * multisingle2single 83 | * 执行yaml文件 84 | 85 | ```yaml 86 | # multisingle2single yaml file 87 | saddr: 88 | - addr: "10.0.0.1:6379" 89 | password: "redistest0102" 90 | dbs: 91 | - 0 92 | - 2 93 | - 3 94 | - addr: "10.0.0.2:6379" 95 | password: "redistest0102" 96 | dbs: 97 | - 1 98 | - 5 99 | - 9 100 | taddr: "10.0.0.3:6379" 101 | tpassword: "redistest0102" 102 | batchsize: 30 103 | threads: 2 104 | ttldiff: 10000 105 | comparetimes: 3 106 | report: true 107 | scenario: "multisingle2single" 108 | ``` 109 | 110 | ```shell 111 | rediscompare compare exec path/miltisingle2single.yml 112 | ``` 113 | 114 | * multisingle2cluster 115 | * 执行yaml文件 116 | 117 | ```yaml 118 | # multisingle2cluster yaml file 119 | saddr: 120 | - addr: "10.0.0.1:6379" 121 | password: "redistest0102" 122 | dbs: 123 | - 0 124 | - 2 125 | - 3 126 | - addr: "10.0.0.2:6379" 127 | password: "redistest0102" 128 | dbs: 129 | - 1 130 | - 5 131 | - 9 132 | taddr: "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" 133 | tpassword: "testredis0102" 134 | batchsize: 30 135 | threads: 2 136 | ttldiff: 10000 137 | comparetimes: 3 138 | report: true 139 | scenario: "multisingle2cluster" 140 | ``` 141 | 142 | ```shell 143 | rediscompare compare exec path/miltisingle2cluster.yml 144 | ``` 145 | 146 | multisingle2single、multisingle2cluster两个场景由于原库映射关系比较复杂命令行不易表示顾目前只支持yaml文件执行;single2single、 single2cluster、cluster2cluster支持命令行和yaml文件模式。 147 | 148 | #### yaml 文件示例 149 | 150 | yaml示例文件请参考 execyamlexample 目录中的 .yml文件 151 | 152 | #### result 子命令 153 | 154 | result 子命令用来格式化.result或.rep文件,文件为对比结果,json明文。result命令将文件转换为二维表格增加可读性 155 | 156 | ```shell 157 | rediscompare result parse compare_xxxxxxxx.rep 158 | ``` 159 | -------------------------------------------------------------------------------- /check/checkenv.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "errors" 5 | "github.com/spf13/viper" 6 | ) 7 | 8 | func CheckEnv() error { 9 | if viper.Get("syncserver") == "" { 10 | return errors.New("You must set syncserver from '-s' flag example 'redissyncer-cli -s http://yourip:port' or \n" + 11 | "use environment variable example 'export SYNCSERVER=http://yourip:port' or \n edit .config.yaml append 'SYNCSERVER=http://yourip:port'") 12 | } 13 | return nil 14 | 15 | } 16 | -------------------------------------------------------------------------------- /cmd/compare.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "github.com/ghodss/yaml" 8 | "github.com/go-redis/redis/v7" 9 | "github.com/olekukonko/tablewriter" 10 | "github.com/pkg/errors" 11 | "github.com/spf13/cobra" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "rediscompare/commons" 16 | "rediscompare/compare" 17 | "rediscompare/globalzap" 18 | "sort" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | var zaplogger = globalzap.GetLogger() 24 | 25 | const ( 26 | ScenarioSingle2single = "single2single" 27 | ScenarioSingle2cluster = "single2cluster" 28 | ScenarioMultiSingle2single = "multisingle2single" 29 | ScenarioMultiSingle2cluster = "multisingle2cluster" 30 | ScenarioCluster2cluster = "cluster2cluster" 31 | ) 32 | 33 | type SAddr struct { 34 | Addr string 35 | Password string 36 | Dbs []int 37 | } 38 | type RedisCompare struct { 39 | Saddr []SAddr `json:"saddr"` 40 | Taddr string `json:"taddr"` 41 | Spassword string `json:"spassword"` 42 | Tpassword string `json:"tpassword"` 43 | Sdb int `json:"sdb"` 44 | Tdb int `json:"tdb"` 45 | BatchSize int `json:"batchsize"` 46 | Threads int `json:"threads"` 47 | TTLDiff int `json:"ttldiff"` 48 | CompareTimes int `json:"comparetimes"` 49 | CompareInterval int `json:"compareinterval"` 50 | Report bool `json:"report"` 51 | Scenario string `json:"scenario"` 52 | } 53 | 54 | func NewCompareCommand() *cobra.Command { 55 | compare := &cobra.Command{ 56 | Use: "compare ", 57 | Short: "compare redis db", 58 | } 59 | 60 | compare.AddCommand(NewParametersCommand()) 61 | compare.AddCommand(NewExecuteCommand()) 62 | compare.AddCommand(NewSingle2SingleCommand()) 63 | compare.AddCommand(NewSingle2ClusterCommand()) 64 | compare.AddCommand(NewCluster2ClusterCommand()) 65 | compare.AddCommand(NewMultiSingle2SingleCommand()) 66 | //compare.AddCommand(NewMultiSingle2ClusterCommand()) 67 | return compare 68 | } 69 | 70 | func NewExecuteCommand() *cobra.Command { 71 | sc := &cobra.Command{ 72 | Use: "exec ", 73 | Short: "compare single instance redis", 74 | Run: executeCommandFunc, 75 | } 76 | return sc 77 | } 78 | 79 | func NewParametersCommand() *cobra.Command { 80 | sc := &cobra.Command{ 81 | Use: "parameters ", 82 | Short: "compare single instance redis instance parameters", 83 | Run: parametersCommandFunc, 84 | } 85 | //sc.AddCommand(NewTaskCreateSourceCommand()) 86 | //sc.Flags().Bool("afresh", false, "afresh task from begin") 87 | sc.Flags().String("saddr", "127.0.0.1:6379", "Source redis address default is 127.0.0.1:6379") 88 | sc.Flags().String("taddr", "127.0.0.1:6379", "Target redis address default is 127.0.0.1:6379") 89 | sc.Flags().String("spassword", "", "Source redis password") 90 | sc.Flags().String("tpassword", "", "Target redis password") 91 | sc.Flags().Bool("report", false, "whether generate report default is false") 92 | return sc 93 | 94 | } 95 | 96 | func NewSingle2SingleCommand() *cobra.Command { 97 | sc := &cobra.Command{ 98 | Use: "single2single ", 99 | Short: "compare single instance redis", 100 | Run: single2singleCommandFunc, 101 | } 102 | //sc.AddCommand(NewTaskCreateSourceCommand()) 103 | //sc.Flags().Bool("afresh", false, "afresh task from begin") 104 | sc.Flags().String("saddr", "127.0.0.1:6379", "Source redis address default is 127.0.0.1:6379") 105 | sc.Flags().String("taddr", "127.0.0.1:6379", "Target redis address default is 127.0.0.1:6379") 106 | sc.Flags().String("spassword", "", "Source redis password") 107 | sc.Flags().String("tpassword", "", "Target redis password") 108 | sc.Flags().Int("sdb", 0, "Source redis DB number default is 0") 109 | sc.Flags().Int("tdb", 0, "Source redis DB number default is 0") 110 | sc.Flags().Int("batchsize", 50, "Compare List、Set、Zset type batch default is 50") 111 | sc.Flags().Int("threads", 0, "Compare threads default is cpu core number") 112 | sc.Flags().Int("ttldiff", 10000, "Diffrent of TTL,Allowed max ttl microseconds default is 10000 as ten seconds") 113 | sc.Flags().Int("comparetimes", 1, "compare loop times,default is 1") 114 | sc.Flags().Int("compareinterval", 1, "compare loop interval,default is 1 second") 115 | sc.Flags().Bool("report", false, "whether generate report default is false") 116 | return sc 117 | 118 | } 119 | 120 | func NewSingle2ClusterCommand() *cobra.Command { 121 | sc := &cobra.Command{ 122 | Use: "single2cluster ", 123 | Short: "compare single instance redis and cluster data", 124 | Run: single2clusterCommandFunc, 125 | } 126 | //sc.AddCommand(NewTaskCreateSourceCommand()) 127 | sc.Flags().String("saddr", "127.0.0.1:6379", "Source redis address default is 127.0.0.1:6379") 128 | sc.Flags().String("taddr", "127.0.0.1:6379", "Target redis cluster addresses splite with ',' default is 127.0.0.1:6379") 129 | sc.Flags().String("spassword", "", "Source redis password") 130 | sc.Flags().String("tpassword", "", "Target redis password") 131 | sc.Flags().Int("sdb", 0, "Source redis DB number default is 0") 132 | //sc.Flags().Int("tdb", 0, "Source redis DB number default is 0") 133 | sc.Flags().Int("batchsize", 50, "Compare List、Set、Zset type batch default is 50") 134 | sc.Flags().Int("threads", 0, "Compare threads default is cpu core number") 135 | sc.Flags().Int("ttldiff", 10000, "Diffrent of TTL,Allowed max ttl microseconds default is 10000 as ten seconds") 136 | sc.Flags().Int("comparetimes", 1, "compare loop times,default is 1") 137 | sc.Flags().Int("compareinterval", 1, "compare loop interval,default is 1 second") 138 | sc.Flags().Bool("report", false, "whether generate report default is false") 139 | return sc 140 | 141 | } 142 | 143 | func NewMultiSingle2SingleCommand() *cobra.Command { 144 | sc := &cobra.Command{ 145 | Use: "multisingle2single ", 146 | Short: "compare single instance redis and cluster data", 147 | Run: multisingle2singleCommandFunc, 148 | } 149 | sc.Flags().String("saddr", "127.0.0.1:6379", "Source redis address default is 127.0.0.1:6379,multi address splite by ','") 150 | sc.Flags().String("taddr", "127.0.0.1:6379", "Target redis addresses default is 127.0.0.1:6379") 151 | sc.Flags().String("spassword", "", "Source redis password") 152 | sc.Flags().String("tpassword", "", "Target redis password") 153 | sc.Flags().Int("sdb", 0, "Source redis DB number default is 0") 154 | sc.Flags().Int("tdb", 0, "Source redis DB number default is 0") 155 | sc.Flags().Int("batchsize", 50, "Compare List、Set、Zset type batch default is 50") 156 | sc.Flags().Int("threads", 0, "Compare threads default is cpu core number") 157 | sc.Flags().Int("ttldiff", 10000, "Diffrent of TTL,Allowed max ttl microseconds default is 10000 as ten seconds") 158 | sc.Flags().Int("comparetimes", 1, "compare loop times,default is 1") 159 | sc.Flags().Int("compareinterval", 1, "compare loop interval,default is 1 second") 160 | sc.Flags().Bool("report", false, "whether generate report default is false") 161 | return sc 162 | } 163 | 164 | func NewCluster2ClusterCommand() *cobra.Command { 165 | sc := &cobra.Command{ 166 | Use: "cluster2cluster ", 167 | Short: "compare single instance redis", 168 | Run: cluster2clusterCommandFunc, 169 | } 170 | //sc.AddCommand(NewTaskCreateSourceCommand()) 171 | 172 | sc.Flags().String("saddr", "127.0.0.1:6379", "Source redis address default is 127.0.0.1:6379,multi address splite by ','") 173 | sc.Flags().String("taddr", "127.0.0.1:6379", "Target redis addresses default is 127.0.0.1:6379") 174 | sc.Flags().String("spassword", "", "Source redis password") 175 | sc.Flags().String("tpassword", "", "Target redis password") 176 | //sc.Flags().Int("sdb", 0, "Source redis DB number default is 0") 177 | //sc.Flags().Int("tdb", 0, "Source redis DB number default is 0") 178 | sc.Flags().Int("batchsize", 50, "Compare List、Set、Zset type batch default is 50") 179 | sc.Flags().Int("threads", 0, "Compare threads default is cpu core number") 180 | sc.Flags().Int("ttldiff", 10000, "Diffrent of TTL,Allowed max ttl microseconds default is 10000 as ten seconds") 181 | sc.Flags().Int("comparetimes", 1, "compare loop times,default is 1") 182 | sc.Flags().Int("compareinterval", 1, "compare loop interval,default is 1 second") 183 | sc.Flags().Bool("report", false, "whether generate report default is false") 184 | return sc 185 | 186 | } 187 | 188 | func executeCommandFunc(cmd *cobra.Command, args []string) { 189 | if len(args) != 1 { 190 | cmd.PrintErrln(errors.New("Must input execute file path")) 191 | return 192 | } 193 | 194 | ymlbytes, err := ioutil.ReadFile(args[0]) 195 | if err != nil { 196 | cmd.PrintErrln(err) 197 | return 198 | } 199 | 200 | jsonbytes, err := yaml.YAMLToJSON(ymlbytes) 201 | if err != nil { 202 | cmd.PrintErrln(err) 203 | return 204 | } 205 | var rc RedisCompare 206 | 207 | json.Unmarshal(jsonbytes, &rc) 208 | 209 | execerr := rc.Execute() 210 | if execerr != nil { 211 | cmd.PrintErrln(execerr) 212 | } 213 | 214 | } 215 | 216 | func parametersCommandFunc(cmd *cobra.Command, args []string) { 217 | saddr, _ := cmd.Flags().GetString("saddr") 218 | taddr, _ := cmd.Flags().GetString("taddr") 219 | spassword, _ := cmd.Flags().GetString("spassword") 220 | tpassword, _ := cmd.Flags().GetString("tpassword") 221 | //report, _ := cmd.Flags().GetBool("report") 222 | 223 | sOpt := &redis.Options{ 224 | Addr: saddr, 225 | DB: 0, // use default DB 226 | } 227 | sOpt.Password = spassword 228 | sClient := commons.GetGoRedisClient(sOpt) 229 | 230 | topt := &redis.Options{ 231 | Addr: taddr, 232 | DB: 0, // use default DB 233 | } 234 | topt.Password = tpassword 235 | tclient := commons.GetGoRedisClient(topt) 236 | 237 | defer sClient.Close() 238 | defer tclient.Close() 239 | 240 | serr := commons.CheckRedisClientConnect(sClient) 241 | terr := commons.CheckRedisClientConnect(tclient) 242 | if serr != nil { 243 | cmd.PrintErrln(serr) 244 | return 245 | } 246 | 247 | if terr != nil { 248 | cmd.PrintErrln(terr) 249 | return 250 | } 251 | 252 | ce := &compare.CompoareEnvironment{ 253 | Sclinet: sClient, 254 | Tclient: tclient, 255 | } 256 | 257 | m := ce.DiffParameters() 258 | 259 | //排序 260 | var keys []string 261 | for k := range m { 262 | keys = append(keys, k) 263 | } 264 | sort.Strings(keys) 265 | var data [][]string 266 | for _, k := range keys { 267 | line := []string{ 268 | k, m[k][0], m[k][1], 269 | } 270 | data = append(data, line) 271 | } 272 | 273 | table := tablewriter.NewWriter(os.Stdout) 274 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 275 | table.SetAlignment(tablewriter.ALIGN_LEFT) 276 | table.SetColWidth(12) 277 | table.SetHeader([]string{"Parameters", sClient.Options().Addr, tclient.Options().Addr}) 278 | //table.SetBorder(false) 279 | table.AppendBulk(data) 280 | table.Render() 281 | 282 | } 283 | 284 | func single2singleCommandFunc(cmd *cobra.Command, args []string) { 285 | saddr, _ := cmd.Flags().GetString("saddr") 286 | taddr, _ := cmd.Flags().GetString("taddr") 287 | spassword, _ := cmd.Flags().GetString("spassword") 288 | tpassword, _ := cmd.Flags().GetString("tpassword") 289 | sdb, _ := cmd.Flags().GetInt("sdb") 290 | tdb, _ := cmd.Flags().GetInt("tdb") 291 | batchsize, _ := cmd.Flags().GetInt("batchsize") 292 | threas, _ := cmd.Flags().GetInt("threads") 293 | ttldiff, _ := cmd.Flags().GetInt("ttldiff") 294 | comparetimes, _ := cmd.Flags().GetInt("comparetimes") 295 | compareinterval, _ := cmd.Flags().GetInt("compareinterval") 296 | report, _ := cmd.Flags().GetBool("report") 297 | 298 | saddrstruct := SAddr{ 299 | Addr: saddr, 300 | Password: spassword, 301 | Dbs: []int{sdb}, 302 | } 303 | 304 | rc := RedisCompare{ 305 | Saddr: []SAddr{saddrstruct}, 306 | Taddr: taddr, 307 | Spassword: spassword, 308 | Tpassword: tpassword, 309 | Sdb: sdb, 310 | Tdb: tdb, 311 | BatchSize: batchsize, 312 | Threads: threas, 313 | TTLDiff: ttldiff, 314 | CompareTimes: comparetimes, 315 | CompareInterval: compareinterval, 316 | Report: report, 317 | Scenario: ScenarioSingle2single, 318 | } 319 | 320 | zaplogger.Sugar().Info(rc) 321 | err := rc.Single2Single() 322 | if err != nil { 323 | cmd.PrintErrln(err) 324 | } 325 | } 326 | 327 | func multisingle2singleCommandFunc(cmd *cobra.Command, args []string) { 328 | 329 | saddr, _ := cmd.Flags().GetString("saddr") 330 | taddr, _ := cmd.Flags().GetString("taddr") 331 | spassword, _ := cmd.Flags().GetString("spassword") 332 | tpassword, _ := cmd.Flags().GetString("tpassword") 333 | sdb, _ := cmd.Flags().GetInt("sdb") 334 | tdb, _ := cmd.Flags().GetInt("tdb") 335 | batchsize, _ := cmd.Flags().GetInt("batchsize") 336 | threas, _ := cmd.Flags().GetInt("threads") 337 | ttldiff, _ := cmd.Flags().GetInt("ttldiff") 338 | comparetimes, _ := cmd.Flags().GetInt("comparetimes") 339 | compareinterval, _ := cmd.Flags().GetInt("compareinterval") 340 | report, _ := cmd.Flags().GetBool("report") 341 | 342 | saddrstruct := SAddr{ 343 | Addr: saddr, 344 | Password: spassword, 345 | Dbs: []int{sdb}, 346 | } 347 | 348 | rc := RedisCompare{ 349 | Saddr: []SAddr{saddrstruct}, 350 | Taddr: taddr, 351 | Spassword: spassword, 352 | Tpassword: tpassword, 353 | Sdb: sdb, 354 | Tdb: tdb, 355 | BatchSize: batchsize, 356 | Threads: threas, 357 | TTLDiff: ttldiff, 358 | CompareTimes: comparetimes, 359 | CompareInterval: compareinterval, 360 | Report: report, 361 | Scenario: ScenarioMultiSingle2single, 362 | } 363 | 364 | err := rc.MultiSingle2Single() 365 | 366 | if err != nil { 367 | cmd.Println(err) 368 | } 369 | } 370 | 371 | func single2clusterCommandFunc(cmd *cobra.Command, args []string) { 372 | saddr, _ := cmd.Flags().GetString("saddr") 373 | taddr, _ := cmd.Flags().GetString("taddr") 374 | spassword, _ := cmd.Flags().GetString("spassword") 375 | tpassword, _ := cmd.Flags().GetString("tpassword") 376 | sdb, _ := cmd.Flags().GetInt("sdb") 377 | //tdb, _ := cmd.Flags().GetInt("tdb") 378 | batchsize, _ := cmd.Flags().GetInt("batchsize") 379 | threas, _ := cmd.Flags().GetInt("threads") 380 | ttldiff, _ := cmd.Flags().GetInt("ttldiff") 381 | comparetimes, _ := cmd.Flags().GetInt("comparetimes") 382 | compareinterval, _ := cmd.Flags().GetInt("compareinterval") 383 | report, _ := cmd.Flags().GetBool("report") 384 | 385 | saddrstruct := SAddr{ 386 | Addr: saddr, 387 | Password: spassword, 388 | Dbs: []int{sdb}, 389 | } 390 | 391 | rc := RedisCompare{ 392 | Saddr: []SAddr{saddrstruct}, 393 | Taddr: taddr, 394 | Spassword: spassword, 395 | Tpassword: tpassword, 396 | Sdb: sdb, 397 | //Tdb: tdb, 398 | BatchSize: batchsize, 399 | Threads: threas, 400 | TTLDiff: ttldiff, 401 | CompareTimes: comparetimes, 402 | CompareInterval: compareinterval, 403 | Report: report, 404 | Scenario: ScenarioSingle2cluster, 405 | } 406 | 407 | err := rc.Single2Cluster() 408 | if err != nil { 409 | cmd.Println(err) 410 | } 411 | } 412 | 413 | func cluster2clusterCommandFunc(cmd *cobra.Command, args []string) { 414 | saddr, _ := cmd.Flags().GetString("saddr") 415 | taddr, _ := cmd.Flags().GetString("taddr") 416 | spassword, _ := cmd.Flags().GetString("spassword") 417 | tpassword, _ := cmd.Flags().GetString("tpassword") 418 | //sdb, _ := cmd.Flags().GetInt("sdb") 419 | //tdb, _ := cmd.Flags().GetInt("tdb") 420 | batchsize, _ := cmd.Flags().GetInt("batchsize") 421 | threas, _ := cmd.Flags().GetInt("threads") 422 | ttldiff, _ := cmd.Flags().GetInt("ttldiff") 423 | comparetimes, _ := cmd.Flags().GetInt("comparetimes") 424 | compareinterval, _ := cmd.Flags().GetInt("compareinterval") 425 | report, _ := cmd.Flags().GetBool("report") 426 | 427 | saddrs := strings.Split(saddr, ",") 428 | var saddrstructs []SAddr 429 | 430 | for _, v := range saddrs { 431 | saddr := SAddr{ 432 | Addr: v, 433 | Password: spassword, 434 | } 435 | saddrstructs = append(saddrstructs, saddr) 436 | } 437 | 438 | rc := RedisCompare{ 439 | Saddr: saddrstructs, 440 | Taddr: taddr, 441 | Spassword: spassword, 442 | Tpassword: tpassword, 443 | //Sdb: sdb, 444 | //Tdb: tdb, 445 | BatchSize: batchsize, 446 | Threads: threas, 447 | TTLDiff: ttldiff, 448 | CompareTimes: comparetimes, 449 | CompareInterval: compareinterval, 450 | Report: report, 451 | Scenario: ScenarioCluster2cluster, 452 | } 453 | execerr := rc.Cluster2Cluster() 454 | if execerr != nil { 455 | cmd.PrintErrln(execerr) 456 | } 457 | } 458 | 459 | func (rc *RedisCompare) Execute() error { 460 | switch rc.Scenario { 461 | case ScenarioSingle2single: 462 | return rc.Single2Single() 463 | case ScenarioSingle2cluster: 464 | return rc.Single2Cluster() 465 | case ScenarioCluster2cluster: 466 | return rc.Cluster2Cluster() 467 | case ScenarioMultiSingle2single: 468 | return rc.MultiSingle2Single() 469 | case ScenarioMultiSingle2cluster: 470 | return rc.MultiSingle2Cluster() 471 | default: 472 | return errors.New("Scenario not exists") 473 | } 474 | } 475 | 476 | func (rc *RedisCompare) Single2Single() error { 477 | 478 | if len(rc.Saddr) == 0 { 479 | return errors.New("No saddrs") 480 | } 481 | 482 | if rc.CompareTimes < 1 { 483 | rc.CompareTimes = 1 484 | } 485 | saddr := rc.Saddr[0] 486 | 487 | sopt := &redis.Options{ 488 | Addr: saddr.Addr, 489 | DB: saddr.Dbs[0], 490 | } 491 | 492 | if saddr.Password != "" { 493 | sopt.Password = saddr.Password 494 | } 495 | sclient := commons.GetGoRedisClient(sopt) 496 | 497 | topt := &redis.Options{ 498 | Addr: rc.Taddr, 499 | DB: rc.Tdb, 500 | } 501 | 502 | if rc.Tpassword != "" { 503 | topt.Password = rc.Tpassword 504 | } 505 | 506 | tclient := commons.GetGoRedisClient(topt) 507 | 508 | defer sclient.Close() 509 | defer tclient.Close() 510 | 511 | //check redis 连通性 512 | sconnerr := commons.CheckRedisClientConnect(sclient) 513 | if sconnerr != nil { 514 | return errors.New(sclient.Options().Addr + " " + sconnerr.Error()) 515 | } 516 | 517 | tconnerr := commons.CheckRedisClientConnect(tclient) 518 | if tconnerr != nil { 519 | return errors.New(tclient.Options().Addr + " " + tconnerr.Error()) 520 | } 521 | 522 | //删除目录下上次运行时临时产生的result文件 523 | files, _ := filepath.Glob("*.result") 524 | for _, f := range files { 525 | if err := os.Remove(f); err != nil { 526 | panic(err) 527 | } 528 | } 529 | 530 | compare := &compare.CompareSingle2Single{ 531 | Source: sclient, 532 | Target: tclient, 533 | BatchSize: int64(rc.BatchSize), 534 | TTLDiff: float64(rc.TTLDiff), 535 | RecordResult: true, 536 | CompareThreads: rc.Threads, 537 | } 538 | var compares []interface{} 539 | compare.CompareDB() 540 | 541 | for i := 0; i < rc.CompareTimes-1; i++ { 542 | time.Sleep(time.Duration(rc.CompareInterval) * time.Second) 543 | compare.CompareKeysFromResultFile([]string{compare.ResultFile}) 544 | } 545 | 546 | comparemap, _ := commons.Struct2Map(compare) 547 | comparemap["Source"] = compare.Source.Options().Addr 548 | comparemap["Target"] = compare.Target.Options().Addr 549 | compares = append(compares, comparemap) 550 | 551 | //生成报告 552 | if rc.Report { 553 | GenReport([]string{compare.ResultFile}, compares) 554 | } 555 | return nil 556 | } 557 | 558 | func (rc *RedisCompare) Single2Cluster() error { 559 | if len(rc.Saddr) == 0 { 560 | return errors.New("No saddrs") 561 | } 562 | 563 | if rc.CompareTimes < 1 { 564 | rc.CompareTimes = 1 565 | } 566 | 567 | saddr := rc.Saddr[0] 568 | 569 | sopt := &redis.Options{ 570 | Addr: saddr.Addr, 571 | DB: saddr.Dbs[0], 572 | } 573 | 574 | if saddr.Password != "" { 575 | sopt.Password = saddr.Password 576 | } 577 | sclient := commons.GetGoRedisClient(sopt) 578 | 579 | topt := &redis.ClusterOptions{ 580 | Addrs: strings.Split(rc.Taddr, ","), 581 | } 582 | 583 | if rc.Tpassword != "" { 584 | topt.Password = rc.Tpassword 585 | } 586 | 587 | tclient := redis.NewClusterClient(topt) 588 | 589 | defer sclient.Close() 590 | defer tclient.Close() 591 | 592 | //check redis 连通性 593 | sconnerr := commons.CheckRedisClientConnect(sclient) 594 | if sconnerr != nil { 595 | return errors.New(sclient.Options().Addr + " " + sconnerr.Error()) 596 | } 597 | tconnerr := commons.CheckRedisClusterClientConnect(tclient) 598 | if tconnerr != nil { 599 | addrs := "" 600 | for k, v := range tclient.Options().Addrs { 601 | if k == len(tclient.Options().Addrs)-1 { 602 | addrs = addrs + v 603 | } else { 604 | addrs = addrs + v + ";" 605 | } 606 | 607 | } 608 | return errors.New(addrs + " " + sconnerr.Error()) 609 | } 610 | 611 | //删除目录下上次运行时临时产生的result文件 612 | files, _ := filepath.Glob("*.result") 613 | for _, f := range files { 614 | if err := os.Remove(f); err != nil { 615 | panic(err) 616 | } 617 | } 618 | 619 | compare := &compare.CompareSingle2Cluster{ 620 | Source: sclient, 621 | Target: tclient, 622 | BatchSize: int64(rc.BatchSize), 623 | TTLDiff: float64(rc.TTLDiff), 624 | RecordResult: true, 625 | CompareThreads: rc.Threads, 626 | } 627 | 628 | var compares []interface{} 629 | 630 | compare.CompareDB() 631 | 632 | for i := 0; i < rc.CompareTimes-1; i++ { 633 | time.Sleep(time.Duration(rc.CompareInterval) * time.Second) 634 | compare.CompareKeysFromResultFile([]string{compare.ResultFile}) 635 | } 636 | comparemap, _ := commons.Struct2Map(compare) 637 | comparemap["Source"] = compare.Source.Options().Addr 638 | comparemap["Target"] = compare.Target.Options().Addrs 639 | compares = append(compares, comparemap) 640 | 641 | //生成报告 642 | if rc.Report { 643 | GenReport([]string{compare.ResultFile}, compares) 644 | 645 | } 646 | return nil 647 | } 648 | 649 | func (rc *RedisCompare) MultiSingle2Single() error { 650 | 651 | if len(rc.Saddr) == 0 { 652 | return errors.New("No source address") 653 | } 654 | 655 | var sclients []*redis.Client 656 | 657 | if rc.CompareTimes < 1 { 658 | rc.CompareTimes = 1 659 | } 660 | 661 | for _, v := range rc.Saddr { 662 | 663 | if len(v.Dbs) == 0 { 664 | continue 665 | } 666 | for _, vdb := range v.Dbs { 667 | sopt := &redis.Options{ 668 | Addr: v.Addr, 669 | DB: vdb, 670 | } 671 | if v.Password != "" { 672 | sopt.Password = v.Password 673 | } 674 | sclient := commons.GetGoRedisClient(sopt) 675 | sclients = append(sclients, sclient) 676 | } 677 | 678 | } 679 | 680 | topt := &redis.Options{ 681 | Addr: rc.Taddr, 682 | DB: rc.Tdb, 683 | } 684 | 685 | if rc.Tpassword != "" { 686 | topt.Password = rc.Tpassword 687 | } 688 | 689 | tclient := commons.GetGoRedisClient(topt) 690 | 691 | defer tclient.Close() 692 | 693 | //check redis 连通性 694 | for _, v := range sclients { 695 | sconnerr := commons.CheckRedisClientConnect(v) 696 | if sconnerr != nil { 697 | return errors.New(v.Options().Addr + " " + sconnerr.Error()) 698 | } 699 | } 700 | tconnerr := commons.CheckRedisClientConnect(tclient) 701 | if tconnerr != nil { 702 | return errors.New(tclient.Options().Addr + " " + tconnerr.Error()) 703 | } 704 | 705 | //删除目录下上次运行时临时产生的result文件 706 | files, _ := filepath.Glob("*.result") 707 | for _, f := range files { 708 | if err := os.Remove(f); err != nil { 709 | panic(err) 710 | } 711 | } 712 | 713 | var resultfiles []string 714 | var compares []interface{} 715 | for _, v := range sclients { 716 | compare := &compare.CompareSingle2Single{ 717 | Source: v, 718 | Target: tclient, 719 | BatchSize: int64(rc.BatchSize), 720 | TTLDiff: float64(rc.TTLDiff), 721 | RecordResult: true, 722 | CompareThreads: rc.Threads, 723 | } 724 | 725 | compare.CompareDB() 726 | 727 | for i := 0; i < rc.CompareTimes-1; i++ { 728 | time.Sleep(time.Duration(rc.CompareInterval) * time.Second) 729 | rfile := compare.ResultFile 730 | compare.CompareKeysFromResultFile([]string{rfile}) 731 | } 732 | resultfiles = append(resultfiles, compare.ResultFile) 733 | comparemap, _ := commons.Struct2Map(compare) 734 | comparemap["Source"] = compare.Source.Options().Addr 735 | comparemap["Target"] = compare.Target.Options().Addr 736 | compares = append(compares, comparemap) 737 | 738 | } 739 | 740 | //生成报告 741 | if rc.Report { 742 | GenReport(resultfiles, compares) 743 | } 744 | for _, v := range sclients { 745 | v.Close() 746 | } 747 | 748 | return nil 749 | } 750 | 751 | func (rc *RedisCompare) MultiSingle2Cluster() error { 752 | 753 | if len(rc.Saddr) == 0 { 754 | return errors.New("No source address") 755 | } 756 | 757 | var sclients []*redis.Client 758 | 759 | if rc.CompareTimes < 1 { 760 | rc.CompareTimes = 1 761 | } 762 | 763 | for _, v := range rc.Saddr { 764 | if len(v.Dbs) == 0 { 765 | continue 766 | } 767 | for _, vdb := range v.Dbs { 768 | sopt := &redis.Options{ 769 | Addr: v.Addr, 770 | DB: vdb, 771 | } 772 | if v.Password != "" { 773 | sopt.Password = v.Password 774 | } 775 | sclient := commons.GetGoRedisClient(sopt) 776 | sclients = append(sclients, sclient) 777 | } 778 | } 779 | 780 | topt := &redis.ClusterOptions{ 781 | Addrs: strings.Split(rc.Taddr, ","), 782 | } 783 | 784 | if rc.Tpassword != "" { 785 | topt.Password = rc.Tpassword 786 | } 787 | 788 | tclient := redis.NewClusterClient(topt) 789 | defer tclient.Close() 790 | 791 | //check redis 连通性 792 | for _, v := range sclients { 793 | sconnerr := commons.CheckRedisClientConnect(v) 794 | if sconnerr != nil { 795 | return errors.New(v.Options().Addr + " " + sconnerr.Error()) 796 | } 797 | } 798 | tconnerr := commons.CheckRedisClusterClientConnect(tclient) 799 | if tconnerr != nil { 800 | addrs := "" 801 | for k, v := range tclient.Options().Addrs { 802 | if k == len(tclient.Options().Addrs)-1 { 803 | addrs = addrs + v 804 | } else { 805 | addrs = addrs + v + ";" 806 | } 807 | 808 | } 809 | return errors.New(addrs + " " + tconnerr.Error()) 810 | } 811 | 812 | //删除目录下上次运行时临时产生的result文件 813 | files, _ := filepath.Glob("*.result") 814 | for _, f := range files { 815 | if err := os.Remove(f); err != nil { 816 | panic(err) 817 | } 818 | } 819 | 820 | var resultfiles []string 821 | //compares := []*compare.CompareSingle2Cluster{} 822 | var compares []interface{} 823 | for _, v := range sclients { 824 | compare := &compare.CompareSingle2Cluster{ 825 | Source: v, 826 | Target: tclient, 827 | BatchSize: int64(rc.BatchSize), 828 | TTLDiff: float64(rc.TTLDiff), 829 | RecordResult: true, 830 | CompareThreads: rc.Threads, 831 | } 832 | 833 | compare.CompareDB() 834 | 835 | for i := 0; i < rc.CompareTimes-1; i++ { 836 | time.Sleep(time.Duration(rc.CompareInterval) * time.Second) 837 | rfile := compare.ResultFile 838 | compare.CompareKeysFromResultFile([]string{rfile}) 839 | } 840 | resultfiles = append(resultfiles, compare.ResultFile) 841 | comparemap, _ := commons.Struct2Map(compare) 842 | comparemap["Source"] = compare.Source.Options().Addr 843 | comparemap["Target"] = compare.Target.Options().Addrs 844 | compares = append(compares, comparemap) 845 | 846 | } 847 | 848 | //生成报告 849 | if rc.Report { 850 | GenReport(resultfiles, compares) 851 | 852 | } 853 | for _, v := range sclients { 854 | v.Close() 855 | } 856 | 857 | return nil 858 | } 859 | 860 | func (rc *RedisCompare) Cluster2Cluster() error { 861 | 862 | if len(rc.Saddr) == 0 { 863 | return errors.New("No source address") 864 | } 865 | 866 | var sclients []*redis.Client 867 | 868 | if rc.CompareTimes < 1 { 869 | rc.CompareTimes = 1 870 | } 871 | 872 | for _, v := range rc.Saddr { 873 | sopt := &redis.Options{ 874 | Addr: v.Addr, 875 | DB: 0, 876 | } 877 | if v.Password != "" { 878 | sopt.Password = v.Password 879 | } 880 | sclient := commons.GetGoRedisClient(sopt) 881 | sclients = append(sclients, sclient) 882 | } 883 | 884 | topt := &redis.ClusterOptions{ 885 | Addrs: strings.Split(rc.Taddr, ","), 886 | } 887 | 888 | if rc.Tpassword != "" { 889 | topt.Password = rc.Tpassword 890 | } 891 | 892 | tclient := redis.NewClusterClient(topt) 893 | defer tclient.Close() 894 | 895 | //check redis 连通性 896 | for _, v := range sclients { 897 | sconnerr := commons.CheckRedisClientConnect(v) 898 | if sconnerr != nil { 899 | if sconnerr != nil { 900 | return errors.New(v.Options().Addr + " " + sconnerr.Error()) 901 | } 902 | } 903 | } 904 | tconnerr := commons.CheckRedisClusterClientConnect(tclient) 905 | if tconnerr != nil { 906 | addrs := "" 907 | for k, v := range tclient.Options().Addrs { 908 | if k == len(tclient.Options().Addrs)-1 { 909 | addrs = addrs + v 910 | } else { 911 | addrs = addrs + v + ";" 912 | } 913 | 914 | } 915 | return errors.New(addrs + " " + tconnerr.Error()) 916 | } 917 | 918 | //删除目录下上次运行时临时产生的result文件 919 | files, _ := filepath.Glob("*.result") 920 | for _, f := range files { 921 | if err := os.Remove(f); err != nil { 922 | panic(err) 923 | } 924 | } 925 | 926 | var resultfiles []string 927 | var compares []interface{} 928 | for _, v := range sclients { 929 | compare := &compare.CompareSingle2Cluster{ 930 | Source: v, 931 | Target: tclient, 932 | BatchSize: int64(rc.BatchSize), 933 | TTLDiff: float64(rc.TTLDiff), 934 | RecordResult: true, 935 | CompareThreads: rc.Threads, 936 | } 937 | compare.CompareDB() 938 | for i := 0; i < rc.CompareTimes-1; i++ { 939 | time.Sleep(time.Duration(rc.CompareInterval) * time.Second) 940 | rfile := compare.ResultFile 941 | compare.CompareKeysFromResultFile([]string{rfile}) 942 | zaplogger.Sugar().Info(rfile + "|" + compare.ResultFile) 943 | } 944 | resultfiles = append(resultfiles, compare.ResultFile) 945 | 946 | comparemap, _ := commons.Struct2Map(compare) 947 | comparemap["Source"] = compare.Source.Options().Addr 948 | comparemap["Target"] = compare.Target.Options().Addrs 949 | compares = append(compares, comparemap) 950 | 951 | } 952 | 953 | //生成报告 954 | if rc.Report { 955 | GenReport(resultfiles, compares) 956 | } 957 | 958 | for _, v := range sclients { 959 | v.Close() 960 | } 961 | return nil 962 | } 963 | 964 | func GenReport(resultfiles []string, compares []interface{}) error { 965 | reportfile := "./compare_" + time.Now().Format("20060102150405") + ".rep" 966 | 967 | jsonBytes, _ := json.Marshal(compares) 968 | commons.AppendLineToFile(bytes.NewBuffer(jsonBytes), reportfile) 969 | for _, v := range resultfiles { 970 | fi, err := os.Open(v) 971 | defer fi.Close() 972 | if err != nil { 973 | return err 974 | } 975 | 976 | scanner := bufio.NewScanner(fi) 977 | for scanner.Scan() { 978 | line := scanner.Text() 979 | commons.AppendLineToFile(bytes.NewBuffer([]byte(line)), reportfile) 980 | } 981 | if err := scanner.Err(); err != nil { 982 | return err 983 | } 984 | } 985 | return nil 986 | } 987 | -------------------------------------------------------------------------------- /cmd/result.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "github.com/spf13/cobra" 7 | "github.com/tidwall/gjson" 8 | 9 | "github.com/olekukonko/tablewriter" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | // NewResultCommand return a config subcommand of rootCmd 15 | func NewResultCommand() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "result ", 18 | Short: "deal result or report file", 19 | } 20 | cmd.AddCommand(NewParseCommand()) 21 | return cmd 22 | } 23 | 24 | // NewParseCommand return a show subcommand of configCmd 25 | func NewParseCommand() *cobra.Command { 26 | sc := &cobra.Command{ 27 | Use: "parse ", 28 | Short: "parse result or report file", 29 | Run: parseResultFileCommandFunc, 30 | } 31 | return sc 32 | } 33 | 34 | func parseResultFileCommandFunc(cmd *cobra.Command, args []string) { 35 | 36 | if len(args) != 1 { 37 | cmd.PrintErrln(errors.New("Please input result or report file")) 38 | return 39 | } 40 | 41 | if !strings.HasSuffix(args[0], ".result") && !strings.HasSuffix(args[0], ".rep") { 42 | cmd.PrintErrln(errors.New("File must has suffix '.result' or '.rep'")) 43 | return 44 | } 45 | 46 | firestline := true 47 | fi, err := os.Open(args[0]) 48 | if err != nil { 49 | cmd.PrintErrln(err) 50 | return 51 | } 52 | defer fi.Close() 53 | 54 | metadata := [][]string{} 55 | data := [][]string{} 56 | scanner := bufio.NewScanner(fi) 57 | for scanner.Scan() { 58 | fileline := scanner.Text() 59 | if firestline && strings.HasSuffix(args[0], ".rep") { 60 | metaarray := gjson.Parse(fileline).Array() 61 | 62 | for _, v := range metaarray { 63 | line := []string{ 64 | gjson.Get(v.String(), "Source").String(), 65 | gjson.Get(v.String(), "Target").String(), 66 | gjson.Get(v.String(), "SourceDB").String(), 67 | gjson.Get(v.String(), "TargetDB").String(), 68 | gjson.Get(v.String(), "BatchSize").String(), 69 | gjson.Get(v.String(), "CompareThreads").String(), 70 | gjson.Get(v.String(), "KeyDiffReason").String(), 71 | } 72 | metadata = append(metadata, line) 73 | } 74 | firestline = false 75 | continue 76 | } 77 | 78 | source := "" 79 | target := "" 80 | sarray := gjson.Get(fileline, "Source").Array() 81 | tarray := gjson.Get(fileline, "Target").Array() 82 | 83 | for k, v := range sarray { 84 | if k == len(sarray)-1 { 85 | source = source + v.String() 86 | } else { 87 | source = source + v.String() + "\n" 88 | } 89 | 90 | } 91 | 92 | for k, v := range tarray { 93 | if k == len(tarray)-1 { 94 | target = target + v.String() 95 | } else { 96 | target = target + v.String() + "\n" 97 | } 98 | } 99 | 100 | line := []string{ 101 | source, 102 | target, 103 | gjson.Get(fileline, "Key").String(), 104 | gjson.Get(fileline, "SourceDB").String(), 105 | gjson.Get(fileline, "TargetDB").String(), 106 | gjson.Get(fileline, "KeyDiffReason").String(), 107 | } 108 | 109 | data = append(data, line) 110 | } 111 | 112 | if err := scanner.Err(); err != nil { 113 | cmd.PrintErrln(err) 114 | return 115 | } 116 | 117 | table := tablewriter.NewWriter(os.Stdout) 118 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 119 | table.SetAlignment(tablewriter.ALIGN_LEFT) 120 | table.SetColWidth(12) 121 | table.SetHeader([]string{"Source", "Target", "Key", "SourceDB", "TargetDB", "KeyDiffReason"}) 122 | //table.SetBorder(false) 123 | table.AppendBulk(data) 124 | table.Render() 125 | 126 | } 127 | -------------------------------------------------------------------------------- /commons/fileutil.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | ) 11 | 12 | //AppendLineToFile 向文件追加行 13 | func AppendLineToFile(line *bytes.Buffer, filename string) { 14 | lock.Lock() 15 | defer lock.Unlock() 16 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | defer f.Close() 22 | w := bufio.NewWriter(f) 23 | fmt.Fprintln(w, line.String()) 24 | w.Flush() 25 | } 26 | 27 | func WriteFile(content []byte) error { 28 | err := ioutil.WriteFile("output.txt", content, 0666) 29 | return err 30 | } 31 | 32 | // Exists 用于判断所给路径文件或文件夹是否存在 33 | func FileExists(path string) bool { 34 | _, err := os.Stat(path) //os.Stat获取文件信息 35 | if err != nil { 36 | if os.IsExist(err) { 37 | return true 38 | } 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | // IsDir 判断所给路径是否为文件夹 45 | func IsDir(path string) bool { 46 | s, err := os.Stat(path) 47 | if err != nil { 48 | return false 49 | } 50 | return s.IsDir() 51 | } 52 | 53 | //IsFile 判断所给路径是否为文件 54 | func IsFile(path string) bool { 55 | return !IsDir(path) 56 | } 57 | 58 | //复制文件 59 | func CopyFile(src string, dst string, buffersize int) error { 60 | buf := make([]byte, buffersize) 61 | source, err := os.Open(src) 62 | if err != nil { 63 | return err 64 | } 65 | defer source.Close() 66 | destination, err := os.Create(dst) 67 | if err != nil { 68 | return err 69 | } 70 | defer destination.Close() 71 | for { 72 | n, err := source.Read(buf) 73 | if err != nil && err != io.EOF { 74 | return err 75 | } 76 | if n == 0 { 77 | break 78 | } 79 | 80 | if _, err := destination.Write(buf[:n]); err != nil { 81 | return err 82 | } 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /commons/interfaceutil.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import "reflect" 4 | 5 | func IsNil(i interface{}) bool { 6 | vi := reflect.ValueOf(i) 7 | if vi.Kind() == reflect.Ptr { 8 | return vi.IsNil() 9 | } 10 | return false 11 | } 12 | -------------------------------------------------------------------------------- /commons/randutil.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | "github.com/satori/go.uuid" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 10 | 11 | //生成随机字符串 12 | func StringWithCharset(length int, charset string) string { 13 | var seededRand *rand.Rand = rand.New( 14 | rand.NewSource(time.Now().UnixNano())) 15 | b := make([]byte, length) 16 | for i := range b { 17 | b[i] = charset[seededRand.Intn(len(charset))] 18 | } 19 | return string(b) 20 | } 21 | 22 | func RandString(length int) string { 23 | //var seededRand *rand.Rand = rand.New( 24 | // rand.NewSource(time.Now().UnixNano())) 25 | rand.Seed(time.Now().UnixNano()) 26 | 27 | b := make([]byte, length) 28 | for i := range b { 29 | //b[i] = charset[seededRand.Intn(len(charset))] 30 | b[i] = charset[rand.Intn(len(charset))] 31 | 32 | } 33 | return string(b) 34 | } 35 | 36 | func GetUUID() string { 37 | return uuid.NewV4().String() 38 | } 39 | -------------------------------------------------------------------------------- /commons/randutil_test.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestStringWithCharset(t *testing.T) { 9 | var randing = RandString(10) 10 | fmt.Println(randing) 11 | } 12 | 13 | func TestGetUUID(t *testing.T) { 14 | fmt.Println(GetUUID()) 15 | } 16 | -------------------------------------------------------------------------------- /commons/redisutil.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | redis "github.com/go-redis/redis/v7" 5 | ) 6 | 7 | //GetGoRedisClient 获取redis client 8 | func GetGoRedisClient(opt *redis.Options) *redis.Client { 9 | client := redis.NewClient(opt) 10 | return client 11 | } 12 | 13 | func GetGoRedisConn(opt *redis.Options) *redis.Conn { 14 | client := redis.NewClient(opt) 15 | return client.Conn() 16 | } 17 | 18 | //redisserver联通性校验 19 | func CheckRedisClientConnect(r *redis.Client) error { 20 | _, err := r.Ping().Result() 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | func CheckRedisClusterClientConnect(r *redis.ClusterClient) error { 28 | _, err := r.Ping().Result() 29 | if err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /commons/report.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "time" 7 | ) 8 | 9 | type Report struct { 10 | ReportContent map[string]interface{} 11 | } 12 | 13 | func (r Report) Json() (jsonresult string, err error) { 14 | bodystr, err := json.MarshalIndent(r, "", " ") 15 | return string(bodystr), err 16 | } 17 | 18 | func (r Report) JsonToFile() error { 19 | 20 | now := time.Now().Format("20060102150405000") 21 | filename := "report_" + now + ".json" 22 | bodystr, err := json.MarshalIndent(r.ReportContent, "", " ") 23 | 24 | if err != nil { 25 | return err 26 | } 27 | writeerr := ioutil.WriteFile(filename, bodystr, 0666) 28 | if writeerr != nil { 29 | return writeerr 30 | } 31 | return nil 32 | 33 | } 34 | -------------------------------------------------------------------------------- /commons/stringutil.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import "strings" 4 | 5 | const replacement = "" 6 | 7 | var replacer = strings.NewReplacer( 8 | "\r\n", replacement, 9 | "\r", replacement, 10 | "\n", replacement, 11 | "\v", replacement, 12 | "\f", replacement, 13 | "\u0085", replacement, 14 | "\u2028", replacement, 15 | "\u2029", replacement, 16 | ) 17 | 18 | func Replacer(s string) string { 19 | return replacer.Replace(s) 20 | } 21 | -------------------------------------------------------------------------------- /commons/yamlutile.go: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | "os" 9 | "sync" 10 | ) 11 | 12 | var lock sync.Mutex 13 | 14 | //YamlFileToMap Convert yaml fil to map 15 | func YamlFileToMap(configfile string) (*map[interface{}]interface{}, error) { 16 | yamlmap := make(map[interface{}]interface{}) 17 | yamlFile, err := ioutil.ReadFile(configfile) 18 | if err != nil { 19 | return nil, err 20 | } 21 | err = yaml.Unmarshal(yamlFile, yamlmap) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &yamlmap, nil 26 | } 27 | 28 | //MapToYamlString conver map to yaml 29 | func MapToYamlString(yamlmap map[string]interface{}) (string, error) { 30 | lock.Lock() 31 | defer lock.Unlock() 32 | d, err := yaml.Marshal(&yamlmap) 33 | if err != nil { 34 | return "", err 35 | } 36 | return string(d), nil 37 | } 38 | 39 | func ParseJsonFile(filepath string) ([]byte, error) { 40 | jsonFile, err := os.Open(filepath) 41 | defer jsonFile.Close() 42 | 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | jsonbytes, err := ioutil.ReadAll(jsonFile) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return jsonbytes, nil 52 | } 53 | 54 | //struct 转 map 55 | func Struct2Map(content interface{}) (map[string]interface{}, error) { 56 | var structmap map[string]interface{} 57 | if marshalContent, err := json.Marshal(content); err != nil { 58 | return nil, err 59 | } else { 60 | d := json.NewDecoder(bytes.NewReader(marshalContent)) 61 | d.UseNumber() // 设置将float64转为一个number 62 | if err := d.Decode(&structmap); err != nil { 63 | return nil, err 64 | } else { 65 | for k, v := range structmap { 66 | structmap[k] = v 67 | } 68 | } 69 | } 70 | return structmap, nil 71 | } 72 | -------------------------------------------------------------------------------- /compare/compare.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import "rediscompare/globalzap" 4 | 5 | var zaplogger = globalzap.GetLogger() 6 | 7 | type CompareResult struct { 8 | IsEqual bool 9 | Source interface{} 10 | Target interface{} 11 | KeyDiffReason []interface{} 12 | KeyType string 13 | Key string 14 | SourceDB int //源redis DB number 15 | TargetDB int //目标redis DB number 16 | } 17 | 18 | func NewCompareResult() CompareResult { 19 | return CompareResult{ 20 | IsEqual: true, 21 | } 22 | } 23 | 24 | type CompareData interface { 25 | CompareDB() 26 | CompareKeys(keys []string) 27 | CompareString(key string) *CompareResult 28 | CompareList(key string) *CompareResult 29 | CompareHash(key string) *CompareResult 30 | CompareSet(key string) *CompareResult 31 | CompareZset(key string) *CompareResult 32 | } 33 | -------------------------------------------------------------------------------- /compare/compareenvironment.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "github.com/go-redis/redis/v7" 5 | ) 6 | 7 | type CompoareEnvironment struct { 8 | Sclinet *redis.Client 9 | Tclient *redis.Client 10 | } 11 | 12 | func (compare *CompoareEnvironment) DiffParameters() map[string][]string { 13 | 14 | m := make(map[string][]string) 15 | sparameters := GetInstanceParameters(compare.Sclinet.Conn()) 16 | tparameters := GetInstanceParameters(compare.Tclient.Conn()) 17 | 18 | for sk, sv := range sparameters { 19 | val := []string{} 20 | if tparameters[sk] != sv { 21 | val = append(val, sv) 22 | val = append(val, tparameters[sk]) 23 | m[sk] = val 24 | } 25 | } 26 | 27 | for tk, tv := range tparameters { 28 | val := []string{} 29 | if sparameters[tk] != tv { 30 | if len(m[tk]) == 0 { 31 | val = append(val, sparameters[tk]) 32 | val = append(val, tv) 33 | m[tk] = val 34 | } 35 | } 36 | 37 | } 38 | 39 | return m 40 | } 41 | 42 | func GetInstanceParameters(conn *redis.Conn) map[string]string { 43 | parameters := GetAllParametersNames() 44 | m := make(map[string]string) 45 | for _, v := range parameters { 46 | var s string = "" 47 | value := conn.ConfigGet(v).Val() 48 | 49 | if len(value) != 0 { 50 | for i := 0; i < len(value); i++ { 51 | if i == 0 { 52 | continue 53 | } 54 | if i == len(value)-1 { 55 | s = s + value[i].(string) 56 | } else { 57 | s = s + value[i].(string) + " " 58 | } 59 | } 60 | //fmt.Println(s) 61 | m[value[0].(string)] = s 62 | } 63 | } 64 | return m 65 | } 66 | 67 | func GetAllParametersNames() []string { 68 | return []string{"aclfile", 69 | "acllog-max-len", 70 | "activedefrag", 71 | "active-defrag-cycle-max", 72 | "active-defrag-cycle-min", 73 | "active-defrag-ignore-bytes", 74 | "active-defrag-max-scan-fields", 75 | "active-defrag-threshold-lower", 76 | "active-defrag-threshold-upper", 77 | "active-expire-effort", 78 | "activerehashing", 79 | "always-show-logo", 80 | "aof-load-truncated", 81 | "aof_rewrite_cpulist", 82 | "aof-rewrite-incremental-fsync", 83 | "aof-use-rdb-preamble", 84 | "appendfilename", 85 | "appendfsync", 86 | "appendonly", 87 | "auto-aof-rewrite-min-size", 88 | "auto-aof-rewrite-percentage", 89 | "bgsave_cpulist", 90 | "bind", 91 | "bio_cpulist", 92 | "client-output-buffer-limit", 93 | "client-query-buffer-limit", 94 | "cluster-allow-reads-when-down", 95 | "cluster-announce-bus-port", 96 | "cluster-announce-ip", 97 | "cluster-announce-port", 98 | "cluster-config-file", 99 | "cluster-enabled", 100 | "cluster-migration-barrier", 101 | "cluster-node-timeout", 102 | "cluster-replica-no-failover", 103 | "cluster-replica-validity-factor", 104 | "cluster-require-full-coverage", 105 | "cluster-slave-no-failover", 106 | "cluster-slave-validity-factor", 107 | "daemonize", 108 | "databases", 109 | "dbfilename", 110 | "dir", 111 | "dynamic-hz", 112 | "gopher-enabled", 113 | "hash-max-ziplist-entries", 114 | "hash-max-ziplist-value", 115 | "hll-sparse-max-bytes", 116 | "hz", 117 | "include", 118 | "io-threads", 119 | "io-threads-do-reads", 120 | "jemalloc-bg-thread", 121 | "latency-monitor-threshold", 122 | "lazyfree-lazy-eviction", 123 | "lazyfree-lazy-expire", 124 | "lazyfree-lazy-server-del", 125 | "lazyfree-lazy-user-del", 126 | "lfu-decay-time", 127 | "lfu-log-factor", 128 | "list-compress-depth", 129 | "list-max-ziplist-entries", 130 | "list-max-ziplist-size", 131 | "list-max-ziplist-value", 132 | "loadmodule", 133 | "logfile", 134 | "loglevel", 135 | "lua-time-limit", 136 | "masterauth", 137 | "masteruser", 138 | "maxclients", 139 | "maxmemory", 140 | "maxmemory-policy", 141 | "maxmemory-samples", 142 | "min-replicas-max-lag", 143 | "min-replicas-to-write", 144 | "min-slaves-max-lag", 145 | "min-slaves-to-write", 146 | "no-appendfsync-on-rewrite", 147 | "notify-keyspace-events", 148 | "oom-score-adj", 149 | "oom-score-adj-values", 150 | "otify-keyspace-events", 151 | "pidfile", 152 | "port", 153 | "protected-mode", 154 | "proto-max-bulk-len", 155 | "rdbchecksum", 156 | "rdbcompression", 157 | "rdb-del-sync-files", 158 | "rdb-save-incremental-fsync", 159 | "rename-command", 160 | "repl-backlog-size", 161 | "repl-backlog-ttl", 162 | "repl-disable-tcp-nodelay", 163 | "repl-diskless-load", 164 | "repl-diskless-sync", 165 | "repl-diskless-sync-delay", 166 | "replica-announce-ip", 167 | "replica-announce-port", 168 | "replica-ignore-maxmemory", 169 | "replica-lazy-flush", 170 | "replicaof", 171 | "replica-priority", 172 | "replica-read-only", 173 | "replica-serve-stale-data", 174 | "repl-ping-replica-period", 175 | "repl-ping-slave-period", 176 | "repl-timeout", 177 | "requirepass", 178 | "save", 179 | "server_cpulist", 180 | "set-max-intset-entries", 181 | "slave-announce-ip", 182 | "slave-announce-port", 183 | "slave-lazy-flush", 184 | "slaveof", 185 | "slave-priority", 186 | "slave-read-only", 187 | "slave-serve-stale-data", 188 | "slowlog-max-len", 189 | "stop-writes-on-bgsave-error", 190 | "stream-node-max-bytes", 191 | "stream-node-max-entries", 192 | "supervised", 193 | "syslog-enabled", 194 | "syslog-facility", 195 | "syslog-ident", 196 | "tcp-backlog", 197 | "tcp-keepalive", 198 | "timeout", 199 | "tls-auth-clients", 200 | "tls-ca-cert-dir", 201 | "tls-ca-cert-file", 202 | "tls-cert-file", 203 | "tls-ciphers", 204 | "tls-ciphersuites", 205 | "tls-cluster", 206 | "tls-dh-params-file", 207 | "tls-key-file", 208 | "tls-port", 209 | "tls-prefer-server-ciphers", 210 | "tls-protocols", 211 | "tls-replication", 212 | "tls-session-cache-size", 213 | "tls-session-cache-timeout", 214 | "tls-session-caching", 215 | "tracking-table-max-keys", 216 | "unixsocket", 217 | "unixsocketperm", 218 | "zset-max-ziplist-entries", 219 | "zset-max-ziplist-value"} 220 | 221 | } 222 | -------------------------------------------------------------------------------- /compare/compareenvironment_test.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis/v7" 6 | "rediscompare/commons" 7 | "testing" 8 | ) 9 | 10 | func TestGetinfo(t *testing.T) { 11 | saddr := "114.67.100.239:6379" 12 | sopt := &redis.Options{ 13 | Addr: saddr, 14 | DB: 0, // use default DB 15 | } 16 | sopt.Password = "redistest0102" 17 | sclient := commons.GetGoRedisClient(sopt) 18 | 19 | taddr := "114.67.83.163:16379" 20 | topt := &redis.Options{ 21 | Addr: taddr, 22 | DB: 0, // use default DB 23 | } 24 | topt.Password = "testredis0102" 25 | tclient := commons.GetGoRedisClient(topt) 26 | 27 | defer sclient.Close() 28 | defer tclient.Close() 29 | 30 | fmt.Println(tclient.Ping()) 31 | ce := &CompoareEnvironment{ 32 | Sclinet: sclient, 33 | Tclient: tclient, 34 | } 35 | 36 | m := ce.DiffParameters() 37 | fmt.Println(m) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /compare/comparesingle2cluster.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "github.com/go-redis/redis/v7" 8 | "github.com/panjf2000/ants/v2" 9 | "github.com/tidwall/gjson" 10 | "go.uber.org/zap" 11 | "math" 12 | "os" 13 | "rediscompare/commons" 14 | "runtime" 15 | "strconv" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | type CompareSingle2Cluster struct { 21 | Source *redis.Client //源redis single 22 | Target *redis.ClusterClient //目标redis single 23 | RecordResult bool 24 | ResultFile string 25 | BatchSize int64 //比较List、Set、Zset类型时的每批次值的数量 26 | CompareThreads int //比较db线程数量 27 | TTLDiff float64 //TTL最小差值 28 | SourceDB int //源redis DB number 29 | TargetDB int //目标redis DB number 30 | } 31 | 32 | func (compare *CompareSingle2Cluster) CompareDB() { 33 | resultfilestring := "./" + "compare_" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".result" 34 | if compare.ResultFile == "" { 35 | compare.ResultFile = resultfilestring 36 | } 37 | 38 | wg := sync.WaitGroup{} 39 | threads := runtime.NumCPU() 40 | if compare.CompareThreads > 0 { 41 | threads = compare.CompareThreads 42 | } 43 | cursor := uint64(0) 44 | zaplogger.Sugar().Info("CompareSingle2Cluster DB beging") 45 | ticker := time.NewTicker(time.Second * 20) 46 | defer ticker.Stop() 47 | 48 | pool, err := ants.NewPool(threads) 49 | 50 | if err != nil { 51 | zaplogger.Sugar().Error(err) 52 | return 53 | } 54 | defer pool.Release() 55 | 56 | for { 57 | result, c, err := compare.Source.Scan(cursor, "*", compare.BatchSize).Result() 58 | 59 | if err != nil { 60 | zaplogger.Sugar().Info(result, c, err) 61 | return 62 | } 63 | 64 | //当pool有活动worker时提交异步任务 65 | for { 66 | if pool.Free() > 0 { 67 | wg.Add(1) 68 | pool.Submit(func() { 69 | compare.CompareKeys(result) 70 | wg.Done() 71 | }) 72 | break 73 | } 74 | } 75 | cursor = c 76 | 77 | if c == 0 { 78 | break 79 | } 80 | select { 81 | case <-ticker.C: 82 | zaplogger.Sugar().Info("Comparing...") 83 | default: 84 | continue 85 | } 86 | } 87 | wg.Wait() 88 | zaplogger.Sugar().Info("CompareSingle2Cluster End") 89 | } 90 | 91 | func (compare *CompareSingle2Cluster) CompareKeysFromResultFile(filespath []string) error { 92 | resultfilestring := "./" + "compare_" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".result" 93 | compare.ResultFile = resultfilestring 94 | 95 | for _, v := range filespath { 96 | fi, err := os.Open(v) 97 | if err != nil { 98 | return err 99 | } 100 | defer fi.Close() 101 | 102 | scanner := bufio.NewScanner(fi) 103 | for scanner.Scan() { 104 | line := scanner.Text() 105 | 106 | key := gjson.Get(line, "Key").String() 107 | 108 | if key != "" { 109 | compare.CompareKeys([]string{key}) 110 | } 111 | } 112 | 113 | if err := scanner.Err(); err != nil { 114 | return err 115 | } 116 | } 117 | return nil 118 | } 119 | 120 | func (compare *CompareSingle2Cluster) CompareKeys(keys []string) { 121 | var result *CompareResult 122 | for _, v := range keys { 123 | keytype, err := compare.Source.Type(v).Result() 124 | if err != nil { 125 | zaplogger.Sugar().Error(err) 126 | continue 127 | } 128 | result = nil 129 | switch { 130 | case keytype == "string": 131 | result = compare.CompareString(v) 132 | case keytype == "list": 133 | result = compare.CompareList(v) 134 | case keytype == "set": 135 | result = compare.CompareSet(v) 136 | case keytype == "zset": 137 | result = compare.CompareZset(v) 138 | case keytype == "hash": 139 | result = compare.CompareHash(v) 140 | default: 141 | zaplogger.Info("No type find in compare list", zap.String("key", v), zap.String("type", keytype)) 142 | } 143 | 144 | if result != nil && !result.IsEqual { 145 | zaplogger.Info("", zap.Any("CompareResult", result)) 146 | if compare.RecordResult { 147 | jsonBytes, _ := json.Marshal(result) 148 | commons.AppendLineToFile(bytes.NewBuffer(jsonBytes), compare.ResultFile) 149 | } 150 | } 151 | } 152 | } 153 | 154 | func (compare *CompareSingle2Cluster) CompareString(key string) *CompareResult { 155 | 156 | //比较key的存在状态是否一致 157 | result := compare.KeyExistsStatusEqual(key) 158 | if !result.IsEqual { 159 | return result 160 | } 161 | 162 | //比较string value是否一致 163 | result = compare.CompareStringVal(key) 164 | if !result.IsEqual { 165 | return result 166 | } 167 | 168 | //比较ttl差值是否在允许范围内 169 | result = compare.DiffTTLOver(key) 170 | if !result.IsEqual { 171 | return result 172 | } 173 | 174 | compareresult := NewCompareResult() 175 | compareresult.Key = key 176 | compareresult.KeyType = "string" 177 | compareresult.SourceDB = compare.SourceDB 178 | compareresult.TargetDB = compare.TargetDB 179 | 180 | return &compareresult 181 | } 182 | 183 | func (compare *CompareSingle2Cluster) CompareList(key string) *CompareResult { 184 | 185 | result := compare.KeyExistsStatusEqual(key) 186 | if !result.IsEqual { 187 | return result 188 | } 189 | 190 | result = compare.CompareListLen(key) 191 | if !result.IsEqual { 192 | return result 193 | } 194 | 195 | result = compare.DiffTTLOver(key) 196 | if !result.IsEqual { 197 | return result 198 | } 199 | 200 | result = compare.CompareListIndexVal(key) 201 | if !result.IsEqual { 202 | return result 203 | } 204 | 205 | compareresult := NewCompareResult() 206 | compareresult.Key = key 207 | compareresult.KeyType = "list" 208 | compareresult.SourceDB = compare.SourceDB 209 | compareresult.TargetDB = compare.TargetDB 210 | return &compareresult 211 | 212 | } 213 | 214 | func (compare *CompareSingle2Cluster) CompareHash(key string) *CompareResult { 215 | 216 | result := compare.KeyExistsStatusEqual(key) 217 | if !result.IsEqual { 218 | return result 219 | } 220 | 221 | result = compare.CompareHashLen(key) 222 | if !result.IsEqual { 223 | return result 224 | } 225 | 226 | result = compare.CompareHashFieldVal(key) 227 | if !result.IsEqual { 228 | return result 229 | } 230 | 231 | compareresult := NewCompareResult() 232 | compareresult.Key = key 233 | compareresult.KeyType = "hash" 234 | compareresult.SourceDB = compare.SourceDB 235 | compareresult.TargetDB = compare.TargetDB 236 | return &compareresult 237 | } 238 | 239 | func (compare *CompareSingle2Cluster) CompareSet(key string) *CompareResult { 240 | 241 | result := compare.KeyExistsStatusEqual(key) 242 | if !result.IsEqual { 243 | return result 244 | } 245 | 246 | result = compare.CompareSetLen(key) 247 | if !result.IsEqual { 248 | return result 249 | } 250 | 251 | result = compare.DiffTTLOver(key) 252 | if !result.IsEqual { 253 | return result 254 | } 255 | 256 | result = compare.CompareSetMember(key) 257 | if !result.IsEqual { 258 | return result 259 | } 260 | 261 | compareresult := NewCompareResult() 262 | compareresult.Key = key 263 | compareresult.KeyType = "set" 264 | compareresult.SourceDB = compare.SourceDB 265 | compareresult.TargetDB = compare.TargetDB 266 | return &compareresult 267 | } 268 | 269 | func (compare *CompareSingle2Cluster) CompareZset(key string) *CompareResult { 270 | result := compare.KeyExistsStatusEqual(key) 271 | if !result.IsEqual { 272 | return result 273 | } 274 | 275 | result = compare.CompareZsetLen(key) 276 | if !result.IsEqual { 277 | return result 278 | } 279 | 280 | result = compare.DiffTTLOver(key) 281 | if !result.IsEqual { 282 | return result 283 | } 284 | 285 | result = compare.CompareZsetMemberScore(key) 286 | if !result.IsEqual { 287 | return result 288 | } 289 | 290 | compareresult := NewCompareResult() 291 | compareresult.Key = key 292 | compareresult.KeyType = "zset" 293 | compareresult.SourceDB = compare.SourceDB 294 | compareresult.TargetDB = compare.TargetDB 295 | return &compareresult 296 | } 297 | 298 | //判断key在source和target同时不存在 299 | func (compare *CompareSingle2Cluster) KeyExistsStatusEqual(key string) *CompareResult { 300 | 301 | compareresult := NewCompareResult() 302 | reason := make(map[string]interface{}) 303 | compareresult.Target = compare.Target.Options().Addrs 304 | compareresult.Key = key 305 | compareresult.Source = compare.Source.Options().Addr 306 | compareresult.SourceDB = compare.SourceDB 307 | compareresult.TargetDB = compare.TargetDB 308 | 309 | sourceexists := KeyExists(compare.Source, key) 310 | targetexists := KeyExistsInCluster(compare.Target, key) 311 | 312 | if sourceexists == targetexists { 313 | return &compareresult 314 | } 315 | 316 | compareresult.IsEqual = false 317 | reason["description"] = "Source or Target key not exists" 318 | reason["source"] = sourceexists 319 | reason["target"] = targetexists 320 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 321 | return &compareresult 322 | } 323 | 324 | //比较Zset member以及sore值是否一致 325 | func (compare *CompareSingle2Cluster) CompareZsetMemberScore(key string) *CompareResult { 326 | compareresult := NewCompareResult() 327 | reason := make(map[string]interface{}) 328 | 329 | compareresult.Target = compare.Target.Options().Addrs 330 | compareresult.Source = compare.Source.Options().Addr 331 | compareresult.Key = key 332 | compareresult.KeyType = "Zset" 333 | compareresult.SourceDB = compare.SourceDB 334 | compareresult.TargetDB = compare.TargetDB 335 | 336 | cursor := uint64(0) 337 | for { 338 | sourceresult, c, err := compare.Source.ZScan(key, cursor, "*", compare.BatchSize).Result() 339 | if err != nil { 340 | compareresult.IsEqual = false 341 | reason["description"] = "Source zscan error" 342 | reason["zscanerror"] = err.Error() 343 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 344 | return &compareresult 345 | } 346 | 347 | for i := 0; i < len(sourceresult); i = i + 2 { 348 | sourecemember := sourceresult[i] 349 | sourcescore, err := strconv.ParseFloat(sourceresult[i+1], 64) 350 | if err != nil { 351 | compareresult.IsEqual = false 352 | reason["description"] = "Convert sourcescore to float64 error" 353 | reason["floattostringerror"] = err.Error() 354 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 355 | return &compareresult 356 | } 357 | 358 | intcmd := compare.Target.ZRank(key, sourecemember) 359 | targetscore := compare.Target.ZScore(key, sourecemember).Val() 360 | 361 | if intcmd == nil { 362 | compareresult.IsEqual = false 363 | reason["description"] = "Source zset member not exists in Target" 364 | reason["member"] = sourecemember 365 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 366 | return &compareresult 367 | } 368 | 369 | if targetscore != sourcescore { 370 | compareresult.IsEqual = false 371 | reason["description"] = "zset member score not equal" 372 | reason["member"] = sourecemember 373 | reason["sourcescore"] = sourcescore 374 | reason["targetscore"] = targetscore 375 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 376 | return &compareresult 377 | } 378 | 379 | } 380 | 381 | cursor = c 382 | if c == 0 { 383 | break 384 | } 385 | } 386 | return &compareresult 387 | } 388 | 389 | //比较zset 长度是否一致 390 | func (compare *CompareSingle2Cluster) CompareZsetLen(key string) *CompareResult { 391 | compareresult := NewCompareResult() 392 | reason := make(map[string]interface{}) 393 | 394 | compareresult.Target = compare.Target.Options().Addrs 395 | compareresult.Source = compare.Source.Options().Addr 396 | compareresult.Key = key 397 | compareresult.KeyType = "Zset" 398 | compareresult.SourceDB = compare.SourceDB 399 | compareresult.TargetDB = compare.TargetDB 400 | 401 | sourcelen := compare.Source.ZCard(key).Val() 402 | targetlen := compare.Target.ZCard(key).Val() 403 | if sourcelen != targetlen { 404 | compareresult.IsEqual = false 405 | reason["description"] = "Zset length not equal" 406 | reason["sourcelen"] = sourcelen 407 | reason["targetlen"] = targetlen 408 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 409 | return &compareresult 410 | } 411 | return &compareresult 412 | } 413 | 414 | //比较set member 是否一致 415 | func (compare *CompareSingle2Cluster) CompareSetMember(key string) *CompareResult { 416 | compareresult := NewCompareResult() 417 | reason := make(map[string]interface{}) 418 | 419 | compareresult.Target = compare.Target.Options().Addrs 420 | compareresult.Source = compare.Source.Options().Addr 421 | compareresult.Key = key 422 | compareresult.KeyType = "set" 423 | compareresult.SourceDB = compare.SourceDB 424 | compareresult.TargetDB = compare.TargetDB 425 | 426 | cursor := uint64(0) 427 | for { 428 | sourceresult, c, err := compare.Source.SScan(key, cursor, "*", compare.BatchSize).Result() 429 | if err != nil { 430 | compareresult.IsEqual = false 431 | reason["description"] = "Source sscan error" 432 | reason["sscanerror"] = err.Error() 433 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 434 | return &compareresult 435 | } 436 | 437 | for _, v := range sourceresult { 438 | if !compare.Target.SIsMember(key, v).Val() { 439 | compareresult.IsEqual = false 440 | reason["description"] = "Source set member not exists in Target" 441 | reason["member"] = v 442 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 443 | return &compareresult 444 | } 445 | } 446 | 447 | cursor = c 448 | if c == 0 { 449 | break 450 | } 451 | } 452 | return &compareresult 453 | } 454 | 455 | //比较set长度 456 | func (compare *CompareSingle2Cluster) CompareSetLen(key string) *CompareResult { 457 | compareresult := NewCompareResult() 458 | reason := make(map[string]interface{}) 459 | compareresult.Target = compare.Target.Options().Addrs 460 | compareresult.Source = compare.Source.Options().Addr 461 | compareresult.Key = key 462 | compareresult.KeyType = "set" 463 | compareresult.SourceDB = compare.SourceDB 464 | compareresult.TargetDB = compare.TargetDB 465 | 466 | sourcelen := compare.Source.SCard(key).Val() 467 | targetlen := compare.Target.SCard(key).Val() 468 | if sourcelen != targetlen { 469 | compareresult.IsEqual = false 470 | reason["description"] = "Set length not equal" 471 | reason["sourcelen"] = sourcelen 472 | reason["targetlen"] = targetlen 473 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 474 | return &compareresult 475 | } 476 | return &compareresult 477 | } 478 | 479 | //比较hash field value 返回首个不相等的field 480 | func (compare *CompareSingle2Cluster) CompareHashFieldVal(key string) *CompareResult { 481 | compareresult := NewCompareResult() 482 | reason := make(map[string]interface{}) 483 | compareresult.Target = compare.Target.Options().Addrs 484 | compareresult.Source = compare.Source.Options().Addr 485 | compareresult.Key = key 486 | compareresult.KeyType = "hash" 487 | compareresult.SourceDB = compare.SourceDB 488 | compareresult.TargetDB = compare.TargetDB 489 | 490 | cursor := uint64(0) 491 | for { 492 | sourceresult, c, err := compare.Source.HScan(key, cursor, "*", compare.BatchSize).Result() 493 | 494 | if err != nil { 495 | compareresult.IsEqual = false 496 | reason["description"] = "Source hscan error" 497 | reason["hscanerror"] = err.Error() 498 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 499 | return &compareresult 500 | } 501 | 502 | for i := 0; i < len(sourceresult); i = i + 2 { 503 | targetfieldval := compare.Target.HGet(key, sourceresult[i]).Val() 504 | if targetfieldval != sourceresult[i+1] { 505 | compareresult.IsEqual = false 506 | reason["description"] = "Field value not equal" 507 | reason["field"] = sourceresult[i] 508 | reason["sourceval"] = sourceresult[i+1] 509 | reason["targetval"] = targetfieldval 510 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 511 | return &compareresult 512 | } 513 | } 514 | cursor = c 515 | if c == uint64(0) { 516 | break 517 | } 518 | } 519 | return &compareresult 520 | } 521 | 522 | //比较hash长度 523 | func (compare *CompareSingle2Cluster) CompareHashLen(key string) *CompareResult { 524 | compareresult := NewCompareResult() 525 | reason := make(map[string]interface{}) 526 | compareresult.Target = compare.Target.Options().Addrs 527 | compareresult.Source = compare.Source.Options().Addr 528 | compareresult.Key = key 529 | compareresult.KeyType = "hash" 530 | compareresult.SourceDB = compare.SourceDB 531 | compareresult.TargetDB = compare.TargetDB 532 | 533 | sourcelen := compare.Source.HLen(key).Val() 534 | targetlen := compare.Target.HLen(key).Val() 535 | 536 | if sourcelen != targetlen { 537 | 538 | compareresult.IsEqual = false 539 | reason["description"] = "Hash length not equal" 540 | reason["sourcelen"] = sourcelen 541 | reason["targetlen"] = targetlen 542 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 543 | return &compareresult 544 | } 545 | return &compareresult 546 | } 547 | 548 | //比较list index对应值是否一致,返回第一条错误的index以及源和目标对应的值 549 | func (compare *CompareSingle2Cluster) CompareListIndexVal(key string) *CompareResult { 550 | compareresult := NewCompareResult() 551 | reason := make(map[string]interface{}) 552 | compareresult.Target = compare.Target.Options().Addrs 553 | compareresult.Source = compare.Source.Options().Addr 554 | compareresult.Key = key 555 | compareresult.KeyType = "list" 556 | compareresult.SourceDB = compare.SourceDB 557 | compareresult.TargetDB = compare.TargetDB 558 | 559 | sourcelen := compare.Source.LLen(key).Val() 560 | //targetlen := compare.Target.LLen(key).Val() 561 | 562 | compareresult.Key = key 563 | quotient := sourcelen / compare.BatchSize // integer division, decimals are truncated 564 | remainder := sourcelen % compare.BatchSize 565 | 566 | if quotient != 0 { 567 | var lrangeend int64 568 | for i := int64(0); i < quotient; i++ { 569 | if i == quotient-int64(1) { 570 | lrangeend = quotient * compare.BatchSize 571 | } else { 572 | lrangeend = (compare.BatchSize - 1) + i*compare.BatchSize 573 | } 574 | sourcevalues := compare.Source.LRange(key, int64(0)+i*compare.BatchSize, lrangeend).Val() 575 | targetvalues := compare.Target.LRange(key, int64(0)+i*compare.BatchSize, lrangeend).Val() 576 | for k, v := range sourcevalues { 577 | if targetvalues[k] != v { 578 | compareresult.IsEqual = false 579 | reason["description"] = "List index value not equal" 580 | reason["Index"] = int64(k) + i*compare.BatchSize 581 | reason["sourceval"] = v 582 | reason["targetval"] = targetvalues[k] 583 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 584 | return &compareresult 585 | } 586 | } 587 | } 588 | } 589 | 590 | if remainder != 0 { 591 | var rangstart int64 592 | 593 | if quotient == int64(0) { 594 | rangstart = int64(0) 595 | } else { 596 | rangstart = quotient*compare.BatchSize + 1 597 | } 598 | 599 | sourcevalues := compare.Source.LRange(key, rangstart, remainder+quotient*compare.BatchSize).Val() 600 | targetvalues := compare.Target.LRange(key, rangstart, remainder+quotient*compare.BatchSize).Val() 601 | for k, v := range sourcevalues { 602 | if targetvalues[k] != v { 603 | compareresult.IsEqual = false 604 | reason["description"] = "List index value not equal" 605 | reason["Index"] = int64(k) + rangstart 606 | reason["sourceval"] = v 607 | reason["targetval"] = targetvalues[k] 608 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 609 | return &compareresult 610 | } 611 | } 612 | } 613 | 614 | return &compareresult 615 | 616 | } 617 | 618 | //比较list长度是否一致 619 | func (compare *CompareSingle2Cluster) CompareListLen(key string) *CompareResult { 620 | compareresult := NewCompareResult() 621 | reason := make(map[string]interface{}) 622 | compareresult.Target = compare.Target.Options().Addrs 623 | compareresult.Source = compare.Source.Options().Addr 624 | compareresult.Key = key 625 | compareresult.KeyType = "list" 626 | compareresult.SourceDB = compare.SourceDB 627 | compareresult.TargetDB = compare.TargetDB 628 | 629 | sourcelen := compare.Source.LLen(key).Val() 630 | targetlen := compare.Target.LLen(key).Val() 631 | 632 | compareresult.Key = key 633 | if sourcelen != targetlen { 634 | compareresult.IsEqual = false 635 | reason["description"] = "List length not equal" 636 | reason["sourcelen"] = sourcelen 637 | reason["targetlen"] = targetlen 638 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 639 | return &compareresult 640 | } 641 | return &compareresult 642 | } 643 | 644 | //对比string类型value是否一致 645 | func (compare *CompareSingle2Cluster) CompareStringVal(key string) *CompareResult { 646 | compareresult := NewCompareResult() 647 | reason := make(map[string]interface{}) 648 | compareresult.Target = compare.Target.Options().Addrs 649 | compareresult.Source = compare.Source.Options().Addr 650 | compareresult.Key = key 651 | compareresult.KeyType = "string" 652 | compareresult.SourceDB = compare.SourceDB 653 | compareresult.TargetDB = compare.TargetDB 654 | 655 | sourceval := compare.Source.Get(key).Val() 656 | targetval := compare.Target.Get(key).Val() 657 | compareresult.Key = key 658 | if sourceval != targetval { 659 | compareresult.IsEqual = false 660 | reason["description"] = "String value not equal" 661 | reason["sval"] = sourceval 662 | reason["tval"] = targetval 663 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 664 | return &compareresult 665 | } 666 | return &compareresult 667 | } 668 | 669 | //对比key TTl差值 670 | func (compare *CompareSingle2Cluster) DiffTTLOver(key string) *CompareResult { 671 | compareresult := NewCompareResult() 672 | reason := make(map[string]interface{}) 673 | compareresult.Target = compare.Target.Options().Addrs 674 | compareresult.Source = compare.Source.Options().Addr 675 | compareresult.Key = key 676 | compareresult.KeyType = "string" 677 | compareresult.SourceDB = compare.SourceDB 678 | compareresult.TargetDB = compare.TargetDB 679 | 680 | sourcettl := compare.Source.PTTL(key).Val().Milliseconds() 681 | targetttl := compare.Target.PTTL(key).Val().Milliseconds() 682 | 683 | sub := targetttl - sourcettl 684 | if math.Abs(float64(sub)) > compare.TTLDiff { 685 | compareresult.IsEqual = false 686 | reason["description"] = "Key ttl difference is too large" 687 | reason["TTLDiff"] = int64(math.Abs(float64(sub))) 688 | reason["sourcettl"] = sourcettl 689 | reason["targetttl"] = targetttl 690 | 691 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 692 | return &compareresult 693 | } 694 | return &compareresult 695 | } 696 | 697 | func KeyExistsInCluster(client *redis.ClusterClient, key string) bool { 698 | exists := client.Exists(key).Val() 699 | if exists == int64(1) { 700 | return true 701 | } else { 702 | return false 703 | } 704 | } 705 | -------------------------------------------------------------------------------- /compare/comparesingle2cluster_test.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "github.com/go-redis/redis/v7" 5 | "rediscompare/commons" 6 | "testing" 7 | ) 8 | 9 | func TestCompareSingle2Single_CompareDB(t *testing.T) { 10 | saddr := "114.67.100.239:6379" 11 | opt := &redis.Options{ 12 | Addr: saddr, 13 | DB: 0, // use default DB 14 | } 15 | opt.Password = "redistest0102" 16 | sclient := commons.GetGoRedisClient(opt) 17 | 18 | //compare := CompareSingle2Single{ 19 | // Source: client, Target: client, BatchSize: 10, 20 | //} 21 | 22 | tclusterclient := redis.NewClusterClient(&redis.ClusterOptions{ 23 | Addrs: []string{"114.67.67.7:16379", 24 | " 114.67.67.7:16380", 25 | " 114.67.83.163:16379 ", 26 | " 114.67.83.163:16380 ", 27 | " 114.67.112.67:16379 ", 28 | " 114.67.112.67:16380"}, 29 | Password: "testredis0102", 30 | }) 31 | 32 | csc := &CompareSingle2Cluster{ 33 | Source: sclient, //源redis single 34 | Target: tclusterclient, //目标redis single 35 | BatchSize: int64(30), //比较List、Set、Zset类型时的每批次值的数量 36 | CompareThreads: 4, //比较db线程数量 37 | TTLDiff: float64(100000), //TTL最小差值 38 | SourceDB: 0, //源redis DB number 39 | TargetDB: 0, //目标redis DB number 40 | } 41 | csc.CompareDB() 42 | } 43 | -------------------------------------------------------------------------------- /compare/comparesingle2single.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | 8 | "github.com/go-redis/redis/v7" 9 | "github.com/panjf2000/ants/v2" 10 | "github.com/tidwall/gjson" 11 | "go.uber.org/zap" 12 | "math" 13 | "os" 14 | "rediscompare/commons" 15 | "runtime" 16 | "strconv" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | type CompareSingle2Single struct { 22 | Source *redis.Client //源redis single 23 | Target *redis.Client //目标redis single 24 | RecordResult bool 25 | ResultFile string 26 | BatchSize int64 //比较List、Set、Zset类型时的每批次值的数量 27 | CompareThreads int //比较db线程数量 28 | TTLDiff float64 //TTL最小差值 29 | SourceDB int //源redis DB number 30 | TargetDB int //目标redis DB number 31 | } 32 | 33 | func (compare *CompareSingle2Single) CompareDB() { 34 | resultfilestring := "./" + "compare_" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".result" 35 | if compare.ResultFile == "" { 36 | compare.ResultFile = resultfilestring 37 | } 38 | 39 | wg := sync.WaitGroup{} 40 | threads := runtime.NumCPU() 41 | if compare.CompareThreads > 0 { 42 | threads = compare.CompareThreads 43 | } 44 | 45 | if compare.BatchSize <= 0 { 46 | compare.BatchSize = 10 47 | } 48 | 49 | cursor := uint64(0) 50 | zaplogger.Sugar().Info("CompareSingle2single DB begin") 51 | ticker := time.NewTicker(time.Second * 10) 52 | defer ticker.Stop() 53 | 54 | pool, err := ants.NewPool(threads) 55 | 56 | if err != nil { 57 | zaplogger.Sugar().Error(err) 58 | return 59 | } 60 | defer pool.Release() 61 | 62 | for { 63 | result, c, err := compare.Source.Scan(cursor, "*", compare.BatchSize).Result() 64 | 65 | if err != nil { 66 | zaplogger.Sugar().Info(result, c, err) 67 | return 68 | } 69 | 70 | //当pool有活动worker时提交异步任务 71 | for { 72 | if pool.Free() > 0 { 73 | wg.Add(1) 74 | pool.Submit(func() { 75 | compare.CompareKeys(result) 76 | wg.Done() 77 | }) 78 | break 79 | } 80 | } 81 | cursor = c 82 | 83 | if c == 0 { 84 | break 85 | } 86 | 87 | select { 88 | case <-ticker.C: 89 | zaplogger.Sugar().Info("Comparing...") 90 | default: 91 | continue 92 | } 93 | } 94 | wg.Wait() 95 | zaplogger.Sugar().Info("CompareSingle2single End") 96 | } 97 | 98 | func (compare *CompareSingle2Single) CompareKeysFromResultFile(filespath []string) error { 99 | resultfilestring := "./" + "compare_" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".result" 100 | compare.ResultFile = resultfilestring 101 | 102 | for _, v := range filespath { 103 | fi, err := os.Open(v) 104 | if err != nil { 105 | return err 106 | } 107 | defer fi.Close() 108 | 109 | scanner := bufio.NewScanner(fi) 110 | for scanner.Scan() { 111 | line := scanner.Text() 112 | key := gjson.Get(line, "Key").String() 113 | if key != "" { 114 | compare.CompareKeys([]string{key}) 115 | } 116 | } 117 | 118 | if err := scanner.Err(); err != nil { 119 | return err 120 | } 121 | } 122 | return nil 123 | } 124 | 125 | func (compare *CompareSingle2Single) CompareKeys(keys []string) { 126 | 127 | var result *CompareResult 128 | for _, v := range keys { 129 | keytype, err := compare.Source.Type(v).Result() 130 | if err != nil { 131 | zaplogger.Sugar().Error(err) 132 | continue 133 | } 134 | result = nil 135 | switch { 136 | case keytype == "string": 137 | result = compare.CompareString(v) 138 | case keytype == "list": 139 | result = compare.CompareList(v) 140 | case keytype == "set": 141 | result = compare.CompareSet(v) 142 | case keytype == "zset": 143 | result = compare.CompareZset(v) 144 | case keytype == "hash": 145 | result = compare.CompareHash(v) 146 | default: 147 | zaplogger.Info("No type find in compare list", zap.String("key", v), zap.String("type", keytype)) 148 | } 149 | 150 | if result != nil && !result.IsEqual { 151 | zaplogger.Info("", zap.Any("CompareResult", result)) 152 | if compare.RecordResult { 153 | jsonBytes, _ := json.Marshal(result) 154 | commons.AppendLineToFile(bytes.NewBuffer(jsonBytes), compare.ResultFile) 155 | } 156 | } 157 | } 158 | } 159 | 160 | func (compare *CompareSingle2Single) CompareString(key string) *CompareResult { 161 | 162 | //比较key的存在状态是否一致 163 | result := compare.KeyExistsStatusEqual(key) 164 | if !result.IsEqual { 165 | return result 166 | } 167 | 168 | //比较string value是否一致 169 | result = compare.CompareStringVal(key) 170 | if !result.IsEqual { 171 | return result 172 | } 173 | 174 | //比较ttl差值是否在允许范围内 175 | result = compare.DiffTTLOver(key) 176 | if !result.IsEqual { 177 | return result 178 | } 179 | 180 | compareresult := NewCompareResult() 181 | compareresult.Key = key 182 | compareresult.KeyType = "string" 183 | compareresult.SourceDB = compare.SourceDB 184 | compareresult.TargetDB = compare.TargetDB 185 | return &compareresult 186 | } 187 | 188 | func (compare *CompareSingle2Single) CompareList(key string) *CompareResult { 189 | 190 | result := compare.KeyExistsStatusEqual(key) 191 | if !result.IsEqual { 192 | return result 193 | } 194 | 195 | result = compare.CompareListLen(key) 196 | if !result.IsEqual { 197 | return result 198 | } 199 | 200 | result = compare.DiffTTLOver(key) 201 | if !result.IsEqual { 202 | return result 203 | } 204 | 205 | result = compare.CompareListIndexVal(key) 206 | if !result.IsEqual { 207 | return result 208 | } 209 | 210 | compareresult := NewCompareResult() 211 | compareresult.Key = key 212 | compareresult.KeyType = "list" 213 | compareresult.SourceDB = compare.SourceDB 214 | compareresult.TargetDB = compare.TargetDB 215 | return &compareresult 216 | 217 | } 218 | 219 | func (compare *CompareSingle2Single) CompareHash(key string) *CompareResult { 220 | 221 | result := compare.KeyExistsStatusEqual(key) 222 | if !result.IsEqual { 223 | return result 224 | } 225 | 226 | result = compare.CompareHashLen(key) 227 | if !result.IsEqual { 228 | return result 229 | } 230 | 231 | result = compare.CompareHashFieldVal(key) 232 | if !result.IsEqual { 233 | return result 234 | } 235 | 236 | compareresult := NewCompareResult() 237 | compareresult.Key = key 238 | compareresult.KeyType = "hash" 239 | compareresult.SourceDB = compare.SourceDB 240 | compareresult.TargetDB = compare.TargetDB 241 | 242 | return &compareresult 243 | } 244 | 245 | func (compare *CompareSingle2Single) CompareSet(key string) *CompareResult { 246 | 247 | result := compare.KeyExistsStatusEqual(key) 248 | if !result.IsEqual { 249 | return result 250 | } 251 | 252 | result = compare.CompareSetLen(key) 253 | if !result.IsEqual { 254 | return result 255 | } 256 | 257 | result = compare.DiffTTLOver(key) 258 | if !result.IsEqual { 259 | return result 260 | } 261 | 262 | result = compare.CompareSetMember(key) 263 | if !result.IsEqual { 264 | return result 265 | } 266 | 267 | compareresult := NewCompareResult() 268 | compareresult.Key = key 269 | compareresult.KeyType = "set" 270 | compareresult.SourceDB = compare.SourceDB 271 | compareresult.TargetDB = compare.TargetDB 272 | 273 | return &compareresult 274 | } 275 | 276 | func (compare *CompareSingle2Single) CompareZset(key string) *CompareResult { 277 | 278 | result := compare.KeyExistsStatusEqual(key) 279 | if !result.IsEqual { 280 | return result 281 | } 282 | 283 | result = compare.CompareZsetLen(key) 284 | if !result.IsEqual { 285 | return result 286 | } 287 | 288 | result = compare.DiffTTLOver(key) 289 | if !result.IsEqual { 290 | return result 291 | } 292 | 293 | result = compare.CompareZsetMemberScore(key) 294 | if !result.IsEqual { 295 | return result 296 | } 297 | 298 | compareresult := NewCompareResult() 299 | compareresult.Key = key 300 | compareresult.KeyType = "zset" 301 | compareresult.SourceDB = compare.SourceDB 302 | compareresult.TargetDB = compare.TargetDB 303 | return &compareresult 304 | } 305 | 306 | //判断key在source和target同时不存在 307 | func (compare *CompareSingle2Single) KeyExistsStatusEqual(key string) *CompareResult { 308 | compareresult := NewCompareResult() 309 | reason := make(map[string]interface{}) 310 | compareresult.Key = key 311 | compareresult.Source = compare.Source.Options().Addr 312 | compareresult.Target = compare.Target.Options().Addr 313 | compareresult.SourceDB = compare.SourceDB 314 | compareresult.TargetDB = compare.TargetDB 315 | 316 | sourceexists := KeyExists(compare.Source, key) 317 | targetexists := KeyExists(compare.Target, key) 318 | 319 | if sourceexists == targetexists { 320 | return &compareresult 321 | } 322 | 323 | compareresult.IsEqual = false 324 | reason["description"] = "Source or Target key not exists" 325 | reason["source"] = sourceexists 326 | reason["target"] = targetexists 327 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 328 | return &compareresult 329 | } 330 | 331 | //比较Zset member以及sore值是否一致 332 | func (compare *CompareSingle2Single) CompareZsetMemberScore(key string) *CompareResult { 333 | compareresult := NewCompareResult() 334 | reason := make(map[string]interface{}) 335 | compareresult.Key = key 336 | compareresult.KeyType = "Zset" 337 | compareresult.Source = compare.Source.Options().Addr 338 | compareresult.Target = compare.Target.Options().Addr 339 | compareresult.SourceDB = compare.SourceDB 340 | compareresult.TargetDB = compare.TargetDB 341 | 342 | cursor := uint64(0) 343 | for { 344 | sourceresult, c, err := compare.Source.ZScan(key, cursor, "*", compare.BatchSize).Result() 345 | if err != nil { 346 | compareresult.IsEqual = false 347 | reason["description"] = "Source zscan error" 348 | reason["zscanerror"] = err.Error() 349 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 350 | return &compareresult 351 | } 352 | 353 | for i := 0; i < len(sourceresult); i = i + 2 { 354 | sourecemember := sourceresult[i] 355 | sourcescore, err := strconv.ParseFloat(sourceresult[i+1], 64) 356 | if err != nil { 357 | compareresult.IsEqual = false 358 | reason["description"] = "Convert sourcescore to float64 error" 359 | reason["floattostringerror"] = err.Error() 360 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 361 | return &compareresult 362 | } 363 | 364 | intcmd := compare.Target.ZRank(key, sourecemember) 365 | targetscore := compare.Target.ZScore(key, sourecemember).Val() 366 | 367 | if intcmd == nil { 368 | compareresult.IsEqual = false 369 | reason["description"] = "Source zset member not exists in Target" 370 | reason["member"] = sourecemember 371 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 372 | return &compareresult 373 | } 374 | 375 | if targetscore != sourcescore { 376 | compareresult.IsEqual = false 377 | reason["description"] = "zset member score not equal" 378 | reason["member"] = sourecemember 379 | reason["sourcescore"] = sourcescore 380 | reason["targetscore"] = targetscore 381 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 382 | return &compareresult 383 | } 384 | 385 | } 386 | 387 | cursor = c 388 | if c == 0 { 389 | break 390 | } 391 | } 392 | return &compareresult 393 | } 394 | 395 | //比较zset 长度是否一致 396 | func (compare *CompareSingle2Single) CompareZsetLen(key string) *CompareResult { 397 | compareresult := NewCompareResult() 398 | reason := make(map[string]interface{}) 399 | compareresult.Key = key 400 | compareresult.KeyType = "Zset" 401 | compareresult.Source = compare.Source.Options().Addr 402 | compareresult.Target = compare.Target.Options().Addr 403 | compareresult.SourceDB = compare.SourceDB 404 | compareresult.TargetDB = compare.TargetDB 405 | 406 | sourcelen := compare.Source.ZCard(key).Val() 407 | targetlen := compare.Target.ZCard(key).Val() 408 | if sourcelen != targetlen { 409 | compareresult.IsEqual = false 410 | reason["description"] = "Zset length not equal" 411 | reason["sourcelen"] = sourcelen 412 | reason["targetlen"] = targetlen 413 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 414 | return &compareresult 415 | } 416 | return &compareresult 417 | } 418 | 419 | //比较set member 是否一致 420 | func (compare *CompareSingle2Single) CompareSetMember(key string) *CompareResult { 421 | compareresult := NewCompareResult() 422 | reason := make(map[string]interface{}) 423 | compareresult.Key = key 424 | compareresult.KeyType = "set" 425 | compareresult.Source = compare.Source.Options().Addr 426 | compareresult.Target = compare.Target.Options().Addr 427 | compareresult.SourceDB = compare.SourceDB 428 | compareresult.TargetDB = compare.TargetDB 429 | 430 | cursor := uint64(0) 431 | for { 432 | sourceresult, c, err := compare.Source.SScan(key, cursor, "*", compare.BatchSize).Result() 433 | if err != nil { 434 | compareresult.IsEqual = false 435 | reason["description"] = "Source sscan error" 436 | reason["sscanerror"] = err.Error() 437 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 438 | return &compareresult 439 | } 440 | 441 | for _, v := range sourceresult { 442 | if !compare.Target.SIsMember(key, v).Val() { 443 | compareresult.IsEqual = false 444 | reason["description"] = "Source set member not exists in Target" 445 | reason["member"] = v 446 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 447 | return &compareresult 448 | } 449 | } 450 | 451 | cursor = c 452 | if c == 0 { 453 | break 454 | } 455 | } 456 | return &compareresult 457 | } 458 | 459 | //比较set长度 460 | func (compare *CompareSingle2Single) CompareSetLen(key string) *CompareResult { 461 | compareresult := NewCompareResult() 462 | reason := make(map[string]interface{}) 463 | compareresult.Source = compare.Source.Options().Addr 464 | compareresult.Target = compare.Target.Options().Addr 465 | compareresult.Key = key 466 | compareresult.KeyType = "set" 467 | compareresult.SourceDB = compare.SourceDB 468 | compareresult.TargetDB = compare.TargetDB 469 | 470 | sourcelen := compare.Source.SCard(key).Val() 471 | targetlen := compare.Target.SCard(key).Val() 472 | if sourcelen != targetlen { 473 | compareresult.IsEqual = false 474 | reason["description"] = "Set length not equal" 475 | reason["sourcelen"] = sourcelen 476 | reason["targetlen"] = targetlen 477 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 478 | return &compareresult 479 | } 480 | return &compareresult 481 | } 482 | 483 | //比较hash field value 返回首个不相等的field 484 | func (compare *CompareSingle2Single) CompareHashFieldVal(key string) *CompareResult { 485 | compareresult := NewCompareResult() 486 | reason := make(map[string]interface{}) 487 | compareresult.Source = compare.Source.Options().Addr 488 | compareresult.Target = compare.Target.Options().Addr 489 | compareresult.Key = key 490 | compareresult.KeyType = "hash" 491 | compareresult.SourceDB = compare.SourceDB 492 | compareresult.TargetDB = compare.TargetDB 493 | 494 | cursor := uint64(0) 495 | for { 496 | sourceresult, c, err := compare.Source.HScan(key, cursor, "*", compare.BatchSize).Result() 497 | 498 | if err != nil { 499 | compareresult.IsEqual = false 500 | reason["description"] = "Source hscan error" 501 | reason["hscanerror"] = err.Error() 502 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 503 | return &compareresult 504 | } 505 | 506 | for i := 0; i < len(sourceresult); i = i + 2 { 507 | targetfieldval := compare.Target.HGet(key, sourceresult[i]).Val() 508 | if targetfieldval != sourceresult[i+1] { 509 | compareresult.IsEqual = false 510 | reason["description"] = "Field value not equal" 511 | reason["field"] = sourceresult[i] 512 | reason["sourceval"] = sourceresult[i+1] 513 | reason["targetval"] = targetfieldval 514 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 515 | return &compareresult 516 | } 517 | } 518 | cursor = c 519 | if c == uint64(0) { 520 | break 521 | } 522 | } 523 | return &compareresult 524 | } 525 | 526 | //比较hash长度 527 | func (compare *CompareSingle2Single) CompareHashLen(key string) *CompareResult { 528 | compareresult := NewCompareResult() 529 | reason := make(map[string]interface{}) 530 | compareresult.Source = compare.Source.Options().Addr 531 | compareresult.Target = compare.Target.Options().Addr 532 | compareresult.Key = key 533 | compareresult.KeyType = "hash" 534 | compareresult.SourceDB = compare.SourceDB 535 | compareresult.TargetDB = compare.TargetDB 536 | 537 | sourcelen := compare.Source.HLen(key).Val() 538 | targetlen := compare.Target.HLen(key).Val() 539 | 540 | if sourcelen != targetlen { 541 | 542 | compareresult.IsEqual = false 543 | reason["description"] = "Hash length not equal" 544 | reason["sourcelen"] = sourcelen 545 | reason["targetlen"] = targetlen 546 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 547 | return &compareresult 548 | } 549 | return &compareresult 550 | } 551 | 552 | //比较list index对应值是否一致,返回第一条错误的index以及源和目标对应的值 553 | func (compare *CompareSingle2Single) CompareListIndexVal(key string) *CompareResult { 554 | compareresult := NewCompareResult() 555 | reason := make(map[string]interface{}) 556 | compareresult.Source = compare.Source.Options().Addr 557 | compareresult.Target = compare.Target.Options().Addr 558 | compareresult.Key = key 559 | compareresult.KeyType = "list" 560 | compareresult.SourceDB = compare.SourceDB 561 | compareresult.TargetDB = compare.TargetDB 562 | 563 | sourcelen := compare.Source.LLen(key).Val() 564 | //targetlen := compare.Target.LLen(key).Val() 565 | 566 | compareresult.Key = key 567 | quotient := sourcelen / compare.BatchSize // integer division, decimals are truncated 568 | remainder := sourcelen % compare.BatchSize 569 | 570 | if quotient != 0 { 571 | var lrangeend int64 572 | for i := int64(0); i < quotient; i++ { 573 | if i == quotient-int64(1) { 574 | lrangeend = quotient * compare.BatchSize 575 | } else { 576 | lrangeend = (compare.BatchSize - 1) + i*compare.BatchSize 577 | } 578 | sourcevalues := compare.Source.LRange(key, int64(0)+i*compare.BatchSize, lrangeend).Val() 579 | targetvalues := compare.Target.LRange(key, int64(0)+i*compare.BatchSize, lrangeend).Val() 580 | for k, v := range sourcevalues { 581 | if targetvalues[k] != v { 582 | compareresult.IsEqual = false 583 | reason["description"] = "List index value not equal" 584 | reason["Index"] = int64(k) + i*compare.BatchSize 585 | reason["sourceval"] = v 586 | reason["targetval"] = targetvalues[k] 587 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 588 | return &compareresult 589 | } 590 | } 591 | } 592 | } 593 | 594 | if remainder != 0 { 595 | var rangstart int64 596 | 597 | if quotient == int64(0) { 598 | rangstart = int64(0) 599 | } else { 600 | rangstart = quotient*compare.BatchSize + 1 601 | } 602 | 603 | sourcevalues := compare.Source.LRange(key, rangstart, remainder+quotient*compare.BatchSize).Val() 604 | targetvalues := compare.Target.LRange(key, rangstart, remainder+quotient*compare.BatchSize).Val() 605 | for k, v := range sourcevalues { 606 | if targetvalues[k] != v { 607 | compareresult.IsEqual = false 608 | reason["description"] = "List index value not equal" 609 | reason["Index"] = int64(k) + rangstart 610 | reason["sourceval"] = v 611 | reason["targetval"] = targetvalues[k] 612 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 613 | return &compareresult 614 | } 615 | } 616 | } 617 | 618 | return &compareresult 619 | 620 | } 621 | 622 | //比较list长度是否一致 623 | func (compare *CompareSingle2Single) CompareListLen(key string) *CompareResult { 624 | compareresult := NewCompareResult() 625 | reason := make(map[string]interface{}) 626 | compareresult.Source = compare.Source.Options().Addr 627 | compareresult.Target = compare.Target.Options().Addr 628 | compareresult.Key = key 629 | compareresult.KeyType = "list" 630 | compareresult.SourceDB = compare.SourceDB 631 | compareresult.TargetDB = compare.TargetDB 632 | 633 | sourcelen := compare.Source.LLen(key).Val() 634 | targetlen := compare.Target.LLen(key).Val() 635 | 636 | compareresult.Key = key 637 | if sourcelen != targetlen { 638 | compareresult.IsEqual = false 639 | reason["description"] = "List length not equal" 640 | reason["sourcelen"] = sourcelen 641 | reason["targetlen"] = targetlen 642 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 643 | return &compareresult 644 | } 645 | return &compareresult 646 | } 647 | 648 | //对比string类型value是否一致 649 | func (compare *CompareSingle2Single) CompareStringVal(key string) *CompareResult { 650 | compareresult := NewCompareResult() 651 | reason := make(map[string]interface{}) 652 | compareresult.Source = compare.Source.Options().Addr 653 | compareresult.Target = compare.Target.Options().Addr 654 | compareresult.Key = key 655 | compareresult.KeyType = "string" 656 | compareresult.SourceDB = compare.SourceDB 657 | compareresult.TargetDB = compare.TargetDB 658 | 659 | sourceval := compare.Source.Get(key).Val() 660 | targetval := compare.Target.Get(key).Val() 661 | compareresult.Key = key 662 | if sourceval != targetval { 663 | compareresult.IsEqual = false 664 | reason["description"] = "String value not equal" 665 | reason["sval"] = sourceval 666 | reason["tval"] = targetval 667 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 668 | return &compareresult 669 | } 670 | return &compareresult 671 | } 672 | 673 | //对比key TTl差值 674 | func (compare *CompareSingle2Single) DiffTTLOver(key string) *CompareResult { 675 | compareresult := NewCompareResult() 676 | reason := make(map[string]interface{}) 677 | compareresult.Source = compare.Source.Options().Addr 678 | compareresult.Target = compare.Target.Options().Addr 679 | compareresult.Key = key 680 | compareresult.KeyType = "string" 681 | compareresult.SourceDB = compare.SourceDB 682 | compareresult.TargetDB = compare.TargetDB 683 | 684 | sourcettl := compare.Source.PTTL(key).Val().Milliseconds() 685 | targetttl := compare.Target.PTTL(key).Val().Milliseconds() 686 | 687 | sub := targetttl - sourcettl 688 | if math.Abs(float64(sub)) > compare.TTLDiff { 689 | compareresult.IsEqual = false 690 | reason["description"] = "Key ttl difference is too large" 691 | reason["TTLDiff"] = int64(math.Abs(float64(sub))) 692 | reason["sourcettl"] = sourcettl 693 | reason["targetttl"] = targetttl 694 | 695 | compareresult.KeyDiffReason = append(compareresult.KeyDiffReason, reason) 696 | return &compareresult 697 | } 698 | return &compareresult 699 | } 700 | 701 | func KeyExists(client *redis.Client, key string) bool { 702 | exists := client.Exists(key).Val() 703 | if exists == int64(1) { 704 | return true 705 | } else { 706 | return false 707 | } 708 | } 709 | -------------------------------------------------------------------------------- /compare/comparesingle2single_test.go: -------------------------------------------------------------------------------- 1 | package compare 2 | 3 | import ( 4 | "github.com/go-redis/redis/v7" 5 | "rediscompare/commons" 6 | "testing" 7 | ) 8 | 9 | func TestCompare_CompareDB(t *testing.T) { 10 | sopt := &redis.Options{ 11 | Addr: "114.67.67.7:6379", 12 | DB: 0, 13 | } 14 | 15 | sclient := commons.GetGoRedisClient(sopt) 16 | defer sclient.Close() 17 | 18 | } 19 | 20 | func TestCompareSingle2Single_CompareKeysFromResultFile(t *testing.T) { 21 | sopt := &redis.Options{ 22 | Addr: "114.67.67.7:6379", 23 | DB: 0, 24 | } 25 | 26 | sopt.Password = "redistest0102" 27 | 28 | sclient := commons.GetGoRedisClient(sopt) 29 | 30 | topt := &redis.Options{ 31 | Addr: "114.67.83.163:6379", 32 | DB: 0, 33 | } 34 | 35 | topt.Password = "redistest0102" 36 | 37 | tclient := commons.GetGoRedisClient(topt) 38 | 39 | defer sclient.Close() 40 | defer tclient.Close() 41 | 42 | //check redis 连通性 43 | //if !commons.CheckRedisClientConnect(sclient) { 44 | // cmd.PrintErrln(errors.New("Cannot connect source redis")) 45 | // return 46 | //} 47 | //if !commons.CheckRedisClientConnect(Tclient) { 48 | // cmd.PrintErrln(errors.New("Cannot connect target redis")) 49 | // return 50 | //} 51 | compare := &CompareSingle2Single{ 52 | Source: sclient, 53 | Target: tclient, 54 | BatchSize: int64(50), 55 | TTLDiff: float64(10000), 56 | RecordResult: true, 57 | CompareThreads: 3, 58 | } 59 | compare.CompareKeysFromResultFile([]string{"../compare_20200308173929000.result"}) 60 | } 61 | -------------------------------------------------------------------------------- /docs/images/use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TraceNature/rediscompare/7cbffa68526fc3ed65b64635cba0503d884b2dee/docs/images/use.gif -------------------------------------------------------------------------------- /execyamlexample/cluster2cluster.yml: -------------------------------------------------------------------------------- 1 | # Scenario origin to origin cluster example 2 | saddr: 3 | - addr: "10.0.0.1:36379" 4 | password: "xxxxxx" 5 | - addr: "10.0.0.2:36379" 6 | password: "xxxxxx" 7 | - addr: "10.0.0.3:36379" 8 | password: "xxxxxx" 9 | taddr: "192.168.1.1:16379,192.168.1.1:16380,192.168.1.2:16379,192.168.1.2:16380,192.168.1.3:16379,192.168.1.3:16380" 10 | tpassword: "xxxxxx" 11 | batchsize: 30 12 | threads: 2 13 | ttldiff: 10000 14 | comparetimes: 4 15 | compareinterval: 2 16 | report: true 17 | scenario: "cluster2cluster" -------------------------------------------------------------------------------- /execyamlexample/multisingle2cluster.yml: -------------------------------------------------------------------------------- 1 | # Scenario multi single instance to origin cluster example 2 | saddr: 3 | - addr: "10.0.0.1:6379" 4 | password: "redistest0102" 5 | dbs: 6 | - 0 7 | - 2 8 | - 3 9 | - addr: "10.0.0.2:6379" 10 | password: "redistest0102" 11 | dbs: 12 | - 1 13 | - 5 14 | - 9 15 | taddr: "10.0.1.1:16379,10.0.1.1:16380,10.0.1.2:16379,10.0.1.2:16380,10.0.1.3:16379,10.0.1.3:16380" 16 | tpassword: "testredis0102" 17 | batchsize: 30 18 | threads: 2 19 | ttldiff: 10000 20 | comparetimes: 3 21 | compareinterval: 2 22 | report: true 23 | scenario: "multisingle2cluster" -------------------------------------------------------------------------------- /execyamlexample/multisingle2single.yml: -------------------------------------------------------------------------------- 1 | # Scenario multi single instance to single instance example 2 | saddr: 3 | - addr: "10.0.0.1:6379" 4 | password: "redistest0102" 5 | dbs: 6 | - 0 7 | - 2 8 | - 3 9 | - addr: "10.0.0.2:6379" 10 | password: "redistest0102" 11 | dbs: 12 | - 1 13 | - 5 14 | - 9 15 | taddr: "10.0.0.3:6379" 16 | tpassword: "redistest0102" 17 | batchsize: 30 18 | threads: 2 19 | ttldiff: 10000 20 | comparetimes: 3 21 | compareinterval: 3 22 | report: true 23 | scenario: "multisingle2single" -------------------------------------------------------------------------------- /execyamlexample/single2cluster.yml: -------------------------------------------------------------------------------- 1 | # Scenario single instance to origin cluster example 2 | saddr: 3 | - addr: "10.0.0.1:6379" 4 | password: "xxxx" 5 | dbs: 6 | - 0 7 | - 2 8 | - 3 9 | taddr: "192.168.1.1:16379,192.168.1.1:16380,192.168.1.2:16379,192.168.1.2:16380,192.168.1.3:16379,192.168.1.3:16380" 10 | tpassword: "xxxx" 11 | batchsize: 30 12 | threads: 2 13 | ttldiff: 10000 14 | comparetimes: 3 15 | compareinterval: 2 16 | report: true 17 | scenario: "single2cluster" -------------------------------------------------------------------------------- /execyamlexample/single2single.yml: -------------------------------------------------------------------------------- 1 | # Scenario single instance to single instance example 2 | saddr: 3 | - addr: "10.0.0.1:6379" 4 | password: "xxxxxx" 5 | dbs: 6 | - 0 7 | - 2 8 | - 3 9 | taddr: "192.168.1.2:6379" 10 | tpassword: "xxxxxx" 11 | batchsize: 30 12 | threads: 2 13 | ttldiff: 10000 14 | comparetimes: 3 15 | compareinterval: 5 16 | report: true 17 | scenario: "single2single" -------------------------------------------------------------------------------- /globalzap/globlezap.go: -------------------------------------------------------------------------------- 1 | package globalzap 2 | 3 | import ( 4 | "fmt" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | "gopkg.in/natefinch/lumberjack.v2" 8 | "os" 9 | "rediscompare/commons" 10 | "sync" 11 | ) 12 | 13 | var ( 14 | logger *zap.Logger 15 | l = &sync.RWMutex{} 16 | ) 17 | 18 | type EncoderType int 19 | 20 | const ( 21 | EcoderJson EncoderType = iota - 1 22 | EcoderConsol 23 | ) 24 | 25 | func (e EncoderType) String() string { 26 | switch e { 27 | case EcoderJson: 28 | return "json" 29 | case EcoderConsol: 30 | return "console" 31 | default: 32 | return fmt.Sprintf("Econder(%d)", e) 33 | } 34 | } 35 | 36 | type LoggerDefine struct { 37 | Hook lumberjack.Logger 38 | EncoderConfig zapcore.EncoderConfig 39 | Level zapcore.Level 40 | EncoderType EncoderType 41 | Caller bool //是否开启开发模式,堆栈跟踪 42 | } 43 | 44 | var defaultloggerdefine = &LoggerDefine{ 45 | //Hook: lumberjack.Logger{ 46 | // Filename: "./logs/spikeProxy1.log", // 日志文件路径 47 | // MaxSize: 128, // 每个日志文件保存的最大尺寸 单位:M 48 | // MaxBackups: 30, // 日志文件最多保存多少个备份 49 | // MaxAge: 7, // 文件最多保存多少天 50 | // Compress: true, // 是否压缩 51 | //}, 52 | EncoderConfig: zapcore.EncoderConfig{ 53 | TimeKey: "time", 54 | LevelKey: "level", 55 | NameKey: "logger", 56 | CallerKey: "linenum", 57 | MessageKey: "msg", 58 | StacktraceKey: "stacktrace", 59 | LineEnding: zapcore.DefaultLineEnding, 60 | EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器 61 | EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式 62 | EncodeDuration: zapcore.SecondsDurationEncoder, // 63 | EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器 64 | EncodeName: zapcore.FullNameEncoder, 65 | }, 66 | Level: zap.DebugLevel, 67 | EncoderType: EcoderConsol, 68 | //Caller: true, 69 | } 70 | 71 | func GetLogger() *zap.Logger { 72 | if commons.IsNil(logger) { 73 | l.Lock() 74 | defer l.Unlock() 75 | if commons.IsNil(logger) { 76 | defaultloggerdefine.InitLogger() 77 | } 78 | } 79 | 80 | return logger 81 | } 82 | 83 | func (d *LoggerDefine) SetLogger() { 84 | defaultloggerdefine = d 85 | } 86 | 87 | func (d *LoggerDefine) InitLogger() { 88 | 89 | // 设置日志级别 90 | atomicLevel := zap.NewAtomicLevel() 91 | //atomicLevel.SetLevel(zap.InfoLevel) 92 | atomicLevel.SetLevel(d.Level) 93 | 94 | //定义encoder 95 | 96 | var encoder zapcore.Encoder 97 | switch d.EncoderType { 98 | case EcoderJson: 99 | encoder = zapcore.NewJSONEncoder(d.EncoderConfig) 100 | case EcoderConsol: 101 | encoder = zapcore.NewConsoleEncoder(d.EncoderConfig) 102 | default: 103 | encoder = zapcore.NewJSONEncoder(d.EncoderConfig) 104 | } 105 | 106 | if d.EncoderType == EcoderConsol { 107 | encoder = zapcore.NewConsoleEncoder(d.EncoderConfig) 108 | } 109 | core := zapcore.NewCore( 110 | 111 | encoder, 112 | zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&(d.Hook))), // 打印到控制台和文件 113 | atomicLevel, // 日志级别 114 | ) 115 | 116 | options := []zap.Option{} 117 | // 开启开发模式,堆栈跟踪 118 | caller := zap.AddCaller() 119 | 120 | if d.Caller { 121 | options = append(options, caller) 122 | } 123 | // 开启文件及行号 124 | //development := zap.Development() 125 | // 设置初始化字段 126 | //filed := zap.Fields(zap.String("serviceName", "serviceName")) 127 | // 构造日志 128 | logger = zap.New(core, options...) 129 | 130 | } 131 | -------------------------------------------------------------------------------- /globalzap/golobezap_test.go: -------------------------------------------------------------------------------- 1 | package globalzap 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | "gopkg.in/natefinch/lumberjack.v2" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestGetLogger(t *testing.T) { 14 | 15 | log := GetLogger() 16 | defer log.Sync() 17 | slog := log.Sugar() 18 | log.Info("log 初始化成功") 19 | log.Debug("debugmesg") 20 | log.Info("无法获取网址", 21 | zap.String("url", "http://www.baidu.com"), 22 | zap.Int("attempt", 3), 23 | zap.Duration("backoff", time.Second)) 24 | slog.Infow("test sugar", "url", "http://example.com", 25 | "attempt", 3, 26 | "backoff", time.Second) 27 | 28 | log.Sync() 29 | ctx, cancel := context.WithCancel(context.Background()) 30 | 31 | go generatelog(ctx, "json") 32 | 33 | time.Sleep(3 * time.Second) 34 | 35 | ld := LoggerDefine{ 36 | Hook: lumberjack.Logger{ 37 | Filename: "./logs/spikeProxy1.log", // 日志文件路径 38 | MaxSize: 128, // 每个日志文件保存的最大尺寸 单位:M 39 | MaxBackups: 30, // 日志文件最多保存多少个备份 40 | MaxAge: 7, // 文件最多保存多少天 41 | Compress: true, // 是否压缩 42 | }, 43 | EncoderConfig: zapcore.EncoderConfig{ 44 | TimeKey: "time", 45 | LevelKey: "level", 46 | NameKey: "logger", 47 | CallerKey: "linenum", 48 | MessageKey: "msg", 49 | StacktraceKey: "stacktrace", 50 | LineEnding: zapcore.DefaultLineEnding, 51 | EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器 52 | EncodeTime: zapcore.ISO8601TimeEncoder, // ISO8601 UTC 时间格式 53 | EncodeDuration: zapcore.SecondsDurationEncoder, // 54 | EncodeCaller: zapcore.FullCallerEncoder, // 全路径编码器 55 | EncodeName: zapcore.FullNameEncoder, 56 | }, 57 | Level: zap.InfoLevel, 58 | EncoderType: EcoderConsol, 59 | } 60 | 61 | fmt.Println(ld.EncoderType) 62 | ld.SetLogger() 63 | ld.InitLogger() 64 | log = GetLogger() 65 | log.Info("log 初始化成功") 66 | log.Debug("debugmesg") 67 | log.Info("无法获取网址", 68 | zap.String("url", "http://www.baidu.com"), 69 | zap.Int("attempt", 3), 70 | zap.Duration("backoff", time.Second)) 71 | go generatelog(ctx, "console") 72 | time.Sleep(3 * time.Second) 73 | log.Sync() 74 | 75 | cancel() 76 | 77 | } 78 | 79 | func generatelog(ctx context.Context, name string) { 80 | log := GetLogger() 81 | for { 82 | select { 83 | case <-ctx.Done(): 84 | return 85 | default: 86 | log.Info(name + ":" + time.Now().String()) 87 | } 88 | time.Sleep(1 * time.Second) 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module rediscompare 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/chzyer/logex v1.1.10 // indirect 7 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 8 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect 9 | github.com/ghodss/yaml v1.0.0 10 | github.com/go-redis/redis/v7 v7.4.0 11 | github.com/mattn/go-shellwords v1.0.10 12 | github.com/olekukonko/tablewriter v0.0.4 13 | github.com/panjf2000/ants/v2 v2.4.1 14 | github.com/pkg/errors v0.8.1 15 | github.com/satori/go.uuid v1.2.0 16 | github.com/spf13/cobra v1.0.0 17 | github.com/spf13/viper v1.7.1 18 | github.com/tidwall/gjson v1.6.0 19 | go.uber.org/zap v1.10.0 20 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e // indirect 21 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 22 | gopkg.in/yaml.v2 v2.3.0 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 19 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 21 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 22 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 23 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 24 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 25 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 26 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 27 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 28 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 29 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 30 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 31 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 32 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 33 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 34 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 35 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 36 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 37 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 38 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 39 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 40 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 41 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 42 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 43 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 44 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 45 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 46 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 48 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 49 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 50 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 51 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 52 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 53 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 54 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 55 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 56 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 57 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 58 | github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= 59 | github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 60 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 61 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 62 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 63 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 64 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 65 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 67 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 68 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 71 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 72 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 73 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 74 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 75 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 76 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 77 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 78 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 79 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 80 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 81 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 82 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 83 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 85 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 86 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 87 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 88 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 89 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 90 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 91 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 92 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 93 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 94 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 95 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 96 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 97 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 98 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 99 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 100 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 101 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 102 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 103 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 104 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 105 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 106 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 107 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 108 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 109 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 110 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 111 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 112 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 113 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 114 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 115 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 116 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 117 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 118 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 119 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 120 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 121 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 122 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 123 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 124 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 125 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 126 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 127 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 128 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 129 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 130 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 131 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 132 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 133 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 134 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 135 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 136 | github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= 137 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 138 | github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= 139 | github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 140 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 141 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 142 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 143 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 144 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 145 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 146 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 147 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 148 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 149 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 150 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 151 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 152 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 153 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 154 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 155 | github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= 156 | github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= 157 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 158 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 159 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 160 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 161 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 162 | github.com/panjf2000/ants/v2 v2.4.1 h1:7RtUqj5lGOw0WnZhSKDZ2zzJhaX5490ZW1sUolRXCxY= 163 | github.com/panjf2000/ants/v2 v2.4.1/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 164 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 165 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 166 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 167 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 168 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 169 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 170 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 171 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 172 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 173 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 174 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 175 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 176 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 177 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 178 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 179 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 180 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 181 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 182 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 183 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 184 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 185 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 186 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 187 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 188 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 189 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 190 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 191 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 192 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 193 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 194 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 195 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 196 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 197 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 198 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 199 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 200 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 201 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 202 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 203 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 204 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 205 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 206 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 207 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 208 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 209 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 210 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 211 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 212 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 213 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 214 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 215 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 216 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 217 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 218 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 219 | github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= 220 | github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= 221 | github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= 222 | github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= 223 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 224 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 225 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 226 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 227 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 228 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 229 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 230 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 231 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 232 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 233 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 234 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 235 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 236 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 237 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 238 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 239 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 240 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 241 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 242 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 243 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 244 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 245 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 246 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 247 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 248 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 249 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 250 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 251 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 252 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 253 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 254 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 255 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 256 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 257 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 258 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 259 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 260 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 261 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 262 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 263 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 264 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 265 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 266 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 267 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 268 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 269 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 271 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 272 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 273 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 274 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 275 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 276 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 277 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 278 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 279 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 280 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 281 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 282 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 283 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 285 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 286 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 288 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 289 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 290 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 291 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 293 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 294 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 295 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= 302 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= 304 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 306 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 307 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 308 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 309 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 310 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 311 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 312 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 313 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 314 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 315 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 316 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 317 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 318 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 319 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 320 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 321 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 322 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 323 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 324 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 325 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 326 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 327 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 328 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 329 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 330 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 331 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 332 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 333 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 334 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 335 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 336 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 337 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 338 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 339 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 340 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 341 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 342 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 343 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 344 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 345 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 346 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 347 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 348 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 349 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 350 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 351 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 352 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 353 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 354 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 355 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 356 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 357 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 358 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 359 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 360 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 361 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 362 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 363 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 364 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 365 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 366 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 367 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 368 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 369 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 370 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 371 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 372 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 373 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 374 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 375 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 376 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 377 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 378 | -------------------------------------------------------------------------------- /interact/cli.go: -------------------------------------------------------------------------------- 1 | package interact 2 | 3 | import ( 4 | "fmt" 5 | "github.com/chzyer/readline" 6 | "github.com/mattn/go-shellwords" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | "io" 10 | "os" 11 | "rediscompare/check" 12 | "rediscompare/cmd" 13 | "rediscompare/commons" 14 | "strings" 15 | ) 16 | 17 | type CommandFlags struct { 18 | URL string 19 | CAPath string 20 | CertPath string 21 | KeyPath string 22 | Help bool 23 | } 24 | 25 | var ( 26 | commandFlags = CommandFlags{} 27 | cfgFile string 28 | detach bool 29 | syncserver string 30 | Confignotseterr error 31 | interact bool 32 | version bool 33 | ) 34 | 35 | var LivePrefixState struct { 36 | LivePrefix string 37 | IsEnable bool 38 | } 39 | 40 | var query = "" 41 | 42 | //var suggest = []prompt.Suggest{ 43 | // //config 44 | // {Text: "config", Description: "config env"}, 45 | // {Text: "show ", Description: "show config"}, 46 | // {Text: "set ", Description: "set config"}, 47 | // {Text: "delete ", Description: "delete"}, 48 | // 49 | // 50 | // //task 51 | // {Text: "task", Description: "about task"}, 52 | // {Text: "create ", Description: "create task"}, 53 | // {Text: "start ", Description: "start task"}, 54 | // {Text: "--afresh", Description: "start task afresh"}, 55 | // {Text: "remove ", Description: "remove task"}, 56 | // {Text: "stop ", Description: "stop task"}, 57 | // {Text: "status ", Description: "query task status"}, 58 | // {Text: "byname ", Description: "query task status by task name"}, 59 | // {Text: "bytaskid ", Description: "query task status by task id"}, 60 | // {Text: "bygroupid ", Description: "query task status by task group id"}, 61 | // {Text: "all ", Description: "query all tasks status "}, 62 | //} 63 | 64 | var readlinecompleter *readline.PrefixCompleter 65 | 66 | func init() { 67 | cobra.EnablePrefixMatching = true 68 | cobra.OnInitialize(initConfig) 69 | 70 | } 71 | 72 | func cliRun(cmd *cobra.Command, args []string) { 73 | banner := "\n ___ _ ___ \n| _`\\ ( ) _ ( _`\\ \n| (_) ) __ _| |(_) ___ | ( (_) _ ___ ___ _ _ _ _ _ __ __ \n| , / /'__`\\ /'_` || |/',__)| | _ /'_`\\ /' _ ` _ `\\( '_`\\ /'_` )( '__)/'__`\\\n| |\\ \\ ( ___/( (_| || |\\__, \\| (_( )( (_) )| ( ) ( ) || (_) )( (_| || | ( ___/\n(_) (_)`\\____)`\\__,_)(_)(____/(____/'`\\___/'(_) (_) (_)| ,__/'`\\__,_)(_) `\\____)\n | | \n (_) \n" 74 | 75 | if interact { 76 | err := check.CheckEnv() 77 | if err != nil { 78 | fmt.Println(err) 79 | os.Exit(1) 80 | } 81 | cmd.Println(banner) 82 | cmd.Println("Input 'help;' for usage. \nCommand must end with ';'. \n'tab' for command complete.\n^C or exit to quit.") 83 | loop() 84 | return 85 | } 86 | 87 | if len(args) == 0 { 88 | cmd.Help() 89 | return 90 | } 91 | 92 | } 93 | 94 | func getBasicCmd() *cobra.Command { 95 | 96 | rootCmd := &cobra.Command{ 97 | Use: "rediscompare", 98 | Short: "rediscompare command line interface", 99 | Long: "", 100 | } 101 | 102 | rootCmd.PersistentFlags().BoolVarP(&commandFlags.Help, "help", "h", false, "help message") 103 | 104 | rootCmd.AddCommand( 105 | cmd.NewResultCommand(), 106 | cmd.NewCompareCommand(), 107 | ) 108 | 109 | rootCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true 110 | rootCmd.SilenceErrors = true 111 | return rootCmd 112 | } 113 | 114 | func getInteractCmd(args []string) *cobra.Command { 115 | rootCmd := getBasicCmd() 116 | rootCmd.Run = func(cmd *cobra.Command, args []string) { 117 | } 118 | 119 | rootCmd.SetArgs(args) 120 | rootCmd.ParseFlags(args) 121 | rootCmd.SetOut(os.Stdout) 122 | //rootCmd.SetOutput(os.Stdout) 123 | hiddenFlag(rootCmd) 124 | 125 | return rootCmd 126 | } 127 | 128 | func getMainCmd(args []string) *cobra.Command { 129 | rootCmd := getBasicCmd() 130 | 131 | //rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config.yaml)") 132 | //rootCmd.PersistentFlags().StringVarP(&syncserver, "syncserver", "s", "", "sync server address") 133 | //rootCmd.Flags().BoolVarP(&detach, "detach", "d", true, "Run pdctl without readline.") 134 | rootCmd.Flags().BoolVarP(&interact, "interact", "i", false, "Run pdctl with readline.") 135 | rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and exit.") 136 | 137 | rootCmd.Run = cliRun 138 | 139 | rootCmd.SetArgs(args) 140 | rootCmd.ParseFlags(args) 141 | rootCmd.SetOut(os.Stdout) 142 | 143 | readlinecompleter = readline.NewPrefixCompleter(GenCompleter(rootCmd)...) 144 | return rootCmd 145 | } 146 | 147 | // Hide the flags in help and usage messages. 148 | func hiddenFlag(cmd *cobra.Command) { 149 | cmd.LocalFlags().MarkHidden("pd") 150 | cmd.LocalFlags().MarkHidden("cacert") 151 | cmd.LocalFlags().MarkHidden("cert") 152 | cmd.LocalFlags().MarkHidden("key") 153 | } 154 | 155 | // MainStart start main command 156 | func MainStart(args []string) { 157 | startCmd(getMainCmd, args) 158 | } 159 | 160 | // Start start interact command 161 | func Start(args []string) { 162 | startCmd(getInteractCmd, args) 163 | } 164 | 165 | func startCmd(getCmd func([]string) *cobra.Command, args []string) { 166 | rootCmd := getCmd(args) 167 | 168 | if err := rootCmd.Execute(); err != nil { 169 | rootCmd.Println(err) 170 | } 171 | } 172 | 173 | // initConfig reads in config file and ENV variables if set. 174 | func initConfig() { 175 | 176 | if syncserver == "" { 177 | fmt.Println(syncserver) 178 | syncserver = os.Getenv("SYNCSERVER") 179 | } 180 | 181 | if cfgFile != "" && commons.FileExists(cfgFile) { 182 | // Use config file from the flag. 183 | viper.SetConfigFile(cfgFile) 184 | } else { 185 | viper.AddConfigPath(".") 186 | viper.SetConfigName(".config") 187 | } 188 | 189 | viper.ReadInConfig() 190 | 191 | viper.AutomaticEnv() // read in environment variables that match 192 | 193 | if syncserver != "" { 194 | viper.Set("SYNCSERVER", syncserver) 195 | } 196 | 197 | } 198 | 199 | func loop() { 200 | rl, err := readline.NewEx(&readline.Config{ 201 | //Prompt: "\033[31m»\033[0m ", 202 | Prompt: "RedisCompare> ", 203 | HistoryFile: "/tmp/readline.tmp", 204 | AutoComplete: readlinecompleter, 205 | DisableAutoSaveHistory: true, 206 | InterruptPrompt: "^C", 207 | EOFPrompt: "^D", 208 | HistorySearchFold: true, 209 | }) 210 | if err != nil { 211 | panic(err) 212 | } 213 | defer rl.Close() 214 | 215 | var cmds []string 216 | 217 | for { 218 | line, err := rl.Readline() 219 | if err != nil { 220 | if err == readline.ErrInterrupt { 221 | break 222 | } else if err == io.EOF { 223 | break 224 | } 225 | continue 226 | } 227 | if line == "exit" { 228 | os.Exit(0) 229 | } 230 | 231 | line = strings.TrimSpace(line) 232 | if len(line) == 0 { 233 | continue 234 | } 235 | cmds = append(cmds, line) 236 | 237 | if !strings.HasSuffix(line, ";") { 238 | rl.SetPrompt("... ") 239 | continue 240 | } 241 | cmd := strings.Join(cmds, " ") 242 | cmds = cmds[:0] 243 | rl.SetPrompt("RedisCompare> ") 244 | rl.SaveHistory(cmd) 245 | 246 | args, err := shellwords.Parse(cmd) 247 | if err != nil { 248 | fmt.Printf("parse command err: %v\n", err) 249 | continue 250 | } 251 | Start(args) 252 | } 253 | } 254 | 255 | func GenCompleter(cmd *cobra.Command) []readline.PrefixCompleterInterface { 256 | pc := []readline.PrefixCompleterInterface{} 257 | if len(cmd.Commands()) != 0 { 258 | for _, v := range cmd.Commands() { 259 | if v.HasFlags() { 260 | flagsPc := []readline.PrefixCompleterInterface{} 261 | flagUsages := strings.Split(strings.Trim(v.Flags().FlagUsages(), " "), "\n") 262 | for i := 0; i < len(flagUsages)-1; i++ { 263 | flagsPc = append(flagsPc, readline.PcItem(strings.Split(strings.Trim(flagUsages[i], " "), " ")[0])) 264 | } 265 | flagsPc = append(flagsPc, GenCompleter(v)...) 266 | pc = append(pc, readline.PcItem(strings.Split(v.Use, " ")[0], flagsPc...)) 267 | 268 | } else { 269 | pc = append(pc, readline.PcItem(strings.Split(v.Use, " ")[0], GenCompleter(v)...)) 270 | } 271 | } 272 | } 273 | return pc 274 | } 275 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | "os/signal" 23 | "rediscompare/interact" 24 | "strings" 25 | "syscall" 26 | //"github.com/c-bata/go-prompt" 27 | ) 28 | 29 | func main() { 30 | 31 | //cmd.Execute() 32 | 33 | pdAddr := os.Getenv("PD_ADDR") 34 | if pdAddr != "" { 35 | os.Args = append(os.Args, "-u", pdAddr) 36 | } 37 | 38 | sc := make(chan os.Signal, 1) 39 | signal.Notify(sc, 40 | syscall.SIGHUP, 41 | syscall.SIGINT, 42 | syscall.SIGTERM, 43 | syscall.SIGQUIT) 44 | 45 | go func() { 46 | sig := <-sc 47 | fmt.Printf("\nGot signal [%v] to exit.\n", sig) 48 | switch sig { 49 | case syscall.SIGTERM: 50 | os.Exit(0) 51 | default: 52 | os.Exit(1) 53 | } 54 | }() 55 | 56 | var input []string 57 | stat, _ := os.Stdin.Stat() 58 | if (stat.Mode() & os.ModeCharDevice) == 0 { 59 | b, err := ioutil.ReadAll(os.Stdin) 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | input = strings.Split(strings.TrimSpace(string(b[:])), " ") 65 | } 66 | 67 | interact.MainStart(append(os.Args[1:], input...)) 68 | } 69 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | BINARY="rediscompare" 2 | LINUX="linux-amd64" 3 | DARWIN="darwin-adm64" 4 | WIN="windows-amd64" 5 | VERSION=1.0.0 6 | BUILD=`date +%FT%T%z` 7 | 8 | PACKAGES=`go list ./... | grep -v /vendor/` 9 | VETPACKAGES=`go list ./... | grep -v /vendor/ | grep -v /examples/` 10 | GOFILES=`find . -name "*.go" -type f -not -path "./vendor/*"` 11 | 12 | default: 13 | @make clean 14 | @go mod tidy 15 | @go mod vendor 16 | @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${LINUX} 17 | @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${DARWIN} 18 | @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${WIN}.exe 19 | @echo "build finished,please check target directory" 20 | 21 | linux: 22 | @make clean 23 | @go mod tidy 24 | @go mod vendor 25 | @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${LINUX} 26 | 27 | darwin: 28 | @make clean 29 | @go mod tidy 30 | @go mod vendor 31 | @CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${DARWIN} 32 | 33 | windows: 34 | @make clean 35 | @go mod tidy 36 | @go mod vendor 37 | @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags '-w -s' -o target/${BINARY}-${VERSION}-${WIN}.exe 38 | 39 | list: 40 | @echo ${PACKAGES} 41 | @echo ${VETPACKAGES} 42 | @echo ${GOFILES} 43 | 44 | fmt: 45 | @gofmt -s -w ${GOFILES} 46 | 47 | test: 48 | @go test -cpu=1,2,4 -v -tags integration ./... 49 | 50 | vet: 51 | @go vet $(VETPACKAGES) 52 | 53 | docker: 54 | @docker build -t wuxiaoxiaoshen/example:latest . 55 | 56 | clean: 57 | @if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi 58 | @if [ -d ./target ] ; then rm -fr ./target ; fi 59 | 60 | help: 61 | @echo "make - 格式化 Go 代码, 并编译生成二进制文件" 62 | @echo "make linux - 编译 Go 代码, 生成linux平台二进制文件" 63 | @echo "make darwin - 编译 Go 代码, 生成mac平台二进制文件" 64 | @echo "make windows - 编译 Go 代码, 生成windows平台二进制文件" 65 | @echo "make clean - 移除二进制文件和 vim swap files" 66 | 67 | 68 | .PHONY: default linux darwin windows fmt test vet clean help 69 | -------------------------------------------------------------------------------- /redisconfigparameters/all: -------------------------------------------------------------------------------- 1 | "aclfile", 2 | "acllog-max-len", 3 | "activedefrag", 4 | "active-defrag-cycle-max", 5 | "active-defrag-cycle-min", 6 | "active-defrag-ignore-bytes", 7 | "active-defrag-max-scan-fields", 8 | "active-defrag-threshold-lower", 9 | "active-defrag-threshold-upper", 10 | "active-expire-effort", 11 | "activerehashing", 12 | "always-show-logo", 13 | "aof-load-truncated", 14 | "aof_rewrite_cpulist", 15 | "aof-rewrite-incremental-fsync", 16 | "aof-use-rdb-preamble", 17 | "appendfilename", 18 | "appendfsync", 19 | "appendonly", 20 | "auto-aof-rewrite-min-size", 21 | "auto-aof-rewrite-percentage", 22 | "bgsave_cpulist", 23 | "bind", 24 | "bio_cpulist", 25 | "client-output-buffer-limit", 26 | "client-query-buffer-limit", 27 | "cluster-allow-reads-when-down", 28 | "cluster-announce-bus-port", 29 | "cluster-announce-ip", 30 | "cluster-announce-port", 31 | "cluster-config-file", 32 | "cluster-enabled", 33 | "cluster-migration-barrier", 34 | "cluster-node-timeout", 35 | "cluster-replica-no-failover", 36 | "cluster-replica-validity-factor", 37 | "cluster-require-full-coverage", 38 | "cluster-slave-no-failover", 39 | "cluster-slave-validity-factor", 40 | "daemonize", 41 | "databases", 42 | "dbfilename", 43 | "dir", 44 | "dynamic-hz", 45 | "gopher-enabled", 46 | "hash-max-ziplist-entries", 47 | "hash-max-ziplist-value", 48 | "hll-sparse-max-bytes", 49 | "hz", 50 | "include", 51 | "io-threads", 52 | "io-threads-do-reads", 53 | "jemalloc-bg-thread", 54 | "latency-monitor-threshold", 55 | "lazyfree-lazy-eviction", 56 | "lazyfree-lazy-expire", 57 | "lazyfree-lazy-server-del", 58 | "lazyfree-lazy-user-del", 59 | "lfu-decay-time", 60 | "lfu-log-factor", 61 | "list-compress-depth", 62 | "list-max-ziplist-entries", 63 | "list-max-ziplist-size", 64 | "list-max-ziplist-value", 65 | "loadmodule", 66 | "logfile", 67 | "loglevel", 68 | "lua-time-limit", 69 | "masterauth", 70 | "masteruser", 71 | "maxclients", 72 | "maxmemory", 73 | "maxmemory-policy", 74 | "maxmemory-samples", 75 | "min-replicas-max-lag", 76 | "min-replicas-to-write", 77 | "min-slaves-max-lag", 78 | "min-slaves-to-write", 79 | "no-appendfsync-on-rewrite", 80 | "notify-keyspace-events", 81 | "oom-score-adj", 82 | "oom-score-adj-values", 83 | "otify-keyspace-events", 84 | "pidfile", 85 | "port", 86 | "protected-mode", 87 | "proto-max-bulk-len", 88 | "rdbchecksum", 89 | "rdbcompression", 90 | "rdb-del-sync-files", 91 | "rdb-save-incremental-fsync", 92 | "rename-command", 93 | "repl-backlog-size", 94 | "repl-backlog-ttl", 95 | "repl-disable-tcp-nodelay", 96 | "repl-diskless-load", 97 | "repl-diskless-sync", 98 | "repl-diskless-sync-delay", 99 | "replica-announce-ip", 100 | "replica-announce-port", 101 | "replica-ignore-maxmemory", 102 | "replica-lazy-flush", 103 | "replicaof", 104 | "replica-priority", 105 | "replica-read-only", 106 | "replica-serve-stale-data", 107 | "repl-ping-replica-period", 108 | "repl-ping-slave-period", 109 | "repl-timeout", 110 | "requirepass", 111 | "save", 112 | "server_cpulist", 113 | "set-max-intset-entries", 114 | "slave-announce-ip", 115 | "slave-announce-port", 116 | "slave-lazy-flush", 117 | "slaveof", 118 | "slave-priority", 119 | "slave-read-only", 120 | "slave-serve-stale-data", 121 | "slowlog-max-len", 122 | "stop-writes-on-bgsave-error", 123 | "stream-node-max-bytes", 124 | "stream-node-max-entries", 125 | "supervised", 126 | "syslog-enabled", 127 | "syslog-facility", 128 | "syslog-ident", 129 | "tcp-backlog", 130 | "tcp-keepalive", 131 | "timeout", 132 | "tls-auth-clients", 133 | "tls-ca-cert-dir", 134 | "tls-ca-cert-file", 135 | "tls-cert-file", 136 | "tls-ciphers", 137 | "tls-ciphersuites", 138 | "tls-cluster", 139 | "tls-dh-params-file", 140 | "tls-key-file", 141 | "tls-port", 142 | "tls-prefer-server-ciphers", 143 | "tls-protocols", 144 | "tls-replication", 145 | "tls-session-cache-size", 146 | "tls-session-cache-timeout", 147 | "tls-session-caching", 148 | "tracking-table-max-keys", 149 | "unixsocket", 150 | "unixsocketperm", 151 | "zset-max-ziplist-entries", 152 | "zset-max-ziplist-value", 153 | -------------------------------------------------------------------------------- /redisconfigparameters/all.bak: -------------------------------------------------------------------------------- 1 | aclfile 2 | acllog-max-len 3 | activedefrag 4 | active-defrag-cycle-max 5 | active-defrag-cycle-min 6 | active-defrag-ignore-bytes 7 | active-defrag-max-scan-fields 8 | active-defrag-threshold-lower 9 | active-defrag-threshold-upper 10 | active-expire-effort 11 | activerehashing 12 | always-show-logo 13 | aof-load-truncated 14 | aof_rewrite_cpulist 15 | aof-rewrite-incremental-fsync 16 | aof-use-rdb-preamble 17 | appendfilename 18 | appendfsync 19 | appendonly 20 | auto-aof-rewrite-min-size 21 | auto-aof-rewrite-percentage 22 | bgsave_cpulist 23 | bind 24 | bio_cpulist 25 | client-output-buffer-limit 26 | client-query-buffer-limit 27 | cluster-allow-reads-when-down 28 | cluster-announce-bus-port 29 | cluster-announce-ip 30 | cluster-announce-port 31 | cluster-config-file 32 | cluster-enabled 33 | cluster-migration-barrier 34 | cluster-node-timeout 35 | cluster-replica-no-failover 36 | cluster-replica-validity-factor 37 | cluster-require-full-coverage 38 | cluster-slave-no-failover 39 | cluster-slave-validity-factor 40 | daemonize 41 | databases 42 | dbfilename 43 | dir 44 | dynamic-hz 45 | gopher-enabled 46 | hash-max-ziplist-entries 47 | hash-max-ziplist-value 48 | hll-sparse-max-bytes 49 | hz 50 | include 51 | io-threads 52 | io-threads-do-reads 53 | jemalloc-bg-thread 54 | latency-monitor-threshold 55 | lazyfree-lazy-eviction 56 | lazyfree-lazy-expire 57 | lazyfree-lazy-server-del 58 | lazyfree-lazy-user-del 59 | lfu-decay-time 60 | lfu-log-factor 61 | list-compress-depth 62 | list-max-ziplist-entries 63 | list-max-ziplist-size 64 | list-max-ziplist-value 65 | loadmodule 66 | logfile 67 | loglevel 68 | lua-time-limit 69 | masterauth 70 | masteruser 71 | maxclients 72 | maxmemory 73 | maxmemory-policy 74 | maxmemory-samples 75 | min-replicas-max-lag 76 | min-replicas-to-write 77 | min-slaves-max-lag 78 | min-slaves-to-write 79 | no-appendfsync-on-rewrite 80 | notify-keyspace-events 81 | oom-score-adj 82 | oom-score-adj-values 83 | otify-keyspace-events 84 | pidfile 85 | port 86 | protected-mode 87 | proto-max-bulk-len 88 | rdbchecksum 89 | rdbcompression 90 | rdb-del-sync-files 91 | rdb-save-incremental-fsync 92 | rename-command 93 | repl-backlog-size 94 | repl-backlog-ttl 95 | repl-disable-tcp-nodelay 96 | repl-diskless-load 97 | repl-diskless-sync 98 | repl-diskless-sync-delay 99 | replica-announce-ip 100 | replica-announce-port 101 | replica-ignore-maxmemory 102 | replica-lazy-flush 103 | replicaof 104 | replica-priority 105 | replica-read-only 106 | replica-serve-stale-data 107 | repl-ping-replica-period 108 | repl-ping-slave-period 109 | repl-timeout 110 | requirepass 111 | save 112 | server_cpulist 113 | set-max-intset-entries 114 | slave-announce-ip 115 | slave-announce-port 116 | slave-lazy-flush 117 | slaveof 118 | slave-priority 119 | slave-read-only 120 | slave-serve-stale-data 121 | slowlog-max-len 122 | stop-writes-on-bgsave-error 123 | stream-node-max-bytes 124 | stream-node-max-entries 125 | supervised 126 | syslog-enabled 127 | syslog-facility 128 | syslog-ident 129 | tcp-backlog 130 | tcp-keepalive 131 | timeout 132 | tls-auth-clients 133 | tls-ca-cert-dir 134 | tls-ca-cert-file 135 | tls-cert-file 136 | tls-ciphers 137 | tls-ciphersuites 138 | tls-cluster 139 | tls-dh-params-file 140 | tls-key-file 141 | tls-port 142 | tls-prefer-server-ciphers 143 | tls-protocols 144 | tls-replication 145 | tls-session-cache-size 146 | tls-session-cache-timeout 147 | tls-session-caching 148 | tracking-table-max-keys 149 | unixsocket 150 | unixsocketperm 151 | zset-max-ziplist-entries 152 | zset-max-ziplist-value 153 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_all: -------------------------------------------------------------------------------- 1 | activerehashing 2 | aof-load-truncated 3 | aof-rewrite-incremental-fsync 4 | appendfilename 5 | appendfsync 6 | appendonly 7 | auto-aof-rewrite-min-size 8 | auto-aof-rewrite-percentage 9 | bind 10 | client-output-buffer-limit 11 | daemonize 12 | databases 13 | dbfilename 14 | dir 15 | hash-max-ziplist-entries 16 | hash-max-ziplist-value 17 | hll-sparse-max-bytes 18 | hz 19 | latency-monitor-threshold 20 | list-max-ziplist-entries 21 | list-max-ziplist-value 22 | logfile 23 | loglevel 24 | lua-time-limit 25 | masterauth 26 | maxclients 27 | maxmemory 28 | maxmemory-policy 29 | maxmemory-samples 30 | min-slaves-max-lag 31 | min-slaves-to-write 32 | no-appendfsync-on-rewrite 33 | otify-keyspace-events 34 | pidfile 35 | port 36 | rdbchecksum 37 | rdbcompression 38 | repl-backlog-size 39 | repl-backlog-ttl 40 | repl-disable-tcp-nodelay 41 | repl-diskless-sync 42 | repl-diskless-sync-delay 43 | repl-ping-slave-period 44 | repl-timeout 45 | save 46 | set-max-intset-entries 47 | slaveof 48 | slave-priority 49 | slave-read-only 50 | slave-serve-stale-data 51 | slowlog-max-len 52 | stop-writes-on-bgsave-error 53 | syslog-enabled 54 | syslog-facility 55 | syslog-ident 56 | tcp-backlog 57 | tcp-keepalive 58 | timeout 59 | unixsocket 60 | unixsocketperm 61 | zset-max-ziplist-entries 62 | zset-max-ziplist-value 63 | activerehashing 64 | aof-load-truncated 65 | aof-rewrite-incremental-fsync 66 | appendfilename 67 | appendfsync 68 | appendonly 69 | auto-aof-rewrite-min-size 70 | auto-aof-rewrite-percentage 71 | bind 72 | client-output-buffer-limit 73 | cluster-config-file 74 | cluster-migration-barrier 75 | cluster-node-timeout 76 | cluster-require-full-coverage 77 | cluster-slave-validity-factor 78 | databases 79 | dbfilename 80 | dir 81 | hash-max-ziplist-entries 82 | hash-max-ziplist-value 83 | hll-sparse-max-bytes 84 | hz 85 | include 86 | latency-monitor-threshold 87 | list-compress-depth 88 | list-max-ziplist-size 89 | logfile 90 | loglevel 91 | lua-time-limit 92 | masterauth 93 | maxclients 94 | maxmemory 95 | maxmemory-policy 96 | maxmemory-samples 97 | min-slaves-max-lag 98 | min-slaves-to-write 99 | no-appendfsync-on-rewrite 100 | notify-keyspace-events 101 | pidfile 102 | port 103 | protected-mode 104 | rdbchecksum 105 | rdbcompression 106 | rename-command 107 | repl-backlog-size 108 | repl-backlog-ttl 109 | repl-disable-tcp-nodelay 110 | repl-diskless-sync 111 | repl-diskless-sync-delay 112 | repl-ping-slave-period 113 | repl-timeout 114 | requirepass 115 | save 116 | set-max-intset-entries 117 | slave-announce-ip 118 | slave-announce-port 119 | slaveof 120 | slave-priority 121 | slave-read-only 122 | slave-serve-stale-data 123 | slowlog-max-len 124 | stop-writes-on-bgsave-error 125 | supervised 126 | syslog-enabled 127 | syslog-facility 128 | syslog-ident 129 | tcp-backlog 130 | tcp-keepalive 131 | timeout 132 | unixsocket 133 | unixsocketperm 134 | zset-max-ziplist-entries 135 | zset-max-ziplist-value 136 | activedefrag 137 | active-defrag-cycle-max 138 | active-defrag-cycle-min 139 | active-defrag-ignore-bytes 140 | active-defrag-threshold-lower 141 | active-defrag-threshold-upper 142 | activerehashing 143 | always-show-logo 144 | aof-load-truncated 145 | aof-rewrite-incremental-fsync 146 | aof-use-rdb-preamble 147 | appendfilename 148 | appendfsync 149 | appendonly 150 | auto-aof-rewrite-min-size 151 | auto-aof-rewrite-percentage 152 | bind 153 | client-output-buffer-limit 154 | client-query-buffer-limit 155 | cluster-announce-bus-port 156 | cluster-announce-ip 157 | cluster-announce-port 158 | cluster-config-file 159 | cluster-migration-barrier 160 | cluster-node-timeout 161 | cluster-require-full-coverage 162 | cluster-slave-no-failover 163 | cluster-slave-validity-factor 164 | daemonize 165 | databases 166 | dbfilename 167 | dir 168 | hash-max-ziplist-entries 169 | hash-max-ziplist-value 170 | hll-sparse-max-bytes 171 | hz 172 | include 173 | latency-monitor-threshold 174 | lazyfree-lazy-eviction 175 | lazyfree-lazy-expire 176 | lazyfree-lazy-server-del 177 | lfu-decay-time 178 | lfu-log-factor 179 | list-compress-depth 180 | list-max-ziplist-size 181 | loadmodule 182 | logfile 183 | loglevel 184 | lua-time-limit 185 | masterauth 186 | maxclients 187 | maxmemory 188 | maxmemory-policy 189 | maxmemory-samples 190 | min-slaves-max-lag 191 | min-slaves-to-write 192 | no-appendfsync-on-rewrite 193 | notify-keyspace-events 194 | pidfile 195 | port 196 | protected-mode 197 | proto-max-bulk-len 198 | rdbchecksum 199 | rdbcompression 200 | rename-command 201 | repl-backlog-size 202 | repl-backlog-ttl 203 | repl-disable-tcp-nodelay 204 | repl-diskless-sync 205 | repl-diskless-sync-delay 206 | repl-ping-slave-period 207 | repl-timeout 208 | requirepass 209 | save 210 | set-max-intset-entries 211 | slave-announce-ip 212 | slave-announce-port 213 | slave-lazy-flush 214 | slaveof 215 | slave-priority 216 | slave-read-only 217 | slave-serve-stale-data 218 | slowlog-max-len 219 | stop-writes-on-bgsave-error 220 | supervised 221 | syslog-enabled 222 | syslog-facility 223 | syslog-ident 224 | tcp-backlog 225 | tcp-keepalive 226 | timeout 227 | unixsocket 228 | unixsocketperm 229 | zset-max-ziplist-entries 230 | zset-max-ziplist-value 231 | activedefrag 232 | active-defrag-cycle-max 233 | active-defrag-cycle-min 234 | active-defrag-ignore-bytes 235 | active-defrag-max-scan-fields 236 | active-defrag-threshold-lower 237 | active-defrag-threshold-upper 238 | activerehashing 239 | always-show-logo 240 | aof-load-truncated 241 | aof-rewrite-incremental-fsync 242 | aof-use-rdb-preamble 243 | appendfilename 244 | appendfsync 245 | appendonly 246 | auto-aof-rewrite-min-size 247 | auto-aof-rewrite-percentage 248 | bind 249 | client-output-buffer-limit 250 | client-query-buffer-limit 251 | cluster-announce-bus-port 252 | cluster-announce-ip 253 | cluster-announce-port 254 | cluster-config-file 255 | cluster-enabled 256 | cluster-migration-barrier 257 | cluster-node-timeout 258 | cluster-replica-no-failover 259 | cluster-replica-validity-factor 260 | cluster-require-full-coverage 261 | daemonize 262 | databases 263 | dbfilename 264 | dir 265 | dynamic-hz 266 | hash-max-ziplist-entries 267 | hash-max-ziplist-value 268 | hll-sparse-max-bytes 269 | hz 270 | include 271 | latency-monitor-threshold 272 | lazyfree-lazy-eviction 273 | lazyfree-lazy-expire 274 | lazyfree-lazy-server-del 275 | lfu-decay-time 276 | lfu-log-factor 277 | list-compress-depth 278 | list-max-ziplist-size 279 | loadmodule 280 | logfile 281 | loglevel 282 | lua-time-limit 283 | masterauth 284 | maxclients 285 | maxmemory 286 | maxmemory-policy 287 | maxmemory-samples 288 | min-replicas-max-lag 289 | min-replicas-to-write 290 | no-appendfsync-on-rewrite 291 | notify-keyspace-events 292 | pidfile 293 | port 294 | proto-max-bulk-len 295 | rdbchecksum 296 | rdbcompression 297 | rdb-save-incremental-fsync 298 | rename-command 299 | repl-backlog-size 300 | repl-backlog-ttl 301 | repl-disable-tcp-nodelay 302 | repl-diskless-sync 303 | repl-diskless-sync-delay 304 | replica-announce-ip 305 | replica-announce-port 306 | replica-ignore-maxmemory 307 | replica-lazy-flush 308 | replicaof 309 | replica-priority 310 | replica-read-only 311 | replica-serve-stale-data 312 | repl-ping-replica-period 313 | repl-timeout 314 | requirepass 315 | save 316 | set-max-intset-entries 317 | slowlog-max-len 318 | stop-writes-on-bgsave-error 319 | stream-node-max-bytes 320 | stream-node-max-entries 321 | supervised 322 | syslog-enabled 323 | syslog-facility 324 | syslog-ident 325 | tcp-backlog 326 | tcp-keepalive 327 | timeout 328 | unixsocket 329 | unixsocketperm 330 | zset-max-ziplist-entries 331 | zset-max-ziplist-value 332 | aclfile 333 | acllog-max-len 334 | activedefrag 335 | active-defrag-cycle-max 336 | active-defrag-cycle-min 337 | active-defrag-ignore-bytes 338 | active-defrag-max-scan-fields 339 | active-defrag-threshold-lower 340 | active-defrag-threshold-upper 341 | active-expire-effort 342 | activerehashing 343 | always-show-logo 344 | aof-load-truncated 345 | aof_rewrite_cpulist 346 | aof-rewrite-incremental-fsync 347 | aof-use-rdb-preamble 348 | appendfilename 349 | appendfsync 350 | appendonly 351 | auto-aof-rewrite-min-size 352 | auto-aof-rewrite-percentage 353 | bgsave_cpulist 354 | bind 355 | bio_cpulist 356 | client-output-buffer-limit 357 | client-query-buffer-limit 358 | cluster-allow-reads-when-down 359 | cluster-announce-bus-port 360 | cluster-announce-ip 361 | cluster-announce-port 362 | cluster-config-file 363 | cluster-enabled 364 | cluster-migration-barrier 365 | cluster-node-timeout 366 | cluster-replica-no-failover 367 | cluster-replica-validity-factor 368 | cluster-require-full-coverage 369 | databases 370 | dbfilename 371 | dir 372 | dynamic-hz 373 | gopher-enabled 374 | hash-max-ziplist-entries 375 | hash-max-ziplist-value 376 | hll-sparse-max-bytes 377 | hz 378 | include 379 | io-threads 380 | io-threads-do-reads 381 | jemalloc-bg-thread 382 | latency-monitor-threshold 383 | lazyfree-lazy-eviction 384 | lazyfree-lazy-expire 385 | lazyfree-lazy-server-del 386 | lazyfree-lazy-user-del 387 | lfu-decay-time 388 | lfu-log-factor 389 | list-compress-depth 390 | list-max-ziplist-size 391 | loadmodule 392 | logfile 393 | loglevel 394 | lua-time-limit 395 | masterauth 396 | masteruser 397 | maxclients 398 | maxmemory 399 | maxmemory-policy 400 | maxmemory-samples 401 | min-replicas-max-lag 402 | min-replicas-to-write 403 | no-appendfsync-on-rewrite 404 | notify-keyspace-events 405 | oom-score-adj 406 | oom-score-adj-values 407 | pidfile 408 | port 409 | proto-max-bulk-len 410 | rdbchecksum 411 | rdbcompression 412 | rdb-del-sync-files 413 | rdb-save-incremental-fsync 414 | repl-backlog-size 415 | repl-backlog-ttl 416 | repl-disable-tcp-nodelay 417 | repl-diskless-load 418 | repl-diskless-sync 419 | repl-diskless-sync-delay 420 | replica-announce-ip 421 | replica-announce-port 422 | replica-ignore-maxmemory 423 | replica-lazy-flush 424 | replicaof 425 | replica-priority 426 | replica-read-only 427 | replica-serve-stale-data 428 | repl-ping-replica-period 429 | repl-timeout 430 | requirepass 431 | save 432 | server_cpulist 433 | set-max-intset-entries 434 | slowlog-max-len 435 | stop-writes-on-bgsave-error 436 | stream-node-max-bytes 437 | stream-node-max-entries 438 | supervised 439 | syslog-enabled 440 | syslog-facility 441 | syslog-ident 442 | tcp-backlog 443 | tcp-keepalive 444 | timeout 445 | tls-auth-clients 446 | tls-ca-cert-dir 447 | tls-ca-cert-file 448 | tls-cert-file 449 | tls-ciphers 450 | tls-ciphersuites 451 | tls-cluster 452 | tls-dh-params-file 453 | tls-key-file 454 | tls-port 455 | tls-prefer-server-ciphers 456 | tls-protocols 457 | tls-replication 458 | tls-session-cache-size 459 | tls-session-cache-timeout 460 | tls-session-caching 461 | tracking-table-max-keys 462 | unixsocket 463 | unixsocketperm 464 | zset-max-ziplist-entries 465 | zset-max-ziplist-value 466 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_v2: -------------------------------------------------------------------------------- 1 | daemonize no 2 | pidfile /var/run/redis.pid 3 | port 6379 4 | tcp-backlog 511 5 | bind 127.0.0.1 6 | unixsocket /tmp/redis.sock 7 | unixsocketperm 700 8 | timeout 0 9 | tcp-keepalive 0 10 | loglevel notice 11 | logfile "" 12 | syslog-enabled no 13 | syslog-ident redis 14 | syslog-facility local0 15 | databases 16 16 | save 900 1 17 | save 300 10 18 | save 60 10000 19 | stop-writes-on-bgsave-error yes 20 | rdbcompression yes 21 | rdbchecksum yes 22 | dbfilename dump.rdb 23 | dir ./ 24 | slaveof 25 | masterauth 26 | slave-serve-stale-data yes 27 | slave-read-only yes 28 | repl-diskless-sync no 29 | repl-diskless-sync-delay 5 30 | repl-ping-slave-period 10 31 | repl-timeout 60 32 | repl-disable-tcp-nodelay no 33 | repl-backlog-size 1mb 34 | repl-backlog-ttl 3600 35 | slave-priority 100 36 | min-slaves-to-write 3 37 | min-slaves-max-lag 10 38 | maxclients 10000 39 | maxmemory 40 | maxmemory-policy volatile-lru 41 | maxmemory-samples 3 42 | appendonly no 43 | appendfilename "appendonly.aof" 44 | appendfsync everysec 45 | appendfsync no 46 | no-appendfsync-on-rewrite no 47 | auto-aof-rewrite-percentage 100 48 | auto-aof-rewrite-min-size 64mb 49 | aof-load-truncated yes 50 | lua-time-limit 5000 51 | slowlog-max-len 128 52 | latency-monitor-threshold 0 53 | otify-keyspace-events "" 54 | hash-max-ziplist-entries 512 55 | hash-max-ziplist-value 64 56 | list-max-ziplist-entries 512 57 | list-max-ziplist-value 64 58 | set-max-intset-entries 512 59 | zset-max-ziplist-entries 128 60 | zset-max-ziplist-value 64 61 | hll-sparse-max-bytes 3000 62 | activerehashing yes 63 | client-output-buffer-limit normal 0 0 0 64 | client-output-buffer-limit slave 256mb 64mb 60 65 | client-output-buffer-limit pubsub 32mb 8mb 60 66 | hz 10 67 | aof-rewrite-incremental-fsync yes 68 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_v3: -------------------------------------------------------------------------------- 1 | include /path/to/local.conf 2 | bind 127.0.0.1 3 | protected-mode yes 4 | port 6379 5 | tcp-backlog 511 6 | unixsocket /tmp/redis.sock 7 | unixsocketperm 700 8 | timeout 0 9 | tcp-keepalive 300 10 | supervised no 11 | pidfile /var/run/redis_6379.pid 12 | loglevel notice 13 | logfile "" 14 | syslog-enabled no 15 | syslog-ident redis 16 | syslog-facility local0 17 | databases 16 18 | save 900 1 19 | save 300 10 20 | save 60 10000 21 | stop-writes-on-bgsave-error yes 22 | rdbcompression yes 23 | rdbchecksum yes 24 | dbfilename dump.rdb 25 | dir ./ 26 | slaveof 27 | masterauth 28 | slave-serve-stale-data yes 29 | slave-read-only yes 30 | repl-diskless-sync no 31 | repl-diskless-sync-delay 5 32 | repl-ping-slave-period 10 33 | repl-timeout 60 34 | repl-disable-tcp-nodelay no 35 | repl-backlog-size 1mb 36 | repl-backlog-ttl 3600 37 | slave-priority 100 38 | min-slaves-to-write 3 39 | min-slaves-max-lag 10 40 | slave-announce-ip 5.5.5.5 41 | slave-announce-port 1234 42 | requirepass foobared 43 | rename-command CONFIG "" 44 | maxclients 10000 45 | maxmemory 46 | maxmemory-policy noeviction 47 | maxmemory-samples 5 48 | appendonly no 49 | appendfilename "appendonly.aof" 50 | appendfsync everysec 51 | no-appendfsync-on-rewrite no 52 | auto-aof-rewrite-percentage 100 53 | auto-aof-rewrite-min-size 64mb 54 | aof-load-truncated yes 55 | lua-time-limit 5000 56 | cluster-config-file nodes-6379.conf 57 | cluster-node-timeout 15000 58 | cluster-slave-validity-factor 10 59 | cluster-migration-barrier 1 60 | cluster-require-full-coverage yes 61 | slowlog-max-len 128 62 | latency-monitor-threshold 0 63 | notify-keyspace-events "" 64 | hash-max-ziplist-entries 512 65 | hash-max-ziplist-value 64 66 | list-max-ziplist-size -2 67 | list-compress-depth 0 68 | set-max-intset-entries 512 69 | zset-max-ziplist-entries 128 70 | zset-max-ziplist-value 64 71 | hll-sparse-max-bytes 3000 72 | activerehashing yes 73 | client-output-buffer-limit normal 0 0 0 74 | client-output-buffer-limit slave 256mb 64mb 60 75 | client-output-buffer-limit pubsub 32mb 8mb 60 76 | hz 10 77 | aof-rewrite-incremental-fsync yes 78 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_v4: -------------------------------------------------------------------------------- 1 | include /path/to/other.conf 2 | loadmodule /path/to/other_module.so 3 | bind 127.0.0.1 4 | protected-mode yes 5 | port 6379 6 | tcp-backlog 511 7 | unixsocket /tmp/redis.sock 8 | unixsocketperm 700 9 | timeout 0 10 | tcp-keepalive 300 11 | daemonize no 12 | supervised no 13 | pidfile /var/run/redis_6379.pid 14 | loglevel notice 15 | logfile "" 16 | syslog-enabled no 17 | syslog-ident redis 18 | syslog-facility local0 19 | databases 16 20 | always-show-logo yes 21 | save 900 1 22 | save 300 10 23 | save 60 10000 24 | stop-writes-on-bgsave-error yes 25 | rdbcompression yes 26 | rdbchecksum yes 27 | dbfilename dump.rdb 28 | dir ./ 29 | slaveof 30 | masterauth 31 | slave-serve-stale-data yes 32 | slave-read-only yes 33 | repl-diskless-sync no 34 | repl-diskless-sync-delay 5 35 | repl-ping-slave-period 10 36 | repl-timeout 60 37 | repl-disable-tcp-nodelay no 38 | repl-backlog-size 1mb 39 | repl-backlog-ttl 3600 40 | slave-priority 100 41 | min-slaves-to-write 3 42 | min-slaves-max-lag 10 43 | slave-announce-ip 5.5.5.5 44 | slave-announce-port 1234 45 | requirepass foobared 46 | rename-command CONFIG "" 47 | maxclients 10000 48 | maxmemory 49 | maxmemory-policy noeviction 50 | maxmemory-samples 5 51 | lazyfree-lazy-eviction no 52 | lazyfree-lazy-expire no 53 | lazyfree-lazy-server-del no 54 | slave-lazy-flush no 55 | appendonly no 56 | appendfilename "appendonly.aof" 57 | appendfsync everysec 58 | no-appendfsync-on-rewrite no 59 | auto-aof-rewrite-percentage 100 60 | auto-aof-rewrite-min-size 64mb 61 | aof-load-truncated yes 62 | aof-use-rdb-preamble no 63 | lua-time-limit 5000 64 | cluster-config-file nodes-6379.conf 65 | cluster-node-timeout 15000 66 | cluster-slave-validity-factor 10 67 | cluster-migration-barrier 1 68 | cluster-require-full-coverage yes 69 | cluster-slave-no-failover no 70 | cluster-announce-ip 10.1.1.5 71 | cluster-announce-port 6379 72 | cluster-announce-bus-port 6380 73 | slowlog-max-len 128 74 | latency-monitor-threshold 0 75 | notify-keyspace-events "" 76 | hash-max-ziplist-entries 512 77 | hash-max-ziplist-value 64 78 | list-max-ziplist-size -2 79 | list-compress-depth 0 80 | set-max-intset-entries 512 81 | zset-max-ziplist-entries 128 82 | zset-max-ziplist-value 64 83 | hll-sparse-max-bytes 3000 84 | activerehashing yes 85 | client-output-buffer-limit normal 0 0 0 86 | client-output-buffer-limit slave 256mb 64mb 60 87 | client-output-buffer-limit pubsub 32mb 8mb 60 88 | client-query-buffer-limit 1gb 89 | proto-max-bulk-len 512mb 90 | hz 10 91 | aof-rewrite-incremental-fsync yes 92 | lfu-log-factor 10 93 | lfu-decay-time 1 94 | activedefrag yes 95 | active-defrag-ignore-bytes 100mb 96 | active-defrag-threshold-lower 10 97 | active-defrag-threshold-upper 100 98 | active-defrag-cycle-min 25 99 | active-defrag-cycle-max 75 100 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_v5: -------------------------------------------------------------------------------- 1 | include /path/to/local.conf 2 | loadmodule /path/to/other_module.so 3 | bind 127.0.0.1 4 | port 6379 5 | tcp-backlog 511 6 | unixsocket /tmp/redis.sock 7 | unixsocketperm 700 8 | timeout 0 9 | tcp-keepalive 300 10 | daemonize no 11 | supervised no 12 | pidfile /var/run/redis_6379.pid 13 | loglevel notice 14 | logfile "" 15 | syslog-enabled no 16 | syslog-ident redis 17 | syslog-facility local0 18 | databases 16 19 | always-show-logo yes 20 | save 900 1 21 | save 300 10 22 | save 60 10000 23 | stop-writes-on-bgsave-error yes 24 | rdbcompression yes 25 | rdbchecksum yes 26 | dbfilename dump.rdb 27 | dir ./ 28 | replicaof 29 | masterauth 30 | replica-serve-stale-data yes 31 | replica-read-only yes 32 | repl-diskless-sync no 33 | repl-diskless-sync-delay 5 34 | repl-ping-replica-period 10 35 | repl-timeout 60 36 | repl-disable-tcp-nodelay no 37 | repl-backlog-size 1mb 38 | repl-backlog-ttl 3600 39 | replica-priority 100 40 | min-replicas-to-write 3 41 | min-replicas-max-lag 10 42 | replica-announce-ip 5.5.5.5 43 | replica-announce-port 1234 44 | requirepass foobared 45 | rename-command CONFIG "" 46 | maxclients 10000 47 | maxmemory 48 | maxmemory-policy noeviction 49 | maxmemory-samples 5 50 | replica-ignore-maxmemory yes 51 | lazyfree-lazy-eviction no 52 | lazyfree-lazy-expire no 53 | lazyfree-lazy-server-del no 54 | replica-lazy-flush no 55 | appendonly no 56 | appendfilename "appendonly.aof" 57 | appendfsync everysec 58 | no-appendfsync-on-rewrite no 59 | auto-aof-rewrite-percentage 100 60 | auto-aof-rewrite-min-size 64mb 61 | aof-load-truncated yes 62 | aof-use-rdb-preamble yes 63 | lua-time-limit 5000 64 | cluster-enabled yes 65 | cluster-config-file nodes-6379.conf 66 | cluster-node-timeout 15000 67 | cluster-replica-validity-factor 10 68 | cluster-migration-barrier 1 69 | cluster-require-full-coverage yes 70 | cluster-replica-no-failover no 71 | cluster-announce-ip 10.1.1.5 72 | cluster-announce-port 6379 73 | cluster-announce-bus-port 6380 74 | slowlog-max-len 128 75 | latency-monitor-threshold 0 76 | notify-keyspace-events "" 77 | hash-max-ziplist-entries 512 78 | hash-max-ziplist-value 64 79 | list-max-ziplist-size -2 80 | list-compress-depth 0 81 | set-max-intset-entries 512 82 | zset-max-ziplist-entries 128 83 | zset-max-ziplist-value 64 84 | hll-sparse-max-bytes 3000 85 | stream-node-max-bytes 4096 86 | stream-node-max-entries 100 87 | activerehashing yes 88 | client-output-buffer-limit normal 0 0 0 89 | client-output-buffer-limit replica 256mb 64mb 60 90 | client-output-buffer-limit pubsub 32mb 8mb 60 91 | client-query-buffer-limit 1gb 92 | proto-max-bulk-len 512mb 93 | hz 10 94 | dynamic-hz yes 95 | aof-rewrite-incremental-fsync yes 96 | rdb-save-incremental-fsync yes 97 | lfu-log-factor 10 98 | lfu-decay-time 1 99 | activedefrag yes 100 | active-defrag-ignore-bytes 100mb 101 | active-defrag-threshold-lower 10 102 | active-defrag-threshold-upper 100 103 | active-defrag-cycle-min 5 104 | active-defrag-cycle-max 75 105 | active-defrag-max-scan-fields 1000 106 | -------------------------------------------------------------------------------- /redisconfigparameters/parameters_v6: -------------------------------------------------------------------------------- 1 | include /path/to/local.conf 2 | loadmodule /path/to/my_module.so 3 | bind 127.0.0.1 4 | port 6379 5 | tcp-backlog 511 6 | unixsocket /tmp/redis.sock 7 | unixsocketperm 700 8 | timeout 0 9 | tcp-keepalive 300 10 | tls-port 6379 11 | tls-cert-file redis.crt 12 | tls-key-file redis.key 13 | tls-dh-params-file redis.dh 14 | tls-ca-cert-file ca.crt 15 | tls-ca-cert-dir /etc/ssl/certs 16 | tls-auth-clients no 17 | tls-auth-clients optional 18 | tls-replication yes 19 | tls-cluster yes 20 | tls-protocols "TLSv1.2 TLSv1.3" 21 | tls-ciphers DEFAULT:!MEDIUM 22 | tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 23 | tls-prefer-server-ciphers yes 24 | tls-session-caching no 25 | tls-session-cache-size 5000 26 | tls-session-cache-timeout 60 27 | supervised no 28 | pidfile /var/run/redis_6379.pid 29 | loglevel notice 30 | logfile "" 31 | syslog-enabled no 32 | syslog-ident redis 33 | syslog-facility local0 34 | databases 16 35 | always-show-logo yes 36 | save 900 1 37 | save 300 10 38 | save 60 10000 39 | stop-writes-on-bgsave-error yes 40 | rdbcompression yes 41 | rdbchecksum yes 42 | dbfilename dump.rdb 43 | rdb-del-sync-files no 44 | dir ./ 45 | replicaof 46 | masterauth 47 | masteruser 48 | replica-serve-stale-data yes 49 | replica-read-only yes 50 | repl-diskless-sync no 51 | repl-diskless-sync-delay 5 52 | repl-diskless-load disabled 53 | repl-ping-replica-period 10 54 | repl-timeout 60 55 | repl-disable-tcp-nodelay no 56 | repl-backlog-size 1mb 57 | repl-backlog-ttl 3600 58 | replica-priority 100 59 | min-replicas-to-write 3 60 | min-replicas-max-lag 10 61 | replica-announce-ip 5.5.5.5 62 | replica-announce-port 1234 63 | tracking-table-max-keys 1000000 64 | acllog-max-len 128 65 | aclfile /etc/redis/users.acl 66 | requirepass foobared 67 | maxclients 10000 68 | maxmemory 69 | maxmemory-policy noeviction 70 | maxmemory-samples 5 71 | replica-ignore-maxmemory yes 72 | active-expire-effort 1 73 | lazyfree-lazy-eviction no 74 | lazyfree-lazy-expire no 75 | lazyfree-lazy-server-del no 76 | replica-lazy-flush no 77 | lazyfree-lazy-user-del no 78 | io-threads 4 79 | io-threads-do-reads no 80 | oom-score-adj no 81 | oom-score-adj-values 0 200 800 82 | appendonly no 83 | appendfilename "appendonly.aof" 84 | appendfsync everysec 85 | no-appendfsync-on-rewrite no 86 | auto-aof-rewrite-percentage 100 87 | auto-aof-rewrite-min-size 64mb 88 | aof-load-truncated yes 89 | aof-use-rdb-preamble yes 90 | lua-time-limit 5000 91 | cluster-enabled yes 92 | cluster-config-file nodes-6379.conf 93 | cluster-node-timeout 15000 94 | cluster-replica-validity-factor 10 95 | cluster-migration-barrier 1 96 | cluster-require-full-coverage yes 97 | cluster-replica-no-failover no 98 | cluster-allow-reads-when-down no 99 | cluster-announce-ip 10.1.1.5 100 | cluster-announce-port 6379 101 | cluster-announce-bus-port 6380 102 | slowlog-max-len 128 103 | latency-monitor-threshold 0 104 | notify-keyspace-events "" 105 | gopher-enabled no 106 | hash-max-ziplist-entries 512 107 | hash-max-ziplist-value 64 108 | list-max-ziplist-size -2 109 | list-compress-depth 0 110 | set-max-intset-entries 512 111 | zset-max-ziplist-entries 128 112 | zset-max-ziplist-value 64 113 | hll-sparse-max-bytes 3000 114 | stream-node-max-bytes 4096 115 | stream-node-max-entries 100 116 | activerehashing yes 117 | client-output-buffer-limit normal 0 0 0 118 | client-output-buffer-limit replica 256mb 64mb 60 119 | client-output-buffer-limit pubsub 32mb 8mb 60 120 | client-query-buffer-limit 1gb 121 | proto-max-bulk-len 512mb 122 | hz 10 123 | dynamic-hz yes 124 | aof-rewrite-incremental-fsync yes 125 | rdb-save-incremental-fsync yes 126 | lfu-log-factor 10 127 | lfu-decay-time 1 128 | activedefrag no 129 | active-defrag-ignore-bytes 100mb 130 | active-defrag-threshold-lower 10 131 | active-defrag-threshold-upper 100 132 | active-defrag-cycle-min 1 133 | active-defrag-cycle-max 25 134 | active-defrag-max-scan-fields 1000 135 | jemalloc-bg-thread yes 136 | server_cpulist 0-7:2 137 | bio_cpulist 1,3 138 | aof_rewrite_cpulist 8-11 139 | bgsave_cpulist 1,10-11 140 | -------------------------------------------------------------------------------- /redisconfigparameters/v2: -------------------------------------------------------------------------------- 1 | activerehashing 2 | aof-load-truncated 3 | aof-rewrite-incremental-fsync 4 | appendfilename 5 | appendfsync 6 | appendonly 7 | auto-aof-rewrite-min-size 8 | auto-aof-rewrite-percentage 9 | bind 10 | client-output-buffer-limit 11 | daemonize 12 | databases 13 | dbfilename 14 | dir 15 | hash-max-ziplist-entries 16 | hash-max-ziplist-value 17 | hll-sparse-max-bytes 18 | hz 19 | latency-monitor-threshold 20 | list-max-ziplist-entries 21 | list-max-ziplist-value 22 | logfile 23 | loglevel 24 | lua-time-limit 25 | masterauth 26 | maxclients 27 | maxmemory 28 | maxmemory-policy 29 | maxmemory-samples 30 | min-slaves-max-lag 31 | min-slaves-to-write 32 | no-appendfsync-on-rewrite 33 | otify-keyspace-events 34 | pidfile 35 | port 36 | rdbchecksum 37 | rdbcompression 38 | repl-backlog-size 39 | repl-backlog-ttl 40 | repl-disable-tcp-nodelay 41 | repl-diskless-sync 42 | repl-diskless-sync-delay 43 | repl-ping-slave-period 44 | repl-timeout 45 | save 46 | set-max-intset-entries 47 | slaveof 48 | slave-priority 49 | slave-read-only 50 | slave-serve-stale-data 51 | slowlog-max-len 52 | stop-writes-on-bgsave-error 53 | syslog-enabled 54 | syslog-facility 55 | syslog-ident 56 | tcp-backlog 57 | tcp-keepalive 58 | timeout 59 | unixsocket 60 | unixsocketperm 61 | zset-max-ziplist-entries 62 | zset-max-ziplist-value 63 | -------------------------------------------------------------------------------- /redisconfigparameters/v3: -------------------------------------------------------------------------------- 1 | activerehashing 2 | aof-load-truncated 3 | aof-rewrite-incremental-fsync 4 | appendfilename 5 | appendfsync 6 | appendonly 7 | auto-aof-rewrite-min-size 8 | auto-aof-rewrite-percentage 9 | bind 10 | client-output-buffer-limit 11 | cluster-config-file 12 | cluster-migration-barrier 13 | cluster-node-timeout 14 | cluster-require-full-coverage 15 | cluster-slave-validity-factor 16 | databases 17 | dbfilename 18 | dir 19 | hash-max-ziplist-entries 20 | hash-max-ziplist-value 21 | hll-sparse-max-bytes 22 | hz 23 | include 24 | latency-monitor-threshold 25 | list-compress-depth 26 | list-max-ziplist-size 27 | logfile 28 | loglevel 29 | lua-time-limit 30 | masterauth 31 | maxclients 32 | maxmemory 33 | maxmemory-policy 34 | maxmemory-samples 35 | min-slaves-max-lag 36 | min-slaves-to-write 37 | no-appendfsync-on-rewrite 38 | notify-keyspace-events 39 | pidfile 40 | port 41 | protected-mode 42 | rdbchecksum 43 | rdbcompression 44 | rename-command 45 | repl-backlog-size 46 | repl-backlog-ttl 47 | repl-disable-tcp-nodelay 48 | repl-diskless-sync 49 | repl-diskless-sync-delay 50 | repl-ping-slave-period 51 | repl-timeout 52 | requirepass 53 | save 54 | set-max-intset-entries 55 | slave-announce-ip 56 | slave-announce-port 57 | slaveof 58 | slave-priority 59 | slave-read-only 60 | slave-serve-stale-data 61 | slowlog-max-len 62 | stop-writes-on-bgsave-error 63 | supervised 64 | syslog-enabled 65 | syslog-facility 66 | syslog-ident 67 | tcp-backlog 68 | tcp-keepalive 69 | timeout 70 | unixsocket 71 | unixsocketperm 72 | zset-max-ziplist-entries 73 | zset-max-ziplist-value 74 | -------------------------------------------------------------------------------- /redisconfigparameters/v4: -------------------------------------------------------------------------------- 1 | activedefrag 2 | active-defrag-cycle-max 3 | active-defrag-cycle-min 4 | active-defrag-ignore-bytes 5 | active-defrag-threshold-lower 6 | active-defrag-threshold-upper 7 | activerehashing 8 | always-show-logo 9 | aof-load-truncated 10 | aof-rewrite-incremental-fsync 11 | aof-use-rdb-preamble 12 | appendfilename 13 | appendfsync 14 | appendonly 15 | auto-aof-rewrite-min-size 16 | auto-aof-rewrite-percentage 17 | bind 18 | client-output-buffer-limit 19 | client-query-buffer-limit 20 | cluster-announce-bus-port 21 | cluster-announce-ip 22 | cluster-announce-port 23 | cluster-config-file 24 | cluster-migration-barrier 25 | cluster-node-timeout 26 | cluster-require-full-coverage 27 | cluster-slave-no-failover 28 | cluster-slave-validity-factor 29 | daemonize 30 | databases 31 | dbfilename 32 | dir 33 | hash-max-ziplist-entries 34 | hash-max-ziplist-value 35 | hll-sparse-max-bytes 36 | hz 37 | include 38 | latency-monitor-threshold 39 | lazyfree-lazy-eviction 40 | lazyfree-lazy-expire 41 | lazyfree-lazy-server-del 42 | lfu-decay-time 43 | lfu-log-factor 44 | list-compress-depth 45 | list-max-ziplist-size 46 | loadmodule 47 | logfile 48 | loglevel 49 | lua-time-limit 50 | masterauth 51 | maxclients 52 | maxmemory 53 | maxmemory-policy 54 | maxmemory-samples 55 | min-slaves-max-lag 56 | min-slaves-to-write 57 | no-appendfsync-on-rewrite 58 | notify-keyspace-events 59 | pidfile 60 | port 61 | protected-mode 62 | proto-max-bulk-len 63 | rdbchecksum 64 | rdbcompression 65 | rename-command 66 | repl-backlog-size 67 | repl-backlog-ttl 68 | repl-disable-tcp-nodelay 69 | repl-diskless-sync 70 | repl-diskless-sync-delay 71 | repl-ping-slave-period 72 | repl-timeout 73 | requirepass 74 | save 75 | set-max-intset-entries 76 | slave-announce-ip 77 | slave-announce-port 78 | slave-lazy-flush 79 | slaveof 80 | slave-priority 81 | slave-read-only 82 | slave-serve-stale-data 83 | slowlog-max-len 84 | stop-writes-on-bgsave-error 85 | supervised 86 | syslog-enabled 87 | syslog-facility 88 | syslog-ident 89 | tcp-backlog 90 | tcp-keepalive 91 | timeout 92 | unixsocket 93 | unixsocketperm 94 | zset-max-ziplist-entries 95 | zset-max-ziplist-value 96 | -------------------------------------------------------------------------------- /redisconfigparameters/v5: -------------------------------------------------------------------------------- 1 | activedefrag 2 | active-defrag-cycle-max 3 | active-defrag-cycle-min 4 | active-defrag-ignore-bytes 5 | active-defrag-max-scan-fields 6 | active-defrag-threshold-lower 7 | active-defrag-threshold-upper 8 | activerehashing 9 | always-show-logo 10 | aof-load-truncated 11 | aof-rewrite-incremental-fsync 12 | aof-use-rdb-preamble 13 | appendfilename 14 | appendfsync 15 | appendonly 16 | auto-aof-rewrite-min-size 17 | auto-aof-rewrite-percentage 18 | bind 19 | client-output-buffer-limit 20 | client-query-buffer-limit 21 | cluster-announce-bus-port 22 | cluster-announce-ip 23 | cluster-announce-port 24 | cluster-config-file 25 | cluster-enabled 26 | cluster-migration-barrier 27 | cluster-node-timeout 28 | cluster-replica-no-failover 29 | cluster-replica-validity-factor 30 | cluster-require-full-coverage 31 | daemonize 32 | databases 33 | dbfilename 34 | dir 35 | dynamic-hz 36 | hash-max-ziplist-entries 37 | hash-max-ziplist-value 38 | hll-sparse-max-bytes 39 | hz 40 | include 41 | latency-monitor-threshold 42 | lazyfree-lazy-eviction 43 | lazyfree-lazy-expire 44 | lazyfree-lazy-server-del 45 | lfu-decay-time 46 | lfu-log-factor 47 | list-compress-depth 48 | list-max-ziplist-size 49 | loadmodule 50 | logfile 51 | loglevel 52 | lua-time-limit 53 | masterauth 54 | maxclients 55 | maxmemory 56 | maxmemory-policy 57 | maxmemory-samples 58 | min-replicas-max-lag 59 | min-replicas-to-write 60 | no-appendfsync-on-rewrite 61 | notify-keyspace-events 62 | pidfile 63 | port 64 | proto-max-bulk-len 65 | rdbchecksum 66 | rdbcompression 67 | rdb-save-incremental-fsync 68 | rename-command 69 | repl-backlog-size 70 | repl-backlog-ttl 71 | repl-disable-tcp-nodelay 72 | repl-diskless-sync 73 | repl-diskless-sync-delay 74 | replica-announce-ip 75 | replica-announce-port 76 | replica-ignore-maxmemory 77 | replica-lazy-flush 78 | replicaof 79 | replica-priority 80 | replica-read-only 81 | replica-serve-stale-data 82 | repl-ping-replica-period 83 | repl-timeout 84 | requirepass 85 | save 86 | set-max-intset-entries 87 | slowlog-max-len 88 | stop-writes-on-bgsave-error 89 | stream-node-max-bytes 90 | stream-node-max-entries 91 | supervised 92 | syslog-enabled 93 | syslog-facility 94 | syslog-ident 95 | tcp-backlog 96 | tcp-keepalive 97 | timeout 98 | unixsocket 99 | unixsocketperm 100 | zset-max-ziplist-entries 101 | zset-max-ziplist-value 102 | -------------------------------------------------------------------------------- /redisconfigparameters/v6: -------------------------------------------------------------------------------- 1 | aclfile 2 | acllog-max-len 3 | activedefrag 4 | active-defrag-cycle-max 5 | active-defrag-cycle-min 6 | active-defrag-ignore-bytes 7 | active-defrag-max-scan-fields 8 | active-defrag-threshold-lower 9 | active-defrag-threshold-upper 10 | active-expire-effort 11 | activerehashing 12 | always-show-logo 13 | aof-load-truncated 14 | aof_rewrite_cpulist 15 | aof-rewrite-incremental-fsync 16 | aof-use-rdb-preamble 17 | appendfilename 18 | appendfsync 19 | appendonly 20 | auto-aof-rewrite-min-size 21 | auto-aof-rewrite-percentage 22 | bgsave_cpulist 23 | bind 24 | bio_cpulist 25 | client-output-buffer-limit 26 | client-query-buffer-limit 27 | cluster-allow-reads-when-down 28 | cluster-announce-bus-port 29 | cluster-announce-ip 30 | cluster-announce-port 31 | cluster-config-file 32 | cluster-enabled 33 | cluster-migration-barrier 34 | cluster-node-timeout 35 | cluster-replica-no-failover 36 | cluster-replica-validity-factor 37 | cluster-require-full-coverage 38 | databases 39 | dbfilename 40 | dir 41 | dynamic-hz 42 | gopher-enabled 43 | hash-max-ziplist-entries 44 | hash-max-ziplist-value 45 | hll-sparse-max-bytes 46 | hz 47 | include 48 | io-threads 49 | io-threads-do-reads 50 | jemalloc-bg-thread 51 | latency-monitor-threshold 52 | lazyfree-lazy-eviction 53 | lazyfree-lazy-expire 54 | lazyfree-lazy-server-del 55 | lazyfree-lazy-user-del 56 | lfu-decay-time 57 | lfu-log-factor 58 | list-compress-depth 59 | list-max-ziplist-size 60 | loadmodule 61 | logfile 62 | loglevel 63 | lua-time-limit 64 | masterauth 65 | masteruser 66 | maxclients 67 | maxmemory 68 | maxmemory-policy 69 | maxmemory-samples 70 | min-replicas-max-lag 71 | min-replicas-to-write 72 | no-appendfsync-on-rewrite 73 | notify-keyspace-events 74 | oom-score-adj 75 | oom-score-adj-values 76 | pidfile 77 | port 78 | proto-max-bulk-len 79 | rdbchecksum 80 | rdbcompression 81 | rdb-del-sync-files 82 | rdb-save-incremental-fsync 83 | repl-backlog-size 84 | repl-backlog-ttl 85 | repl-disable-tcp-nodelay 86 | repl-diskless-load 87 | repl-diskless-sync 88 | repl-diskless-sync-delay 89 | replica-announce-ip 90 | replica-announce-port 91 | replica-ignore-maxmemory 92 | replica-lazy-flush 93 | replicaof 94 | replica-priority 95 | replica-read-only 96 | replica-serve-stale-data 97 | repl-ping-replica-period 98 | repl-timeout 99 | requirepass 100 | save 101 | server_cpulist 102 | set-max-intset-entries 103 | slowlog-max-len 104 | stop-writes-on-bgsave-error 105 | stream-node-max-bytes 106 | stream-node-max-entries 107 | supervised 108 | syslog-enabled 109 | syslog-facility 110 | syslog-ident 111 | tcp-backlog 112 | tcp-keepalive 113 | timeout 114 | tls-auth-clients 115 | tls-ca-cert-dir 116 | tls-ca-cert-file 117 | tls-cert-file 118 | tls-ciphers 119 | tls-ciphersuites 120 | tls-cluster 121 | tls-dh-params-file 122 | tls-key-file 123 | tls-port 124 | tls-prefer-server-ciphers 125 | tls-protocols 126 | tls-replication 127 | tls-session-cache-size 128 | tls-session-cache-timeout 129 | tls-session-caching 130 | tracking-table-max-keys 131 | unixsocket 132 | unixsocketperm 133 | zset-max-ziplist-entries 134 | zset-max-ziplist-value 135 | --------------------------------------------------------------------------------