├── .gitignore ├── LICENSE ├── README.md ├── bitmap_test.go ├── bitmaps.go ├── bitmaps_persistence_test.go ├── bitmaps_test.go ├── cmd ├── dboat_server │ ├── README.md │ ├── basalt.go │ ├── http_server.go │ ├── rpcx_client.go │ ├── rpcx_common.go │ ├── rpcx_server.go │ ├── server.go │ └── statemachine.go ├── http_client │ └── curl.sh ├── raft_server │ ├── README.md │ └── basalt.go ├── redis_client │ └── client.go ├── rpcx_client │ └── client.go └── server │ └── basalt.go ├── examples ├── basalt.jpg └── weibo │ ├── README.md │ └── follow.go ├── go.mod ├── go.sum ├── listener.go ├── raft.go ├── raft_server.go ├── server.go ├── service_http.go ├── service_redis.go └── service_rpcx.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | basalt 18 | basalt_cli 19 | .idea/ 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于Raft的数据一致性分布式的 bitmap 服务 2 | 3 | `bitmap`(位图)技术是数据库、大数据和互联网业务等场景下经常使用的一种技术。 4 | 5 | - 存在性判断 6 | - 爬虫url去重 7 | - 垃圾邮件过滤 8 | - 用户已阅读 9 | - 用户已赞 10 | - ... 11 | - 去重 12 | - 数据库 13 | - 大数据计算 14 | 15 | ![](examples/basalt.jpg) 16 | 17 | ## 服务 18 | 19 | 进入`cmd/server`, 运行`go run server.go`启动一个bitmap服务。 20 | 21 | 它同时支持三种服务: 22 | 23 | - rpcx: 你可以使用rpcx服务获取高性能的网络调用, `cmd/rpcx_client`是一个rpcx客户端的demo 24 | - redis: 你可以使用redis客户端访问Bitmap服务(如果你的redis client支持自定义命令), 方便兼容redis调用代码, `cmd/redis_client`是redis demo 25 | - http: 通过http服务调用,调用简单,支持各种编程语言和脚本,`cmd/http_client/curl.sh`是通过`curl`调用服务 26 | 27 | ## 集群模式 28 | 29 | 支持raft集群模式: [basalt集群](https://github.com/rpcxio/basalt/tree/master/cmd/raft_server) 30 | 31 | ## API接口 32 | 33 | basalt位图服务支持三种接口模式: 34 | 35 | - HTTP API: 通过http api的方式进行访问 36 | - Redis模式: 扩展了redis命令,可以通过redis client进行访问 37 | - rpcx模式: 可以通过rpcx框架进行访问 38 | 39 | ### Redis命令 40 | 41 | - `ping`: ping-pong消息 42 | - `quit`: 退出连接 43 | - `bmadd name value`: 在名为`name`的bitmap增加一个uint32值`value` 44 | - `bmaddmany name value1 value2 value3...`: 为名为`name`的bitmap增加一批值 45 | - `bmdel name value`: 在名为`name`的bitmap删除一个uint32值`value` 46 | - `bmdrop name`: 删除名为`name`的bitmap 47 | - `bmclear name`: 清空名为`name`的bitmap 48 | - `bmcard name`: 获取为`name`的bitmap包含的元素数 49 | - `bmexists name value`: 检查uint32值`value`是否存在于名为`name`的bitmap中,整数`1`代表存在,`0`代表不存在 50 | - `bminter name1 name2 name3...`: 求几个bitmap的交集,返回交集的uint32整数列表 51 | - `bminterstore dst name1 name2 name3...`: 求几个bitmap(`name1`、`name2`、`name3`...)的交集,并将结果保存到`dst`中 52 | - `bmunion name1 name2 name3...`: 求几个bitmap的并集,返回并集的uint32整数列表 53 | - `bmunionstore dst name1 name2 name3...`: 求几个bitmap(`name1`、`name2`、`name3`...)的并集,并将结果保存到`dst`中 54 | - `bmxor name1 name2.`: 求两个bitmap的`xor`集(双方不共有的集合,相当于并集减交集),返回`xor`集的uint32整数列表 55 | - `bmxorstore dst name1 name2`: 求两个bitmap的`xor`集,并将结果保存到`dst`中 56 | - `bmdiff name1 name2`: 求`name1`中和`name2`没有交集的数据,返回结果的uint32整数列表 57 | - `bmdiffstore dst name1 name2`: 求`name1`中和`name2`没有交集的数据,并将结果保存到`dst`中 58 | - `bmstats name`: 返回`name`的bitmap的统计信息 59 | 60 | ### rpcx 服务 61 | 62 | 查看 [godoc](https://godoc.org/github.com/rpcxio/basalt)以了解提供的rpcx服务 63 | 64 | ### HTTP 服务 65 | 66 | HTTP 服务提供和 redis、rpcx服务相同的功能,通过http调用就可以访问Bitmap服务。 67 | 68 | 所有的参数都是在路径中提供,路径格式为`/action/param1/param2`。 69 | 复数形式`values`、`names`包含多个元素,元素以逗号`,`分隔。 70 | 71 | 为了简化操作,所有的http服务都是通过`GET`方法提供的。 72 | 73 | 返回的`HTTP StatusCode`代表的含义如下: 74 | 75 | - `200` 代表`OK`、`存在` 76 | - `400` 代表参数不对,比如应该是uint32格式,结果却是无法解析的字符串 77 | - `404` 代表不存在 78 | - `500` 代表内部处理错误 79 | 80 | 81 | HTTP服务路径列表如下: 82 | 83 | - `/add/:name/:value` 84 | - `/addmany/:name/:values` 85 | - `/remove/:name/:value` 86 | - `/drop/:name` 87 | - `/clear/:name` 88 | - `/exists/:name/:value` 89 | - `/card/:name` 90 | - `/inter/:names` 91 | - `/interstore/:dst/:names` 92 | - `/union/:names` 93 | - `/unionstore/:dst/:names` 94 | - `/xor/:name1/:name2` 95 | - `/xorstore/:dst/:name1/:name2` 96 | - `/diff/:name1/:name2` 97 | - `/diffstore/:dst/:name1/:name2` 98 | - `/stats/:name` 99 | 100 | ## 例子 101 | 102 | 以微博关注关系数据集做例子,我们使用Bitmap服务来存储某人是否关注了某人,以及两人是否互相关注。 103 | 104 | 例子参看 [weibo_follow](https://github.com/rpcxio/basalt/tree/master/examples/weibo) 105 | 106 | 107 | ## Roadmap 108 | 109 | - [x] Multiple-key Bitmap 110 | - [x] rpcx services for Bitmap 111 | - [x] HTTP services for Bitmap 112 | - [x] Redis services for Bitmap 113 | - [x] Persistence 114 | - [x] Cluster mode 115 | 116 | ## Credits 117 | 118 | - [roaring](https://github.com/RoaringBitmap/roaring) 119 | - [redcon](https://github.com/tidwall/redcon) 120 | - [httprouter](https://github.com/julienschmidt/httprouter) 121 | - [rpcx](https://github.com/smallnest/rpcx) 122 | - [etcd](https://github.com/etcd-io/etcd) -------------------------------------------------------------------------------- /bitmap_test.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/RoaringBitmap/roaring" 9 | ) 10 | 11 | func TestBitmap_Stats(t *testing.T) { 12 | rand.Seed(time.Now().UnixNano()) 13 | var bitmap = roaring.NewBitmap() 14 | total := uint32(10 * 10000 * 10000) 15 | for i := uint32(0); i < total; i++ { 16 | if rand.Int()%2 == 0 { 17 | bitmap.Add(i) 18 | } 19 | } 20 | 21 | t.Logf("cardinality: %d", bitmap.GetCardinality()) 22 | t.Logf("stats: %+v", bitmap.Stats()) 23 | 24 | start := time.Now() 25 | data, err := bitmap.MarshalBinary() 26 | t.Logf("marshal took: %d ms", time.Since(start).Milliseconds()) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | t.Logf("serialized size: %d MB, read bytes: %d MB", bitmap.GetSerializedSizeInBytes()/(1024*1024), len(data)/(1024*1024)) 31 | 32 | start = time.Now() 33 | persistedBitmap := bitmap.Clone() 34 | t.Logf("clone took: %d ms", time.Since(start).Milliseconds()) 35 | 36 | persistedBitmap.Clear() 37 | 38 | if bitmap.GetCardinality() < uint64(total/4) { 39 | t.Errorf("not real clone") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bitmaps.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "sync" 8 | 9 | "github.com/RoaringBitmap/roaring" 10 | "github.com/smallnest/log" 11 | ) 12 | 13 | // OP bitmaps operations 14 | type OP byte 15 | 16 | const ( 17 | BmOpAdd OP = 1 18 | BmOpAddMany = 2 19 | BmOpRemove = 3 20 | BmOpDrop = 4 21 | BmOpClear = 5 22 | ) 23 | 24 | // Bitmaps contains all bitmaps of namespace. 25 | type Bitmaps struct { 26 | mu sync.RWMutex 27 | bitmaps map[string]*Bitmap 28 | writeCallback func(op OP, value string) 29 | } 30 | 31 | // NewBitmaps creates a Bitmaps. 32 | func NewBitmaps() *Bitmaps { 33 | return &Bitmaps{ 34 | bitmaps: make(map[string]*Bitmap), 35 | } 36 | } 37 | 38 | // Bitmap is the goroutine-safe bitmap. 39 | type Bitmap struct { 40 | mu sync.RWMutex 41 | bitmap *roaring.Bitmap 42 | } 43 | 44 | // Add adds a value. 45 | func (bs *Bitmaps) Add(name string, v uint32, callback bool) { 46 | if bs.writeCallback != nil && callback { 47 | bs.writeCallback(BmOpAdd, fmt.Sprintf("%s,%d", name, v)) 48 | return 49 | } 50 | 51 | bs.mu.Lock() 52 | bm := bs.bitmaps[name] 53 | if bm == nil { 54 | bm = &Bitmap{ 55 | bitmap: roaring.NewBitmap(), 56 | } 57 | bs.bitmaps[name] = bm 58 | } 59 | bs.mu.Unlock() 60 | 61 | bm.mu.Lock() 62 | bm.bitmap.Add(v) 63 | bm.mu.Unlock() 64 | } 65 | 66 | // AddMany adds multiple values. 67 | func (bs *Bitmaps) AddMany(name string, v []uint32, callback bool) { 68 | if bs.writeCallback != nil && callback { 69 | bs.writeCallback(BmOpAddMany, fmt.Sprintf("%s,%d", name, ints2str(v))) 70 | return 71 | } 72 | 73 | bs.mu.Lock() 74 | bm := bs.bitmaps[name] 75 | if bm == nil { 76 | bm = &Bitmap{ 77 | bitmap: roaring.NewBitmap(), 78 | } 79 | bs.bitmaps[name] = bm 80 | } 81 | bs.mu.Unlock() 82 | 83 | bm.mu.Lock() 84 | bm.bitmap.AddMany(v) 85 | bm.mu.Unlock() 86 | } 87 | 88 | // Remove removes a value. 89 | func (bs *Bitmaps) Remove(name string, v uint32, callback bool) { 90 | if bs.writeCallback != nil && callback { 91 | bs.writeCallback(BmOpRemove, fmt.Sprintf("%s,%d", name, v)) 92 | return 93 | } 94 | 95 | bs.mu.Lock() 96 | bm := bs.bitmaps[name] 97 | if bm == nil { 98 | bm = &Bitmap{ 99 | bitmap: roaring.NewBitmap(), 100 | } 101 | bs.bitmaps[name] = bm 102 | } 103 | bs.mu.Unlock() 104 | 105 | bm.mu.Lock() 106 | bm.bitmap.Remove(v) 107 | bm.mu.Unlock() 108 | } 109 | 110 | // RemoveBitmap removes a bitmap. 111 | func (bs *Bitmaps) RemoveBitmap(name string, callback bool) { 112 | if bs.writeCallback != nil && callback { 113 | bs.writeCallback(BmOpDrop, name) 114 | return 115 | } 116 | 117 | bs.mu.Lock() 118 | delete(bs.bitmaps, name) 119 | bs.mu.Unlock() 120 | } 121 | 122 | // ClearBitmap clear a bitmap. 123 | func (bs *Bitmaps) ClearBitmap(name string, callback bool) { 124 | if bs.writeCallback != nil && callback { 125 | bs.writeCallback(BmOpClear, name) 126 | return 127 | } 128 | 129 | bs.mu.RLock() 130 | bm := bs.bitmaps[name] 131 | if bm == nil { 132 | bs.mu.RUnlock() 133 | return 134 | } 135 | bs.mu.RUnlock() 136 | bm.bitmap.Clear() 137 | } 138 | 139 | // Exists checks whether a value exists. 140 | func (bs *Bitmaps) Exists(name string, v uint32) bool { 141 | bs.mu.RLock() 142 | bm := bs.bitmaps[name] 143 | if bm == nil { 144 | bs.mu.RUnlock() 145 | return false 146 | } 147 | bs.mu.RUnlock() 148 | 149 | bm.mu.RLock() 150 | existed := bm.bitmap.Contains(v) 151 | bm.mu.RUnlock() 152 | 153 | return existed 154 | } 155 | 156 | // Card returns the number of integers contained in the bitmap. 157 | func (bs *Bitmaps) Card(name string) uint64 { 158 | bs.mu.RLock() 159 | bm := bs.bitmaps[name] 160 | if bm == nil { 161 | bs.mu.RUnlock() 162 | return 0 163 | } 164 | bs.mu.RUnlock() 165 | 166 | bm.mu.RLock() 167 | num := bm.bitmap.GetCardinality() 168 | bm.mu.RUnlock() 169 | 170 | return num 171 | } 172 | 173 | type Stats struct { 174 | Cardinality uint64 175 | Containers uint64 176 | 177 | ArrayContainers uint64 178 | ArrayContainerBytes uint64 179 | ArrayContainerValues uint64 180 | 181 | BitmapContainers uint64 182 | BitmapContainerBytes uint64 183 | BitmapContainerValues uint64 184 | 185 | RunContainers uint64 186 | RunContainerBytes uint64 187 | RunContainerValues uint64 188 | } 189 | 190 | // Stats gets the stats of named bitmap. 191 | func (bs *Bitmaps) Stats(name string) Stats { 192 | bs.mu.RLock() 193 | bm := bs.bitmaps[name] 194 | if bm == nil { 195 | bs.mu.RUnlock() 196 | return Stats{} 197 | } 198 | bs.mu.RUnlock() 199 | 200 | bm.mu.RLock() 201 | stats := bm.bitmap.Stats() 202 | bm.mu.RUnlock() 203 | 204 | return Stats(stats) 205 | } 206 | 207 | func (bs *Bitmaps) intersection(names ...string) *roaring.Bitmap { 208 | var bms []*roaring.Bitmap 209 | 210 | bs.mu.RLock() 211 | for _, name := range names { 212 | bm := bs.bitmaps[name] 213 | if bm == nil { 214 | bs.mu.RUnlock() 215 | return nil 216 | } 217 | bms = append(bms, bm.bitmap) 218 | 219 | } 220 | bs.mu.RUnlock() 221 | 222 | return roaring.ParAnd(0, bms...) 223 | } 224 | 225 | // Inter computes the intersection (AND) of all provided bitmaps. 226 | func (bs *Bitmaps) Inter(names ...string) []uint32 { 227 | bm := bs.intersection(names...) 228 | if bm == nil { 229 | return nil 230 | } 231 | return bm.ToArray() 232 | } 233 | 234 | // InterStore computes the intersection (AND) of all provided bitmaps and save to destination. 235 | func (bs *Bitmaps) InterStore(destination string, names ...string) uint64 { 236 | bm := bs.intersection(names...) 237 | if bm == nil { 238 | return 0 239 | } 240 | 241 | bs.mu.Lock() 242 | bs.bitmaps[destination] = &Bitmap{bitmap: bm} 243 | bs.mu.Unlock() 244 | 245 | return bm.GetCardinality() 246 | } 247 | 248 | func (bs *Bitmaps) union(names ...string) *roaring.Bitmap { 249 | var bms []*roaring.Bitmap 250 | 251 | bs.mu.RLock() 252 | for _, name := range names { 253 | bm := bs.bitmaps[name] 254 | if bm != nil { 255 | bms = append(bms, bm.bitmap) 256 | } 257 | } 258 | bs.mu.RUnlock() 259 | 260 | return roaring.ParHeapOr(0, bms...) 261 | } 262 | 263 | // Union computes the union (OR) of all provided bitmaps. 264 | func (bs *Bitmaps) Union(names ...string) []uint32 { 265 | bm := bs.union(names...) 266 | return bm.ToArray() 267 | } 268 | 269 | // UnionStore computes the union (OR) of all provided bitmaps and store to destination. 270 | func (bs *Bitmaps) UnionStore(destination string, names ...string) uint64 { 271 | bm := bs.union(names...) 272 | 273 | bs.mu.Lock() 274 | bs.bitmaps[destination] = &Bitmap{bitmap: bm} 275 | bs.mu.Unlock() 276 | 277 | return bm.GetCardinality() 278 | } 279 | 280 | func (bs *Bitmaps) xor(name1, name2 string) *roaring.Bitmap { 281 | var rbm1, rbm2 *roaring.Bitmap 282 | 283 | bs.mu.RLock() 284 | bm1 := bs.bitmaps[name1] 285 | if bm1 == nil { 286 | rbm1 = roaring.NewBitmap() 287 | } else { 288 | rbm1 = bm1.bitmap 289 | } 290 | bm2 := bs.bitmaps[name2] 291 | if bm2 == nil { 292 | rbm2 = roaring.NewBitmap() 293 | } else { 294 | rbm2 = bm2.bitmap 295 | } 296 | bs.mu.RUnlock() 297 | 298 | return roaring.Xor(rbm1, rbm2) 299 | } 300 | 301 | // Xor computes the symmetric difference between two bitmaps and returns the result 302 | func (bs *Bitmaps) Xor(name1, name2 string) []uint32 { 303 | bm := bs.xor(name1, name2) 304 | return bm.ToArray() 305 | } 306 | 307 | // XorStore computes the symmetric difference between two bitmaps and save the result to destination. 308 | func (bs *Bitmaps) XorStore(destination, name1, name2 string) uint64 { 309 | bm := bs.xor(name1, name2) 310 | 311 | bs.mu.Lock() 312 | bs.bitmaps[destination] = &Bitmap{bitmap: bm} 313 | bs.mu.Unlock() 314 | 315 | return bm.GetCardinality() 316 | } 317 | 318 | func (bs *Bitmaps) diff(name1, name2 string) *roaring.Bitmap { 319 | var rbm1, rbm2 *roaring.Bitmap 320 | 321 | bs.mu.RLock() 322 | bm1 := bs.bitmaps[name1] 323 | if bm1 == nil { 324 | rbm1 = roaring.NewBitmap() 325 | } else { 326 | rbm1 = bm1.bitmap 327 | } 328 | bm2 := bs.bitmaps[name2] 329 | if bm2 == nil { 330 | rbm2 = roaring.NewBitmap() 331 | } else { 332 | rbm2 = bm2.bitmap 333 | } 334 | bs.mu.RUnlock() 335 | 336 | return roaring.AndNot(rbm1, rbm2) 337 | } 338 | 339 | // Diff computes the difference between two bitmaps and returns the result. 340 | func (bs *Bitmaps) Diff(name1, name2 string) []uint32 { 341 | bm := bs.diff(name1, name2) 342 | return bm.ToArray() 343 | } 344 | 345 | // DiffStore computes the difference between two bitmaps and save the result to destination. 346 | func (bs *Bitmaps) DiffStore(destination, name1, name2 string) uint64 { 347 | bm := bs.diff(name1, name2) 348 | 349 | bs.mu.Lock() 350 | bs.bitmaps[destination] = &Bitmap{bitmap: bm} 351 | bs.mu.Unlock() 352 | 353 | return bm.GetCardinality() 354 | } 355 | 356 | // Save saves bitmaps to the io.Writer. 357 | func (bs *Bitmaps) Save(w io.Writer) error { 358 | var keys []string 359 | bs.mu.RLock() 360 | for k := range bs.bitmaps { 361 | keys = append(keys, k) 362 | } 363 | bs.mu.RUnlock() 364 | 365 | for _, k := range keys { 366 | bs.mu.RLock() 367 | bm := bs.bitmaps[k] 368 | bs.mu.RUnlock() 369 | if bm != nil { 370 | if err := bs.saveBitmap(w, k, bm.bitmap); err != nil { 371 | return err 372 | } 373 | } 374 | } 375 | 376 | return nil 377 | } 378 | 379 | func (bs *Bitmaps) saveBitmap(w io.Writer, name string, bm *roaring.Bitmap) error { 380 | if bm == nil { 381 | return nil 382 | } 383 | 384 | err := binary.Write(w, binary.LittleEndian, uint32(len(name))) 385 | if err != nil { 386 | log.Errorf("failed to write len of name %s: %v", name, err) 387 | return err 388 | } 389 | _, err = w.Write([]byte(name)) 390 | if err != nil { 391 | log.Errorf("failed to write name %s: %v", name, err) 392 | return err 393 | } 394 | 395 | bs.mu.RLock() 396 | pBitmap := bm.Clone() 397 | bs.mu.RUnlock() 398 | _, err = pBitmap.WriteTo(w) 399 | if err != nil { 400 | log.Errorf("failed to write bitmap %s: %v", name, err) 401 | return err 402 | } 403 | 404 | return nil 405 | } 406 | 407 | // Read restores bitmaps from a io.Reader. 408 | func (bs *Bitmaps) Read(r io.Reader) error { 409 | for { 410 | name, bm, err := readBitmap(r) 411 | if err == io.EOF { 412 | return nil 413 | } 414 | if err != nil { 415 | return err 416 | } 417 | 418 | b := &Bitmap{ 419 | bitmap: bm, 420 | } 421 | 422 | bs.mu.Lock() 423 | bs.bitmaps[name] = b 424 | bs.mu.Unlock() 425 | 426 | } 427 | } 428 | 429 | func readBitmap(r io.Reader) (name string, bm *roaring.Bitmap, err error) { 430 | var l uint32 431 | err = binary.Read(r, binary.LittleEndian, &l) 432 | if err != nil { 433 | if err == io.EOF { 434 | return "", nil, err 435 | } 436 | log.Errorf("failed to read len of name: %v", err) 437 | return "", nil, err 438 | } 439 | 440 | var data = make([]byte, int(l)) 441 | _, err = io.ReadFull(r, data) 442 | if err != nil { 443 | log.Errorf("failed to read name: %v", err) 444 | return "", nil, err 445 | } 446 | name = string(data) 447 | 448 | bm = roaring.NewBitmap() 449 | _, err = bm.ReadFrom(r) 450 | if err != nil { 451 | log.Errorf("failed to read name %s: %v", name, err) 452 | return "", nil, err 453 | } 454 | 455 | return name, bm, nil 456 | } 457 | -------------------------------------------------------------------------------- /bitmaps_persistence_test.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "testing" 7 | ) 8 | 9 | func TestBitmaps_Persistence(t *testing.T) { 10 | var buf = bytes.NewBuffer(nil) 11 | 12 | bms := NewBitmaps() 13 | 14 | var values1 []uint32 15 | for i := 0; i < 100; i++ { 16 | v := uint32(rand.Int31()) 17 | values1 = append(values1, v) 18 | bms.Add("test1", v) 19 | } 20 | var values2 []uint32 21 | for i := 0; i < 100; i++ { 22 | v := uint32(rand.Int31()) 23 | values2 = append(values2, v) 24 | bms.Add("test2", v) 25 | } 26 | 27 | err := bms.Save(buf) 28 | if err != nil { 29 | t.Fatalf("failed to save Bitmaps: %v", err) 30 | } 31 | 32 | // read 33 | 34 | bms = NewBitmaps() 35 | err = bms.Read(buf) 36 | if err != nil { 37 | t.Fatalf("failed to restore Bitmaps: %v", err) 38 | } 39 | 40 | for i := 0; i < 100; i++ { 41 | if !bms.Exists("test1", values1[i]) { 42 | t.Fatalf("not found %d in retored bitmap test1", values1[i]) 43 | } 44 | } 45 | for i := 0; i < 100; i++ { 46 | if !bms.Exists("test2", values2[i]) { 47 | t.Fatalf("not found %d in retored bitmap test2", values2[i]) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bitmaps_test.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestBitmaps_Basic(t *testing.T) { 9 | bms := NewBitmaps() 10 | 11 | var values []uint32 12 | for i := 0; i < 100; i++ { 13 | v := uint32(rand.Int31()) 14 | values = append(values, v) 15 | bms.Add("test", v) 16 | } 17 | 18 | for _, v := range values { 19 | if !bms.Exists("test", v) { 20 | t.Errorf("expect %d exists but not found", v) 21 | } 22 | } 23 | 24 | for _, v := range values { 25 | bms.Remove("test", v) 26 | } 27 | 28 | for _, v := range values { 29 | if bms.Exists("test", v) { 30 | t.Errorf("expect %d non-exists but found it", v) 31 | } 32 | } 33 | 34 | for i := 0; i < 10; i++ { 35 | bms.AddMany("test", values[i*10:i*10+10]) 36 | } 37 | for _, v := range values { 38 | if !bms.Exists("test", v) { 39 | t.Errorf("expect %d exists but not found", v) 40 | } 41 | } 42 | 43 | num := bms.Card("test") 44 | if num != 100 { 45 | t.Errorf("expect 100 elements but got %d", num) 46 | } 47 | } 48 | 49 | func TestBitmaps_Inter(t *testing.T) { 50 | bms := NewBitmaps() 51 | 52 | bms.AddMany("test1", []uint32{1, 2, 3, 10, 11}) 53 | bms.AddMany("test2", []uint32{1, 2, 3, 20, 21}) 54 | 55 | result := bms.Inter("test1", "test2") 56 | if result[0] != 1 || result[1] != 2 || result[2] != 3 { 57 | t.Fatalf("expect 1,2,3 but got %v", result) 58 | } 59 | } 60 | 61 | func TestBitmaps_Union(t *testing.T) { 62 | bms := NewBitmaps() 63 | 64 | bms.AddMany("test1", []uint32{1, 2, 3, 10, 11}) 65 | bms.AddMany("test2", []uint32{1, 2, 3, 20, 21}) 66 | 67 | result := bms.Union("test1", "test2") 68 | if len(result) != 7 || result[0] != 1 || result[1] != 2 || result[2] != 3 || 69 | result[3] != 10 || result[4] != 11 || 70 | result[5] != 20 || result[6] != 21 { 71 | t.Fatalf("expect 1,2,3,10,11,20,21 but got %v", result) 72 | } 73 | } 74 | 75 | func TestBitmaps_Xor(t *testing.T) { 76 | bms := NewBitmaps() 77 | 78 | bms.AddMany("test1", []uint32{1, 2, 3, 10, 11}) 79 | bms.AddMany("test2", []uint32{1, 2, 3, 20, 21}) 80 | 81 | result := bms.Xor("test1", "test2") 82 | if len(result) != 4 || result[0] != 10 || result[1] != 11 || result[2] != 20 || result[3] != 21 { 83 | t.Fatalf("expect 10,11,20,21 but got %v", result) 84 | } 85 | } 86 | 87 | func TestBitmaps_Diff(t *testing.T) { 88 | bms := NewBitmaps() 89 | 90 | bms.AddMany("test1", []uint32{1, 2, 3, 10, 11}) 91 | bms.AddMany("test2", []uint32{1, 2, 3, 20, 21}) 92 | 93 | result := bms.Diff("test1", "test2") 94 | if len(result) != 2 || result[0] != 10 || result[1] != 11 { 95 | t.Fatalf("expect 10,11 but got %v", result) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /cmd/dboat_server/README.md: -------------------------------------------------------------------------------- 1 | ### 启动集群(3个DragonBoat Raft节点) 2 | ``` 3 | ./basalt -port 18419 -peers localhost:63001,localhost:63002,localhost:63003 -nodeid 1 4 | ./basalt -port 28419 -peers localhost:63001,localhost:63002,localhost:63003 -nodeid 2 5 | ./basalt -port 38419 -peers localhost:63001,localhost:63002,localhost:63003 -nodeid 3 6 | ``` 7 | 8 | ### 测试 9 | 添加数据(采用http访问方式) 10 | ``` 11 | curl -XPOST http://127.0.0.1:18419/add/test/1000 12 | ``` 13 | 14 | 检查数据(采用http访问方式) 15 | ``` 16 | curl http://127.0.0.1:38419/exists/test/1000 17 | ``` -------------------------------------------------------------------------------- /cmd/dboat_server/basalt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/lni/dragonboat/v3" 7 | "github.com/lni/dragonboat/v3/config" 8 | "log" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "path/filepath" 13 | "strings" 14 | "syscall" 15 | ) 16 | 17 | const ( 18 | basaltClusterId uint64 = 100 19 | ) 20 | 21 | var ( 22 | port = flag.Int("port", 18419, "server port") 23 | 24 | peers = flag.String("peers", "localhost:63001", "dragonboat peers addresses with comma separated") 25 | nodeId = flag.Int("nodeid", 1, "dragonboat node id") 26 | join = flag.Bool("join", false, "new added node") 27 | dataBaseDir = flag.String("basedir", "/Users/jayn1985/basalt", "dragonboat wal & node host base dir") 28 | ) 29 | 30 | func main() { 31 | flag.Parse() 32 | 33 | if *peers == "" { 34 | log.Fatal("peers can not be empty") 35 | } 36 | 37 | hosts := strings.Split(*peers, ",") 38 | if *nodeId < 1 || *nodeId > len(hosts) { 39 | log.Fatal("nodeid should be in [1, len(peer hosts)] scope") 40 | } 41 | 42 | var nodeAddr string 43 | members := make(map[uint64]string) 44 | for idx, host := range hosts { 45 | if idx + 1 == *nodeId { 46 | nodeAddr = host 47 | } 48 | 49 | members[uint64(idx + 1)] = host 50 | } 51 | 52 | rc := config.Config{ 53 | NodeID: uint64(*nodeId), 54 | ClusterID: basaltClusterId, 55 | ElectionRTT: 10, 56 | HeartbeatRTT: 1, 57 | CheckQuorum: true, 58 | SnapshotEntries: 10000, 59 | CompactionOverhead: 500, 60 | 61 | } 62 | 63 | dataDir := filepath.Join(*dataBaseDir, fmt.Sprintf("node-%d", *nodeId)) 64 | nhc := config.NodeHostConfig{ 65 | WALDir: dataDir, 66 | NodeHostDir: dataDir, 67 | RTTMillisecond: 200, 68 | RaftAddress: nodeAddr, 69 | } 70 | 71 | nh, err := dragonboat.NewNodeHost(nhc) 72 | if err != nil { 73 | log.Fatalf("failed to start dragonboat node host: %v", err) 74 | } 75 | 76 | //bitmaps := basalt.NewBitmaps() 77 | //hdr := CreateBasaltStateMachineHandler(bitmaps) 78 | if err = nh.StartCluster(members, *join, NewBasalStateMachine, rc); err != nil { 79 | log.Fatalf("failed to start cluster: %v\n", err) 80 | } 81 | 82 | srv := NewServer(fmt.Sprintf(":%d", *port), nh, nil) 83 | 84 | go func() { 85 | if err := srv.Serve(); err != nil && err != http.ErrServerClosed { 86 | log.Fatalf("failed to start basalt server: %v", err) 87 | } 88 | }() 89 | 90 | quit := make(chan os.Signal) 91 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 92 | <-quit 93 | 94 | srv.Close() 95 | } 96 | -------------------------------------------------------------------------------- /cmd/dboat_server/http_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/julienschmidt/httprouter" 9 | "github.com/smallnest/log" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type BasaltHttpServer struct { 17 | base *BasaltServer 18 | srv *http.Server 19 | } 20 | 21 | func (s *BasaltHttpServer) initRouter() { 22 | router := httprouter.New() 23 | 24 | router.POST("/add/:name/:value", s.add) 25 | router.POST("/addmany/:name/:values", s.addMany) 26 | router.POST("/remove/:name/:value", s.remove) 27 | router.POST("/drop/:name", s.drop) 28 | router.POST("/clear/:name", s.clear) 29 | router.GET("/exists/:name/:value", s.exists) 30 | router.GET("/card/:name", s.card) 31 | 32 | router.GET("/inter/:names", s.inter) 33 | router.GET("/interstore/:dst/:names", s.interStore) 34 | 35 | router.GET("/union/:names", s.union) 36 | router.GET("/unionstore/:dst/:names", s.unionStore) 37 | 38 | router.GET("/xor/:name1/:name2", s.xor) 39 | router.GET("/xorstore/:dst/:name1/:name2", s.xorStore) 40 | 41 | router.GET("/diff/:name1/:name2", s.diff) 42 | router.GET("/diffstore/:dst/:name1/:name2", s.diffStore) 43 | 44 | s.srv.Handler = router 45 | } 46 | 47 | func (s *BasaltHttpServer) add(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 48 | name := params.ByName("name") 49 | value := params.ByName("value") 50 | 51 | val, err := strconv.ParseUint(value, 10, 32) 52 | if err != nil { 53 | w.Write([]byte("INVALID DATA")) 54 | return 55 | } 56 | 57 | bd := &BasaltData{ 58 | Type: Add, 59 | Names: []string { name }, 60 | Values: []uint32 { uint32(val) }, 61 | } 62 | 63 | s.doSyncPropose(bd, w) 64 | } 65 | 66 | func (s *BasaltHttpServer) addMany(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 67 | name := params.ByName("name") 68 | values := params.ByName("values") 69 | 70 | var vals []uint32 71 | vs := strings.Split(values, ",") 72 | for _, val := range vs { 73 | v, err := strconv.ParseUint(val, 10, 32) 74 | if err != nil { 75 | w.Write([]byte("INVALID DATA")) 76 | return 77 | } 78 | 79 | vals = append(vals, uint32(v)) 80 | } 81 | 82 | bd := &BasaltData{ 83 | Type: AddMany, 84 | Names: []string { name }, 85 | Values: vals, 86 | } 87 | 88 | s.doSyncPropose(bd, w) 89 | } 90 | 91 | func (s *BasaltHttpServer) drop(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 92 | name := params.ByName("name") 93 | 94 | bd := &BasaltData{ 95 | Type: Drop, 96 | Names: []string { name }, 97 | Values: nil, 98 | } 99 | 100 | s.doSyncPropose(bd, w) 101 | } 102 | 103 | func (s *BasaltHttpServer) clear(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 104 | name := params.ByName("name") 105 | 106 | bd := &BasaltData{ 107 | Type: Clear, 108 | Names: []string { name }, 109 | Values: nil, 110 | } 111 | 112 | s.doSyncPropose(bd, w) 113 | } 114 | 115 | func (s *BasaltHttpServer) remove(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 116 | name := params.ByName("name") 117 | value := params.ByName("value") 118 | 119 | val, err := strconv.ParseUint(value, 10, 32) 120 | if err != nil { 121 | w.Write([]byte("INVALID DATA")) 122 | return 123 | } 124 | 125 | bd := &BasaltData{ 126 | Type: Remove, 127 | Names: []string { name }, 128 | Values: []uint32 { uint32(val) }, 129 | } 130 | 131 | s.doSyncPropose(bd, w) 132 | } 133 | 134 | func (s *BasaltHttpServer) exists(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 135 | name := params.ByName("name") 136 | value := params.ByName("value") 137 | 138 | val, err := strconv.ParseUint(value, 10, 32) 139 | if err != nil { 140 | w.Write([]byte("INVALID DATA")) 141 | return 142 | } 143 | 144 | bd := &BasaltData{ 145 | Type: Exists, 146 | Names: []string { name }, 147 | Values: []uint32 { uint32(val) }, 148 | } 149 | 150 | result := s.doSyncRead(bd) 151 | if _, ok := result.(error); ok { 152 | w.Write([]byte("OPERATION ERROR")) 153 | return 154 | } 155 | 156 | w.Write([]byte(strconv.FormatBool(result.(bool)))) 157 | } 158 | 159 | func (s *BasaltHttpServer) card(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 160 | name := params.ByName("name") 161 | 162 | bd := &BasaltData{ 163 | Type: Card, 164 | Names: []string { name }, 165 | Values: nil, 166 | } 167 | 168 | result := s.doSyncRead(bd) 169 | if _, ok := result.(error); ok { 170 | w.Write([]byte("OPERATION ERROR")) 171 | return 172 | } 173 | 174 | w.Write([]byte(strconv.FormatUint(result.(uint64), 10))) 175 | } 176 | 177 | func (s *BasaltHttpServer) inter(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 178 | names := params.ByName("names") 179 | 180 | bd := &BasaltData{ 181 | Type: Inter, 182 | Names: strings.Split(names, ","), 183 | Values: nil, 184 | } 185 | 186 | result := s.doSyncRead(bd) 187 | if _, ok := result.(error); ok { 188 | w.Write([]byte("OPERATION ERROR")) 189 | return 190 | } 191 | 192 | w.Write([]byte(strings.Join(strings.Fields(fmt.Sprint(result.([]uint32))), ","))) 193 | } 194 | 195 | func (s *BasaltHttpServer) interStore(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 196 | names := params.ByName("names") 197 | dst := params.ByName("dst") 198 | 199 | ns := []string { dst } 200 | ns = append(ns, strings.Split(names, ",")...) 201 | 202 | bd := &BasaltData{ 203 | Type: Inter, 204 | Names: ns, 205 | Values: nil, 206 | } 207 | 208 | s.doSyncPropose(bd, w) 209 | } 210 | 211 | func (s *BasaltHttpServer) union(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 212 | names := params.ByName("names") 213 | 214 | bd := &BasaltData{ 215 | Type: Union, 216 | Names: strings.Split(names, ","), 217 | Values: nil, 218 | } 219 | 220 | result := s.doSyncRead(bd) 221 | if _, ok := result.(error); ok { 222 | w.Write([]byte("OPERATION ERROR")) 223 | return 224 | } 225 | 226 | w.Write([]byte(strings.Join(strings.Fields(fmt.Sprint(result.([]uint32))), ","))) 227 | } 228 | 229 | func (s *BasaltHttpServer) unionStore(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 230 | names := params.ByName("names") 231 | dst := params.ByName("dst") 232 | 233 | ns := []string { dst } 234 | ns = append(ns, strings.Split(names, ",")...) 235 | 236 | bd := &BasaltData{ 237 | Type: UnionStore, 238 | Names: ns, 239 | Values: nil, 240 | } 241 | 242 | s.doSyncPropose(bd, w) 243 | } 244 | 245 | func (s *BasaltHttpServer) diff(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 246 | name1 := params.ByName("name1") 247 | name2 := params.ByName("name2") 248 | 249 | bd := &BasaltData{ 250 | Type: Diff, 251 | Names: []string { name1, name2 }, 252 | Values: nil, 253 | } 254 | 255 | result := s.doSyncRead(bd) 256 | if _, ok := result.(error); ok { 257 | w.Write([]byte("OPERATION ERROR")) 258 | return 259 | } 260 | 261 | w.Write([]byte(strings.Join(strings.Fields(fmt.Sprint(result.([]uint32))), ","))) 262 | } 263 | 264 | func (s *BasaltHttpServer) diffStore(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 265 | name1 := params.ByName("name1") 266 | name2 := params.ByName("name2") 267 | dst := params.ByName("dst") 268 | 269 | bd := &BasaltData{ 270 | Type: DiffStore, 271 | Names: []string { dst, name1, name2 }, 272 | Values: nil, 273 | } 274 | 275 | s.doSyncPropose(bd, w) 276 | } 277 | 278 | func (s *BasaltHttpServer) xor(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 279 | name1 := params.ByName("name1") 280 | name2 := params.ByName("name2") 281 | 282 | bd := &BasaltData{ 283 | Type: Xor, 284 | Names: []string { name1, name2 }, 285 | Values: nil, 286 | } 287 | 288 | result := s.doSyncRead(bd) 289 | if _, ok := result.(error); ok { 290 | w.Write([]byte("OPERATION ERROR")) 291 | return 292 | } 293 | 294 | w.Write([]byte(strings.Join(strings.Fields(fmt.Sprint(result.([]uint32))), ","))) 295 | } 296 | 297 | func (s *BasaltHttpServer) xorStore(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 298 | name1 := params.ByName("name1") 299 | name2 := params.ByName("name2") 300 | dst := params.ByName("dst") 301 | 302 | bd := &BasaltData{ 303 | Type: XorStore, 304 | Names: []string { dst, name1, name2 }, 305 | Values: nil, 306 | } 307 | 308 | s.doSyncPropose(bd, w) 309 | } 310 | 311 | func (s *BasaltHttpServer) doSyncPropose(reqData *BasaltData, w http.ResponseWriter) { 312 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 313 | defer cancel() 314 | 315 | data, _ := json.Marshal(reqData) 316 | _, err := s.base.nh.SyncPropose(ctx, s.base.rs, data) 317 | if err != nil { 318 | log.Errorf("sync propose error: %v", err) 319 | 320 | w.Write([]byte("OPERATION ERROR")) 321 | return 322 | } 323 | 324 | w.Write([]byte("SUCCESS")) 325 | } 326 | 327 | func (s *BasaltHttpServer) doSyncRead(reqData *BasaltData) interface{} { 328 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 329 | defer cancel() 330 | 331 | data, _ := json.Marshal(reqData) 332 | result, err := s.base.nh.SyncRead(ctx, basaltClusterId, data) 333 | if err != nil { 334 | log.Errorf("sync read error: %v", err) 335 | return errors.New("sync read error") 336 | } 337 | 338 | return result 339 | } 340 | 341 | 342 | -------------------------------------------------------------------------------- /cmd/dboat_server/rpcx_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/smallnest/rpcx/client" 7 | "log" 8 | ) 9 | 10 | var ( 11 | addr = flag.String("addr", "127.0.0.1:38419", "the listened address") 12 | ) 13 | 14 | func main() { 15 | flag.Parse() 16 | 17 | d := client.NewPeer2PeerDiscovery("tcp@" + *addr, "") 18 | xclient := client.NewXClient("Bitmap", client.Failtry, client.RandomSelect, d, client.DefaultOption) 19 | defer xclient.Close() 20 | 21 | var ok bool 22 | xclient.Call(context.Background(), "Add", &BitmapValueRequest{"test2", 1}, &ok) 23 | xclient.Call(context.Background(), "AddMany", &BitmapValuesRequest{"test2", []uint32{2, 3, 10, 11}}, &ok) 24 | 25 | xclient.Call(context.Background(), "Add", &BitmapValueRequest{"test3", 1}, &ok) 26 | xclient.Call(context.Background(), "AddMany", &BitmapValuesRequest{"test3", []uint32{2, 3, 20, 21}}, &ok) 27 | 28 | var exist bool 29 | xclient.Call(context.Background(), "Exists", &BitmapValueRequest{"test2", 10}, &exist) 30 | if !exist { 31 | log.Fatalf("10 not found") 32 | } 33 | 34 | xclient.Call(context.Background(), "DiffStore", &BitmapDstAndPairRequest{"test4", "test2", "test3"}, &ok) 35 | xclient.Call(context.Background(), "Exists", &BitmapValueRequest{"test4", 10}, &exist) 36 | if !exist { 37 | log.Fatalf("10 not found") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/dboat_server/rpcx_common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // BitmapValueRequest contains the name of bitmap and value. 4 | type BitmapValueRequest struct { 5 | Name string 6 | Value uint32 7 | } 8 | 9 | // BitmapValuesRequest contains the name of bitmap and values. 10 | type BitmapValuesRequest struct { 11 | Name string 12 | Values []uint32 13 | } 14 | 15 | // BitmapStoreRequest contains the name of destination and names of bitmaps. 16 | type BitmapStoreRequest struct { 17 | Destination string 18 | Names []string 19 | } 20 | 21 | // BitmapPairRequest contains the name of two bitmaps. 22 | type BitmapPairRequest struct { 23 | Name1 string 24 | Name2 string 25 | } 26 | 27 | // BitmapDstAndPairRequest contains destination and the name of two bitmaps. 28 | type BitmapDstAndPairRequest struct { 29 | Destination string 30 | Name1 string 31 | Name2 string 32 | } 33 | -------------------------------------------------------------------------------- /cmd/dboat_server/rpcx_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "github.com/smallnest/log" 8 | "github.com/smallnest/rpcx/server" 9 | "time" 10 | ) 11 | 12 | type ConfigRpcxOption func(*BasaltServer, *server.Server) 13 | 14 | type BasaltRpcxServer struct { 15 | base *BasaltServer 16 | srv *server.Server 17 | } 18 | 19 | // Add adds a value in the bitmap with name. 20 | func (s *BasaltRpcxServer) Add(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 21 | bd := &BasaltData{ 22 | Type: Add, 23 | Names: []string { req.Name }, 24 | Values: []uint32 { uint32(req.Value) }, 25 | } 26 | 27 | err := s.doSyncPropose1(bd) 28 | 29 | *reply = true 30 | if err != nil { 31 | *reply = false 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // AddMany adds multiple values in the bitmap with name. 38 | func (s *BasaltRpcxServer) AddMany(ctx context.Context, req *BitmapValuesRequest, reply *bool) error { 39 | bd := &BasaltData{ 40 | Type: AddMany, 41 | Names: []string { req.Name }, 42 | Values: req.Values, 43 | } 44 | 45 | err := s.doSyncPropose1(bd) 46 | 47 | *reply = true 48 | if err != nil { 49 | *reply = false 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // Remove removes a value in the bitmap with name. 56 | func (s *BasaltRpcxServer) Remove(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 57 | bd := &BasaltData{ 58 | Type: Remove, 59 | Names: []string { req.Name }, 60 | Values: []uint32 { uint32(req.Value) }, 61 | } 62 | 63 | err := s.doSyncPropose1(bd) 64 | 65 | *reply = true 66 | if err != nil { 67 | *reply = false 68 | } 69 | 70 | return nil 71 | } 72 | 73 | // RemoveBitmap removes the bitmap. 74 | func (s *BasaltRpcxServer) RemoveBitmap(ctx context.Context, name string, reply *bool) error { 75 | bd := &BasaltData{ 76 | Type: Drop, 77 | Names: []string { name }, 78 | Values: nil, 79 | } 80 | 81 | err := s.doSyncPropose1(bd) 82 | 83 | *reply = true 84 | if err != nil { 85 | *reply = false 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // ClearBitmap clears the bitmap and set it to be empty. 92 | func (s *BasaltRpcxServer) ClearBitmap(ctx context.Context, name string, reply *bool) error { 93 | bd := &BasaltData{ 94 | Type: Clear, 95 | Names: []string { name }, 96 | Values: nil, 97 | } 98 | 99 | err := s.doSyncPropose1(bd) 100 | 101 | *reply = true 102 | if err != nil { 103 | *reply = false 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // Exists checks whether the value exists. 110 | func (s *BasaltRpcxServer) Exists(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 111 | bd := &BasaltData{ 112 | Type: Exists, 113 | Names: []string { req.Name }, 114 | Values: []uint32 { uint32(req.Value) }, 115 | } 116 | 117 | result := s.doSyncRead1(bd) 118 | if _, ok := result.(error); ok { 119 | *reply = false 120 | return nil 121 | } 122 | 123 | *reply = result.(bool) 124 | return nil 125 | } 126 | 127 | // Card gets number of integers in the bitmap. 128 | func (s *BasaltRpcxServer) Card(ctx context.Context, name string, reply *uint64) error { 129 | bd := &BasaltData{ 130 | Type: Card, 131 | Names: []string { name }, 132 | Values: nil, 133 | } 134 | 135 | result := s.doSyncRead1(bd) 136 | if _, ok := result.(error); ok { 137 | *reply = 0 138 | return nil 139 | } 140 | 141 | *reply = result.(uint64) 142 | return nil 143 | } 144 | 145 | // Inter gets the intersection of bitmaps. 146 | func (s *BasaltRpcxServer) Inter(ctx context.Context, names []string, reply *[]uint32) error { 147 | bd := &BasaltData{ 148 | Type: Inter, 149 | Names: names, 150 | Values: nil, 151 | } 152 | 153 | result := s.doSyncRead1(bd) 154 | if _, ok := result.(error); ok { 155 | *reply = nil 156 | return nil 157 | } 158 | 159 | *reply = result.([]uint32) 160 | return nil 161 | } 162 | 163 | // InterStore gets the intersection of bitmaps and stores into destination. 164 | func (s *BasaltRpcxServer) InterStore(ctx context.Context, req *BitmapStoreRequest, reply *bool) error { 165 | ns := []string { req.Destination } 166 | ns = append(ns, req.Names...) 167 | 168 | bd := &BasaltData{ 169 | Type: InterStore, 170 | Names: ns, 171 | Values: nil, 172 | } 173 | 174 | err := s.doSyncPropose1(bd) 175 | 176 | *reply = true 177 | if err != nil { 178 | *reply = false 179 | } 180 | 181 | return nil 182 | } 183 | 184 | // Union gets the union of bitmaps. 185 | func (s *BasaltRpcxServer) Union(ctx context.Context, names []string, reply *[]uint32) error { 186 | bd := &BasaltData{ 187 | Type: Union, 188 | Names: names, 189 | Values: nil, 190 | } 191 | 192 | result := s.doSyncRead1(bd) 193 | if _, ok := result.(error); ok { 194 | *reply = nil 195 | return nil 196 | } 197 | 198 | *reply = result.([]uint32) 199 | return nil 200 | } 201 | 202 | // UnionStore gets the union of bitmaps and stores into destination. 203 | func (s *BasaltRpcxServer) UnionStore(ctx context.Context, req *BitmapStoreRequest, reply *bool) error { 204 | ns := []string { req.Destination } 205 | ns = append(ns, req.Names...) 206 | 207 | bd := &BasaltData{ 208 | Type: UnionStore, 209 | Names: ns, 210 | Values: nil, 211 | } 212 | 213 | err := s.doSyncPropose1(bd) 214 | 215 | *reply = true 216 | if err != nil { 217 | *reply = false 218 | } 219 | 220 | return nil 221 | } 222 | 223 | // Xor gets the symmetric difference between bitmaps. 224 | func (s *BasaltRpcxServer) Xor(ctx context.Context, names *BitmapPairRequest, reply *[]uint32) error { 225 | bd := &BasaltData{ 226 | Type: Xor, 227 | Names: []string { names.Name1, names.Name2 }, 228 | Values: nil, 229 | } 230 | 231 | result := s.doSyncRead1(bd) 232 | if _, ok := result.(error); ok { 233 | *reply = nil 234 | return nil 235 | } 236 | 237 | *reply = result.([]uint32) 238 | return nil 239 | } 240 | 241 | // XorStore gets the symmetric difference between bitmaps and stores into destination. 242 | func (s *BasaltRpcxServer) XorStore(ctx context.Context, names *BitmapDstAndPairRequest, reply *bool) error { 243 | bd := &BasaltData{ 244 | Type: XorStore, 245 | Names: []string { names.Destination, names.Name1, names.Name2 }, 246 | Values: nil, 247 | } 248 | 249 | err := s.doSyncPropose1(bd) 250 | 251 | *reply = true 252 | if err != nil { 253 | *reply = false 254 | } 255 | 256 | return nil 257 | } 258 | 259 | // Diff gets the difference between two bitmaps. 260 | func (s *BasaltRpcxServer) Diff(ctx context.Context, names *BitmapPairRequest, reply *[]uint32) error { 261 | bd := &BasaltData{ 262 | Type: Diff, 263 | Names: []string { names.Name1, names.Name2 }, 264 | Values: nil, 265 | } 266 | 267 | result := s.doSyncRead1(bd) 268 | if _, ok := result.(error); ok { 269 | *reply = nil 270 | return nil 271 | } 272 | 273 | *reply = result.([]uint32) 274 | return nil 275 | } 276 | 277 | // DiffStore gets the difference between two bitmaps and stores into destination. 278 | func (s *BasaltRpcxServer) DiffStore(ctx context.Context, names *BitmapDstAndPairRequest, reply *bool) error { 279 | bd := &BasaltData{ 280 | Type: DiffStore, 281 | Names: []string { names.Destination, names.Name1, names.Name2 }, 282 | Values: nil, 283 | } 284 | 285 | err := s.doSyncPropose1(bd) 286 | 287 | *reply = true 288 | if err != nil { 289 | *reply = false 290 | } 291 | 292 | return nil 293 | } 294 | 295 | func (s *BasaltRpcxServer) doSyncPropose1(reqData *BasaltData) error { 296 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 297 | defer cancel() 298 | 299 | data, _ := json.Marshal(reqData) 300 | _, err := s.base.nh.SyncPropose(ctx, s.base.rs, data) 301 | if err != nil { 302 | log.Errorf("sync propose error: %v", err) 303 | } 304 | 305 | return err 306 | } 307 | 308 | func (s *BasaltRpcxServer) doSyncRead1(reqData *BasaltData) interface{} { 309 | ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 310 | defer cancel() 311 | 312 | data, _ := json.Marshal(reqData) 313 | result, err := s.base.nh.SyncRead(ctx, basaltClusterId, data) 314 | if err != nil { 315 | log.Errorf("sync read error: %v", err) 316 | return errors.New("sync read error") 317 | } 318 | 319 | return result 320 | } 321 | -------------------------------------------------------------------------------- /cmd/dboat_server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/lni/dragonboat/v3" 6 | "github.com/lni/dragonboat/v3/client" 7 | "github.com/smallnest/rpcx/protocol" 8 | "github.com/smallnest/rpcx/server" 9 | "github.com/soheilhy/cmux" 10 | "io" 11 | "net" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | type ReqType byte 17 | 18 | const ( 19 | Add ReqType = iota 20 | AddMany 21 | Remove 22 | Drop 23 | Clear 24 | Exists 25 | Card 26 | Inter 27 | InterStore 28 | Union 29 | UnionStore 30 | Xor 31 | XorStore 32 | Diff 33 | DiffStore 34 | ) 35 | 36 | type BasaltData struct { 37 | Type ReqType 38 | Names []string // for collection operations, use [dst, name1, name2, ...] 39 | Values []uint32 40 | } 41 | 42 | type BasaltServer struct { 43 | addr string 44 | nh *dragonboat.NodeHost 45 | rs *client.Session 46 | rpcxOpts []ConfigRpcxOption 47 | 48 | httpSrv *BasaltHttpServer 49 | rpcxSrv *BasaltRpcxServer 50 | } 51 | 52 | func NewServer(addr string, nh *dragonboat.NodeHost, rpcxOptions []ConfigRpcxOption) *BasaltServer { 53 | rs := nh.GetNoOPSession(basaltClusterId) 54 | 55 | return &BasaltServer{ 56 | addr: addr, 57 | nh: nh, 58 | rs: rs, 59 | rpcxOpts: rpcxOptions, 60 | } 61 | } 62 | 63 | func (s *BasaltServer) Serve() error { 64 | ln, err := net.Listen("tcp", s.addr) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | m := cmux.New(ln) 70 | 71 | // rpcx 72 | rln := m.Match(rpcxPrefixByteMatcher()) 73 | 74 | // http 75 | hln := m.Match(cmux.HTTP1Fast()) 76 | 77 | go s.startRpcxServer(rln) 78 | go s.startHttpServer(hln) 79 | 80 | return m.Serve() 81 | } 82 | 83 | func (s *BasaltServer) startRpcxServer(ln net.Listener) error { 84 | srv := server.NewServer() 85 | 86 | for _, opt := range s.rpcxOpts { 87 | opt(s, srv) 88 | } 89 | 90 | brs := &BasaltRpcxServer{ 91 | base: s, 92 | srv: srv, 93 | } 94 | 95 | srv.RegisterName("Bitmap", brs, "") 96 | s.rpcxSrv = brs 97 | 98 | return srv.ServeListener("tcp", ln) 99 | } 100 | 101 | func (s *BasaltServer) startHttpServer(ln net.Listener) error { 102 | srv := &http.Server{ 103 | ReadTimeout: 60 * time.Second, 104 | WriteTimeout: 60 * time.Second, 105 | } 106 | 107 | bhs := &BasaltHttpServer{ 108 | base: s, 109 | srv: srv, 110 | } 111 | 112 | bhs.initRouter() 113 | s.httpSrv = bhs 114 | 115 | return srv.Serve(ln) 116 | } 117 | 118 | func (s *BasaltServer) Close() { 119 | ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) 120 | defer cancel() 121 | 122 | s.nh.Stop() 123 | s.httpSrv.srv.Shutdown(ctx) 124 | s.rpcxSrv.srv.Close() 125 | } 126 | 127 | func rpcxPrefixByteMatcher() cmux.Matcher { 128 | magic := protocol.MagicNumber() 129 | 130 | return func(r io.Reader) bool { 131 | buf := make([]byte, 1) 132 | n, _ := r.Read(buf) 133 | 134 | return n == 1 && buf[0] == magic 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /cmd/dboat_server/statemachine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | sm "github.com/lni/dragonboat/v3/statemachine" 9 | "github.com/rpcxio/basalt" 10 | "io" 11 | ) 12 | 13 | type BasaltStateMachine struct { 14 | ClusterId uint64 15 | NodeId uint64 16 | Bitmaps *basalt.Bitmaps 17 | } 18 | 19 | func NewBasalStateMachine(clusterId, nodeId uint64) sm.IStateMachine { 20 | return &BasaltStateMachine{ 21 | ClusterId: clusterId, 22 | NodeId: nodeId, 23 | Bitmaps: basalt.NewBitmaps(), 24 | } 25 | } 26 | 27 | func CreateBasaltStateMachineHandler(bm *basalt.Bitmaps) func(uint64, uint64) sm.IStateMachine { 28 | bsm := &BasaltStateMachine{ 29 | Bitmaps: bm, 30 | } 31 | 32 | return func(clusterId, nodeId uint64) sm.IStateMachine { 33 | bsm.ClusterId = clusterId 34 | bsm.NodeId = nodeId 35 | 36 | return bsm 37 | } 38 | } 39 | 40 | func (bsm *BasaltStateMachine) Lookup(query interface{}) (interface{}, error) { 41 | var reqData BasaltData 42 | err := json.Unmarshal(query.([]byte), &reqData) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | switch reqData.Type { 48 | case Exists: 49 | return bsm.Bitmaps.Exists(reqData.Names[0], reqData.Values[0]), nil 50 | case Card: 51 | return bsm.Bitmaps.Card(reqData.Names[0]), nil 52 | case Inter: 53 | return bsm.Bitmaps.Inter(reqData.Names...), nil 54 | case Union: 55 | return bsm.Bitmaps.Union(reqData.Names...), nil 56 | case Xor: 57 | return bsm.Bitmaps.Xor(reqData.Names[0], reqData.Names[1]), nil 58 | case Diff: 59 | return bsm.Bitmaps.Diff(reqData.Names[0], reqData.Names[1]), nil 60 | } 61 | 62 | return nil, errors.New("invalid request type") 63 | } 64 | 65 | func (bsm *BasaltStateMachine) Update(data []byte) (sm.Result, error) { 66 | var reqData BasaltData 67 | err := json.Unmarshal(data, &reqData) 68 | if err != nil { 69 | return sm.Result{}, err 70 | } 71 | 72 | switch reqData.Type { 73 | case Add: 74 | bsm.Bitmaps.Add(reqData.Names[0], reqData.Values[0], false) 75 | case AddMany: 76 | bsm.Bitmaps.AddMany(reqData.Names[0], reqData.Values, false) 77 | case Remove: 78 | bsm.Bitmaps.Remove(reqData.Names[0], reqData.Values[0], false) 79 | case Drop: 80 | bsm.Bitmaps.RemoveBitmap(reqData.Names[0], false) 81 | case Clear: 82 | bsm.Bitmaps.ClearBitmap(reqData.Names[0], false) 83 | case InterStore: 84 | bsm.Bitmaps.InterStore(reqData.Names[0], reqData.Names[1:]...) 85 | case UnionStore: 86 | bsm.Bitmaps.UnionStore(reqData.Names[0], reqData.Names[1:]...) 87 | case XorStore: 88 | bsm.Bitmaps.XorStore(reqData.Names[0], reqData.Names[1], reqData.Names[2]) 89 | case DiffStore: 90 | bsm.Bitmaps.DiffStore(reqData.Names[0], reqData.Names[1], reqData.Names[2]) 91 | default: 92 | return sm.Result{}, errors.New("invalid request type") 93 | } 94 | 95 | return sm.Result{}, nil 96 | } 97 | 98 | func (bsm *BasaltStateMachine) SaveSnapshot(w io.Writer, fc sm.ISnapshotFileCollection, done <-chan struct{}) error { 99 | return bsm.Bitmaps.Save(w) 100 | } 101 | 102 | func (bsm *BasaltStateMachine) RecoverFromSnapshot(r io.Reader, files []sm.SnapshotFile, done <-chan struct{}) error { 103 | bm := basalt.NewBitmaps() 104 | 105 | if err := bm.Read(r); err != nil { 106 | return err 107 | } 108 | 109 | bsm.Bitmaps = bm 110 | return nil 111 | } 112 | 113 | func (bsm *BasaltStateMachine) Close() error { 114 | return nil 115 | } 116 | 117 | func (bsm *BasaltStateMachine) GetHash() (uint64, error) { 118 | h := md5.New() 119 | 120 | if err := bsm.Bitmaps.Save(h); err != nil { 121 | return 0, err 122 | } 123 | 124 | data := h.Sum(nil) 125 | return binary.LittleEndian.Uint64(data[:8]), nil 126 | } 127 | 128 | 129 | -------------------------------------------------------------------------------- /cmd/http_client/curl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | curl http://localhost:8972/add/test1/1 4 | curl http://localhost:8972/addmany/test1/2,3,10,11 5 | curl http://localhost:8972/addmany/test2/1,2,3,20,21 6 | curl http://localhost:8972/diffstore/test3/test1/test2 7 | curl http://localhost:8972/exists/test3/10 -------------------------------------------------------------------------------- /cmd/raft_server/README.md: -------------------------------------------------------------------------------- 1 | ## 分布式位图服务 2 | 3 | 基于raft的位图服务,可以搭建一个n个节点的raft集群,保证数据一致性。 4 | 5 | 位图服务是一个写少读多的服务,所以基于raft的实现可以满足性能的要求。 6 | 7 | 同时,考虑到位图服务的应用场景并不是严格强一致性的场景, 读操作并不基于raft的线性读或者lease read,而是保证最终一致性。 8 | 9 | 10 | ### 测试集群 11 | 12 | 以三个节点的集群为例: 13 | 14 | ``` 15 | basalt --id 1 --peers http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --addr :18972 --data bitmaps1.bdb 16 | basalt --id 2 --peers http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --addr :28972 --data bitmaps2.bdb 17 | basalt --id 3 --peers http://127.0.0.1:12379,http://127.0.0.1:22379,http://127.0.0.1:32379 --addr :38972 --data bitmaps3.bdb 18 | ``` 19 | 20 | 21 | 测试在第一个节点增加一个数据: 22 | ```sh 23 | basalt git:(master) ✗ curl -X POST "http://127.0.0.1:18972/add/test/1000" 24 | ``` 25 | 26 | 在第二个节点检查这个数据是否存在,正常应该返回`200 OK` 27 | ``` 28 | ➜ basalt git:(master) ✗ curl -v "http://127.0.0.1:28972/exists/test/1000" 29 | < HTTP/1.1 200 OK 30 | ``` -------------------------------------------------------------------------------- /cmd/raft_server/basalt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | "github.com/rpcxio/basalt" 10 | "github.com/rpcxio/etcd/raft/raftpb" 11 | ) 12 | 13 | var ( 14 | addr = flag.String("addr", ":18972", "the listened address") 15 | dataFile = flag.String("data", "bitmaps.bdb", "the persisted file") 16 | 17 | peers = flag.String("peers", "http://127.0.0.1:12379", "comma separated peers in a cluster") 18 | id = flag.Int("id", 1, "node ID") 19 | join = flag.Bool("join", false, "join an existing cluster") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | if _, err := os.Stat(*dataFile); os.IsNotExist(err) { 26 | f, err := os.Create(*dataFile) 27 | if err != nil { 28 | log.Fatalf("failed to create file %s: %v", *dataFile, err) 29 | } 30 | f.Close() 31 | } 32 | 33 | // bitmap 34 | bitmaps := basalt.NewBitmaps() 35 | srv := basalt.NewServer(*addr, bitmaps, nil, *dataFile) 36 | 37 | // raft 38 | proposeC := make(chan string) 39 | defer close(proposeC) 40 | confChangeC := make(chan raftpb.ConfChange) 41 | defer close(confChangeC) 42 | 43 | var raftServer *basalt.RaftServer 44 | getSnapshot := func() ([]byte, error) { return raftServer.GetSnapshot() } 45 | commitC, errorC, snapshotterReady := basalt.NewRaftNode(*id, strings.Split(*peers, ","), *join, getSnapshot, proposeC, confChangeC) 46 | 47 | raftServer = basalt.NewRaftServer(srv, <-snapshotterReady, confChangeC, proposeC, commitC, errorC) 48 | 49 | // set confchange handler 50 | srv.SetConfChangeCallback(raftServer) 51 | 52 | if err := srv.Serve(); err != nil { 53 | log.Fatalf("failed to start basalt services:%v", err) 54 | } 55 | srv.Close() 56 | } 57 | -------------------------------------------------------------------------------- /cmd/redis_client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/go-redis/redis" 8 | ) 9 | 10 | var ( 11 | addr = flag.String("addr", "127.0.0.1:8972", "the listened address") 12 | ) 13 | 14 | func main() { 15 | flag.Parse() 16 | 17 | client := redis.NewClient(&redis.Options{ 18 | Addr: *addr, 19 | }) 20 | 21 | _, err := client.Ping().Result() 22 | if err != nil { 23 | log.Fatalf("failed to ping-pong: %v", err) 24 | } 25 | 26 | _, err = client.Do("bmadd", "test1", 1).Result() // int64 27 | if err != nil { 28 | log.Fatalf("failed to bmadd: %v", err) 29 | } 30 | 31 | _, err = client.Do("bmaddmany", "test1", 2, 3, 10, 11).Result() // int64 32 | if err != nil { 33 | log.Fatalf("failed to bmaddmany: %v", err) 34 | } 35 | 36 | _, err = client.Do("bmaddmany", "test2", 1, 2, 3, 20, 21).Result() // int64 37 | if err != nil { 38 | log.Fatalf("failed to bmaddmany: %v", err) 39 | } 40 | 41 | _, err = client.Do("bmdiffstore", "test3", "test1", "test2").Result() // int64 42 | if err != nil { 43 | log.Fatalf("failed to bmaddmany: %v", err) 44 | } 45 | 46 | res, err := client.Do("bmexists", "test3", 10).Result() // int64 47 | if err != nil { 48 | log.Fatalf("failed to bmaddmany: %v", err) 49 | } 50 | if res.(int64) != 1 { 51 | log.Fatalf("expect exists but found none (0)") 52 | } 53 | 54 | res, err = client.Do("bmexists", "test3", 20).Result() // int64 55 | if err != nil { 56 | log.Fatalf("failed to bmaddmany: %v", err) 57 | } 58 | if res.(int64) != 0 { 59 | log.Fatalf("expect not found but found one (1)") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cmd/rpcx_client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/rpcxio/basalt" 9 | "github.com/smallnest/rpcx/client" 10 | ) 11 | 12 | var ( 13 | addr = flag.String("addr", "127.0.0.1:8972", "the listened address") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | 19 | d := client.NewPeer2PeerDiscovery("tcp@"+*addr, "") 20 | xclient := client.NewXClient("Bitmap", client.Failtry, client.RandomSelect, d, client.DefaultOption) 21 | defer xclient.Close() 22 | 23 | var ok bool 24 | 25 | xclient.Call(context.Background(), "Add", &basalt.BitmapValueRequest{"test1", 1}, &ok) 26 | xclient.Call(context.Background(), "AddMany", &basalt.BitmapValuesRequest{"test1", []uint32{2, 3, 10, 11}}, &ok) 27 | 28 | xclient.Call(context.Background(), "Add", &basalt.BitmapValueRequest{"test2", 1}, &ok) 29 | xclient.Call(context.Background(), "AddMany", &basalt.BitmapValuesRequest{"test2", []uint32{2, 3, 20, 21}}, &ok) 30 | 31 | var exist bool 32 | xclient.Call(context.Background(), "Exists", &basalt.BitmapValueRequest{"test1", 10}, &exist) 33 | if !exist { 34 | log.Fatalf("10 not found") 35 | } 36 | 37 | xclient.Call(context.Background(), "DiffStore", &basalt.BitmapDstAndPairRequest{"test3", "test1", "test2"}, &ok) 38 | xclient.Call(context.Background(), "Exists", &basalt.BitmapValueRequest{"test3", 10}, &exist) 39 | if !exist { 40 | log.Fatalf("10 not found") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cmd/server/basalt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | 8 | "github.com/rpcxio/basalt" 9 | ) 10 | 11 | var ( 12 | addr = flag.String("addr", ":8972", "the listened address") 13 | dataFile = flag.String("data", "bitmaps.bdb", "the persisted file") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | 19 | if _, err := os.Stat(*dataFile); os.IsNotExist(err) { 20 | f, err := os.Create(*dataFile) 21 | if err != nil { 22 | log.Fatalf("failed to create file %s: %v", *dataFile, err) 23 | } 24 | f.Close() 25 | } 26 | 27 | bitmaps := basalt.NewBitmaps() 28 | 29 | srv := basalt.NewServer(*addr, bitmaps, nil, *dataFile) 30 | err := srv.Restore() 31 | if err != nil { 32 | log.Fatalf("failed to start basalt services:%v", err) 33 | } else { 34 | log.Printf("succeeded to restore bitmaps from %s", *dataFile) 35 | } 36 | 37 | if err := srv.Serve(); err != nil { 38 | log.Fatalf("failed to start basalt services:%v", err) 39 | } 40 | srv.Close() 41 | } 42 | -------------------------------------------------------------------------------- /examples/basalt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jayn1985/basalt/3724409ef4b48ee2d98e4bc3b4b5cd7050cd6ebd/examples/basalt.jpg -------------------------------------------------------------------------------- /examples/weibo/README.md: -------------------------------------------------------------------------------- 1 | ## 新浪微博数据集MicroblogPCU 2 | 3 | [MicroblogPCU](https://archive.ics.uci.edu/ml/machine-learning-databases/00323/)是数据集原作者从新浪微博采集到的。原本被用于[研究机器学习方法和社会关系研究](https://archive.ics.uci.edu/ml/datasets/microblogPCU)。 4 | 5 | 这个数据集被原作者用于探索微博中的spammers(发送垃圾信息的人)。他们的demo在[这里](http://sd.skyclass.net:8080/Spammer/dia.jsp)。 6 | 7 | 我们解析`follower_followee.csv`(关注者-被关注者关系), 以`follower_id`-`followee_id`作为key进行hash,然后将输入放入`follow` Bitmap中。 8 | 9 | 随后找一些ID看看是否有关注关系。 10 | 11 | 12 | 你需要解压[microblogPCU数据集](https://archive.ics.uci.edu/ml/machine-learning-databases/00323/microblogPCU.zip),将其中的`follower_followee.csv`文件复制到本文件夹。 13 | 14 | 15 | ### 运行 16 | 17 | 1、 首先运行`bitmap`服务 18 | 19 | 到 `cmd/server`下运行 `go run basalt.go` 20 | 21 | 2、运行测试程序 22 | 23 | 到本文件夹(`examples/weibo`)下运行 `go run follow.go`。 24 | 25 | > Notice: 确保已复制`follower_followee.csv`到本文件夹 -------------------------------------------------------------------------------- /examples/weibo/follow.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "hash/fnv" 8 | "log" 9 | "os" 10 | "strings" 11 | 12 | "github.com/go-redis/redis" 13 | ) 14 | 15 | var ( 16 | addr = flag.String("addr", "127.0.0.1:8972", "the listened address") 17 | importData = flag.Bool("import-data", true, "need to import data") 18 | ) 19 | 20 | var x = fnv.New32() 21 | 22 | var names = make(map[string]string) 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | client := redis.NewClient(&redis.Options{ 28 | Addr: *addr, 29 | }) 30 | 31 | importFollowCsv(client) 32 | fmt.Println("import succeeded") 33 | 34 | // test 35 | followee_id := "1640571365" // 罗永浩 36 | follower_id := "1766187712" // 天天动听 37 | v := hash(follower_id + "-" + followee_id) 38 | if exists(client, v) { 39 | log.Printf("%s 关注了 %s", names[follower_id], names[followee_id]) 40 | } else { 41 | log.Printf("%s 没有关注 %s", names[follower_id], names[followee_id]) 42 | } 43 | 44 | follower_id = "1618051664" // 头条新闻 45 | v = hash(follower_id + "-" + followee_id) 46 | if exists(client, v) { 47 | log.Printf("%s 关注了 %s", names[follower_id], names[followee_id]) 48 | } else { 49 | log.Printf("%s 没有关注 %s", names[follower_id], names[followee_id]) 50 | } 51 | 52 | followee_id = "1618051664" // 头条新闻 53 | follower_id = "1640571365" // 罗永浩 54 | v = hash(follower_id + "-" + followee_id) 55 | if exists(client, v) { 56 | log.Printf("%s 关注了 %s", names[follower_id], names[followee_id]) 57 | } else { 58 | log.Printf("%s 没有关注 %s", names[follower_id], names[followee_id]) 59 | } 60 | 61 | // 检查互相关注 62 | checkFollowEachOther(client) 63 | } 64 | 65 | func exists(client *redis.Client, v uint32) bool { 66 | res, err := client.Do("bmadd", "follow", v).Result() 67 | if err != nil { 68 | return false 69 | } 70 | if rt, ok := res.(int64); !ok || rt == 1 { 71 | return true 72 | } 73 | return false 74 | } 75 | 76 | func hash(s string) uint32 { 77 | x.Reset() 78 | x.Write([]byte(s)) 79 | return x.Sum32() 80 | } 81 | 82 | func importFollowCsv(client *redis.Client) { 83 | file, err := os.Open("follower_followee.csv") 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | defer file.Close() 88 | 89 | scanner := bufio.NewScanner(file) 90 | for scanner.Scan() { 91 | items := strings.Split(scanner.Text(), ",") 92 | key := items[2] + "-" + items[4] 93 | v := hash(key) 94 | 95 | names[items[2]] = items[1] 96 | names[items[4]] = items[3] 97 | 98 | if *importData { 99 | res, err := client.Do("bmadd", "follow", v).Result() 100 | if err != nil { 101 | log.Printf("failed to bmadd %s: %v", key, err) 102 | } 103 | if rt, ok := res.(int64); !ok || rt != 1 { 104 | log.Printf("failed to bmadd %s because the result is %v", key, res) 105 | } 106 | } 107 | 108 | } 109 | 110 | if err := scanner.Err(); err != nil { 111 | log.Fatal(err) 112 | } 113 | } 114 | 115 | func checkFollowEachOther(client *redis.Client) { 116 | file, err := os.Open("follower_followee.csv") 117 | if err != nil { 118 | log.Fatal(err) 119 | } 120 | defer file.Close() 121 | 122 | scanner := bufio.NewScanner(file) 123 | for scanner.Scan() { 124 | items := strings.Split(scanner.Text(), ",") 125 | key := items[4] + "-" + items[2] 126 | v := hash(key) 127 | if exists(client, v) { 128 | log.Printf("%s: %s 和 %s 互相关注", items[0], names[items[2]], names[items[4]]) 129 | } 130 | } 131 | 132 | if err := scanner.Err(); err != nil { 133 | log.Fatal(err) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rpcxio/basalt 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/RoaringBitmap/roaring v0.4.21 7 | github.com/go-redis/redis v6.15.7+incompatible 8 | github.com/julienschmidt/httprouter v1.3.0 9 | github.com/lni/dragonboat/v3 v3.2.3 10 | github.com/rpcxio/etcd v0.0.0-20200729120139-f9cde972fd94 11 | github.com/smallnest/log v0.0.0-20190128090703-5dc5752d8772 12 | github.com/smallnest/rpcx v0.0.0-20200213044823-78d7a4d32e2a 13 | github.com/soheilhy/cmux v0.1.4 14 | github.com/tidwall/redcon v1.2.1 15 | go.uber.org/zap v1.14.1 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 5 | github.com/ChimeraCoder/gojson v1.1.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw= 6 | github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 7 | github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= 8 | github.com/RoaringBitmap/roaring v0.4.21 h1:WJ/zIlNX4wQZ9x8Ey33O1UaD9TCTakYsdLFSBcTwH+8= 9 | github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= 10 | github.com/VictoriaMetrics/metrics v1.6.2 h1:VMe8c8ZBPgNVZkPoT06LsoU2nb+8e7iPaOWbVRNhxjo= 11 | github.com/VictoriaMetrics/metrics v1.6.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ= 12 | github.com/abronan/valkeyrie v0.0.0-20190822142731-f2e1850dc905 h1:JG0OqQLCILn6ywoXJncu+/MFTTapP3aIIDDqB593HMc= 13 | github.com/abronan/valkeyrie v0.0.0-20190822142731-f2e1850dc905/go.mod h1:hTreU6x9m2IP2h8e0TGrSzAXSCI3lxic8/JT5CMknjY= 14 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 15 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 16 | github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191122014323-8cc8f217b23a h1:Y7Aia4DpAF6Kr7RH7cquqiQ+7xjOQdkcR/FalKyjd6w= 17 | github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20191122014323-8cc8f217b23a/go.mod h1:mNZkuqaeM5UCiAdkV4r+lrheu8Q5fe/487bRFrGYZ8A= 18 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 19 | github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 20 | github.com/anacrolix/envpprof v1.0.1 h1:lShFeOuHFuzLAfyP6WplWvIfHKmbKu1u9/rDOtcFGX4= 21 | github.com/anacrolix/envpprof v1.0.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= 22 | github.com/anacrolix/log v0.3.0 h1:Btxh7GkT4JYWvWJ1uKOwgobf+7q/1eFQaDdCUXCtssw= 23 | github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= 24 | github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s= 25 | github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= 26 | github.com/anacrolix/missinggo v1.2.1 h1:0IE3TqX5y5D0IxeMwTyIgqdDew4QrzcXaaEnJQyjHvw= 27 | github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= 28 | github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= 29 | github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= 30 | github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778 h1:XpCDEixzXOB8yaTW/4YBzKrJdMcFI0DzpPTYNv75wzk= 31 | github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk= 32 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= 33 | github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572 h1:kpt6TQTVi6gognY+svubHfxxpq0DLU9AfTQyZVc3UOc= 34 | github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= 35 | github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs= 36 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 37 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 38 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 39 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= 40 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= 41 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 42 | github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 43 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 44 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 45 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 46 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 47 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 48 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 49 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0= 50 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 51 | github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e h1:oJCXMss/3rg5F6Poy9wG3JQusc58Mzk5B9Z6wSnssNE= 52 | github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e/go.mod h1:errmMKH8tTB49UR2A8C8DPYkyudelsYJwJFaZHQ6ik8= 53 | github.com/cenk/backoff v2.2.1+incompatible h1:djdFT7f4gF2ttuzRKPbMOWgZajgesItGLwG5FTQKmmE= 54 | github.com/cenk/backoff v2.2.1+incompatible/go.mod h1:7FtoeaSnHoZnmZzz47cM35Y9nSW7tNyaidugnHTaFDE= 55 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 56 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 57 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 58 | github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= 59 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 60 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 61 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 62 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 63 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= 64 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 65 | github.com/cockroachdb/pebble v0.0.0-20200121195918-c23353d37a7f/go.mod h1:97dSg7Ku6fZIyYdu6XUrh31SaKMdnSUN3aDW2Fi8Zp8= 66 | github.com/cockroachdb/pebble v0.0.0-20200219202912-046831eaec09 h1:7HiB3BKLC9KPolCA4qdHLv0oEuOcjzPfVHnW2bP3jV0= 67 | github.com/cockroachdb/pebble v0.0.0-20200219202912-046831eaec09/go.mod h1:97dSg7Ku6fZIyYdu6XUrh31SaKMdnSUN3aDW2Fi8Zp8= 68 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 69 | github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= 70 | github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 71 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 72 | github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo= 73 | github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 74 | github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= 75 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 76 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= 77 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 78 | github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= 79 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= 80 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 81 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 82 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 83 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 84 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 85 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 86 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 87 | github.com/dgryski/go-jump v0.0.0-20170409065014-e1f439676b57 h1:qZNIK8jjHgLFHAW2wzCWPEv0ZIgcBhU7X3oDt/p3Sv0= 88 | github.com/dgryski/go-jump v0.0.0-20170409065014-e1f439676b57/go.mod h1:4hKCXuwrJoYvHZxJ86+bRVTOMyJ0Ej+RqfSm8mHi6KA= 89 | github.com/docker/libkv v0.2.1 h1:PNXYaftMVCFS5CmnDtDWTg3wbBO61Q/cEo3KX1oKxto= 90 | github.com/docker/libkv v0.2.1/go.mod h1:r5hEwHwW8dr0TFBYGCarMNbrQOiwL1xoqDYZ/JqoTK0= 91 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 92 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 93 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e h1:Fw7ZmgiklsLh5EQWyHh1sumKSCG1+yjEctIpGKib87s= 94 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 95 | github.com/edwingeng/doublejump v0.0.0-20190102103700-461a0155c7be h1:FnUE/uuuegwvhGE9z61q9krL5km5Mnwlusq3BT06yy8= 96 | github.com/edwingeng/doublejump v0.0.0-20190102103700-461a0155c7be/go.mod h1:sqbHCF7b7eMiCtiwNY5+2bqhT+Zx6Duj2VU5WigITOQ= 97 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 98 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 99 | github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE= 100 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= 101 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= 102 | github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= 103 | github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= 104 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 105 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 106 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 107 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 108 | github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= 109 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 110 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 111 | github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= 112 | github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 113 | github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 114 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= 115 | github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 116 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= 117 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 118 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 119 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 120 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 121 | github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= 122 | github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 123 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 124 | github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 125 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 126 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 127 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 128 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 129 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 130 | github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= 131 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 132 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 133 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 134 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= 135 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 136 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 137 | github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= 138 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 139 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 140 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 141 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 142 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 143 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 144 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 145 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 146 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 147 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 148 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 149 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 150 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 151 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 152 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 153 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 154 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 155 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 156 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 157 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 158 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 159 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 160 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 161 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 162 | github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= 163 | github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 164 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 165 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 166 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 167 | github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c h1:svzQzfVE9t7Y1CGULS5PsMWs4/H4Au/ZTJzU/0CKgqc= 168 | github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c/go.mod h1:YjKB0WsLXlMkO9p+wGTCoPIDGRJH0mz7E526PxkQVxI= 169 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= 170 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 171 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= 172 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 173 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 174 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 175 | github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= 176 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 177 | github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= 178 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 179 | github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= 180 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 181 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 182 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 183 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 184 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 185 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 186 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 187 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 188 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 189 | github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= 190 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 191 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 192 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 193 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 194 | github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= 195 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 196 | github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= 197 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 198 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 199 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 200 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 201 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 202 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 203 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 204 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 205 | github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= 206 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 207 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 208 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 209 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 210 | github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs= 211 | github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 212 | github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= 213 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 214 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 215 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 216 | github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk= 217 | github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= 218 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 219 | github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e h1:txQltCyjXAqVVSZDArPEhUTg35hKwVIuXwtQo7eAMNQ= 220 | github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= 221 | github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= 222 | github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= 223 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 224 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 225 | github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= 226 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 227 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 228 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 229 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 230 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 231 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 232 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 233 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 234 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 235 | github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= 236 | github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= 237 | github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441 h1:b5Jqi7ir58EzfeZDyp7OSYQG/IVgyY4JWfHuJUF2AZI= 238 | github.com/juju/ratelimit v1.0.2-0.20191002062651-f60b32039441/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= 239 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 240 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 241 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 242 | github.com/kavu/go_reuseport v1.4.0 h1:YIp/96RZ3sJfn0LN+FFkkXIq3H3dfVOdRUtNejhDcxc= 243 | github.com/kavu/go_reuseport v1.4.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 244 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 245 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 246 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 247 | github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= 248 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 249 | github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18= 250 | github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= 251 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 252 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 253 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 254 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 255 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 256 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 257 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 258 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 259 | github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= 260 | github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= 261 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= 262 | github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= 263 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= 264 | github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= 265 | github.com/lni/dragonboat/v3 v3.2.3 h1:MzW2MhYkkwitLfV0UNbv9bTQ4cDCaFoBBNY8TpkxKg4= 266 | github.com/lni/dragonboat/v3 v3.2.3/go.mod h1:GuPAs/bgs2IWrdb1/b5sYdsdY8aZl8p0lQiFekcwdqM= 267 | github.com/lni/goutils v1.1.0 h1:dnLcvFyaJ3Wt1YkcFUOd2Vxq+E+ioGW1HVJtDLseC4U= 268 | github.com/lni/goutils v1.1.0/go.mod h1:NDD+zR5cMNH2OsJj+r/X7zBSzBlmxytUUot268gtOOw= 269 | github.com/lucas-clemente/quic-go v0.11.0 h1:R7uxGrBWWSp817cdhkrunFsOA26vadf4EI9slWzkjlQ= 270 | github.com/lucas-clemente/quic-go v0.11.0/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= 271 | github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA= 272 | github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= 273 | github.com/marten-seemann/quic-conn v0.0.0-20190827120552-a06e62da55b7 h1:LHBrfRHvL1gANdnSvVXeEr79pJNj5H3jmODN1jeDjlI= 274 | github.com/marten-seemann/quic-conn v0.0.0-20190827120552-a06e62da55b7/go.mod h1:BTUDloYEkSYt9Yf98rSVDnIFrSJc04H3/6FfE+lsjNg= 275 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 276 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 277 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 278 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 279 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 280 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 281 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 282 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 283 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 284 | github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= 285 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 286 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 287 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 288 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 289 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 290 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 291 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 292 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 293 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 294 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 295 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 296 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 297 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 298 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 299 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 300 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 301 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 302 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= 303 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= 304 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 305 | github.com/nacos-group/nacos-sdk-go v0.0.0-20190820112454-5245ea3cded6 h1:vsvC4X/YjNRjgrdbFz1E6r7vybD+ifxJDt66l9B5ioU= 306 | github.com/nacos-group/nacos-sdk-go v0.0.0-20190820112454-5245ea3cded6/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo= 307 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 308 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 309 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 310 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 311 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 312 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 313 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 314 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 315 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 316 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 317 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 318 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 319 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 320 | github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea h1:sKwxy1H95npauwu8vtF95vG/syrL0p8fSZo/XlDg5gk= 321 | github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg= 322 | github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= 323 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 324 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 325 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 326 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 327 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 328 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 329 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 330 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 331 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 332 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 333 | github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= 334 | github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= 335 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 336 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 337 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= 338 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 339 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 340 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 341 | github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= 342 | github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= 343 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 344 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 345 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 346 | github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= 347 | github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= 348 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= 349 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 350 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 351 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 352 | github.com/rpcxio/etcd v0.0.0-20200729120139-f9cde972fd94 h1:Pq1pI8mmLiW2HPRqJfcv7/dGA5hZWVop5xBJAeeEl0Y= 353 | github.com/rpcxio/etcd v0.0.0-20200729120139-f9cde972fd94/go.mod h1:GC8AxLqga6U6GfKopaj+T+Wbb3jw33Clt4zB71DibGQ= 354 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 355 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 356 | github.com/rubyist/circuitbreaker v2.2.1+incompatible h1:KUKd/pV8Geg77+8LNDwdow6rVCAYOp8+kHUyFvL6Mhk= 357 | github.com/rubyist/circuitbreaker v2.2.1+incompatible/go.mod h1:Ycs3JgJADPuzJDwffe12k6BZT8hxVi6lFK+gWYJLN4A= 358 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 359 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= 360 | github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= 361 | github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 362 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 363 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 364 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 365 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 366 | github.com/serialx/hashring v0.0.0-20180504054112-49a4782e9908/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= 367 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 368 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 369 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 370 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 371 | github.com/smallnest/libkv-etcdv3-store v0.0.0-20191101045330-f92940446965 h1:YQtdLz+7JQdKn7f5cG+xSrSbI7X4jObx0Jy6ZzffGew= 372 | github.com/smallnest/libkv-etcdv3-store v0.0.0-20191101045330-f92940446965/go.mod h1:SYR75Tf6jJoZ8wZca3fqyzXorUyOwnorUYIKv4T+u9Q= 373 | github.com/smallnest/log v0.0.0-20190128090703-5dc5752d8772 h1:jJZt9z9XNMhBc/GwKYYIsuFe32H/RgZoQdiNMWSlqg8= 374 | github.com/smallnest/log v0.0.0-20190128090703-5dc5752d8772/go.mod h1:TpyTiodXNHQQOupXebpINKWbiufMLzuDrc4rZfyx4Ls= 375 | github.com/smallnest/rpcx v0.0.0-20200213044823-78d7a4d32e2a h1:gmwx7GjtIu8aksSHDRZ5EfcRCO1LAt+AysgG0LfOMgo= 376 | github.com/smallnest/rpcx v0.0.0-20200213044823-78d7a4d32e2a/go.mod h1:r+vj6jQHuO049V0uVSzDlxtSoxpoOUqCYBNtk6lyZmc= 377 | github.com/smallnest/valkeyrie v0.0.0-20191030064635-54a884e4b303 h1:NDOAHb1sE8pYWd0Dge8W6bGQ63FHfa0/QjClXG2hrgw= 378 | github.com/smallnest/valkeyrie v0.0.0-20191030064635-54a884e4b303/go.mod h1:ixoXqhXnCjUFM17O2cOVoJ98KzDnCb2+iGtJDmtC/qs= 379 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 380 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 381 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 382 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= 383 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 384 | github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= 385 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 386 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 387 | github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= 388 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 389 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 390 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 391 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 392 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 393 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 394 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 395 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 396 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 397 | github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e h1:nt2877sKfojlHCTOBXbpWjBkuWKritFaGIfgQwbQUls= 398 | github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e/go.mod h1:B4+Kq1u5FlULTjFSM707Q6e/cOHFv0z/6QRoxubDIQ8= 399 | github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= 400 | github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= 401 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 402 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 403 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM= 404 | github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 405 | github.com/tidwall/redcon v1.2.1 h1:k34E1ROcdCvL9Ks7R40ycLApPABbWuXplFHQxuqzn80= 406 | github.com/tidwall/redcon v1.2.1/go.mod h1:bdYBm4rlcWpst2XMwKVzWDF9CoUxEbUmM7CQrKeOZas= 407 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 408 | github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= 409 | github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 410 | github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= 411 | github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= 412 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 413 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= 414 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 415 | github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= 416 | github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= 417 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 418 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 419 | github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI= 420 | github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= 421 | github.com/valyala/histogram v1.0.1 h1:FzA7n2Tz/wKRMejgu3PV1vw3htAklTjjuoI6z3d4KDg= 422 | github.com/valyala/histogram v1.0.1/go.mod h1:lQy0xA4wUz2+IUnf97SivorsJIp8FxsnRd6x25q7Mto= 423 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= 424 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 425 | github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 426 | github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= 427 | github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 428 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 429 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 430 | github.com/xtaci/kcp-go v5.4.4+incompatible h1:QIJ0a0Q0N1G20yLHL2+fpdzyy2v/Cb3PI+xiwx/KK9c= 431 | github.com/xtaci/kcp-go v5.4.4+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 432 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= 433 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= 434 | go.etcd.io/bbolt v1.3.1-etcd.8 h1:6J7QAKqfFBGnU80KRnuQxfjjeE5xAGE/qB810I3FQHQ= 435 | go.etcd.io/bbolt v1.3.1-etcd.8/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 436 | go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= 437 | go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 438 | go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= 439 | go.etcd.io/etcd v3.3.17+incompatible h1:g8iRku1SID8QAW8cDlV0L/PkZlw63LSiYEHYHoE6j/s= 440 | go.etcd.io/etcd v3.3.17+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= 441 | go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= 442 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 443 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 444 | go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= 445 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 446 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 447 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 448 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 449 | go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= 450 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 451 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 452 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 453 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 454 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 455 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 456 | go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= 457 | go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 458 | go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= 459 | go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 460 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 461 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 462 | golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 463 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 464 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 465 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 466 | golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 467 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= 468 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 469 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 470 | golang.org/x/exp v0.0.0-20190426190305-956cc1757749 h1:Bduxdpx1O6126WsH6F6NwKywZ/FPncphlTduoPxFG78= 471 | golang.org/x/exp v0.0.0-20190426190305-956cc1757749/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 472 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 473 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 474 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 475 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 476 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 477 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 478 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 479 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 480 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 481 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 482 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 483 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 484 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 485 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 486 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 487 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 488 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 489 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 490 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 491 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 492 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 493 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 494 | golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= 495 | golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 496 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 497 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 498 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 499 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 500 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 501 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 502 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 503 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 504 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 505 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 506 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 507 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 508 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 509 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 510 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 511 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 512 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 513 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 514 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 515 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 516 | golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 517 | golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 518 | golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= 519 | golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 520 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= 521 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 522 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 523 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 524 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 525 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 526 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 527 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 528 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 529 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 530 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 531 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 532 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 533 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 534 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 535 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 536 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 537 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 538 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 539 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 540 | golang.org/x/tools v0.0.0-20191031220737-6d8f1af9ccc0 h1:+o3suEKE/4hCUt6qjV8SDcVZhz2dO8UWlHliCa+4bvg= 541 | golang.org/x/tools v0.0.0-20191031220737-6d8f1af9ccc0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 542 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 543 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 544 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 545 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 546 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 547 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 548 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 549 | google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= 550 | google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 551 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 552 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 553 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 554 | google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= 555 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 556 | google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= 557 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 558 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 559 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 560 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 561 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 562 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 563 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 564 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 565 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 566 | gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= 567 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 568 | gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= 569 | gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= 570 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 571 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 572 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 573 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 574 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 575 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 576 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 577 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 578 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 579 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 580 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 581 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 582 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 583 | -------------------------------------------------------------------------------- /listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The etcd Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package basalt 16 | 17 | import ( 18 | "errors" 19 | "net" 20 | "time" 21 | ) 22 | 23 | // stoppableListener sets TCP keep-alive timeouts on accepted 24 | // connections and waits on stopc message 25 | type stoppableListener struct { 26 | *net.TCPListener 27 | stopc <-chan struct{} 28 | } 29 | 30 | func newStoppableListener(addr string, stopc <-chan struct{}) (*stoppableListener, error) { 31 | ln, err := net.Listen("tcp", addr) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &stoppableListener{ln.(*net.TCPListener), stopc}, nil 36 | } 37 | 38 | func (ln stoppableListener) Accept() (c net.Conn, err error) { 39 | connc := make(chan *net.TCPConn, 1) 40 | errc := make(chan error, 1) 41 | go func() { 42 | tc, err := ln.AcceptTCP() 43 | if err != nil { 44 | errc <- err 45 | return 46 | } 47 | connc <- tc 48 | }() 49 | select { 50 | case <-ln.stopc: 51 | return nil, errors.New("server stopped") 52 | case err := <-errc: 53 | return nil, err 54 | case tc := <-connc: 55 | tc.SetKeepAlive(true) 56 | tc.SetKeepAlivePeriod(3 * time.Minute) 57 | return tc, nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /raft.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The etcd Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package basalt 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "log" 21 | "net/http" 22 | "net/url" 23 | "os" 24 | "strconv" 25 | "time" 26 | 27 | "github.com/rpcxio/etcd/etcdserver/api/rafthttp" 28 | "github.com/rpcxio/etcd/etcdserver/api/snap" 29 | stats "github.com/rpcxio/etcd/etcdserver/api/v2stats" 30 | "github.com/rpcxio/etcd/pkg/fileutil" 31 | "github.com/rpcxio/etcd/pkg/types" 32 | "github.com/rpcxio/etcd/raft" 33 | "github.com/rpcxio/etcd/raft/raftpb" 34 | "github.com/rpcxio/etcd/wal" 35 | "github.com/rpcxio/etcd/wal/walpb" 36 | 37 | "go.uber.org/zap" 38 | ) 39 | 40 | // A key-value stream backed by raft 41 | type raftNode struct { 42 | proposeC <-chan string // proposed messages (k,v) 43 | confChangeC <-chan raftpb.ConfChange // proposed cluster config changes 44 | commitC chan<- *string // entries committed to log (k,v) 45 | errorC chan<- error // errors from raft session 46 | 47 | id int // client ID for raft session 48 | peers []string // raft peer URLs 49 | join bool // node is joining an existing cluster 50 | waldir string // path to WAL directory 51 | snapdir string // path to snapshot directory 52 | getSnapshot func() ([]byte, error) 53 | lastIndex uint64 // index of log at start 54 | 55 | confState raftpb.ConfState 56 | snapshotIndex uint64 57 | appliedIndex uint64 58 | 59 | // raft backing for the commit/error channel 60 | node raft.Node 61 | raftStorage *raft.MemoryStorage 62 | wal *wal.WAL 63 | 64 | snapshotter *snap.Snapshotter 65 | snapshotterReady chan *snap.Snapshotter // signals when snapshotter is ready 66 | 67 | snapCount uint64 68 | transport *rafthttp.Transport 69 | stopc chan struct{} // signals proposal channel closed 70 | httpstopc chan struct{} // signals http server to shutdown 71 | httpdonec chan struct{} // signals http server shutdown complete 72 | } 73 | 74 | var defaultSnapshotCount uint64 = 10000 75 | 76 | // newRaftNode initiates a raft instance and returns a committed log entry 77 | // channel and error channel. Proposals for log updates are sent over the 78 | // provided the proposal channel. All log entries are replayed over the 79 | // commit channel, followed by a nil message (to indicate the channel is 80 | // current), then new log entries. To shutdown, close proposeC and read errorC. 81 | func NewRaftNode(id int, peers []string, join bool, getSnapshot func() ([]byte, error), proposeC <-chan string, 82 | confChangeC <-chan raftpb.ConfChange) (<-chan *string, <-chan error, <-chan *snap.Snapshotter) { 83 | 84 | commitC := make(chan *string) 85 | errorC := make(chan error) 86 | 87 | rc := &raftNode{ 88 | proposeC: proposeC, 89 | confChangeC: confChangeC, 90 | commitC: commitC, 91 | errorC: errorC, 92 | id: id, 93 | peers: peers, 94 | join: join, 95 | waldir: fmt.Sprintf("raftexample-%d", id), 96 | snapdir: fmt.Sprintf("raftexample-%d-snap", id), 97 | getSnapshot: getSnapshot, 98 | snapCount: defaultSnapshotCount, 99 | stopc: make(chan struct{}), 100 | httpstopc: make(chan struct{}), 101 | httpdonec: make(chan struct{}), 102 | 103 | snapshotterReady: make(chan *snap.Snapshotter, 1), 104 | // rest of structure populated after WAL replay 105 | } 106 | go rc.startRaft() 107 | return commitC, errorC, rc.snapshotterReady 108 | } 109 | 110 | func (rc *raftNode) saveSnap(snap raftpb.Snapshot) error { 111 | // must save the snapshot index to the WAL before saving the 112 | // snapshot to maintain the invariant that we only Open the 113 | // wal at previously-saved snapshot indexes. 114 | walSnap := walpb.Snapshot{ 115 | Index: snap.Metadata.Index, 116 | Term: snap.Metadata.Term, 117 | } 118 | if err := rc.wal.SaveSnapshot(walSnap); err != nil { 119 | return err 120 | } 121 | if err := rc.snapshotter.SaveSnap(snap); err != nil { 122 | return err 123 | } 124 | return rc.wal.ReleaseLockTo(snap.Metadata.Index) 125 | } 126 | 127 | func (rc *raftNode) entriesToApply(ents []raftpb.Entry) (nents []raftpb.Entry) { 128 | if len(ents) == 0 { 129 | return ents 130 | } 131 | firstIdx := ents[0].Index 132 | if firstIdx > rc.appliedIndex+1 { 133 | log.Fatalf("first index of committed entry[%d] should <= progress.appliedIndex[%d]+1", firstIdx, rc.appliedIndex) 134 | } 135 | if rc.appliedIndex-firstIdx+1 < uint64(len(ents)) { 136 | nents = ents[rc.appliedIndex-firstIdx+1:] 137 | } 138 | return nents 139 | } 140 | 141 | // publishEntries writes committed log entries to commit channel and returns 142 | // whether all entries could be published. 143 | func (rc *raftNode) publishEntries(ents []raftpb.Entry) bool { 144 | for i := range ents { 145 | switch ents[i].Type { 146 | case raftpb.EntryNormal: 147 | if len(ents[i].Data) == 0 { 148 | // ignore empty messages 149 | break 150 | } 151 | s := string(ents[i].Data) 152 | select { 153 | case rc.commitC <- &s: 154 | case <-rc.stopc: 155 | return false 156 | } 157 | 158 | case raftpb.EntryConfChange: 159 | var cc raftpb.ConfChange 160 | cc.Unmarshal(ents[i].Data) 161 | rc.confState = *rc.node.ApplyConfChange(cc) 162 | switch cc.Type { 163 | case raftpb.ConfChangeAddNode: 164 | if len(cc.Context) > 0 { 165 | rc.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)}) 166 | } 167 | case raftpb.ConfChangeRemoveNode: 168 | if cc.NodeID == uint64(rc.id) { 169 | log.Println("I've been removed from the cluster! Shutting down.") 170 | return false 171 | } 172 | rc.transport.RemovePeer(types.ID(cc.NodeID)) 173 | } 174 | } 175 | 176 | // after commit, update appliedIndex 177 | rc.appliedIndex = ents[i].Index 178 | 179 | // special nil commit to signal replay has finished 180 | if ents[i].Index == rc.lastIndex { 181 | select { 182 | case rc.commitC <- nil: 183 | case <-rc.stopc: 184 | return false 185 | } 186 | } 187 | } 188 | return true 189 | } 190 | 191 | func (rc *raftNode) loadSnapshot() *raftpb.Snapshot { 192 | snapshot, err := rc.snapshotter.Load() 193 | if err != nil && err != snap.ErrNoSnapshot { 194 | log.Fatalf("raftexample: error loading snapshot (%v)", err) 195 | } 196 | return snapshot 197 | } 198 | 199 | // openWAL returns a WAL ready for reading. 200 | func (rc *raftNode) openWAL(snapshot *raftpb.Snapshot) *wal.WAL { 201 | if !wal.Exist(rc.waldir) { 202 | if err := os.Mkdir(rc.waldir, 0750); err != nil { 203 | log.Fatalf("raftexample: cannot create dir for wal (%v)", err) 204 | } 205 | 206 | w, err := wal.Create(zap.NewExample(), rc.waldir, nil) 207 | if err != nil { 208 | log.Fatalf("raftexample: create wal error (%v)", err) 209 | } 210 | w.Close() 211 | } 212 | 213 | walsnap := walpb.Snapshot{} 214 | if snapshot != nil { 215 | walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term 216 | } 217 | log.Printf("loading WAL at term %d and index %d", walsnap.Term, walsnap.Index) 218 | w, err := wal.Open(zap.NewExample(), rc.waldir, walsnap) 219 | if err != nil { 220 | log.Fatalf("raftexample: error loading wal (%v)", err) 221 | } 222 | 223 | return w 224 | } 225 | 226 | // replayWAL replays WAL entries into the raft instance. 227 | func (rc *raftNode) replayWAL() *wal.WAL { 228 | log.Printf("replaying WAL of member %d", rc.id) 229 | snapshot := rc.loadSnapshot() 230 | w := rc.openWAL(snapshot) 231 | _, st, ents, err := w.ReadAll() 232 | if err != nil { 233 | log.Fatalf("raftexample: failed to read WAL (%v)", err) 234 | } 235 | rc.raftStorage = raft.NewMemoryStorage() 236 | if snapshot != nil { 237 | rc.raftStorage.ApplySnapshot(*snapshot) 238 | } 239 | rc.raftStorage.SetHardState(st) 240 | 241 | // append to storage so raft starts at the right place in log 242 | rc.raftStorage.Append(ents) 243 | // send nil once lastIndex is published so client knows commit channel is current 244 | if len(ents) > 0 { 245 | rc.lastIndex = ents[len(ents)-1].Index 246 | } else { 247 | rc.commitC <- nil 248 | } 249 | return w 250 | } 251 | 252 | func (rc *raftNode) writeError(err error) { 253 | rc.stopHTTP() 254 | close(rc.commitC) 255 | rc.errorC <- err 256 | close(rc.errorC) 257 | rc.node.Stop() 258 | } 259 | 260 | func (rc *raftNode) startRaft() { 261 | if !fileutil.Exist(rc.snapdir) { 262 | if err := os.Mkdir(rc.snapdir, 0750); err != nil { 263 | log.Fatalf("raftexample: cannot create dir for snapshot (%v)", err) 264 | } 265 | } 266 | rc.snapshotter = snap.New(zap.NewExample(), rc.snapdir) 267 | rc.snapshotterReady <- rc.snapshotter 268 | 269 | oldwal := wal.Exist(rc.waldir) 270 | rc.wal = rc.replayWAL() 271 | 272 | rpeers := make([]raft.Peer, len(rc.peers)) 273 | for i := range rpeers { 274 | rpeers[i] = raft.Peer{ID: uint64(i + 1)} 275 | } 276 | c := &raft.Config{ 277 | ID: uint64(rc.id), 278 | ElectionTick: 10, 279 | HeartbeatTick: 1, 280 | Storage: rc.raftStorage, 281 | MaxSizePerMsg: 1024 * 1024, 282 | MaxInflightMsgs: 256, 283 | MaxUncommittedEntriesSize: 1 << 30, 284 | } 285 | 286 | if oldwal { 287 | rc.node = raft.RestartNode(c) 288 | } else { 289 | startPeers := rpeers 290 | if rc.join { 291 | startPeers = nil 292 | } 293 | rc.node = raft.StartNode(c, startPeers) 294 | } 295 | 296 | rc.transport = &rafthttp.Transport{ 297 | Logger: zap.NewExample(), 298 | ID: types.ID(rc.id), 299 | ClusterID: 0x1000, 300 | Raft: rc, 301 | ServerStats: stats.NewServerStats("", ""), 302 | LeaderStats: stats.NewLeaderStats(zap.NewExample(), strconv.Itoa(rc.id)), 303 | ErrorC: make(chan error), 304 | } 305 | 306 | rc.transport.Start() 307 | for i := range rc.peers { 308 | if i+1 != rc.id { 309 | rc.transport.AddPeer(types.ID(i+1), []string{rc.peers[i]}) 310 | } 311 | } 312 | 313 | go rc.serveRaft() 314 | go rc.serveChannels() 315 | } 316 | 317 | // stop closes http, closes all channels, and stops raft. 318 | func (rc *raftNode) stop() { 319 | rc.stopHTTP() 320 | close(rc.commitC) 321 | close(rc.errorC) 322 | rc.node.Stop() 323 | } 324 | 325 | func (rc *raftNode) stopHTTP() { 326 | rc.transport.Stop() 327 | close(rc.httpstopc) 328 | <-rc.httpdonec 329 | } 330 | 331 | func (rc *raftNode) publishSnapshot(snapshotToSave raftpb.Snapshot) { 332 | if raft.IsEmptySnap(snapshotToSave) { 333 | return 334 | } 335 | 336 | log.Printf("publishing snapshot at index %d", rc.snapshotIndex) 337 | defer log.Printf("finished publishing snapshot at index %d", rc.snapshotIndex) 338 | 339 | if snapshotToSave.Metadata.Index <= rc.appliedIndex { 340 | log.Fatalf("snapshot index [%d] should > progress.appliedIndex [%d]", snapshotToSave.Metadata.Index, rc.appliedIndex) 341 | } 342 | rc.commitC <- nil // trigger kvstore to load snapshot 343 | 344 | rc.confState = snapshotToSave.Metadata.ConfState 345 | rc.snapshotIndex = snapshotToSave.Metadata.Index 346 | rc.appliedIndex = snapshotToSave.Metadata.Index 347 | } 348 | 349 | var snapshotCatchUpEntriesN uint64 = 10000 350 | 351 | func (rc *raftNode) maybeTriggerSnapshot() { 352 | if rc.appliedIndex-rc.snapshotIndex <= rc.snapCount { 353 | return 354 | } 355 | 356 | log.Printf("start snapshot [applied index: %d | last snapshot index: %d]", rc.appliedIndex, rc.snapshotIndex) 357 | data, err := rc.getSnapshot() 358 | if err != nil { 359 | log.Panic(err) 360 | } 361 | snap, err := rc.raftStorage.CreateSnapshot(rc.appliedIndex, &rc.confState, data) 362 | if err != nil { 363 | panic(err) 364 | } 365 | if err := rc.saveSnap(snap); err != nil { 366 | panic(err) 367 | } 368 | 369 | compactIndex := uint64(1) 370 | if rc.appliedIndex > snapshotCatchUpEntriesN { 371 | compactIndex = rc.appliedIndex - snapshotCatchUpEntriesN 372 | } 373 | if err := rc.raftStorage.Compact(compactIndex); err != nil { 374 | panic(err) 375 | } 376 | 377 | log.Printf("compacted log at index %d", compactIndex) 378 | rc.snapshotIndex = rc.appliedIndex 379 | } 380 | 381 | func (rc *raftNode) serveChannels() { 382 | snap, err := rc.raftStorage.Snapshot() 383 | if err != nil { 384 | panic(err) 385 | } 386 | rc.confState = snap.Metadata.ConfState 387 | rc.snapshotIndex = snap.Metadata.Index 388 | rc.appliedIndex = snap.Metadata.Index 389 | 390 | defer rc.wal.Close() 391 | 392 | ticker := time.NewTicker(100 * time.Millisecond) 393 | defer ticker.Stop() 394 | 395 | // send proposals over raft 396 | go func() { 397 | confChangeCount := uint64(0) 398 | 399 | for rc.proposeC != nil && rc.confChangeC != nil { 400 | select { 401 | case prop, ok := <-rc.proposeC: 402 | if !ok { 403 | rc.proposeC = nil 404 | } else { 405 | // blocks until accepted by raft state machine 406 | rc.node.Propose(context.TODO(), []byte(prop)) 407 | } 408 | 409 | case cc, ok := <-rc.confChangeC: 410 | if !ok { 411 | rc.confChangeC = nil 412 | } else { 413 | confChangeCount++ 414 | cc.ID = confChangeCount 415 | rc.node.ProposeConfChange(context.TODO(), cc) 416 | } 417 | } 418 | } 419 | // client closed channel; shutdown raft if not already 420 | close(rc.stopc) 421 | }() 422 | 423 | // event loop on raft state machine updates 424 | for { 425 | select { 426 | case <-ticker.C: 427 | rc.node.Tick() 428 | 429 | // store raft entries to wal, then publish over commit channel 430 | case rd := <-rc.node.Ready(): 431 | rc.wal.Save(rd.HardState, rd.Entries) 432 | if !raft.IsEmptySnap(rd.Snapshot) { 433 | rc.saveSnap(rd.Snapshot) 434 | rc.raftStorage.ApplySnapshot(rd.Snapshot) 435 | rc.publishSnapshot(rd.Snapshot) 436 | } 437 | rc.raftStorage.Append(rd.Entries) 438 | rc.transport.Send(rd.Messages) 439 | if ok := rc.publishEntries(rc.entriesToApply(rd.CommittedEntries)); !ok { 440 | rc.stop() 441 | return 442 | } 443 | rc.maybeTriggerSnapshot() 444 | rc.node.Advance() 445 | 446 | case err := <-rc.transport.ErrorC: 447 | rc.writeError(err) 448 | return 449 | 450 | case <-rc.stopc: 451 | rc.stop() 452 | return 453 | } 454 | } 455 | } 456 | 457 | func (rc *raftNode) serveRaft() { 458 | url, err := url.Parse(rc.peers[rc.id-1]) 459 | if err != nil { 460 | log.Fatalf("raftexample: Failed parsing URL (%v)", err) 461 | } 462 | 463 | ln, err := newStoppableListener(url.Host, rc.httpstopc) 464 | if err != nil { 465 | log.Fatalf("raftexample: Failed to listen rafthttp (%v)", err) 466 | } 467 | 468 | err = (&http.Server{Handler: rc.transport.Handler()}).Serve(ln) 469 | select { 470 | case <-rc.httpstopc: 471 | default: 472 | log.Fatalf("raftexample: Failed to serve rafthttp (%v)", err) 473 | } 474 | close(rc.httpdonec) 475 | } 476 | 477 | func (rc *raftNode) Process(ctx context.Context, m raftpb.Message) error { 478 | return rc.node.Step(ctx, m) 479 | } 480 | func (rc *raftNode) IsIDRemoved(id uint64) bool { return false } 481 | func (rc *raftNode) ReportUnreachable(id uint64) {} 482 | func (rc *raftNode) ReportSnapshot(id uint64, status raft.SnapshotStatus) {} 483 | -------------------------------------------------------------------------------- /raft_server.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "log" 7 | "strings" 8 | 9 | "github.com/rpcxio/etcd/etcdserver/api/snap" 10 | "github.com/rpcxio/etcd/raft/raftpb" 11 | ) 12 | 13 | type ConfChange interface { 14 | AddNode(id uint64, addr []byte) error 15 | RemoveNode(id uint64) error 16 | } 17 | type RaftServer struct { 18 | proposeC chan<- string 19 | confChangeC chan raftpb.ConfChange 20 | bmServer *Server 21 | snapshotter *snap.Snapshotter 22 | } 23 | 24 | type operaton struct { 25 | OP OP 26 | Val string 27 | } 28 | 29 | func NewRaftServer(bmServer *Server, snapshotter *snap.Snapshotter, confChangeC chan raftpb.ConfChange, proposeC chan<- string, commitC <-chan *string, errorC <-chan error) *RaftServer { 30 | s := &RaftServer{proposeC: proposeC, confChangeC: confChangeC, bmServer: bmServer, snapshotter: snapshotter} 31 | bmServer.bitmaps.writeCallback = s.Propose 32 | s.readCommits(commitC, errorC) 33 | go s.readCommits(commitC, errorC) 34 | 35 | return s 36 | } 37 | 38 | func (s *RaftServer) Propose(op OP, value string) { 39 | var buf bytes.Buffer 40 | if err := gob.NewEncoder(&buf).Encode(operaton{op, value}); err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | s.proposeC <- buf.String() 45 | } 46 | 47 | func (s *RaftServer) readCommits(commitC <-chan *string, errorC <-chan error) { 48 | for data := range commitC { 49 | if data == nil { 50 | snapshot, err := s.snapshotter.Load() 51 | if err == snap.ErrNoSnapshot { 52 | return 53 | } 54 | if err != nil { 55 | log.Panic(err) 56 | } 57 | log.Printf("loading snapshot at term %d and index %d", snapshot.Metadata.Term, snapshot.Metadata.Index) 58 | if err := s.recoverFromSnapshot(snapshot.Data); err != nil { 59 | log.Panic(err) 60 | } 61 | continue 62 | } 63 | 64 | var op operaton 65 | dec := gob.NewDecoder(bytes.NewBufferString(*data)) 66 | if err := dec.Decode(&op); err != nil { 67 | log.Fatalf("raftexample: could not decode message (%v)", err) 68 | } 69 | s.processOP(op) 70 | } 71 | if err, ok := <-errorC; ok { 72 | log.Fatal(err) 73 | } 74 | } 75 | 76 | func (s *RaftServer) processOP(op operaton) { 77 | switch op.OP { 78 | case BmOpAdd: 79 | items := strings.SplitN(op.Val, ",", 2) 80 | if len(items) != 2 { 81 | log.Printf("wrong request: %+v", op) 82 | return 83 | } 84 | s.bmServer.add(items[0], items[1], false) 85 | case BmOpAddMany: 86 | items := strings.SplitN(op.Val, ",", 2) 87 | if len(items) != 2 { 88 | log.Printf("wrong request: %+v", op) 89 | return 90 | } 91 | s.bmServer.addMany(items[0], items[1], false) 92 | case BmOpRemove: 93 | items := strings.SplitN(op.Val, ",", 2) 94 | if len(items) != 2 { 95 | log.Printf("wrong request: %+v", op) 96 | return 97 | } 98 | s.bmServer.remove(items[0], items[1], false) 99 | case BmOpDrop: 100 | s.bmServer.drop(op.Val, false) 101 | case BmOpClear: 102 | s.bmServer.clear(op.Val, false) 103 | } 104 | } 105 | 106 | func (s *RaftServer) GetSnapshot() ([]byte, error) { 107 | var buf bytes.Buffer 108 | err := s.bmServer.bitmaps.Save(&buf) 109 | return buf.Bytes(), err 110 | } 111 | 112 | func (s *RaftServer) recoverFromSnapshot(snapshot []byte) error { 113 | var buf = bytes.NewBuffer(snapshot) 114 | return s.bmServer.bitmaps.Read(buf) 115 | } 116 | 117 | func (s *RaftServer) AddNode(id uint64, addr []byte) error { 118 | cc := raftpb.ConfChange{ 119 | Type: raftpb.ConfChangeAddNode, 120 | NodeID: id, 121 | Context: addr, 122 | } 123 | s.confChangeC <- cc 124 | return nil 125 | } 126 | 127 | func (s *RaftServer) RemoveNode(id uint64) error { 128 | cc := raftpb.ConfChange{ 129 | Type: raftpb.ConfChangeRemoveNode, 130 | NodeID: id, 131 | } 132 | s.confChangeC <- cc 133 | return nil 134 | } 135 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "log" 8 | "net" 9 | "os" 10 | 11 | "github.com/smallnest/rpcx/protocol" 12 | "github.com/smallnest/rpcx/server" 13 | "github.com/soheilhy/cmux" 14 | "github.com/tidwall/redcon" 15 | ) 16 | 17 | // Errors for bitmaps 18 | var ( 19 | ErrPersistFileNotFound = errors.New("persist file not found") 20 | ) 21 | 22 | // Server is the bitmap server that supports multiple services. 23 | type Server struct { 24 | addr string 25 | bitmaps *Bitmaps 26 | ln net.Listener 27 | confChangeCallback ConfChange 28 | 29 | rpcxOptions []ConfigRpcxOption 30 | 31 | persistFile string 32 | } 33 | 34 | // NewServer returns a server. 35 | func NewServer(addr string, bitmaps *Bitmaps, rpcxOptions []ConfigRpcxOption, persistFile string) *Server { 36 | return &Server{ 37 | addr: addr, 38 | bitmaps: bitmaps, 39 | rpcxOptions: rpcxOptions, 40 | persistFile: persistFile, 41 | } 42 | } 43 | 44 | // SetConfChangeCallback must invoke before Serve. 45 | func (s *Server) SetConfChangeCallback(confChangeCallback ConfChange) { 46 | s.confChangeCallback = confChangeCallback 47 | } 48 | 49 | // Serve serves basalt services. 50 | func (s *Server) Serve() error { 51 | ln, err := net.Listen("tcp", s.addr) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | return s.configListener(ln) 57 | } 58 | 59 | // Close closes this server. 60 | func (s *Server) Close() error { 61 | if s.ln == nil { 62 | return nil 63 | } 64 | 65 | return s.ln.Close() 66 | } 67 | 68 | func (s *Server) configListener(ln net.Listener) error { 69 | m := cmux.New(ln) 70 | 71 | // rpcx 72 | rpcxLn := m.Match(rpcxPrefixByteMatcher()) 73 | 74 | // admin http 75 | httpLn := m.Match(cmux.HTTP1Fast()) 76 | 77 | // redis 78 | redisLn := m.Match(cmux.Any()) 79 | 80 | go s.startRpcxService(rpcxLn) 81 | go s.startHTTPService(httpLn) 82 | go s.startRedisService(redisLn) 83 | 84 | return m.Serve() 85 | } 86 | 87 | func (s *Server) startRpcxService(ln net.Listener) { 88 | srv := server.NewServer() 89 | 90 | for _, opt := range s.rpcxOptions { 91 | opt(s, srv) 92 | } 93 | 94 | srv.RegisterName("Bitmap", &RpcxBitmapService{s: s, confChangeCallback: s.confChangeCallback}, "") 95 | if err := srv.ServeListener("tcp", ln); err != nil { 96 | log.Fatalf("failed to start rpcx services: %v", err) 97 | } 98 | } 99 | 100 | // if not config adminAddr, we don't start admin service. 101 | // It is useful for security purpose. 102 | func (s *Server) startHTTPService(ln net.Listener) { 103 | hs := &HTTPService{ 104 | s: s, 105 | confChangeCallback: s.confChangeCallback, 106 | } 107 | 108 | if err := hs.Serve(ln); err != nil { 109 | log.Fatalf("failed to start http service: %v", err) 110 | } 111 | } 112 | 113 | func (s *Server) startRedisService(ln net.Listener) { 114 | redisService := &RedisService{ 115 | s: s, 116 | confChangeCallback: s.confChangeCallback, 117 | } 118 | if err := redcon.Serve(ln, redisService.redisHandler, redisService.redisAccept, redisService.redisClose); err != nil { 119 | log.Fatalf("failed to start redis services: %v", err) 120 | } 121 | } 122 | 123 | func rpcxPrefixByteMatcher() cmux.Matcher { 124 | magic := protocol.MagicNumber() 125 | return func(r io.Reader) bool { 126 | buf := make([]byte, 1) 127 | n, _ := r.Read(buf) 128 | return n == 1 && buf[0] == magic 129 | } 130 | } 131 | 132 | // Save saves the data into file. 133 | func (s *Server) Save() error { 134 | if s.persistFile == "" { 135 | return ErrPersistFileNotFound 136 | } 137 | file, err := os.Create(s.persistFile) 138 | if err != nil { 139 | return err 140 | } 141 | 142 | w := bufio.NewWriter(file) 143 | err = s.bitmaps.Save(w) 144 | if err != nil { 145 | file.Close() 146 | return err 147 | } 148 | err = w.Flush() 149 | if err != nil { 150 | return err 151 | } 152 | 153 | return file.Close() 154 | } 155 | 156 | // Restore retores the data from file. 157 | func (s *Server) Restore() error { 158 | if s.persistFile == "" { 159 | return ErrPersistFileNotFound 160 | } 161 | 162 | file, err := os.Open(s.persistFile) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | r := bufio.NewReader(file) 168 | err = s.bitmaps.Read(r) 169 | if err != nil { 170 | return err 171 | } 172 | 173 | return file.Close() 174 | } 175 | 176 | func (s *Server) add(name, value string, callback bool) error { 177 | v, err := str2uint32(value) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | s.bitmaps.Add(name, v, callback) 183 | return nil 184 | } 185 | 186 | func (s *Server) addMany(name, values string, callback bool) error { 187 | vs, err := str2uint32s(values) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | s.bitmaps.AddMany(name, vs, callback) 193 | return nil 194 | } 195 | 196 | func (s *Server) remove(name, value string, callback bool) error { 197 | v, err := str2uint32(value) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | s.bitmaps.Remove(name, v, callback) 203 | return err 204 | } 205 | 206 | func (s *Server) drop(name string, callback bool) { 207 | s.bitmaps.RemoveBitmap(name, callback) 208 | } 209 | 210 | func (s *Server) clear(name string, callback bool) { 211 | s.bitmaps.ClearBitmap(name, callback) 212 | } 213 | -------------------------------------------------------------------------------- /service_http.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/julienschmidt/httprouter" 13 | ) 14 | 15 | // HTTPService is a http service. 16 | type HTTPService struct { 17 | router *httprouter.Router 18 | s *Server 19 | confChangeCallback ConfChange 20 | } 21 | 22 | // Serve serves http service. 23 | func (s *HTTPService) Serve(ln net.Listener) error { 24 | s.config() 25 | 26 | return http.Serve(ln, s.router) 27 | } 28 | 29 | func (s *HTTPService) config() { 30 | router := httprouter.New() 31 | s.router = router 32 | 33 | router.POST("/add/:name/:value", s.add) 34 | router.POST("/addmany/:name/:values", s.addMany) 35 | router.POST("/remove/:name/:value", s.remove) 36 | router.POST("/drop/:name", s.drop) 37 | router.POST("/clear/:name", s.clear) 38 | router.GET("/exists/:name/:value", s.exists) 39 | router.GET("/card/:name", s.card) 40 | 41 | router.GET("/inter/:names", s.inter) 42 | router.GET("/interstore/:dst/:names", s.interStore) 43 | 44 | router.GET("/union/:names", s.union) 45 | router.GET("/unionstore/:dst/:names", s.unionStore) 46 | 47 | router.GET("/xor/:name1/:name2", s.xor) 48 | router.GET("/xorstore/:dst/:name1/:name2", s.xorStore) 49 | 50 | router.GET("/diff/:name1/:name2", s.diff) 51 | router.GET("/diffstore/:dst/:name1/:name2", s.diffStore) 52 | 53 | router.GET("/stats/:name", s.stats) 54 | router.POST("/save", s.save) 55 | 56 | router.POST("/peers/:nodeID", s.addNode) 57 | router.DELETE("/peers/:nodeID", s.removeNode) 58 | } 59 | 60 | func (s *HTTPService) add(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 61 | name := ps.ByName("name") 62 | value := ps.ByName("value") 63 | err := s.s.add(name, value, true) 64 | if err != nil { 65 | http.Error(w, err.Error(), http.StatusBadRequest) 66 | return 67 | } 68 | } 69 | 70 | func (s *HTTPService) addMany(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 71 | name := ps.ByName("name") 72 | values := ps.ByName("values") 73 | err := s.s.addMany(name, values, true) 74 | if err != nil { 75 | http.Error(w, err.Error(), http.StatusBadRequest) 76 | return 77 | } 78 | } 79 | 80 | func (s *HTTPService) remove(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 81 | name := ps.ByName("name") 82 | value := ps.ByName("value") 83 | err := s.s.remove(name, value, true) 84 | if err != nil { 85 | http.Error(w, err.Error(), http.StatusBadRequest) 86 | return 87 | } 88 | } 89 | 90 | func (s *HTTPService) drop(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 91 | name := ps.ByName("name") 92 | s.s.drop(name, true) 93 | } 94 | 95 | func (s *HTTPService) clear(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 96 | name := ps.ByName("name") 97 | s.s.clear(name, true) 98 | } 99 | 100 | func (s *HTTPService) card(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 101 | name := ps.ByName("name") 102 | count := s.s.bitmaps.Card(name) 103 | w.Write([]byte(strconv.FormatUint(count, 10))) 104 | } 105 | 106 | func (s *HTTPService) exists(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 107 | name := ps.ByName("name") 108 | value := ps.ByName("value") 109 | v, err := str2uint32(value) 110 | if err != nil { 111 | http.Error(w, err.Error(), http.StatusBadRequest) 112 | } 113 | 114 | existed := s.s.bitmaps.Exists(name, v) 115 | if !existed { 116 | http.Error(w, "not found", http.StatusNotFound) 117 | } 118 | } 119 | 120 | func (s *HTTPService) inter(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 121 | names := strings.Split(ps.ByName("name"), ",") 122 | rt := s.s.bitmaps.Inter(names...) 123 | 124 | w.Write([]byte(ints2str(rt))) 125 | } 126 | 127 | func (s *HTTPService) interStore(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 128 | dst := ps.ByName("dst") 129 | names := strings.Split(ps.ByName("name"), ",") 130 | count := s.s.bitmaps.InterStore(dst, names...) 131 | 132 | w.Write([]byte(strconv.FormatUint(count, 10))) 133 | } 134 | 135 | func (s *HTTPService) union(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 136 | names := strings.Split(ps.ByName("name"), ",") 137 | rt := s.s.bitmaps.Union(names...) 138 | 139 | w.Write([]byte(ints2str(rt))) 140 | } 141 | 142 | func (s *HTTPService) unionStore(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 143 | dst := ps.ByName("dst") 144 | names := strings.Split(ps.ByName("name"), ",") 145 | count := s.s.bitmaps.UnionStore(dst, names...) 146 | 147 | w.Write([]byte(strconv.FormatUint(count, 10))) 148 | } 149 | 150 | func (s *HTTPService) xor(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 151 | name1 := ps.ByName("name1") 152 | name2 := ps.ByName("name2") 153 | rt := s.s.bitmaps.Xor(name1, name2) 154 | 155 | w.Write([]byte(ints2str(rt))) 156 | } 157 | 158 | func (s *HTTPService) xorStore(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 159 | dst := ps.ByName("dst") 160 | name1 := ps.ByName("name1") 161 | name2 := ps.ByName("name2") 162 | count := s.s.bitmaps.XorStore(dst, name1, name2) 163 | 164 | w.Write([]byte(strconv.FormatUint(count, 10))) 165 | } 166 | 167 | func (s *HTTPService) diff(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 168 | name1 := ps.ByName("name1") 169 | name2 := ps.ByName("name2") 170 | rt := s.s.bitmaps.Diff(name1, name2) 171 | 172 | w.Write([]byte(ints2str(rt))) 173 | } 174 | 175 | func (s *HTTPService) diffStore(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 176 | dst := ps.ByName("dst") 177 | name1 := ps.ByName("name1") 178 | name2 := ps.ByName("name2") 179 | count := s.s.bitmaps.DiffStore(dst, name1, name2) 180 | 181 | w.Write([]byte(strconv.FormatUint(count, 10))) 182 | } 183 | 184 | func (s *HTTPService) stats(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 185 | name := ps.ByName("name") 186 | stats := s.s.bitmaps.Stats(name) 187 | w.Header().Set("Content-Type", "application/json") 188 | data, err := json.Marshal(stats) 189 | if err != nil { 190 | http.Error(w, err.Error(), http.StatusInternalServerError) 191 | } 192 | w.Write(data) 193 | } 194 | 195 | func (s *HTTPService) save(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 196 | err := s.s.Save() 197 | if err != nil { 198 | http.Error(w, err.Error(), http.StatusNotFound) 199 | } 200 | } 201 | 202 | func (s *HTTPService) addNode(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 203 | nodeID := ps.ByName("nodeID") 204 | url, err := ioutil.ReadAll(r.Body) 205 | if err != nil { 206 | http.Error(w, "Failed on POST", http.StatusBadRequest) 207 | return 208 | } 209 | id, err := strconv.ParseUint(nodeID, 0, 64) 210 | if err != nil { 211 | http.Error(w, "Failed on convert ID", http.StatusBadRequest) 212 | return 213 | } 214 | 215 | if s.confChangeCallback != nil { 216 | err = s.confChangeCallback.AddNode(id, url) 217 | if err != nil { 218 | http.Error(w, "failed to add node: "+err.Error(), http.StatusInternalServerError) 219 | return 220 | } 221 | } 222 | } 223 | 224 | func (s *HTTPService) removeNode(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 225 | nodeID := ps.ByName("nodeID") 226 | id, err := strconv.ParseUint(nodeID, 0, 64) 227 | if err != nil { 228 | http.Error(w, "Failed on convert ID", http.StatusBadRequest) 229 | return 230 | } 231 | 232 | if s.confChangeCallback != nil { 233 | err = s.confChangeCallback.RemoveNode(id) 234 | if err != nil { 235 | http.Error(w, "failed to remove node: "+err.Error(), http.StatusInternalServerError) 236 | return 237 | } 238 | } 239 | } 240 | 241 | func ints2str(vs []uint32) string { 242 | // return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(vs)), ","), "[]") 243 | return strings.Join(strings.Fields(fmt.Sprint(vs)), ",") 244 | } 245 | 246 | func str2uint32(s string) (uint32, error) { 247 | i, err := strconv.ParseUint(s, 10, 32) 248 | return uint32(i), err 249 | } 250 | 251 | func str2uint32s(s string) ([]uint32, error) { 252 | var rt []uint32 253 | b := strings.Split(s, ",") 254 | for _, bt := range b { 255 | i, err := strconv.ParseUint(bt, 10, 32) 256 | if err != nil { 257 | return nil, err 258 | } 259 | rt = append(rt, uint32(i)) 260 | } 261 | return rt, nil 262 | } 263 | -------------------------------------------------------------------------------- /service_redis.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/tidwall/redcon" 8 | ) 9 | 10 | // RedisService is a redis service only supports bitmap commands. 11 | type RedisService struct { 12 | s *Server 13 | confChangeCallback ConfChange 14 | } 15 | 16 | func (rs *RedisService) redisAccept(conn redcon.Conn) bool { 17 | return true 18 | } 19 | func (rs *RedisService) redisClose(conn redcon.Conn, err error) { 20 | } 21 | 22 | // redisHandler handles redis commands. 23 | func (rs *RedisService) redisHandler(conn redcon.Conn, cmd redcon.Command) { 24 | switch strings.ToLower(string(cmd.Args[0])) { 25 | default: 26 | conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") 27 | case "ping": 28 | conn.WriteString("PONG") 29 | case "quit": 30 | conn.WriteString("OK") 31 | conn.Close() 32 | case "bmadd": // bitmap add 33 | if len(cmd.Args) != 3 { 34 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 35 | return 36 | } 37 | 38 | v, err := byte2uint32(cmd.Args[2]) 39 | if err != nil { 40 | conn.WriteError("ERR wrong value for '" + string(cmd.Args[0]) + "' command because of " + err.Error()) 41 | return 42 | } 43 | 44 | rs.s.bitmaps.Add(string(cmd.Args[1]), v, true) 45 | conn.WriteInt(1) 46 | 47 | case "bmaddmany": // bitmap addmany 48 | if len(cmd.Args) < 3 { 49 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 50 | return 51 | } 52 | 53 | values, err := bytes2uint32(cmd.Args[2:]) 54 | if err != nil { 55 | conn.WriteError("ERR wrong value for '" + string(cmd.Args[0]) + "' command because of " + err.Error()) 56 | return 57 | } 58 | 59 | rs.s.bitmaps.AddMany(string(cmd.Args[1]), values, true) 60 | conn.WriteInt(len(values)) 61 | 62 | case "bmdel": // bitmap remove 63 | if len(cmd.Args) != 3 { 64 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 65 | return 66 | } 67 | 68 | v, err := byte2uint32(cmd.Args[2]) 69 | if err != nil { 70 | conn.WriteError("ERR wrong value for '" + string(cmd.Args[0]) + "' command because of " + err.Error()) 71 | return 72 | } 73 | 74 | rs.s.bitmaps.Remove(string(cmd.Args[1]), v, true) 75 | conn.WriteInt(1) 76 | 77 | case "bmdrop": // bitmap remove_bitmap 78 | if len(cmd.Args) != 2 { 79 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 80 | return 81 | } 82 | 83 | rs.s.bitmaps.RemoveBitmap(string(cmd.Args[1]), true) 84 | conn.WriteString("OK") 85 | 86 | case "bmclear": // bitmap clear_bitmap 87 | if len(cmd.Args) != 2 { 88 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 89 | return 90 | } 91 | 92 | rs.s.bitmaps.ClearBitmap(string(cmd.Args[1]), true) 93 | conn.WriteString("OK") 94 | case "bmcard": // bitmap clear_bitmap 95 | if len(cmd.Args) != 2 { 96 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 97 | return 98 | } 99 | 100 | count := rs.s.bitmaps.Card(string(cmd.Args[1])) 101 | conn.WriteInt64(int64(count)) 102 | 103 | case "bmexists": // bitmap exists 104 | if len(cmd.Args) != 3 { 105 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 106 | return 107 | } 108 | 109 | v, err := byte2uint32(cmd.Args[2]) 110 | if err != nil { 111 | conn.WriteError("ERR wrong value for '" + string(cmd.Args[0]) + "' command because of " + err.Error()) 112 | return 113 | } 114 | 115 | existed := rs.s.bitmaps.Exists(string(cmd.Args[1]), v) 116 | if existed { 117 | conn.WriteInt(1) 118 | } else { 119 | conn.WriteInt(0) 120 | } 121 | 122 | case "bminter": // bitmap intersect 123 | if len(cmd.Args) < 3 { 124 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 125 | return 126 | } 127 | 128 | names := bytes2string(cmd.Args[1:]) 129 | rt := rs.s.bitmaps.Inter(names...) 130 | 131 | conn.WriteArray(len(rt)) 132 | for _, v := range rt { 133 | conn.WriteInt64(int64(v)) 134 | } 135 | 136 | case "bminterstore": // bitmap intersect store 137 | if len(cmd.Args) < 4 { 138 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 139 | return 140 | } 141 | 142 | names := bytes2string(cmd.Args[1:]) 143 | count := rs.s.bitmaps.InterStore(names[0], names[1:]...) 144 | conn.WriteInt64(int64(count)) 145 | 146 | case "bmunion": // bitmap union 147 | if len(cmd.Args) < 3 { 148 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 149 | return 150 | } 151 | 152 | names := bytes2string(cmd.Args[1:]) 153 | rt := rs.s.bitmaps.Union(names...) 154 | 155 | conn.WriteArray(len(rt)) 156 | for _, v := range rt { 157 | conn.WriteInt64(int64(v)) 158 | } 159 | case "bmunionstore": // bitmap union store 160 | if len(cmd.Args) < 4 { 161 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 162 | return 163 | } 164 | 165 | names := bytes2string(cmd.Args[1:]) 166 | count := rs.s.bitmaps.UnionStore(names[0], names[1:]...) 167 | conn.WriteInt64(int64(count)) 168 | 169 | case "bmxor": // bitmap xor 170 | if len(cmd.Args) != 3 { 171 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 172 | return 173 | } 174 | 175 | rt := rs.s.bitmaps.Xor(string(cmd.Args[1]), string(cmd.Args[2])) 176 | 177 | conn.WriteArray(len(rt)) 178 | for _, v := range rt { 179 | conn.WriteInt64(int64(v)) 180 | } 181 | case "bmxorstore": // bitmap xor store 182 | if len(cmd.Args) != 4 { 183 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 184 | return 185 | } 186 | 187 | count := rs.s.bitmaps.XorStore(string(cmd.Args[1]), string(cmd.Args[2]), string(cmd.Args[3])) 188 | conn.WriteInt64(int64(count)) 189 | 190 | case "bmdiff": // bitmap diff 191 | if len(cmd.Args) != 3 { 192 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 193 | return 194 | } 195 | 196 | rt := rs.s.bitmaps.Diff(string(cmd.Args[1]), string(cmd.Args[2])) 197 | 198 | conn.WriteArray(len(rt)) 199 | for _, v := range rt { 200 | conn.WriteInt64(int64(v)) 201 | } 202 | case "bmdiffstore": // bitmap diff store 203 | if len(cmd.Args) != 4 { 204 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 205 | return 206 | } 207 | 208 | count := rs.s.bitmaps.DiffStore(string(cmd.Args[1]), string(cmd.Args[2]), string(cmd.Args[3])) 209 | conn.WriteInt64(int64(count)) 210 | case "bmstats": // bitmap diff store 211 | if len(cmd.Args) != 2 { 212 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 213 | return 214 | } 215 | 216 | stats := rs.s.bitmaps.Stats(string(cmd.Args[1])) 217 | 218 | var sb strings.Builder 219 | appendMetric(&sb, "cardinality", stats.Cardinality) 220 | appendMetric(&sb, "Containers", stats.Containers) 221 | 222 | appendMetric(&sb, "ArrayContainers", stats.ArrayContainers) 223 | appendMetric(&sb, "ArrayContainerBytes", stats.ArrayContainerBytes) 224 | appendMetric(&sb, "ArrayContainerValues", stats.ArrayContainerValues) 225 | 226 | appendMetric(&sb, "BitmapContainers", stats.BitmapContainers) 227 | appendMetric(&sb, "BitmapContainerBytes", stats.BitmapContainerBytes) 228 | appendMetric(&sb, "BitmapContainerValues", stats.BitmapContainerValues) 229 | 230 | appendMetric(&sb, "RunContainers", stats.RunContainers) 231 | appendMetric(&sb, "RunContainerBytes", stats.RunContainerBytes) 232 | appendMetric(&sb, "RunContainerValues", stats.RunContainerValues) 233 | 234 | conn.WriteBulkString(sb.String()) 235 | case "bmsave": // bitmap persist 236 | if len(cmd.Args) != 1 { 237 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 238 | return 239 | } 240 | 241 | err := rs.s.Save() 242 | if err != nil { 243 | conn.WriteError("ERR save because of " + err.Error()) 244 | return 245 | } 246 | 247 | conn.WriteInt(1) 248 | case "addnode": // add raft node 249 | if len(cmd.Args) != 3 { 250 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 251 | return 252 | } 253 | 254 | if rs.confChangeCallback != nil { 255 | 256 | nodeID := string(cmd.Args[1]) 257 | url := cmd.Args[2] 258 | 259 | id, err := strconv.ParseUint(nodeID, 0, 64) 260 | if err != nil { 261 | conn.WriteError("ERR parse id because of " + err.Error()) 262 | return 263 | } 264 | 265 | err = rs.confChangeCallback.AddNode(id, url) 266 | if err != nil { 267 | conn.WriteError("ERR failed to add node because of " + err.Error()) 268 | return 269 | } 270 | 271 | } 272 | conn.WriteInt(1) 273 | case "removenode": // remove raft node 274 | if len(cmd.Args) != 2 { 275 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 276 | return 277 | } 278 | 279 | if rs.confChangeCallback != nil { 280 | 281 | nodeID := string(cmd.Args[1]) 282 | 283 | id, err := strconv.ParseUint(nodeID, 0, 64) 284 | if err != nil { 285 | conn.WriteError("ERR parse id because of " + err.Error()) 286 | return 287 | } 288 | 289 | err = rs.confChangeCallback.RemoveNode(id) 290 | if err != nil { 291 | conn.WriteError("ERR failed to remove node because of " + err.Error()) 292 | return 293 | } 294 | 295 | } 296 | conn.WriteInt(1) 297 | } 298 | } 299 | 300 | func appendMetric(sb *strings.Builder, name string, v uint64) { 301 | sb.WriteString(name) 302 | sb.WriteString(":") 303 | sb.WriteString(strconv.FormatUint(v, 10)) 304 | sb.WriteString("\r\n") 305 | } 306 | 307 | func byte2uint32(b []byte) (uint32, error) { 308 | i, err := strconv.ParseUint(string(b), 10, 32) 309 | return uint32(i), err 310 | } 311 | 312 | func bytes2uint32(b [][]byte) ([]uint32, error) { 313 | var rt []uint32 314 | for _, bt := range b { 315 | i, err := strconv.ParseUint(string(bt), 10, 32) 316 | if err != nil { 317 | return nil, err 318 | } 319 | rt = append(rt, uint32(i)) 320 | } 321 | return rt, nil 322 | } 323 | 324 | func bytes2string(b [][]byte) []string { 325 | var rt []string 326 | for _, bt := range b { 327 | rt = append(rt, string(bt)) 328 | } 329 | return rt 330 | } 331 | -------------------------------------------------------------------------------- /service_rpcx.go: -------------------------------------------------------------------------------- 1 | package basalt 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/smallnest/rpcx/server" 7 | ) 8 | 9 | // ConfigRpcxOption defines the rpcx config function. 10 | type ConfigRpcxOption func(*Server, *server.Server) 11 | 12 | // RpcxBitmapService provides the rpcx service for Bitmaps. 13 | type RpcxBitmapService struct { 14 | s *Server 15 | confChangeCallback ConfChange 16 | } 17 | 18 | // BitmapValueRequest contains the name of bitmap and value. 19 | type BitmapValueRequest struct { 20 | Name string 21 | Value uint32 22 | } 23 | 24 | // BitmapValuesRequest contains the name of bitmap and values. 25 | type BitmapValuesRequest struct { 26 | Name string 27 | Values []uint32 28 | } 29 | 30 | // BitmapStoreRequest contains the name of destination and names of bitmaps. 31 | type BitmapStoreRequest struct { 32 | Destination string 33 | Names []string 34 | } 35 | 36 | // BitmapPairRequest contains the name of two bitmaps. 37 | type BitmapPairRequest struct { 38 | Name1 string 39 | Name2 string 40 | } 41 | 42 | // BitmapDstAndPairRequest contains destination and the name of two bitmaps. 43 | type BitmapDstAndPairRequest struct { 44 | Destination string 45 | Name1 string 46 | Name2 string 47 | } 48 | 49 | // Add adds a value in the bitmap with name. 50 | func (s *RpcxBitmapService) Add(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 51 | s.s.bitmaps.Add(req.Name, req.Value, true) 52 | *reply = true 53 | return nil 54 | } 55 | 56 | // AddMany adds multiple values in the bitmap with name. 57 | func (s *RpcxBitmapService) AddMany(ctx context.Context, req *BitmapValuesRequest, reply *bool) error { 58 | s.s.bitmaps.AddMany(req.Name, req.Values, true) 59 | *reply = true 60 | return nil 61 | } 62 | 63 | // Remove removes a value in the bitmap with name. 64 | func (s *RpcxBitmapService) Remove(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 65 | s.s.bitmaps.Remove(req.Name, req.Value, true) 66 | *reply = true 67 | return nil 68 | } 69 | 70 | // RemoveBitmap removes the bitmap. 71 | func (s *RpcxBitmapService) RemoveBitmap(ctx context.Context, name string, reply *bool) error { 72 | s.s.bitmaps.RemoveBitmap(name, true) 73 | *reply = true 74 | return nil 75 | } 76 | 77 | // ClearBitmap clears the bitmap and set it to be empty. 78 | func (s *RpcxBitmapService) ClearBitmap(ctx context.Context, name string, reply *bool) error { 79 | s.s.bitmaps.ClearBitmap(name, true) 80 | *reply = true 81 | return nil 82 | } 83 | 84 | // Exists checks whether the value exists. 85 | func (s *RpcxBitmapService) Exists(ctx context.Context, req *BitmapValueRequest, reply *bool) error { 86 | *reply = s.s.bitmaps.Exists(req.Name, req.Value) 87 | return nil 88 | } 89 | 90 | // Card gets number of integers in the bitmap. 91 | func (s *RpcxBitmapService) Card(ctx context.Context, name string, reply *uint64) error { 92 | *reply = s.s.bitmaps.Card(name) 93 | return nil 94 | } 95 | 96 | // Inter gets the intersection of bitmaps. 97 | func (s *RpcxBitmapService) Inter(ctx context.Context, names []string, reply *[]uint32) error { 98 | *reply = s.s.bitmaps.Inter(names...) 99 | return nil 100 | } 101 | 102 | // InterStore gets the intersection of bitmaps and stores into destination. 103 | func (s *RpcxBitmapService) InterStore(ctx context.Context, req *BitmapStoreRequest, reply *bool) error { 104 | s.s.bitmaps.InterStore(req.Destination, req.Names...) 105 | *reply = true 106 | return nil 107 | } 108 | 109 | // Union gets the union of bitmaps. 110 | func (s *RpcxBitmapService) Union(ctx context.Context, names []string, reply *[]uint32) error { 111 | *reply = s.s.bitmaps.Union(names...) 112 | return nil 113 | } 114 | 115 | // UnionStore gets the union of bitmaps and stores into destination. 116 | func (s *RpcxBitmapService) UnionStore(ctx context.Context, req *BitmapStoreRequest, reply *bool) error { 117 | s.s.bitmaps.UnionStore(req.Destination, req.Names...) 118 | *reply = true 119 | return nil 120 | } 121 | 122 | // Xor gets the symmetric difference between bitmaps. 123 | func (s *RpcxBitmapService) Xor(ctx context.Context, names *BitmapPairRequest, reply *[]uint32) error { 124 | *reply = s.s.bitmaps.Xor(names.Name1, names.Name2) 125 | return nil 126 | } 127 | 128 | // XorStore gets the symmetric difference between bitmaps and stores into destination. 129 | func (s *RpcxBitmapService) XorStore(ctx context.Context, names *BitmapDstAndPairRequest, reply *bool) error { 130 | s.s.bitmaps.XorStore(names.Destination, names.Name1, names.Name2) 131 | *reply = true 132 | return nil 133 | } 134 | 135 | // Diff gets the difference between two bitmaps. 136 | func (s *RpcxBitmapService) Diff(ctx context.Context, names *BitmapPairRequest, reply *[]uint32) error { 137 | *reply = s.s.bitmaps.Diff(names.Name1, names.Name2) 138 | return nil 139 | } 140 | 141 | // DiffStore gets the difference between two bitmaps and stores into destination. 142 | func (s *RpcxBitmapService) DiffStore(ctx context.Context, names *BitmapDstAndPairRequest, reply *bool) error { 143 | s.s.bitmaps.DiffStore(names.Destination, names.Name1, names.Name2) 144 | *reply = true 145 | return nil 146 | } 147 | 148 | // Stats get the stats of bitmap `name`. 149 | func (s *RpcxBitmapService) Stats(ctx context.Context, name string, reply *Stats) error { 150 | stats := s.s.bitmaps.Stats(name) 151 | *reply = stats 152 | return nil 153 | } 154 | 155 | // Save persists bitmaps. 156 | func (s *RpcxBitmapService) Save(ctx context.Context, dummy string, reply *bool) error { 157 | err := s.s.Save() 158 | if err == nil { 159 | *reply = true 160 | } 161 | return err 162 | } 163 | 164 | type AddNodeRequest struct { 165 | ID uint64 166 | Addr string 167 | } 168 | 169 | // AddNode adds a raft node. 170 | func (s *RpcxBitmapService) AddNode(ctx context.Context, req *AddNodeRequest, reply *bool) error { 171 | if s.confChangeCallback != nil { 172 | s.confChangeCallback.AddNode(req.ID, []byte(req.Addr)) 173 | } 174 | 175 | *reply = true 176 | return nil 177 | } 178 | 179 | // RemoveNode removes a raft node. 180 | func (s *RpcxBitmapService) RemoveNode(ctx context.Context, req uint64, reply *bool) error { 181 | if s.confChangeCallback != nil { 182 | s.confChangeCallback.RemoveNode(req) 183 | } 184 | 185 | *reply = true 186 | return nil 187 | } 188 | --------------------------------------------------------------------------------