├── .env.example ├── .github ├── ISSUE_TEMPLATE │ └── custom.md └── workflows │ └── go.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── Readme_CN.md ├── app ├── client │ └── client.go ├── dashboard │ └── dashboard.go └── server │ └── server.go ├── benchmark ├── docker-compose.yml ├── docs └── plan.md ├── go.mod ├── go.sum ├── main └── main.go ├── pkg ├── aof.go ├── aof_test.go ├── atomic.go ├── client.go ├── client_test.go ├── cluster.go ├── cluster_test.go ├── command.go ├── command │ ├── cluster │ │ └── cluster.go │ ├── command.go │ ├── connection │ │ └── connection.go │ ├── geo │ │ └── geo.go │ ├── hashes │ │ └── hashes.go │ ├── hyperloglog │ │ └── hyperloglog.go │ ├── key │ │ └── key.go │ ├── list │ │ ├── list.go │ │ └── list_test.go │ ├── monitor │ │ └── monitor.go │ ├── multi │ │ └── multi.go │ ├── pubsub │ │ └── pubsub.go │ ├── script │ │ └── script.go │ ├── server │ │ └── server.go │ ├── set │ │ └── set.go │ ├── sorted_set │ │ └── sorted_set.go │ ├── stream │ │ └── stream.go │ ├── string │ │ ├── string.go │ │ └── string_test.go │ └── table │ │ └── command.go ├── config │ ├── config.go │ └── config_test.go ├── cron.go ├── cron_test.go ├── dict.go ├── dict_test.go ├── ds │ ├── intset │ │ ├── intset.go │ │ └── intset_test.go │ ├── list │ │ ├── list.go │ │ └── list_test.go │ ├── quicklist │ │ ├── node.go │ │ └── quicklist.go │ ├── sds │ │ ├── sds.go │ │ └── sds_test.go │ ├── skiplist │ │ ├── skiplist.go │ │ └── skiplist_test.go │ ├── stream │ │ ├── stream.go │ │ └── stream_test.go │ └── ziplist │ │ ├── zipList.go │ │ └── zipList_test.go ├── log │ ├── log.go │ └── log_test.go ├── other.go ├── parse.go ├── parse_test.go ├── protocol.go ├── protocol_test.go ├── pubsub.go ├── rdb.go ├── rdb_test.go ├── redisdb.go ├── redisdb_test.go ├── replication.go ├── replication_test.go ├── robj.go ├── robj_test.go ├── server.go ├── server_test.go ├── shared │ ├── share_test.go │ └── shared.go ├── types │ └── types.go └── utils │ └── utils.go ├── readme.md └── server.example.json /.env.example: -------------------------------------------------------------------------------- 1 | CHINESE_ENABLE=false 2 | GOPROXY= -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | # branches: 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | go: ['1.13', '1.14','1.15'] 17 | services: 18 | redis: 19 | image: redis 20 | ports: 21 | - 6379:6379 22 | 23 | steps: 24 | - name: Set up Go ${{ matrix.go }} 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go }} 28 | id: go 29 | - name: codecov 30 | uses: codecov/codecov-action@v1 31 | with: 32 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 33 | name: redisbygo # optional 34 | fail_ci_if_error: true 35 | - name: Check out code into the Go module directory 36 | uses: actions/checkout@v2 37 | - name: Format 38 | run: gofmt -d -s . 39 | - name: Get dependencies 40 | run: go get -v -t -d ./... 41 | - name: Build 42 | run: go build -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | redis 2 | dump.rdb 3 | appendonly.aof 4 | server.json 5 | build 6 | .idea 7 | dump.rdb 8 | .env 9 | coverage.txt 10 | main/root.go -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to redisbygo 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14-alpine as builder 2 | ARG CHINESE_ENABLE 3 | ARG GOPROXY 4 | 5 | ENV GOPROXY ${GOPROXY} 6 | ENV CHINESE_ENABLE true 7 | 8 | WORKDIR /app 9 | 10 | RUN if [ ${CHINESE_ENABLE} ]; then \ 11 | sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \ 12 | ;fi 13 | 14 | COPY . /app 15 | RUN export GOPROXY="https://goproxy.cn,direct" && go mod download && apk add make && make clean && make && cp server.example.json server.json 16 | 17 | FROM alpine:latest as prod 18 | 19 | RUN if [ ${CHINESE_ENABLE} ]; then \ 20 | sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \ 21 | ;fi 22 | 23 | RUN apk --no-cache add ca-certificates 24 | 25 | WORKDIR /app/ 26 | 27 | COPY --from=0 /app/build/linux/redis . 28 | COPY --from=0 /app/server.json . 29 | 30 | CMD ["/app/redis"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ccb1900 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | CGO_ENABLED=0 GOARCH=amd64 go build -o build/darwin/redis main/main.go && GOOS=linux go build -o build/linux/redis main/main.go && GOOS=windows go build -o build/windows/redis.exe main/main.go 4 | clean: 5 | rm -rf build && rm -rf appendonly.aof -------------------------------------------------------------------------------- /Readme_CN.md: -------------------------------------------------------------------------------- 1 | # RedisServerByGo( 开发中... ) 2 | 3 | [English](/readme.md) 4 | 5 | 这是一个golang实现的redis服务器 6 | 7 | 如果发现了bug,或者有相关的建议,可以提出来。 8 | 9 | 还有很多功能需要实现,但是基本的结构已经完备了。我有时间会持续开发的。 10 | 11 | 本项目是本着学习的目的,毕竟 go 在内存效率方面是无法和 c 比拟的,所以也不考虑能用在生产环境。 12 | 13 | ## 分支说明 14 | 15 | dev 分支是开发分支,可能无法运行,请使用 master 测试学习。 16 | 17 | ## Repository 18 | 19 | - [Gitee,中国用户访问](https://gitee.com/waterloocode/redisbygo) 20 | - [Github](https://github.com/ccb1900/redisbygo) 21 | 22 | ## 已支持 23 | 24 | - 加载配置文件 25 | - 加载aof文件 26 | - ping 命令 27 | - echo 命令 28 | - pubsub 命令 29 | - subscribe 命令 30 | - get 命令 31 | - set 命令 32 | - select 命令 33 | 34 | ## 用法 35 | 36 | ```shell 37 | make 38 | ``` 39 | 40 | ```shell 41 | cp server.example.json server.json 42 | ``` 43 | 44 | ### win 45 | 46 | ```shell 47 | ./build/windows/redis.exe 48 | ``` 49 | 50 | ### mac 51 | 52 | ```shell 53 | ./build/darwin/redis 54 | ``` 55 | 56 | ### linux 57 | 58 | ```shell 59 | ./build/linux/redis 60 | ``` 61 | 62 | ## 测试 63 | 64 | ```shell 65 | redis-benchmark -h 127.0.0.1 -p 6378 -n 100000 -c 1000 66 | ``` 67 | 68 | ## 参考 69 | 70 | [Redis 官方文档](https://redis.io/documentation) -------------------------------------------------------------------------------- /app/dashboard/dashboard.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/command/table" 6 | "github.com/ccb1900/redisbygo/pkg/config" 7 | "github.com/gin-gonic/gin" 8 | "os" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | // 根路由 14 | func root(r *gin.Engine) func(c *gin.Context) { 15 | return func(c *gin.Context) { 16 | results := make([]string, 0) 17 | for _, route := range r.Routes() { 18 | results = append(results, route.Path) 19 | } 20 | c.JSON(200, results) 21 | } 22 | } 23 | 24 | func commands(c *gin.Context) { 25 | results := make([]string, 0) 26 | for _, redisCommand := range table.RedisCommandTable { 27 | results = append(results, redisCommand.Name) 28 | } 29 | c.JSON(200, results) 30 | } 31 | 32 | func configs(c *gin.Context) { 33 | s := config.NewConfig() 34 | c.JSON(200, s) 35 | } 36 | 37 | func clients(c *gin.Context) { 38 | s := pkg.NewServer() 39 | type cl struct { 40 | Addr string 41 | Index int 42 | Db int 43 | } 44 | results := make([]*cl, 0) 45 | for _, cc := range s.Clients { 46 | results = append(results, &cl{ 47 | Addr: cc.Conn.RemoteAddr().String(), 48 | Index: cc.Index, 49 | Db: cc.Db.Id, 50 | }) 51 | } 52 | c.JSON(200, results) 53 | } 54 | 55 | // 由于map不能并发访问,此方法会导致服务器挂掉 56 | func keys(c *gin.Context) { 57 | type key struct { 58 | Name string 59 | Type string 60 | Db int 61 | } 62 | results := make([]key, 0) 63 | 64 | s := pkg.NewServer() 65 | 66 | for _, d := range s.Db { 67 | dd := d.Dict.Storage 68 | for k := range dd { 69 | results = append(results, key{ 70 | Name: k, 71 | Type: k, 72 | Db: d.Id, 73 | }) 74 | } 75 | } 76 | 77 | c.JSON(200, results) 78 | } 79 | 80 | func metrics(c *gin.Context) { 81 | type ServerInfo struct { 82 | RedisVersion string 83 | CRLF string 84 | GoVersion string 85 | CPUNum int 86 | GoRoutineNum int 87 | Arch string 88 | Goos string 89 | GoRoot string 90 | Date string 91 | Hostname string 92 | Memory string 93 | Disk string 94 | RedisKeyNum int 95 | RedisClientNum int 96 | Envs []string 97 | } 98 | 99 | s := pkg.NewServer() 100 | 101 | hostname, _ := os.Hostname() 102 | count := 0 103 | 104 | for i := 0; i < len(s.Db); i++ { 105 | count += len(s.Db[i].Dict.Storage) 106 | } 107 | c.JSON(200, ServerInfo{ 108 | RedisVersion: pkg.RedisVersion, 109 | CRLF: pkg.CRLF, 110 | GoVersion: runtime.Version(), 111 | CPUNum: runtime.NumCPU(), 112 | GoRoutineNum: runtime.NumGoroutine(), 113 | Arch: runtime.GOARCH, 114 | Goos: runtime.GOOS, 115 | GoRoot: runtime.GOROOT(), 116 | Date: time.Now().Format("2006-01-02 15:04:05"), 117 | Hostname: hostname, 118 | Envs: os.Environ(), 119 | RedisClientNum: len(s.Clients), 120 | RedisKeyNum: count, 121 | }) 122 | } 123 | func CreateDashboard() { 124 | r := gin.Default() 125 | // 获取所有路由 126 | r.GET("/", root(r)) 127 | // 获取所有命令 128 | r.GET("commands", commands) 129 | // 获取所有配置 130 | r.GET("configs", configs) 131 | // 获取当前连接的客户端 132 | r.GET("clients", clients) 133 | // 获取所有的key,暂时未支持分页 134 | r.GET("keys", keys) 135 | // 获取服务器监控信息 136 | r.GET("metrics", metrics) 137 | _ = r.Run(":9003") 138 | } 139 | -------------------------------------------------------------------------------- /app/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/ccb1900/redisbygo/pkg" 8 | "github.com/ccb1900/redisbygo/pkg/command/table" 9 | "github.com/ccb1900/redisbygo/pkg/config" 10 | "io" 11 | "net" 12 | "strconv" 13 | ) 14 | 15 | func InitServerConfig(s *pkg.Server) { 16 | s.Log.Info("oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo") 17 | l := len(table.RedisCommandTable) 18 | s.Commands = make(map[string]*pkg.RedisCommand, l) 19 | for i := 0; i < l; i++ { 20 | s.Commands[table.RedisCommandTable[i].Name] = &table.RedisCommandTable[i] 21 | } 22 | } 23 | 24 | func CreateServer() { 25 | s := pkg.NewServer() 26 | c := config.NewConfig() 27 | InitServerConfig(s) 28 | s.Aof.LoadAppendOnlyFile("appendonly.aof") 29 | s.Log.Info("server start") 30 | s.Log.Info("serve on :" + strconv.Itoa(c.Port)) 31 | 32 | ln, err := net.Listen("tcp", ":"+strconv.Itoa(c.Port)) 33 | fmt.Println("waiting redis client....on 6378") 34 | s.Listener = ln 35 | if err != nil { 36 | s.Log.Info("listen::" + err.Error()) 37 | } else { 38 | go acceptRequest(s) 39 | go handleCommands(s) 40 | } 41 | } 42 | 43 | // 处理命令 44 | func handleCommands(s *pkg.Server) { 45 | for { 46 | select { 47 | case cl := <-s.CurrentClient: 48 | cl.ParseCommand() 49 | //s.Aof.Write(command.Query) 50 | cl.Free() 51 | cl.Pending <- new(pkg.Pending) 52 | } 53 | } 54 | } 55 | 56 | // 处理单行命令 57 | func ProcessInlineBuffer(c *pkg.Client) { 58 | // 读取内容 59 | // 分离字符串 60 | // 创建roj对象 61 | } 62 | 63 | // 回复客户端 64 | func response(conn net.Conn, message string) { 65 | writer := bufio.NewWriter(conn) 66 | _, _ = writer.WriteString(pkg.ProtocolLine(message)) 67 | _ = writer.Flush() 68 | } 69 | 70 | func errorResponse(conn net.Conn, message string) { 71 | writer := bufio.NewWriter(conn) 72 | _, _ = writer.WriteString(pkg.ProtocolLineErr(message)) 73 | _ = writer.Flush() 74 | } 75 | 76 | // 接受客户端请求 77 | func acceptRequest(s *pkg.Server) { 78 | go func() { 79 | //c := 0 80 | for { 81 | select { 82 | case index := <-s.WaitCloseClients: 83 | delete(s.Clients, index) 84 | case conn := <-s.NewClients: 85 | s.No = s.No + 1 86 | newClient := pkg.NewClient(conn) 87 | newClient.Index = s.No 88 | newClient.Db = s.Db[0] 89 | cc := config.NewConfig() 90 | 91 | if len(s.Clients) >= cc.Maxclients { 92 | w := bufio.NewWriter(newClient.Conn) 93 | _, _ = w.WriteString(pkg.ProtocolLineErr("ERR max number of clients reached")) 94 | s.StatRejectedConn++ 95 | _ = w.Flush() 96 | } else { 97 | s.Clients[s.No] = newClient 98 | go handleConnection(s, newClient) 99 | } 100 | //default: 101 | // c++ 102 | // fmt.Println("beforeasleep..." + strconv.Itoa(c)) 103 | } 104 | } 105 | }() 106 | for { 107 | conn, err := s.Listener.Accept() 108 | if err != nil { 109 | s.Log.Info(err.Error()) 110 | } else { 111 | go func() { 112 | s.NewClients <- conn 113 | }() 114 | } 115 | } 116 | } 117 | 118 | // 处理客户端连接 119 | func handleConnection(s *pkg.Server, cl *pkg.Client) { 120 | s.Log.Info(cl.Conn.RemoteAddr().String()) 121 | cl.Pending <- new(pkg.Pending) 122 | for { 123 | <-cl.Pending 124 | readLen := pkg.ProtoIoBufLen 125 | buf := make([]byte, readLen) 126 | size, err := cl.Conn.Read(buf) 127 | if size == 0 && err == io.EOF { 128 | err = cl.Conn.Close() 129 | if err != nil { 130 | s.Log.Error(err.Error()) 131 | } else { 132 | s.Log.Info("close success") 133 | } 134 | // 删除客户端 135 | s.WaitCloseClients <- cl.Index 136 | break 137 | } else { 138 | cl.QueryBuf = append(cl.QueryBuf, buf[:size]...) 139 | if cl.MultiBulkLen == 0 { 140 | pos := bytes.Index(cl.QueryBuf, []byte{'\r', '\n'}) 141 | if pos > 0 && cl.QueryBuf[0] == '*' { 142 | cl.MultiBulkLen = pkg.S2Int(string(cl.QueryBuf[1:pos])) 143 | cl.QueryBuf = cl.QueryBuf[pos+2:] 144 | } else { 145 | if pos > 0 && cl.QueryBuf[0] != '*' { 146 | ptr := string(cl.QueryBuf[0:pos]) 147 | cl.Argv = append(cl.Argv, pkg.NewRedisObject(pkg.ObjString, &ptr)) 148 | } else { 149 | cl.Pending <- new(pkg.Pending) 150 | continue 151 | } 152 | } 153 | 154 | } 155 | 156 | for cl.MultiBulkLen > 0 { 157 | pos := bytes.Index(cl.QueryBuf[0:], []byte{'\r', '\n'}) 158 | 159 | if pos == -1 { 160 | break 161 | } 162 | if cl.QueryBuf[0] == '$' { 163 | cl.BulkLen = pkg.S2Int(string(cl.QueryBuf[1:pos])) 164 | } else { 165 | ptr := string(cl.QueryBuf[0:pos]) 166 | cl.Argv = append(cl.Argv, pkg.NewRedisObject(pkg.ObjString, &ptr)) 167 | cl.MultiBulkLen-- 168 | } 169 | 170 | cl.QueryBuf = cl.QueryBuf[pos+2:] 171 | } 172 | 173 | if cl.MultiBulkLen == 0 { 174 | s.CurrentClient <- cl 175 | } else { 176 | cl.Pending <- new(pkg.Pending) 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /benchmark: -------------------------------------------------------------------------------- 1 | redis-benchmark -h 127.0.0.1 -p 6378 -n 100000 -c 1000 -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | redisdev: 4 | build: 5 | context: . 6 | args: 7 | - CHINESE_ENABLE=${CHINESE_ENABLE} 8 | - GOPROXY=${GOPROXY} 9 | redis: 10 | image: redis:5 -------------------------------------------------------------------------------- /docs/plan.md: -------------------------------------------------------------------------------- 1 | # 计划x 2 | - 协议解析 3 | - 命令处理 4 | - 底层存储 5 | - 持久化 6 | - 响应 7 | - 事务 8 | - 发布订阅 9 | - stream 10 | - 过期 11 | - 主从复制 12 | - 集群 13 | - sentinel 14 | - lua脚本 15 | - 慢日志 16 | 17 | ## 命令支持 18 | - ping 19 | - echo 20 | - select 21 | - get 22 | - set 23 | - config 24 | 25 | 26 | 关键代码都可以去server.c去看,衍生到其他位置 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ccb1900/redisbygo 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.6.3 7 | github.com/go-redis/redis/v8 v8.0.0-beta.8 8 | github.com/sirupsen/logrus v1.2.0 9 | github.com/spf13/viper v1.7.1 10 | ) 11 | -------------------------------------------------------------------------------- /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/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 23 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 24 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 25 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 26 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 27 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 28 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 29 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 30 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 31 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 32 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 33 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 34 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 35 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 36 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 37 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 38 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 43 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 44 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 45 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 46 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 47 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 48 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 53 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 54 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 55 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= 56 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 57 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 58 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 59 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 60 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 61 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 62 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 63 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 64 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 65 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 66 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 67 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 68 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= 69 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 70 | github.com/go-redis/redis/v8 v8.0.0-beta.8 h1:zoCuzcK6zAUsExPMQH/b/8gWGbHSeXYilcSZN7z6jBQ= 71 | github.com/go-redis/redis/v8 v8.0.0-beta.8/go.mod h1:ZIhquluuA2M8NM+0sIq1kmlG0pqzgH+Ud6bI6nveXh8= 72 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 73 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 74 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 75 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 76 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 77 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 78 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 79 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 80 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 81 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 82 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 83 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 84 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 85 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 86 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 87 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 88 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 89 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 90 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 91 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 92 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 93 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 94 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 95 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 96 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 97 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 98 | github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= 99 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 100 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 101 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 102 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 103 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 104 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 105 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 106 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 107 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 108 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 109 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 110 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 111 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 112 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 113 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 114 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 115 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 116 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 117 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 118 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 119 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 120 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 121 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 122 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 123 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 124 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 125 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 126 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 127 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 128 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 129 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 130 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 131 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 132 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 133 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 134 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 135 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 136 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 137 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 138 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 139 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 140 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 141 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 142 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 143 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 144 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 145 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 146 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 147 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 148 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 149 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 150 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 151 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 152 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 153 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 154 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 155 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 156 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 157 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 158 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 159 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 160 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 161 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 162 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 163 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 164 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 165 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 166 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 167 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 168 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 169 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 170 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 171 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 172 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 173 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 174 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 175 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 176 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 177 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 178 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 179 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 180 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 181 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 182 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 183 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 184 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 185 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 186 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 187 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 188 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 189 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 190 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 191 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 192 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 193 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 194 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 195 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 196 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 197 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 198 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 199 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 200 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 201 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 202 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 203 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 204 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 205 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 206 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 207 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 208 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 209 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 210 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 211 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 212 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 213 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 214 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 215 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 216 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 217 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 218 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 219 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 220 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 221 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 222 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 223 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 224 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 225 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 226 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 227 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 228 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 229 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 230 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 231 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 232 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 233 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 234 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 235 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 236 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 237 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 238 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 239 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 240 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 241 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 242 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 243 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 244 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 245 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 246 | go.opentelemetry.io/otel v0.10.0 h1:2y/HYj1dIfG1nPh0Z15X4se8WwYWuTyKHLSgRb/mbQ0= 247 | go.opentelemetry.io/otel v0.10.0/go.mod h1:n3v1JGUBpn5DafiF1UeoDs5fr5XZMG+43kigDtFB8Vk= 248 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 249 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 250 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 251 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 252 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 253 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 254 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 255 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= 256 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 257 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 258 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 259 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 260 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 261 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 262 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 263 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 264 | golang.org/x/exp v0.0.0-20200821190819-94841d0725da h1:vfV2BR+q1+/jmgJR30Ms3RHbryruQ3Yd83lLAAue9cs= 265 | golang.org/x/exp v0.0.0-20200821190819-94841d0725da/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 266 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 267 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 268 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 269 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 270 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 271 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 272 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 273 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 274 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 275 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 276 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 277 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 278 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 279 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 280 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 281 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 282 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 283 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 284 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 285 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 286 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 287 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 288 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 289 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 290 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 291 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 292 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 293 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 294 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 295 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 296 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 297 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 298 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 299 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 300 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 301 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 302 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 305 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 306 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 307 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 308 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 309 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 310 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 311 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 312 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 313 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 314 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 315 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= 321 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 324 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= 326 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 328 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 329 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 330 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 331 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 332 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 333 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 334 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 335 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 336 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 337 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 338 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 339 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 340 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 341 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 342 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 343 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 344 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 345 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 346 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 347 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 348 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 349 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 350 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 351 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 352 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 353 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 354 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 355 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 356 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 357 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 358 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 359 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 360 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 361 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 362 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 363 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 364 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 365 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 366 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 367 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 368 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 369 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 370 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 371 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 372 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 373 | google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 374 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= 375 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 376 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 377 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 378 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 379 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 380 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 381 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 382 | google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= 383 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 384 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 385 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 386 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 387 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 388 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 389 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 390 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 391 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 392 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 393 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 394 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 395 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 396 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 397 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 398 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 399 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 400 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 401 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 402 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 403 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 404 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 405 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 406 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 407 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 408 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 409 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 410 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 411 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 412 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 413 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 414 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 415 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 416 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 417 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 418 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 419 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 420 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 421 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/ccb1900/redisbygo/app/dashboard" 7 | "github.com/ccb1900/redisbygo/app/server" 8 | "github.com/ccb1900/redisbygo/pkg" 9 | "github.com/ccb1900/redisbygo/pkg/config" 10 | "os" 11 | "sync" 12 | ) 13 | 14 | func main() { 15 | version := flag.Bool("version", false, "show redis version") 16 | help := flag.Bool("h", false, "show help") 17 | host := flag.String("host", "127.0.0.1", "set host") 18 | port := flag.Int("p", 6378, "set port") 19 | 20 | flag.Parse() 21 | 22 | if *version { 23 | fmt.Println("test") 24 | fmt.Println(pkg.RedisVersion) 25 | } 26 | if *help { 27 | flag.PrintDefaults() 28 | } 29 | c := config.NewConfig() 30 | c.Host = *host 31 | c.Port = *port 32 | serve() 33 | } 34 | func version() { 35 | os.Exit(0) 36 | } 37 | 38 | func usage() { 39 | 40 | } 41 | func serve() { 42 | var wg sync.WaitGroup 43 | wg.Add(3) 44 | 45 | go func() { 46 | dashboard.CreateDashboard() 47 | defer wg.Done() 48 | }() 49 | 50 | go func() { 51 | defer wg.Done() 52 | server.CreateServer() 53 | }() 54 | 55 | go func() { 56 | defer wg.Done() 57 | pkg.Cron() 58 | }() 59 | wg.Wait() 60 | } 61 | -------------------------------------------------------------------------------- /pkg/aof.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/ccb1900/redisbygo/pkg/log" 8 | "io" 9 | "os" 10 | ) 11 | 12 | /** 13 | *** 写入aof文件 14 | **/ 15 | type Aof struct { 16 | Fd *os.File 17 | Log log.ILog 18 | } 19 | 20 | // 创建aof文件 21 | func (a *Aof) Create() { 22 | name := "appendonly.aof" 23 | var f *os.File 24 | var e error 25 | if _, err := os.Stat(name); os.IsNotExist(err) { 26 | f, e = os.Create(name) 27 | 28 | if e != nil { 29 | panic("create file failed..") 30 | } 31 | } else { 32 | f, e = os.OpenFile(name, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 33 | if e != nil { 34 | panic("create file failed..") 35 | } 36 | } 37 | a.Log = log.NewLog(*a) 38 | a.Fd = f 39 | } 40 | func StartLoading(file *os.File) { 41 | 42 | } 43 | 44 | // 加载aof文件 45 | func (a *Aof) LoadAppendOnlyFile(filename string) int { 46 | s := NewServer() 47 | cl := CreateFakeClient() 48 | 49 | s.AofState = AofOn 50 | 51 | f, err := os.Open(filename) 52 | 53 | if err != nil { 54 | a.Log.Error(err.Error()) 55 | 56 | return CErr 57 | } 58 | 59 | i, done := a.LoadFile(f, cl) 60 | if done { 61 | return i 62 | } 63 | 64 | fmt.Println("test") 65 | return COk 66 | } 67 | 68 | func (a *Aof) LoadFile(f *os.File, cl *Client) (int, bool) { 69 | for { 70 | readLen := ProtoIoBufLen 71 | buf := make([]byte, readLen) 72 | size, err := f.Read(buf) 73 | 74 | if err != nil { 75 | a.Log.Error(err.Error()) 76 | return CErr, true 77 | } 78 | 79 | if err == io.EOF { 80 | break 81 | } 82 | cl.QueryBuf = append(cl.QueryBuf, buf[:size]...) 83 | 84 | a.Load(cl) 85 | } 86 | return 0, false 87 | } 88 | 89 | func (a *Aof) Load(cl *Client) { 90 | for { 91 | if a.ParseStart(cl) { 92 | break 93 | } 94 | 95 | if !a.ParseMultiBulkLen(cl) { 96 | break 97 | } 98 | 99 | if cl.MultiBulkLen == 0 { 100 | if !cl.ParseCommand() { 101 | a.Log.Error("parse error") 102 | } 103 | cl.FreeFakeClient() 104 | continue 105 | } 106 | } 107 | } 108 | 109 | func (a *Aof) ParseStart(cl *Client) bool { 110 | if cl.MultiBulkLen == 0 { 111 | pos := bytes.Index(cl.QueryBuf, []byte{'\r', '\n'}) 112 | if pos > 0 && cl.QueryBuf[0] == '*' { 113 | cl.MultiBulkLen = S2Int(string(cl.QueryBuf[1:pos])) 114 | cl.QueryBuf = cl.QueryBuf[pos+2:] 115 | } else { 116 | if pos > 0 && cl.QueryBuf[0] != '*' { 117 | ptr := string(cl.QueryBuf[0:pos]) 118 | cl.Argv = append(cl.Argv, NewRedisObject(ObjString, &ptr)) 119 | return false 120 | } else { 121 | return true 122 | } 123 | } 124 | } 125 | 126 | return false 127 | } 128 | 129 | func (a *Aof) ParseMultiBulkLen(cl *Client) bool { 130 | for cl.MultiBulkLen > 0 { 131 | pos := bytes.Index(cl.QueryBuf[0:], []byte{'\r', '\n'}) 132 | 133 | if pos == -1 { 134 | return false 135 | } 136 | if cl.QueryBuf[0] == '$' { 137 | cl.BulkLen = S2Int(string(cl.QueryBuf[1:pos])) 138 | } else { 139 | ptr := string(cl.QueryBuf[0:pos]) 140 | cl.Argv = append(cl.Argv, NewRedisObject(ObjString, &ptr)) 141 | cl.MultiBulkLen-- 142 | } 143 | 144 | cl.QueryBuf = cl.QueryBuf[pos+2:] 145 | } 146 | 147 | return true 148 | } 149 | 150 | // 写入 151 | func (a *Aof) Write(content string) { 152 | bf := bufio.NewWriter(a.Fd) 153 | _, e := bf.WriteString(content) 154 | 155 | if e != nil { 156 | panic(e) 157 | } 158 | 159 | e = bf.Flush() 160 | 161 | if e != nil { 162 | panic(e) 163 | } 164 | } 165 | 166 | // 关闭 167 | func (a *Aof) Close() { 168 | err := a.Fd.Close() 169 | if err != nil { 170 | fmt.Println("aof close err::", err) 171 | } 172 | } 173 | 174 | func NewAof() *Aof { 175 | a := new(Aof) 176 | a.Create() 177 | return a 178 | } 179 | 180 | func CatAppendOnlyGenericCommand(buf string, argv []*RedisObject) string { 181 | return "" 182 | } 183 | -------------------------------------------------------------------------------- /pkg/aof_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func TestCreate(t *testing.T) { 6 | f := NewAof() 7 | 8 | f.Write("test\r\nddd\r\n") 9 | f.Write("test\r\nddd\r\n") 10 | f.Write("test\r\nddd\r\n") 11 | f.Write("test\r\nddd\r\n") 12 | 13 | f.Close() 14 | 15 | f.LoadAppendOnlyFile("appendonly.aof") 16 | } 17 | -------------------------------------------------------------------------------- /pkg/atomic.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | //func Incr(value, count) { 4 | // 5 | //} 6 | -------------------------------------------------------------------------------- /pkg/client.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/ccb1900/redisbygo/pkg/config" 7 | "github.com/ccb1900/redisbygo/pkg/log" 8 | "net" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type Client struct { 14 | Conn net.Conn 15 | Index int 16 | Name string 17 | Log log.ILog 18 | Db *RedisDb 19 | QueryBuf []byte 20 | Query string 21 | Argv []*RedisObject 22 | LastInteraction string 23 | ReqType int // 协议类型 24 | MultiBulkLen int 25 | QbPos int 26 | BulkLen int 27 | Cmd *RedisCommand 28 | LastCmd *RedisCommand 29 | Pending chan *Pending 30 | Flags int 31 | PubSubChannel map[string]bool 32 | } 33 | type Pending struct { 34 | } 35 | 36 | func NewClient(conn net.Conn) *Client { 37 | c := new(Client) 38 | c.Log = log.NewLog(c) 39 | c.SelectDb(0) 40 | c.Argv = make([]*RedisObject, 0) 41 | c.Conn = conn 42 | c.QueryBuf = make([]byte, 0) 43 | c.Pending = make(chan *Pending, 1) 44 | c.BulkLen = -1 45 | c.Flags = 1 46 | c.PubSubChannel = make(map[string]bool) 47 | return c 48 | } 49 | 50 | func CreateFakeClient() *Client { 51 | cl := NewClient(nil) 52 | cl.SelectDb(0) 53 | cl.Flags = 0 54 | return cl 55 | } 56 | func (cl *Client) Free() { 57 | cl.MultiBulkLen = 0 58 | cl.Argv = make([]*RedisObject, 0) 59 | cl.BulkLen = -1 60 | cl.Cmd = nil 61 | cl.LastCmd = nil 62 | //cl.QueryBuf = make([]byte, 0) 63 | } 64 | 65 | func (cl *Client) GetArgvByIndex(i int) string { 66 | return *cl.Argv[i].Ptr.(*string) 67 | } 68 | func (cl *Client) FreeFakeClient() { 69 | cl.Free() 70 | cl.Flags = 0 71 | } 72 | func (cl *Client) reply(message string) { 73 | if cl.Flags != 0 { 74 | bf := bufio.NewWriter(cl.Conn) 75 | _, _ = bf.WriteString(message) 76 | _ = bf.Flush() 77 | } 78 | } 79 | func (cl *Client) AddReplyRedisObject(object *RedisObject) { 80 | cl.AddReply(*object.Ptr.(*string)) 81 | } 82 | func (cl *Client) AddReply(message string) { 83 | cl.reply(ProtocolLine(message)) 84 | } 85 | 86 | func (cl *Client) AddReplyErrorFormat(args []string, messages ...string) { 87 | s := "" 88 | 89 | for i := 0; i < len(args); i++ { 90 | message := "" 91 | if i > 1 { 92 | message = messages[1] 93 | } else { 94 | message = messages[0] 95 | } 96 | s += fmt.Sprintf(message, args[i]) 97 | } 98 | 99 | cl.reply(ProtocolLineErr(s)) 100 | } 101 | 102 | func (cl *Client) AddReplyError(message string) { 103 | cl.reply(ProtocolLineErr(message)) 104 | } 105 | func (cl *Client) AddReplyBulk(object *RedisObject) { 106 | cl.AddReplyRedisObject(object) 107 | } 108 | func (cl *Client) AddReplyBulkLen(object *RedisObject) { 109 | 110 | } 111 | 112 | func (cl *Client) AddReplyHelp(help []string) { 113 | cl.AddReply(strings.Join(help, ",")) 114 | } 115 | 116 | func (cl *Client) AddReplySubcommandSyntaxError() { 117 | cl.AddReplyErrorFormat([]string{ 118 | *cl.Argv[1].Ptr.(*string), 119 | cl.Cmd.Name, 120 | }, "Unknown subcommand or wrong number of arguments for '%s'.", "Try %s HELP.") 121 | } 122 | 123 | func (cl *Client) LookupKeyReadOrReply(key *RedisObject, reply *RedisObject) *RedisObject { 124 | o := cl.Db.LookupKeyRead(key) 125 | 126 | if o == nil { 127 | cl.AddReplyRedisObject(reply) 128 | } 129 | return o 130 | } 131 | 132 | func (cl *Client) SelectDb(id int) int { 133 | c := config.NewConfig() 134 | s := NewServer() 135 | if id < 0 || id >= c.Dbnum { 136 | cl.Log.Info("db num err") 137 | return CErr 138 | } else { 139 | cl.Db = s.Db[id] 140 | return COk 141 | } 142 | } 143 | 144 | func (cl *Client) ParseCommand() bool { 145 | var ok bool 146 | cl.LastCmd, ok = LookupCommand(*cl.Argv[0].Ptr.(*string)) 147 | if !ok { 148 | return true 149 | } 150 | cl.Cmd = cl.LastCmd 151 | errCommand := []string{"ERR unknown command `%s`,", 152 | "with args beginning with: `%s`,"} 153 | errArgument := "wrong number of arguments for '%s' command" 154 | if cl.Cmd == nil { 155 | cl.AddReplyErrorFormat(GetCommandMessage(cl.Argv), errCommand...) 156 | return true 157 | } else if (cl.Cmd.Arity > 0 && cl.Cmd.Arity != len(cl.Argv)) || (len(cl.Argv) < -cl.Cmd.Arity) { 158 | cl.AddReplyErrorFormat([]string{cl.Cmd.Name}, errArgument) 159 | return true 160 | } 161 | // 检查是否需要密码 162 | 163 | // 检查集群 164 | 165 | // 处理最大内存 166 | 167 | // 不接受写命令的情况处理 168 | 169 | // 发布订阅的上下文 170 | 171 | // 执行命令 172 | 173 | //fmt.Println(c.Argv) 174 | cl.Cmd.Proc(cl) 175 | return true 176 | } 177 | 178 | func (cl *Client) AddReplyLongLong(s int) { 179 | cl.reply(strconv.Itoa(s)) 180 | } 181 | -------------------------------------------------------------------------------- /pkg/client_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/cluster.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type ClusterNode struct { 4 | } 5 | 6 | type ClusterState struct { 7 | } 8 | -------------------------------------------------------------------------------- /pkg/cluster_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/command.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "fmt" 4 | 5 | type RedisCommand struct { 6 | Name string 7 | Proc func(c *Client) 8 | Arity int 9 | SFlags string 10 | Flags int 11 | GetKeysProc *int 12 | FirstKey int 13 | LastKey int 14 | KeyStep int 15 | Microseconds int 16 | Calls int 17 | } 18 | 19 | func (rc *RedisCommand) Propagate(dbid int, argv []*RedisObject, flags int) { 20 | rc.FeedAppendOnlyFile(dbid, argv) 21 | replicationFeedSlaves() 22 | } 23 | 24 | func (rc *RedisCommand) FeedAppendOnlyFile(dbid int, argv []*RedisObject) { 25 | s := NewServer() 26 | buf := "" 27 | if dbid != s.AofSelectedDb { 28 | buf = fmt.Sprintf("*2\\r\\n$6\\r\\nSELECT\\r\\n$%s\\r\\n%s\\r\\n", "2", "2") 29 | s.AofSelectedDb = dbid 30 | } 31 | buf = CatAppendOnlyGenericCommand(buf, argv) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/command/cluster/cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | -------------------------------------------------------------------------------- /pkg/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/shared" 6 | "strings" 7 | ) 8 | 9 | func EchoCommand(cl *pkg.Client) { 10 | cl.AddReplyBulk(cl.Argv[1]) 11 | } 12 | 13 | func TimeCommand(cl *pkg.Client) { 14 | cl.AddReply("echo") 15 | } 16 | 17 | func ConfigCommand(cl *pkg.Client) { 18 | if len(cl.Argv) == 2 && strings.EqualFold(*cl.Argv[1].Ptr.(*string), "help") { 19 | help := []string{ 20 | "GET -- Return parameters matching the glob-like and their values.", 21 | "SET -- Set parameter to value.", 22 | "RESETSTAT -- Reset statistics reported by INFO.", 23 | "REWRITE -- Rewrite the configuration file.", 24 | } 25 | 26 | cl.AddReplyHelp(help) 27 | } else { 28 | cl.AddReplySubcommandSyntaxError() 29 | } 30 | } 31 | 32 | func PingCommand(cl *pkg.Client) { 33 | if len(cl.Argv) > 2 { 34 | cl.AddReplyErrorFormat([]string{"wrong number of arguments for '%s' command"}, cl.Cmd.Name) 35 | } else { 36 | if len(cl.Argv) == 1 { 37 | sh := shared.NewShared() 38 | cl.AddReplyRedisObject(sh.Pong) 39 | } else { 40 | cl.AddReplyBulk(cl.Argv[1]) 41 | } 42 | } 43 | } 44 | 45 | func AuthCommand(cl *pkg.Client) { 46 | cl.AddReply("echo") 47 | } 48 | 49 | func Command(cl *pkg.Client) { 50 | cl.AddReply("success") 51 | } 52 | 53 | func InfoCommand(cl *pkg.Client) { 54 | cl.AddReply("echo") 55 | } 56 | 57 | func MonitorCommand(cl *pkg.Client) { 58 | cl.AddReply("echo") 59 | } 60 | -------------------------------------------------------------------------------- /pkg/command/connection/connection.go: -------------------------------------------------------------------------------- 1 | package connection 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "strconv" 6 | ) 7 | 8 | func SelectDbCommand(c *pkg.Client) { 9 | id, err := strconv.Atoi(*c.Argv[1].Ptr.(*string)) 10 | 11 | if err != nil { 12 | c.Log.Error(err.Error()) 13 | c.AddReplyError(err.Error()) 14 | } else { 15 | if c.SelectDb(id) == pkg.COk { 16 | c.AddReply("success") 17 | } else { 18 | c.AddReplyError("it is too big") 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/command/geo/geo.go: -------------------------------------------------------------------------------- 1 | package geo 2 | -------------------------------------------------------------------------------- /pkg/command/hashes/hashes.go: -------------------------------------------------------------------------------- 1 | package hashes 2 | -------------------------------------------------------------------------------- /pkg/command/hyperloglog/hyperloglog.go: -------------------------------------------------------------------------------- 1 | package hyperloglog 2 | -------------------------------------------------------------------------------- /pkg/command/key/key.go: -------------------------------------------------------------------------------- 1 | package key 2 | -------------------------------------------------------------------------------- /pkg/command/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/shared" 6 | ) 7 | 8 | const Head = 0 9 | const Tail = 1 10 | 11 | func RPushCommand(cl *pkg.Client) { 12 | pushGenericCommand(cl, Tail) 13 | } 14 | 15 | func LPushCommand(cl *pkg.Client) { 16 | pushGenericCommand(cl, Head) 17 | } 18 | func pushGenericCommand(cl *pkg.Client, where int) { 19 | value := cl.Db.LookupKeyWrite(cl.Argv[1]) 20 | sh := shared.NewShared() 21 | if value != nil && value.TypeFlag != pkg.ObjList { 22 | cl.AddReplyRedisObject(sh.WrongTypeErr) 23 | return 24 | } 25 | 26 | for i := 2; i < len(cl.Argv); i++ { 27 | if value == nil { 28 | value = pkg.CreateQuickListObject() 29 | cl.Db.Dict.Add(cl.Argv[1], value) 30 | } 31 | 32 | listTypePush(value, cl.Argv[i], where) 33 | } 34 | 35 | cl.AddReply("success") 36 | } 37 | 38 | func listTypePush(value *pkg.RedisObject, element *pkg.RedisObject, where int) { 39 | if value.Encoding == pkg.ObjEncodingQuickList { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/command/list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | -------------------------------------------------------------------------------- /pkg/command/monitor/monitor.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import "github.com/ccb1900/redisbygo/pkg" 4 | 5 | // 监视器 6 | 7 | func MonitorCommand(cl *pkg.Client) { 8 | s := pkg.NewServer() 9 | s.Monitors = append(s.Monitors, cl) 10 | cl.AddReply("ok") 11 | } 12 | -------------------------------------------------------------------------------- /pkg/command/multi/multi.go: -------------------------------------------------------------------------------- 1 | package multi 2 | 3 | // 事务 4 | -------------------------------------------------------------------------------- /pkg/command/pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ccb1900/redisbygo/pkg" 6 | ) 7 | 8 | // 发布订阅 9 | 10 | func PublishCommand(cl *pkg.Client) { 11 | s := pkg.NewServer() 12 | clients := s.PubSubChannels[cl.GetArgvByIndex(1)] 13 | for i := 0; i < len(clients); i++ { 14 | clients[i].AddReply(cl.GetArgvByIndex(2)) 15 | } 16 | cl.AddReply("ok") 17 | } 18 | 19 | func SubscribeCommand(cl *pkg.Client) { 20 | s := pkg.NewServer() 21 | 22 | for i := 1; i < len(cl.Argv); i++ { 23 | fmt.Println(cl.GetArgvByIndex(i)) 24 | if _, ok := cl.PubSubChannel[cl.GetArgvByIndex(i)]; !ok { 25 | cl.PubSubChannel[cl.GetArgvByIndex(i)] = true 26 | s.PubSubChannels[cl.GetArgvByIndex(i)] = append(s.PubSubChannels[cl.GetArgvByIndex(i)], cl) 27 | } 28 | } 29 | cl.AddReply("ok") 30 | } 31 | -------------------------------------------------------------------------------- /pkg/command/script/script.go: -------------------------------------------------------------------------------- 1 | package script 2 | -------------------------------------------------------------------------------- /pkg/command/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/shared" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // 作为从机 11 | func ReplicationOfCommand(cl *pkg.Client) { 12 | s := pkg.NewServer() 13 | // 判断是否是集群,集群中该命令无法使用 14 | 15 | if !strings.EqualFold(cl.GetArgvByIndex(1), "no") && strings.EqualFold(cl.GetArgvByIndex(2), "one") { 16 | if s.Main.Host != "" { 17 | s.Main.ReplicationUnsetMaster() 18 | } 19 | } else { 20 | if cl.Flags&pkg.ClientSlave != 0 { 21 | 22 | } 23 | } 24 | 25 | s.Main.Host = cl.GetArgvByIndex(1) 26 | port, _ := strconv.Atoi(cl.GetArgvByIndex(2)) 27 | s.Main.Port = port 28 | s.Main.State = pkg.REPL_STATE_CONNECT 29 | sh := shared.NewShared() 30 | cl.AddReplyRedisObject(sh.Ok) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/command/set/set.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import "github.com/ccb1900/redisbygo/pkg" 4 | 5 | func SAddCommand(cl *pkg.Client) { 6 | set := cl.Db.LookupKeyWrite(cl.Argv[1]) 7 | 8 | if set != nil { 9 | 10 | } 11 | cl.Db.Dict.Add(cl.Argv[1], set) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/command/sorted_set/sorted_set.go: -------------------------------------------------------------------------------- 1 | package sorted_set 2 | -------------------------------------------------------------------------------- /pkg/command/stream/stream.go: -------------------------------------------------------------------------------- 1 | package stream 2 | -------------------------------------------------------------------------------- /pkg/command/string/string.go: -------------------------------------------------------------------------------- 1 | package string 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/shared" 6 | ) 7 | 8 | func GetCommand(c *pkg.Client) { 9 | getGenericCommand(c) 10 | } 11 | func getGenericCommand(cl *pkg.Client) { 12 | sh := shared.NewShared() 13 | o := cl.LookupKeyReadOrReply(cl.Argv[1], sh.NullBulk) 14 | if o == nil { 15 | return 16 | } else { 17 | if o.TypeFlag != pkg.ObjString { 18 | cl.AddReplyRedisObject(shared.NewShared().WrongTypeErr) 19 | } else { 20 | cl.AddReplyBulk(o) 21 | } 22 | } 23 | } 24 | func SetCommand(c *pkg.Client) { 25 | setGenericCommand(c, 0, c.Argv[1], c.Argv[2], nil, 1, nil, nil) 26 | } 27 | 28 | func setGenericCommand(cl *pkg.Client, 29 | flags int, 30 | key *pkg.RedisObject, 31 | val *pkg.RedisObject, 32 | expire *pkg.RedisObject, unit int, okReply *pkg.RedisObject, abortReply *pkg.RedisObject) { 33 | cl.Db.Add(key, val) 34 | 35 | sh := shared.NewShared() 36 | 37 | if okReply == nil { 38 | cl.AddReplyRedisObject(sh.Ok) 39 | } else { 40 | cl.AddReplyRedisObject(okReply) 41 | } 42 | 43 | } 44 | 45 | func IncrCommand(cl *pkg.Client) { 46 | incrDecrCommand(cl, 1) 47 | } 48 | 49 | func incrDecrCommand(cl *pkg.Client, incr int) { 50 | cl.AddReply("ok") 51 | } 52 | -------------------------------------------------------------------------------- /pkg/command/string/string_test.go: -------------------------------------------------------------------------------- 1 | package string 2 | -------------------------------------------------------------------------------- /pkg/command/table/command.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg" 5 | "github.com/ccb1900/redisbygo/pkg/command" 6 | "github.com/ccb1900/redisbygo/pkg/command/connection" 7 | "github.com/ccb1900/redisbygo/pkg/command/pubsub" 8 | "github.com/ccb1900/redisbygo/pkg/command/server" 9 | "github.com/ccb1900/redisbygo/pkg/command/set" 10 | string2 "github.com/ccb1900/redisbygo/pkg/command/string" 11 | ) 12 | 13 | var RedisCommandTable = []pkg.RedisCommand{ 14 | {"get", string2.GetCommand, 2, "rF", 0, nil, 1, 1, 1, 0, 0}, 15 | {"set", string2.SetCommand, -3, "wm", 0, nil, 1, 1, 1, 0, 0}, 16 | {"slaveof", server.ReplicationOfCommand, 3, "ast", 0, nil, 0, 0, 0, 0, 0}, 17 | {"replicaof", server.ReplicationOfCommand, 3, "ast", 0, nil, 0, 0, 0, 0, 0}, 18 | {"select", connection.SelectDbCommand, 2, "rF", 0, nil, 1, 1, 1, 0, 0}, 19 | {"time", connection.SelectDbCommand, 2, "rF", 0, nil, 1, 1, 1, 0, 0}, 20 | {"info", connection.SelectDbCommand, 2, "rF", 0, nil, 1, 1, 1, 0, 0}, 21 | {"config", command.ConfigCommand, -2, "last", 0, nil, 0, 0, 0, 0, 0}, 22 | {"ping", command.PingCommand, -1, "tF", 0, nil, 0, 0, 0, 0, 0}, 23 | {"echo", command.EchoCommand, 2, "F", 0, nil, 0, 0, 0, 0, 0}, 24 | {"command", command.Command, 0, "ltR", 0, nil, 0, 0, 0, 0, 0}, 25 | {"publish", pubsub.PublishCommand, 3, "ltR", 0, nil, 0, 0, 0, 0, 0}, 26 | {"pubsub", command.Command, -2, "ltR", 0, nil, 0, 0, 0, 0, 0}, 27 | {"subscribe", pubsub.SubscribeCommand, -2, "ltR", 0, nil, 0, 0, 0, 0, 0}, 28 | {"psubscribe", command.Command, -2, "ltR", 0, nil, 0, 0, 0, 0, 0}, 29 | {"unsubscribe", command.Command, -1, "ltR", 0, nil, 0, 0, 0, 0, 0}, 30 | {"punsubscribe", command.Command, -1, "ltR", 0, nil, 0, 0, 0, 0, 0}, 31 | {"monitor", command.MonitorCommand, 1, "as", 0, nil, 0, 0, 0, 0, 0}, 32 | {"sadd", set.SAddCommand, -3, "wmF", 0, nil, 1, 1, 1, 0, 0}, 33 | } 34 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ccb1900/redisbygo/pkg/utils" 6 | "github.com/spf13/viper" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Host string 14 | Timezone string 15 | Dbnum int 16 | Maxclients int 17 | Name string 18 | AppendOnly bool 19 | SaveSeconds int 20 | SaveTimes int 21 | DbFileName string 22 | Dir string 23 | } 24 | 25 | var instance *Config 26 | var once sync.Once 27 | 28 | func GetInstance(path string) *Config { 29 | once.Do(func() { 30 | instance = new(Config) 31 | if utils.Exists(path) { 32 | viper.SetConfigFile(path) 33 | viper.SetConfigType("json") 34 | 35 | err := viper.ReadInConfig() 36 | 37 | if err != nil { // Handle errors reading the config file 38 | panic(fmt.Errorf("Fatal error config file: %s \n", err)) 39 | } 40 | 41 | instance.Port = viper.GetInt("Port") 42 | instance.Host = viper.GetString("Host") 43 | instance.Timezone = viper.GetString("Timezone") 44 | instance.Dbnum = viper.GetInt("Dbnum") 45 | instance.Maxclients = viper.GetInt("maxclients") 46 | instance.Name = viper.GetString("Name") 47 | instance.AppendOnly = viper.GetBool("Appendonly") 48 | instance.SaveSeconds = viper.GetInt("save_seconds") 49 | instance.SaveTimes = viper.GetInt("save_times") 50 | instance.DbFileName = viper.GetString("dbfilename") 51 | instance.Dir = viper.GetString("dir") 52 | } else { 53 | instance.Port = 6378 54 | instance.Host = "127.0.0.1" 55 | instance.Timezone = "PRC" 56 | instance.Dbnum = 16 57 | instance.Maxclients = 10000 58 | instance.Name = "redisbygo" 59 | instance.AppendOnly = true 60 | instance.SaveSeconds = 1000 61 | instance.SaveTimes = 1000 62 | instance.DbFileName = "appendonly.aof" 63 | instance.Dir = "./" 64 | } 65 | }) 66 | 67 | return instance 68 | } 69 | func NewConfig() *Config { 70 | return GetInstance(getConfigPath()) 71 | } 72 | 73 | func getConfigPath() string { 74 | fmt.Println(os.LookupEnv("REDISBYGO_ROOT")) 75 | fmt.Println(os.Getenv("REDISBYGO_ROOT") + "/" + "./server.json") 76 | return os.Getenv("REDISBYGO_ROOT") + "/" + "./server.json" 77 | } 78 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewConfig(t *testing.T) { 8 | c1 := NewConfig() 9 | c2 := NewConfig() 10 | 11 | if c1 == c2 { 12 | t.Log("success") 13 | } else { 14 | t.Error("fail") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/cron.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func runEveryTime(period int, f func(time2 time.Time)) { 8 | ticker := time.NewTicker(time.Duration(period) * time.Millisecond) 9 | 10 | for { 11 | select { 12 | case t := <-ticker.C: 13 | f(t) 14 | } 15 | } 16 | } 17 | func Cron() { 18 | go runEveryTime(1000, func(t time.Time) { 19 | ReplicationCron() 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /pkg/cron_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/dict.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "strings" 4 | 5 | type Dict struct { 6 | Storage map[string]*RedisObject 7 | } 8 | 9 | func (d *Dict) Add(key *RedisObject, value *RedisObject) { 10 | s := strings.ToLower(*key.Ptr.(*string)) 11 | d.Storage[s] = value 12 | } 13 | 14 | func (d *Dict) Get(key *RedisObject) *RedisObject { 15 | value := d.Storage[strings.ToLower(*key.Ptr.(*string))] 16 | return value 17 | } 18 | 19 | func NewDict() *Dict { 20 | d := new(Dict) 21 | d.Storage = make(map[string]*RedisObject, 0) 22 | return d 23 | } 24 | -------------------------------------------------------------------------------- /pkg/dict_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/ds/intset/intset.go: -------------------------------------------------------------------------------- 1 | package intset 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg/ds/list" 5 | "github.com/ccb1900/redisbygo/pkg/types" 6 | ) 7 | 8 | type IntSet struct { 9 | Encoding types.Uint32T 10 | Contents []types.Int8T 11 | } 12 | 13 | func NewIntSet() *IntSet { 14 | o := new(IntSet) 15 | o.Contents = make([]types.Int8T, 0) 16 | return o 17 | } 18 | 19 | func (is *IntSet) Add(value types.Int64T) *IntSet { 20 | exist, index := is.Find(value) 21 | 22 | // exists 23 | if exist > 0 { 24 | return is 25 | } else { 26 | // max 27 | if index >= len(is.Contents) { 28 | is.Contents = append(is.Contents, types.Int8T(value)) 29 | } else { 30 | // middle 31 | list.InsertInt8T(&is.Contents, index, types.Int8T(value)) 32 | } 33 | } 34 | 35 | return is 36 | } 37 | 38 | func (is *IntSet) Remove(value types.Int64T) *IntSet { 39 | exist, index := is.Find(value) 40 | if exist > 0 { 41 | list.DeleteInt8T(&is.Contents, index) 42 | } 43 | return is 44 | } 45 | func (is *IntSet) Find(value types.Int64T) (int, int) { 46 | if len(is.Contents) == 0 { 47 | return -1, 0 48 | } 49 | mid := len(is.Contents) / 2 50 | low := 0 51 | high := len(is.Contents) 52 | for (mid <= len(is.Contents)-1) && mid >= 0 { 53 | if value == types.Int64T(is.Contents[mid]) { 54 | //exists,others are not exist 55 | return 1, mid 56 | } 57 | if value < types.Int64T(is.Contents[mid]) { 58 | high = mid - 1 59 | } 60 | if value > types.Int64T(is.Contents[mid]) { 61 | low = mid + 1 62 | } 63 | if low > high { 64 | break 65 | } 66 | mid = (low + high) / 2 67 | } 68 | return -1, low 69 | } 70 | 71 | func (is *IntSet) Random() types.Int64T { 72 | return 0 73 | } 74 | func (is *IntSet) Get(value types.Int64T, success *types.Uint8T) types.Uint8T { 75 | return 0 76 | } 77 | func (is *IntSet) Len() types.SizeT { 78 | return types.SizeT(len(is.Contents)) 79 | } 80 | 81 | func (is *IntSet) BlobLen(value types.Int64T, success *types.Uint8T) *IntSet { 82 | return NewIntSet() 83 | } 84 | -------------------------------------------------------------------------------- /pkg/ds/intset/intset_test.go: -------------------------------------------------------------------------------- 1 | package intset 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ccb1900/redisbygo/pkg/types" 6 | "testing" 7 | ) 8 | 9 | func TestIntSet_Add(t *testing.T) { 10 | ll := []int{ 11 | 1, 4, 3, 2, 7, 6, 9, 0, 0, 9, 8, 13, 2, 12 | } 13 | is := NewIntSet() 14 | for i := 0; i < len(ll); i++ { 15 | is.Add(types.Int64T(ll[i])) 16 | } 17 | 18 | fmt.Println(is.Contents) 19 | 20 | is.Remove(4) 21 | fmt.Println(is.Contents) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/ds/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import "github.com/ccb1900/redisbygo/pkg/types" 4 | 5 | func DeleteInt8T(s *[]types.Int8T, i int) { 6 | *s = append((*s)[:i], (*s)[i+1:]...) 7 | } 8 | func InsertInt8T(s *[]types.Int8T, index int, e types.Int8T) { 9 | rear := append([]types.Int8T{}, (*s)[index:]...) 10 | 11 | *s = append((*s)[:index], e) 12 | *s = append(*s, rear...) 13 | } 14 | 15 | func DeleteInt(s *[]int, i int) { 16 | *s = append((*s)[:i], (*s)[i+1:]...) 17 | } 18 | 19 | func InsertInt(s *[]int, index int, e int) { 20 | rear := append([]int{}, (*s)[index:]...) 21 | *s = append((*s)[:index], e) 22 | *s = append(*s, rear...) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/ds/list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | -------------------------------------------------------------------------------- /pkg/ds/quicklist/node.go: -------------------------------------------------------------------------------- 1 | package quicklist 2 | 3 | type Node struct { 4 | Prev *Node 5 | Next *Node 6 | Zl string 7 | Sz int 8 | Count int 9 | Encoding int 10 | Container int 11 | Recompress int 12 | AttemptedCompress int 13 | Extra int 14 | } 15 | -------------------------------------------------------------------------------- /pkg/ds/quicklist/quicklist.go: -------------------------------------------------------------------------------- 1 | package quicklist 2 | 3 | type QuickList struct { 4 | Head *Node 5 | Tail *Node 6 | Count int 7 | Len int 8 | Fill int 9 | Compress int 10 | } 11 | 12 | const TAIL = -1 13 | const Head = 0 14 | 15 | func CreateQuickList() *QuickList { 16 | ql := new(QuickList) 17 | return ql 18 | } 19 | -------------------------------------------------------------------------------- /pkg/ds/sds/sds.go: -------------------------------------------------------------------------------- 1 | package sds 2 | 3 | type Sds struct { 4 | Buf string 5 | } 6 | -------------------------------------------------------------------------------- /pkg/ds/sds/sds_test.go: -------------------------------------------------------------------------------- 1 | package sds 2 | -------------------------------------------------------------------------------- /pkg/ds/skiplist/skiplist.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg/ds/sds" 5 | "github.com/ccb1900/redisbygo/pkg/types" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | const ( 11 | skiplistmaxlevel = 64 12 | skiplistp = 0.25 13 | ) 14 | 15 | type ZLevel struct { 16 | forward *Node 17 | span int 18 | } 19 | 20 | type Node struct { 21 | level []ZLevel 22 | score float64 23 | ele *sds.Sds 24 | backward *Node 25 | } 26 | type SkipList struct { 27 | Header *Node 28 | Tail *Node 29 | length types.Long 30 | Level int 31 | } 32 | 33 | func NewNode(level int, score float64, ele *sds.Sds) *Node { 34 | n := new(Node) 35 | n.level = make([]ZLevel, level) 36 | n.score = score 37 | n.ele = ele 38 | 39 | return n 40 | } 41 | func NewSkipList() *SkipList { 42 | s := new(SkipList) 43 | s.Level = 1 44 | s.Header = NewNode(skiplistmaxlevel, 0, nil) 45 | return s 46 | } 47 | 48 | func RandomLevel() int { 49 | level := 1 50 | rand.Seed(time.Now().UnixNano()) 51 | for float64(rand.Int63()&0xffff) < (skiplistp * 0xffff) { 52 | rand.Seed(time.Now().UnixNano()) 53 | level += 1 54 | } 55 | 56 | if level < skiplistmaxlevel { 57 | return level 58 | } 59 | return skiplistmaxlevel 60 | } 61 | func (sl *SkipList) Insert(score float64, ele *sds.Sds) { 62 | 63 | } 64 | 65 | func (sl *SkipList) deleteNode(score float64, ele *sds.Sds) { 66 | 67 | } 68 | -------------------------------------------------------------------------------- /pkg/ds/skiplist/skiplist_test.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRandomLevel(t *testing.T) { 8 | a := [64]int{} 9 | for i := 0; i < 1000000; i++ { 10 | a[RandomLevel()]++ 11 | } 12 | 13 | t.Log(a) 14 | } 15 | 16 | func TestNewSkipList(t *testing.T) { 17 | t.Log(NewSkipList()) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/ds/stream/stream.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | type ListPack struct { 4 | } 5 | -------------------------------------------------------------------------------- /pkg/ds/stream/stream_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | -------------------------------------------------------------------------------- /pkg/ds/ziplist/zipList.go: -------------------------------------------------------------------------------- 1 | package ziplist 2 | 3 | type ZipList struct { 4 | } 5 | -------------------------------------------------------------------------------- /pkg/ds/ziplist/zipList_test.go: -------------------------------------------------------------------------------- 1 | package ziplist 2 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "os" 6 | "reflect" 7 | ) 8 | 9 | type ILog interface { 10 | Trace(messages ...interface{}) 11 | Debug(message ...interface{}) 12 | Info(message ...interface{}) 13 | Warn(message ...interface{}) 14 | Error(message ...interface{}) 15 | Fatal(message ...interface{}) 16 | } 17 | 18 | type Log struct { 19 | name string 20 | instance *logrus.Logger 21 | } 22 | 23 | func NewLog(name interface{}) ILog { 24 | l := new(Log) 25 | l.name = reflect.TypeOf(name).PkgPath() 26 | l.instance = logrus.New() 27 | l.init() 28 | return l 29 | } 30 | func (l *Log) init() { 31 | customFormatter := new(logrus.TextFormatter) 32 | customFormatter.FullTimestamp = true 33 | customFormatter.TimestampFormat = "2006-01-02 15:04:05" 34 | l.instance.SetFormatter(customFormatter) 35 | l.instance.SetOutput(os.Stdout) 36 | 37 | } 38 | func (l *Log) Info(messages ...interface{}) { 39 | l.log().Info(messages...) 40 | } 41 | func (l *Log) log() *logrus.Entry { 42 | return l.instance.WithField("package", l.name) 43 | } 44 | func (l *Log) Error(messages ...interface{}) { 45 | l.log().Error(messages...) 46 | } 47 | func (l *Log) Warn(messages ...interface{}) { 48 | l.log().Warn(messages...) 49 | } 50 | func (l *Log) Trace(messages ...interface{}) { 51 | l.log().Trace(messages...) 52 | } 53 | func (l *Log) Debug(messages ...interface{}) { 54 | l.log().Debug(messages...) 55 | } 56 | func (l *Log) Fatal(messages ...interface{}) { 57 | l.log().Fatal(messages...) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "testing" 4 | 5 | func TestNewLog(t *testing.T) { 6 | l := NewLog(*t) 7 | 8 | l.Info("hello") 9 | } 10 | -------------------------------------------------------------------------------- /pkg/other.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | const RedisVersion = "5.0.5" 4 | const CRLF = "\r\n" 5 | 6 | var CommandGroups = [...]string{ 7 | "generic", 8 | "string", 9 | "list", 10 | "set", 11 | "sorted_set", 12 | "hash", 13 | "pubsub", 14 | "transactions", 15 | "connection", 16 | "server", 17 | "scripting", 18 | "hyperloglog", 19 | "cluster", 20 | "geo", 21 | "stream", 22 | } 23 | 24 | const ProtoMaxQueryBufLen = 1024 * 1024 * 1024 /* 1GB max query buffer. */ 25 | const ProtoIoBufLen = 1024 /* Generic I/O buffer size */ 26 | //const ProtoIoBufLen = 1024 * 16 /* Generic I/O buffer size */ 27 | const ProtoReplyChunkBytes = 16 * 1024 /* 16k output buffer */ 28 | const ProtoInlineMaxSize = 1024 * 64 /* Max size of inline reads */ 29 | const ProtoMBulkBigArg = 1024 * 32 30 | const LongStrSize = 21 /* Bytes needed for long -> str + '\0' */ 31 | const RedisAutoSyncBytes = 1024 * 1024 * 32 /* fdatasync every 32MB */ 32 | 33 | const ( 34 | ObjEncodingRaw = iota // raw 35 | ObjEncodingInt = iota // int 36 | ObjEncodingHt = iota //hash 37 | ObjEncodingZipMap = iota // zipmap 38 | ObjEncodingLinkedList = iota // No longer used: old list encoding 39 | ObjEncodingZipList = iota // ziplist 40 | ObjEncodingIntSet = iota // intset 41 | ObjEncodingSkipList = iota // skiplist 42 | ObjEncodingEmbStr = iota // Embedded sds string encoding 43 | ObjEncodingQuickList = iota // linked list of ziplists 44 | ObjEncodingStream = iota // radix tree of listpacks 45 | ) 46 | 47 | const ( 48 | ObjString = iota 49 | ObjList = iota 50 | ObjSet = iota 51 | ObjZSet = iota 52 | ObjHash = iota 53 | ObjModule = iota 54 | ObjStream = iota 55 | ) 56 | 57 | const LookupNone = 0 58 | const CErr = 0 59 | const COk = 1 60 | 61 | const SharedSelectCmds = 10 62 | const ObjSharedIntegers = 10000 63 | const ObjSharedBulkhdrLen = 32 64 | const ObjSharedRefcount = 2147483647 65 | -------------------------------------------------------------------------------- /pkg/parse.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | ) 7 | 8 | func ParseProtocol() { 9 | s := []byte("*3\r\n$3\r\nset\r\n$1\r\na\r\n$1\r\nb\r\n") 10 | 11 | buf := make([]byte, 0) 12 | mulBulkLen := 0 13 | argv := make([]string, 0) 14 | //p := 0 15 | for i := 0; i < len(s); i += 5 { 16 | end := i + 5 17 | 18 | if end > len(s) { 19 | end = len(s) 20 | } 21 | queryBuf := s[i:end] 22 | 23 | // 如果buf包含了多行 24 | buf = append(buf, queryBuf...) 25 | // 检查协议长度 26 | if mulBulkLen == 0 { 27 | pos := bytes.Index(buf, []byte{'\r', '\n'}) 28 | if pos > 0 && buf[0] == '*' { 29 | mulBulkLen = S2Int(string(buf[1:pos])) 30 | buf = buf[pos+2:] 31 | } 32 | } else { 33 | for mulBulkLen > 0 { 34 | pos := bytes.Index(buf[0:], []byte{'\r', '\n'}) 35 | 36 | if pos == -1 { 37 | break 38 | } 39 | if buf[0] == '$' { 40 | //bulkLen = S2Int(string(buf[1:pos])) 41 | } else { 42 | argv = append(argv, string(buf[0:pos])) 43 | mulBulkLen-- 44 | } 45 | buf = buf[pos+2:] 46 | } 47 | } 48 | } 49 | } 50 | 51 | func S2Int(s string) int { 52 | r, _ := strconv.Atoi(s) 53 | return r 54 | } 55 | -------------------------------------------------------------------------------- /pkg/parse_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func TestParseProtocol(t *testing.T) { 6 | ParseProtocol() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/protocol.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // 错误信息 8 | func ProtocolLineErr(message string) string { 9 | return protocol(message, "-") 10 | } 11 | 12 | // 单行回复,简单字符串,非二进制安全 13 | func ProtocolLine(message string) string { 14 | return protocol(message, "+") 15 | } 16 | 17 | // 多行字符串,多行字符串用于表示长度最大为512 MB的单个二进制安全字符串。 18 | func ProtocolMultiLine(messages []string) string { 19 | s := protocol(strconv.Itoa(len(messages)), "$") 20 | for i := 0; i < len(messages); i++ { 21 | s += protocol(messages[i], "") 22 | } 23 | return s 24 | } 25 | 26 | // 多行字符串,返回nil 27 | func ProtocolNull() string { 28 | return protocol("-1", "$") 29 | } 30 | 31 | // 数组nil 32 | func ProtocolArrNull() string { 33 | return protocol("-1", "*") 34 | } 35 | 36 | // 整型数字,有符号64位整型 37 | func ProtocolInt(message int) string { 38 | return protocol(strconv.Itoa(message), ":") 39 | } 40 | 41 | // 数组,数组的情况比较复杂,实现时再说 42 | // @todo 43 | func ProtocolArr(messages []string) string { 44 | s := protocol(strconv.Itoa(len(messages)), "*") 45 | for i := 0; i < len(messages); i++ { 46 | s += ProtocolMultiLine([]string{strconv.Itoa(len(messages[i]))}) 47 | s += protocol(messages[i], "") 48 | } 49 | return s 50 | } 51 | 52 | // 包装协议函数 53 | func protocol(message string, prefix string) string { 54 | return prefix + message + CRLF 55 | } 56 | -------------------------------------------------------------------------------- /pkg/protocol_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func TestProtocolMultiLine(t *testing.T) { 6 | t.Log(ProtocolMultiLine([]string{ 7 | "hello", 8 | "world", 9 | "tome", 10 | "green", 11 | })) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/pubsub.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type PubSubPattern struct { 4 | Client *Client 5 | Pattern *RedisObject 6 | } 7 | -------------------------------------------------------------------------------- /pkg/rdb.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | // rio.h 11 | type Rdb struct { 12 | } 13 | 14 | func Test() { 15 | f, e := os.Create("dump.rdb") 16 | 17 | if e != nil { 18 | fmt.Println("create::", e) 19 | } else { 20 | var buffer bytes.Buffer 21 | ss := "REDIS0009RedisVersion5.0.9" 22 | s := []byte(ss) 23 | s = append(s, 0xfe) 24 | s = append(s, 0x00) 25 | s = append(s, 0x00) 26 | s = append(s, 0x0b) 27 | for i := 0; i < len(s); i++ { 28 | e := binary.Write(&buffer, binary.LittleEndian, s[i]) 29 | fmt.Println("buffer::", buffer) 30 | if e != nil { 31 | fmt.Println("create::", e) 32 | } 33 | } 34 | 35 | size, e := f.Write(buffer.Bytes()) 36 | fmt.Println(size, e) 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/rdb_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func TestTest(t *testing.T) { 6 | Test() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/redisdb.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type RedisDb struct { 4 | Id int 5 | AvgTtl int 6 | Dict *Dict 7 | } 8 | 9 | func NewRedisDb(id int) *RedisDb { 10 | rdb := new(RedisDb) 11 | rdb.Dict = NewDict() 12 | rdb.Id = id 13 | return rdb 14 | } 15 | func (rb *RedisDb) Add(key *RedisObject, value *RedisObject) { 16 | rb.Dict.Add(key, value) 17 | } 18 | 19 | func (rb *RedisDb) LookupKey(key *RedisObject, flags int) *RedisObject { 20 | return rb.Dict.Get(key) 21 | } 22 | 23 | func (rb *RedisDb) LookupKeyReadWithFlags(key *RedisObject, flags int) *RedisObject { 24 | var val *RedisObject 25 | 26 | val = rb.LookupKey(key, flags) 27 | 28 | return val 29 | } 30 | 31 | func (rb *RedisDb) LookupKeyRead(key *RedisObject) *RedisObject { 32 | return rb.LookupKeyReadWithFlags(key, LookupNone) 33 | } 34 | 35 | func (rb RedisDb) LookupKeyWrite(key *RedisObject) *RedisObject { 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/redisdb_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/replication.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | const ClientSlave = 1 << 0 10 | 11 | const REPL_STATE_NONE = 0 12 | const REPL_STATE_CONNECTING = 2 13 | const REPL_STATE_TRANSFER = 14 14 | const REPL_STATE_CONNECT = 1 15 | const REPL_STATE_CONNECTED = 15 16 | 17 | // Main server info 18 | type ReplicationMain struct { 19 | Host string 20 | Port int 21 | Client *Client 22 | Auth string 23 | State int 24 | } 25 | 26 | type ReplicationReplica struct { 27 | Host string 28 | Port int 29 | State int 30 | } 31 | 32 | func NewReplicationMain(conn net.Conn) *ReplicationMain { 33 | m := new(ReplicationMain) 34 | m.Client = NewClient(conn) 35 | m.State = REPL_STATE_NONE 36 | return m 37 | } 38 | func replicationFeedSlaves() { 39 | 40 | } 41 | 42 | func (m *ReplicationMain) ReplicationUnsetMaster() { 43 | 44 | } 45 | func ConnectWithMaster() int { 46 | s := NewServer() 47 | c, err := net.Dial(s.Main.Host, strconv.Itoa(s.Main.Port)) 48 | 49 | if err != nil { 50 | s.Log.Error(err.Error()) 51 | return CErr 52 | } 53 | go func() { 54 | 55 | for { 56 | if s.Main.State == REPL_STATE_NONE { 57 | _ = s.Main.Client.Conn.Close() 58 | return 59 | } 60 | buf := make([]byte, 1024) 61 | size, err := c.Read(buf) 62 | if err != nil { 63 | s.Log.Error(err.Error()) 64 | } else { 65 | realBuf := buf[0:size] 66 | 67 | fmt.Println(realBuf) 68 | } 69 | } 70 | }() 71 | s.Main.State = REPL_STATE_CONNECTING 72 | 73 | return COk 74 | } 75 | func ReplicationCron() { 76 | // 连接主服务器,长连接 77 | // 心跳连接 78 | // 发送同步命令 79 | // 部分同步 80 | // 全量同步 81 | s := NewServer() 82 | if s.Main.State == REPL_STATE_CONNECT { 83 | s.Log.Info(fmt.Sprintf("Connecting to MASTER %s:%d", 84 | s.Main.Host, s.Main.Port)) 85 | if ConnectWithMaster() == COk { 86 | s.Log.Info("MASTER <-> REPLICA sync started") 87 | } 88 | } 89 | 90 | } 91 | 92 | func SyncWithMaster() { 93 | 94 | } 95 | -------------------------------------------------------------------------------- /pkg/replication_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/robj.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/ccb1900/redisbygo/pkg/ds/intset" 5 | "github.com/ccb1900/redisbygo/pkg/ds/quicklist" 6 | ) 7 | 8 | type RedisObject struct { 9 | Encoding int 10 | TypeFlag int 11 | Lru int 12 | Refcount int 13 | Ptr interface{} 14 | } 15 | 16 | func NewRedisObject(typeFlag int, ptr interface{}) *RedisObject { 17 | ro := new(RedisObject) 18 | ro.Encoding = ObjEncodingRaw 19 | ro.TypeFlag = typeFlag 20 | ro.Ptr = ptr 21 | ro.Refcount = 1 22 | return ro 23 | } 24 | 25 | func NewStringRedisObject(s string) *RedisObject { 26 | return NewRedisObject(ObjString, &s) 27 | } 28 | 29 | func (r *RedisObject) MakeShared() *RedisObject { 30 | r.Refcount = ObjSharedRefcount 31 | return r 32 | } 33 | 34 | func CreateQuickListObject() *RedisObject { 35 | ql := quicklist.CreateQuickList() 36 | obj := NewRedisObject(ObjList, ql) 37 | 38 | obj.Encoding = ObjEncodingQuickList 39 | return obj 40 | } 41 | 42 | func SetTypeCreate(s string) *RedisObject { 43 | return nil 44 | } 45 | 46 | func IsSdsRepresentableAsLongLong(s string) int { 47 | return 0 48 | } 49 | 50 | func CreateIntSetObject() *RedisObject { 51 | is := intset.NewIntSet() 52 | o := NewRedisObject(ObjSet, is) 53 | o.Encoding = ObjEncodingIntSet 54 | return o 55 | } 56 | 57 | func CreateSetObject() *RedisObject { 58 | return NewRedisObject(1, 1) 59 | } 60 | 61 | func CreateHashRedisObject() *RedisObject { 62 | return NewRedisObject(1, 1) 63 | } 64 | func (r *RedisObject) DecrRefCount() { 65 | 66 | } 67 | -------------------------------------------------------------------------------- /pkg/robj_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /pkg/server.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ccb1900/redisbygo/pkg/config" 6 | "github.com/ccb1900/redisbygo/pkg/log" 7 | "net" 8 | "strings" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const ( 14 | AofOff = 0 15 | AofOn = 1 16 | ) 17 | 18 | type Server struct { 19 | RequirePass bool 20 | Log log.ILog 21 | Clients map[int]*Client 22 | Db []*RedisDb 23 | No int 24 | StatRejectedConn int 25 | CurrentClient chan *Client 26 | Listener net.Listener 27 | Aof *Aof 28 | WaitCloseClients chan int 29 | NewClients chan net.Conn 30 | Commands map[string]*RedisCommand 31 | ClientMaxQueryBufLen int 32 | AofState int 33 | AofFsync int 34 | AofSelectedDb int 35 | ReplicaList []*ReplicationReplica 36 | Main *ReplicationMain 37 | PubSubChannels map[string][]*Client 38 | Monitors []*Client 39 | } 40 | 41 | var gs *Server 42 | var onceServer sync.Once 43 | 44 | func NewServer() *Server { 45 | if gs == nil { 46 | onceServer.Do(func() { 47 | gs = new(Server) 48 | gs.Log = log.NewLog(*gs) 49 | 50 | c := config.NewConfig() 51 | dbList := make([]*RedisDb, c.Dbnum) 52 | 53 | for i := 0; i < len(dbList); i++ { 54 | dbList[i] = NewRedisDb(i) 55 | } 56 | 57 | gs.Db = dbList 58 | gs.Clients = make(map[int]*Client, 0) 59 | gs.CurrentClient = make(chan *Client, 2048) 60 | gs.Aof = NewAof() 61 | gs.RequirePass = false 62 | gs.AofState = AofOff 63 | 64 | gs.Main = NewReplicationMain(nil) 65 | gs.Monitors = make([]*Client, 0) 66 | gs.PubSubChannels = make(map[string][]*Client, 0) 67 | gs.WaitCloseClients = make(chan int, 16) 68 | gs.NewClients = make(chan net.Conn, 32) 69 | gs.ClientMaxQueryBufLen = ProtoMaxQueryBufLen 70 | }) 71 | } 72 | 73 | return gs 74 | } 75 | 76 | func ServerCron() { 77 | ticker := time.NewTicker(1 * time.Second) 78 | 79 | var wg sync.WaitGroup 80 | 81 | wg.Add(1) 82 | go func() { 83 | for { 84 | select { 85 | case t := <-ticker.C: 86 | fmt.Println("2 s", t) 87 | } 88 | } 89 | }() 90 | 91 | wg.Wait() 92 | } 93 | 94 | func (s *Server) LoadDataFromDisk() { 95 | s.Aof.LoadAppendOnlyFile("") 96 | } 97 | 98 | func LookupCommand(cmd string) (*RedisCommand, bool) { 99 | s := NewServer() 100 | v, ok := s.Commands[strings.ToLower(cmd)] 101 | return v, ok 102 | } 103 | func GetCommandMessage(ss []*RedisObject) []string { 104 | s := make([]string, 0) 105 | for i := 0; i < len(ss); i++ { 106 | s = append(s, *ss[i].Ptr.(*string)) 107 | } 108 | 109 | return s 110 | } 111 | -------------------------------------------------------------------------------- /pkg/server_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNewServer(t *testing.T) { 9 | pending := make(chan int, 1) 10 | 11 | <-pending 12 | 13 | fmt.Println(pending) 14 | } 15 | 16 | func TestServerCron(t *testing.T) { 17 | ServerCron() 18 | } 19 | 20 | func TestLookupCommand(t *testing.T) { 21 | var s map[string]string 22 | s = make(map[string]string) 23 | s["a"] = "b" 24 | v1, b := s["b"] 25 | v2, y := s["a"] 26 | fmt.Println(v1, b, v2, y) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/shared/share_test.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewShared(t *testing.T) { 8 | s1 := NewShared() 9 | s2 := NewShared() 10 | if s1 == s2 { 11 | t.Log("success") 12 | } else { 13 | t.Fatal("failed") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ccb1900/redisbygo/pkg" 6 | "strconv" 7 | "sync" 8 | ) 9 | 10 | type Shared struct { 11 | CRLF *pkg.RedisObject 12 | Ok *pkg.RedisObject 13 | EmptyBulk *pkg.RedisObject 14 | CZero *pkg.RedisObject 15 | Cone *pkg.RedisObject 16 | CNeGone *pkg.RedisObject 17 | Pong *pkg.RedisObject 18 | Space *pkg.RedisObject 19 | Colon *pkg.RedisObject 20 | NullBulk *pkg.RedisObject 21 | NullMultiBulk *pkg.RedisObject 22 | Queued *pkg.RedisObject 23 | EmptyMultiBulk *pkg.RedisObject 24 | WrongTypeErr *pkg.RedisObject 25 | NoKeyErr *pkg.RedisObject 26 | SyntaxErr *pkg.RedisObject 27 | SameObjectErr *pkg.RedisObject 28 | OutOfRangeErr *pkg.RedisObject 29 | NoScriptErr *pkg.RedisObject 30 | LoadingErr *pkg.RedisObject 31 | SlowScriptErr *pkg.RedisObject 32 | BgSaveErr *pkg.RedisObject 33 | MasterDownErr *pkg.RedisObject 34 | RoSalveErr *pkg.RedisObject 35 | ExecAbortErr *pkg.RedisObject 36 | NoAuthErr *pkg.RedisObject 37 | NoReplicasErr *pkg.RedisObject 38 | BusyKeyErr *pkg.RedisObject 39 | OOMErr *pkg.RedisObject 40 | Plus *pkg.RedisObject 41 | MessageBulk *pkg.RedisObject 42 | PMessageBulk *pkg.RedisObject 43 | SubscribeBulk *pkg.RedisObject 44 | UnSubscribeBulk *pkg.RedisObject 45 | PSubscribeBulk *pkg.RedisObject 46 | PUnSubscribeBulk *pkg.RedisObject 47 | Del *pkg.RedisObject 48 | Unlink *pkg.RedisObject 49 | RPop *pkg.RedisObject 50 | LPop *pkg.RedisObject 51 | LPush *pkg.RedisObject 52 | RPopLPush *pkg.RedisObject 53 | ZPopMin *pkg.RedisObject 54 | ZPopMax *pkg.RedisObject 55 | EmptyScan *pkg.RedisObject 56 | Selects [pkg.SharedSelectCmds]*pkg.RedisObject 57 | Integers [pkg.ObjSharedIntegers]*pkg.RedisObject 58 | MBulkHdr [pkg.ObjSharedBulkhdrLen]*pkg.RedisObject 59 | BulkHdr [pkg.ObjSharedBulkhdrLen]*pkg.RedisObject 60 | Err *pkg.RedisObject 61 | MinString string 62 | MaxString string 63 | } 64 | 65 | var once sync.Once 66 | var shared *Shared 67 | 68 | func NewShared() *Shared { 69 | once.Do(func() { 70 | shared = new(Shared) 71 | crlf := "\r\n" 72 | shared.CRLF = pkg.NewStringRedisObject(crlf) 73 | shared.Ok = pkg.NewStringRedisObject("+OK" + crlf) 74 | shared.Err = pkg.NewStringRedisObject("-ERR" + crlf) 75 | shared.EmptyBulk = pkg.NewStringRedisObject("$0" + crlf + crlf) 76 | shared.CZero = pkg.NewStringRedisObject(":0" + crlf) 77 | shared.Cone = pkg.NewStringRedisObject(":1" + crlf) 78 | shared.CNeGone = pkg.NewStringRedisObject(":-1" + crlf) 79 | shared.NullBulk = pkg.NewStringRedisObject("$-1" + crlf) 80 | shared.NullMultiBulk = pkg.NewStringRedisObject("*-1" + crlf) 81 | shared.EmptyMultiBulk = pkg.NewStringRedisObject("*0" + crlf) 82 | shared.Pong = pkg.NewStringRedisObject("+PONG" + crlf) 83 | shared.Queued = pkg.NewStringRedisObject("+QUEUED" + crlf) 84 | shared.EmptyScan = pkg.NewStringRedisObject("*2" + crlf + "$1" + crlf + "0" + crlf + "*0" + crlf) 85 | shared.WrongTypeErr = pkg.NewStringRedisObject("-WRONGTYPE Operation against a key holding the wrong kind of value" + crlf) 86 | shared.NoKeyErr = pkg.NewStringRedisObject("-ERR no such key" + crlf) 87 | shared.SyntaxErr = pkg.NewStringRedisObject("-ERR syntax error" + crlf) 88 | shared.SameObjectErr = pkg.NewStringRedisObject("-ERR source and destination objects are the same" + crlf) 89 | shared.OutOfRangeErr = pkg.NewStringRedisObject("-ERR index out of range" + crlf) 90 | shared.NoScriptErr = pkg.NewStringRedisObject("-NOSCRIPT No matching script. Please use EVAL." + crlf) 91 | shared.LoadingErr = pkg.NewStringRedisObject("-LOADING Redis is loading the dataset in memory" + crlf) 92 | shared.SlowScriptErr = pkg.NewStringRedisObject("-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE." + crlf) 93 | shared.MasterDownErr = pkg.NewStringRedisObject("-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'." + crlf) 94 | shared.BgSaveErr = pkg.NewStringRedisObject("-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error." + crlf) 95 | shared.RoSalveErr = pkg.NewStringRedisObject("-READONLY You can't write against a read only replica." + crlf) 96 | shared.NoAuthErr = pkg.NewStringRedisObject("-NOAUTH Authentication required." + crlf) 97 | shared.OOMErr = pkg.NewStringRedisObject("-OOM command not allowed when used memory > 'maxmemory'." + crlf) 98 | shared.ExecAbortErr = pkg.NewStringRedisObject("-EXECABORT Transaction discarded because of previous errors." + crlf) 99 | shared.NoReplicasErr = pkg.NewStringRedisObject("-NOREPLICAS Not enough good replicas to write." + crlf) 100 | shared.BusyKeyErr = pkg.NewStringRedisObject("-BUSYKEY Target key name already exists." + crlf) 101 | 102 | shared.Space = pkg.NewStringRedisObject(" ") 103 | shared.Plus = pkg.NewStringRedisObject("+") 104 | shared.Colon = pkg.NewStringRedisObject(":") 105 | 106 | shared.MessageBulk = pkg.NewStringRedisObject("$7" + crlf + "message" + crlf) 107 | shared.PMessageBulk = pkg.NewStringRedisObject("$8" + crlf + "pmessage" + crlf) 108 | shared.SubscribeBulk = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 109 | shared.UnSubscribeBulk = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 110 | shared.PSubscribeBulk = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 111 | shared.PUnSubscribeBulk = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 112 | shared.Del = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 113 | shared.Unlink = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 114 | shared.RPop = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 115 | shared.LPop = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 116 | shared.LPush = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 117 | shared.RPopLPush = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 118 | shared.ZPopMin = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 119 | shared.ZPopMax = pkg.NewStringRedisObject("$9" + crlf + "subscribe" + crlf) 120 | 121 | shared.MaxString = "maxstring" 122 | shared.MinString = "minstring" 123 | 124 | for i := 0; i < pkg.SharedSelectCmds; i++ { 125 | shared.Selects[i] = pkg.NewStringRedisObject(fmt.Sprintf("*2"+crlf+"$6"+crlf+"SELECT"+crlf+"$%d"+crlf+"%s"+crlf, 126 | len(strconv.Itoa(i)), strconv.Itoa(i))) 127 | } 128 | for i := 0; i < pkg.ObjSharedBulkhdrLen; i++ { 129 | shared.BulkHdr[i] = pkg.NewStringRedisObject(fmt.Sprintf("$%d"+crlf, i)) 130 | shared.MBulkHdr[i] = pkg.NewStringRedisObject(fmt.Sprintf("*%d"+crlf, i)) 131 | } 132 | for i := 0; i < pkg.ObjSharedIntegers; i++ { 133 | shared.Integers[i] = pkg.NewStringRedisObject(strconv.Itoa(i)).MakeShared() 134 | shared.Integers[i].Encoding = pkg.ObjEncodingInt 135 | } 136 | 137 | }) 138 | return shared 139 | } 140 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Int8T int8 4 | type Int64T int8 5 | type Uint32T int 6 | type Uint8T int 7 | type SizeT int 8 | type Long int 9 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | func Exists(path string) bool { 6 | _, err := os.Stat(path) //os.Stat获取文件信息 7 | if err != nil { 8 | if os.IsExist(err) { 9 | return true 10 | } 11 | return false 12 | } 13 | return true 14 | } 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # RedisServerByGo( in development... ) 2 | 3 | [中文](/Readme_CN.md) 4 | 5 | This is a redis server implemented by golang 6 | 7 | If you find some bugs,just give me an issue. 8 | 9 | There are many features what need to be implemented.When I have time,I will finish it. 10 | 11 | ## Branch 12 | 13 | `dev` maybe not work,please use `master` 14 | 15 | ## Repository 16 | 17 | - [Gitee,where Chinese can visit](https://gitee.com/waterloocode/redisbygo) 18 | - [Github](https://github.com/ccb1900/redisbygo) 19 | 20 | ## Supported 21 | 22 | - load config 23 | - load aof file 24 | - command ping 25 | - command echo 26 | - command pubsub 27 | - command subscribe 28 | - command get 29 | - command set 30 | - command select 31 | 32 | ## Usage 33 | 34 | ```shell 35 | make 36 | ``` 37 | 38 | ```shell 39 | cp server.example.json server.json 40 | ``` 41 | 42 | ### win 43 | 44 | ```shell 45 | ./build/windows/redis.exe 46 | ``` 47 | 48 | ### mac 49 | 50 | ```shell 51 | ./build/darwin/redis 52 | ``` 53 | 54 | ### linux 55 | 56 | ```shell 57 | ./build/linux/redis 58 | ``` 59 | 60 | ## Benchmark 61 | 62 | ```shell 63 | redis-benchmark -h 127.0.0.1 -p 6378 -n 100000 -c 1000 64 | ``` 65 | 66 | ## Reference 67 | 68 | [Redis Doc](https://redis.io/documentation) -------------------------------------------------------------------------------- /server.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 1245, 3 | "host": "127.0.0.1", 4 | "dashboard_port": 9001, 5 | "dashboard_ip": "127.0.0.1", 6 | "timezone": "PRC", 7 | "dbnum": 16, 8 | "maxclients": 100000, 9 | "name": "testredis", 10 | "appendfsync": "always", 11 | "appendonly": true, 12 | "save_seconds": 60, 13 | "save_times": 10000, 14 | "dbfilename": "dump.rdb", 15 | "dir": "./" 16 | } --------------------------------------------------------------------------------