├── .gitignore ├── LICENSE ├── README.md ├── chanconnpool.go ├── copool ├── copool.go └── copool_test.go ├── example └── example.go ├── interfaces.go ├── rediscli ├── rediscli.go └── rediscli_test.go └── types.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /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 | # gopool 2 | * By Golang realize distributed common connection pool. 3 | * Golang分布式的连接池,协程池,内含redis client连接池实现 4 | 5 | * go get github.com/aosen/gopool 6 | 7 | ## example 8 | ``` 9 | var addrs []string = []string{"127.0.0.1:8000", "127.0.0.1:8001", "127.0.0.1:8002", "127.0.0.1:8003"} 10 | 11 | var epool Pooler 12 | 13 | func InitExpPool() (err error) { 14 | if epool == nil { 15 | epool, err = NewChanConnPool(&ConnPoolReq{ 16 | Addrs: addrs, 17 | Create: func(addr string, timeout time.Duration) (interface{}, error) { 18 | cli, err := net.DialTimeout("tcp", addr, timeout) 19 | return cli, err 20 | }, 21 | IsOpen: func(cli interface{}) bool { 22 | if cli != nil { 23 | return true 24 | } 25 | return false 26 | }, 27 | Down: func(cli interface{}) { 28 | c := cli.(net.Conn) 29 | c.Close() 30 | }, 31 | }) 32 | return 33 | } 34 | return 35 | } 36 | 37 | func Get() (cli net.Conn, err error) { 38 | if epool == nil { 39 | err = errors.New("no init epool.") 40 | return 41 | } 42 | cli, err = epool.Get() 43 | return 44 | } 45 | 46 | func Put(cli net.Conn, safe bool) { 47 | if epool == nil { 48 | err := errors.New("no init epool.") 49 | return 50 | } 51 | epool.Put(cli, safe) 52 | } 53 | 54 | func GetHealthy() map[string]bool { 55 | if epool == nil { 56 | return nil 57 | } 58 | return epool.GetHealthy() 59 | } 60 | 61 | func GetConnCount() map[string]int { 62 | if epool == nil { 63 | return nil 64 | } 65 | return epool.GetConnCount() 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /chanconnpool.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | Based on the channel of connection pool 17 | 基于channel的连接池 18 | */ 19 | package gopool 20 | 21 | import ( 22 | "errors" 23 | "math/rand" 24 | "sort" 25 | "sync" 26 | "sync/atomic" 27 | "time" 28 | ) 29 | 30 | //Maximum error number has just been unfrozen connection 31 | //刚被解冻的连接最大出错数 32 | const MaxRetryAfterCooldown = 3 33 | 34 | //downstream address property 35 | //下游地址属性 36 | type addrAttr struct { 37 | //Whether it is not healthy nodes 38 | //是否为不健康节点, 39 | health bool 40 | //After dangerous counter, when the counter reaches threshold will health set to false 41 | //危险计数器,当计数器达到阈值后将health置为false 42 | errcount int64 43 | //Cooling time 44 | //冷却时间点 45 | cooldowntime int64 46 | //Address the current total number of connections 47 | //地址当前总连接数 48 | conncount int32 49 | } 50 | 51 | //Connection properties 52 | //连接属性 53 | type connAttr struct { 54 | //Connect the corresponding address 55 | //连接对应的地址 56 | addr string 57 | //Whether in the pool of true or false is not, When higher concurrency value, considering the performance, the data may not be accurate, but in general is accurate 58 | //是否在池中 true 在 false 不在, 当并发量比较高时, 考虑到性能,此数据可能不太准,但大体来说是准确的 59 | inpool bool 60 | //Whether or not connected to borrow 61 | //是否为借的连接 62 | borrow bool 63 | //Life values, the second 64 | //生命值, 秒 65 | life int64 66 | } 67 | 68 | type ChanConnPool struct { 69 | r *rand.Rand 70 | //The properties of the state address list, corresponding address 71 | //地址列表,对应地址的属性状态 72 | addrs map[string]*addrAttr 73 | //The current legal address list 74 | //当前合法的地址列表 75 | al []string 76 | alocker *sync.RWMutex 77 | //Threshold, the health to nodes threshold will be set to false unhealthy node 78 | //健康阈值,达到阈值将节点置为不健康节点 false 79 | healthyThreshold int64 80 | //The maximum cooling time 81 | //最大冷却时长 82 | maxCooldownTime int64 83 | //Than the minimum health, prevent all removed 84 | //最小健康比, 防止全部摘除 85 | minHealthyRatio float64 86 | //Connection timeout 87 | //连接超时时间 88 | connSvrTimeout time.Duration 89 | //Retry count 90 | //重试次数 91 | retryTimes int 92 | //Health check cycle 93 | //健康检查周期 94 | checkAddrCycle time.Duration 95 | //Connection with address mapping relationship 96 | //连接与地址的映射关系 97 | mapConn map[interface{}]*connAttr 98 | mlocker *sync.RWMutex 99 | //Create a client connection method 100 | //创建客户端连接方法 101 | create func(addr string, timeout time.Duration) (interface{}, error) 102 | //To determine whether a connection closed 103 | //判断连接是否关闭 104 | isOpen func(c interface{}) bool 105 | //Close the connection 106 | //关闭连接 107 | down func(c interface{}) 108 | //The default maximum number of single connection error, error if more than this number will close the connection 109 | //默认最大单连接出错数,如果超过这个出错数就close这个连接 110 | maxConnBeFalseCnt int 111 | //Connection lifecycle 112 | //连接生命周期 113 | connLife int64 114 | //Each corresponding pool downstream nodes 115 | //每个下游节点对应的池子 116 | idle map[string]chan interface{} 117 | locker *sync.Mutex 118 | } 119 | 120 | func NewChanConnPool(req *ConnPoolReq) (*ChanConnPool, error) { 121 | if len(req.Addrs) == 0 { 122 | return nil, errors.New("downstream address is empty.") 123 | } 124 | if req.Create == nil || req.IsOpen == nil || req.Down == nil { 125 | return nil, errors.New("no init create | isopen | down method") 126 | } 127 | cp := &ChanConnPool{ 128 | r: rand.New(rand.NewSource(time.Now().UnixNano())), 129 | addrs: map[string]*addrAttr{}, 130 | alocker: new(sync.RWMutex), 131 | healthyThreshold: req.GetHealthyThreshold(), 132 | maxCooldownTime: req.GetMaxCooldownTime(), 133 | minHealthyRatio: req.GetMinHealthyRatio(), 134 | connSvrTimeout: req.GetConnSvrTimeOut(), 135 | retryTimes: req.GetRetryTimes(), 136 | checkAddrCycle: req.GetCheckAddrCycle(), 137 | mapConn: map[interface{}]*connAttr{}, 138 | mlocker: new(sync.RWMutex), 139 | create: req.Create, 140 | isOpen: req.IsOpen, 141 | down: req.Down, 142 | connLife: req.GetConnLife(), 143 | locker: new(sync.Mutex), 144 | } 145 | for _, addr := range req.Addrs { 146 | cp.addAddr(addr) 147 | } 148 | cp.initIdle(req.GetSize()) 149 | go cp.check() 150 | return cp, nil 151 | } 152 | 153 | //According to the link for the connection properties 154 | //根据连接获取该连接的属性 155 | func (pool *ChanConnPool) getMapConn(c interface{}) (string, bool, bool, bool) { 156 | pool.mlocker.RLock() 157 | defer pool.mlocker.RUnlock() 158 | if connattr, ok := pool.mapConn[c]; ok { 159 | return connattr.addr, connattr.inpool, connattr.borrow, ok 160 | } else { 161 | return "", false, false, false 162 | } 163 | } 164 | 165 | //To obtain the life value of the connection 166 | //获取连接的生命值 167 | func (pool *ChanConnPool) getConnLife(c interface{}) int64 { 168 | pool.mlocker.RLock() 169 | defer pool.mlocker.RUnlock() 170 | if connattr, ok := pool.mapConn[c]; ok { 171 | return connattr.life 172 | } else { 173 | return 0 174 | } 175 | } 176 | 177 | //Set the state of connections in the pool 178 | //设置连接是否在池中的状态 179 | func (pool *ChanConnPool) setMapConnState(c interface{}, in bool) { 180 | pool.mlocker.Lock() 181 | defer pool.mlocker.Unlock() 182 | if attr, ok := pool.mapConn[c]; ok { 183 | attr.inpool = in 184 | } 185 | } 186 | 187 | //Sets whether connect the corresponding address and connection to borrow 188 | //设置连接对应的地址和是否为借的连接 189 | func (pool *ChanConnPool) setMapConnAddr(c interface{}, addr string, borrow bool) { 190 | pool.mlocker.Lock() 191 | defer pool.mlocker.Unlock() 192 | if attr, ok := pool.mapConn[c]; !ok { 193 | pool.mapConn[c] = &connAttr{ 194 | addr: addr, 195 | inpool: false, 196 | borrow: borrow, 197 | life: time.Now().Unix(), 198 | } 199 | } else { 200 | attr.borrow = borrow 201 | } 202 | } 203 | 204 | func (pool *ChanConnPool) deleteMapConn(c interface{}) { 205 | pool.mlocker.Lock() 206 | defer pool.mlocker.Unlock() 207 | delete(pool.mapConn, c) 208 | } 209 | 210 | func (pool *ChanConnPool) lenMapConn() int { 211 | pool.mlocker.RLock() 212 | defer pool.mlocker.RUnlock() 213 | l := len(pool.mapConn) 214 | return l 215 | } 216 | 217 | func (pool *ChanConnPool) getAddr(k string) bool { 218 | pool.alocker.RLock() 219 | defer pool.alocker.RUnlock() 220 | attr, ok := pool.addrs[k] 221 | if !ok { 222 | return ok 223 | } 224 | return attr.health 225 | } 226 | 227 | func (pool *ChanConnPool) getAllAddr() map[string]*addrAttr { 228 | pool.alocker.RLock() 229 | defer pool.alocker.RUnlock() 230 | addrs := pool.addrs 231 | return addrs 232 | } 233 | 234 | func (pool *ChanConnPool) addAddr(addr string) { 235 | pool.alocker.Lock() 236 | defer pool.alocker.Unlock() 237 | pool.addrs[addr] = &addrAttr{true, 0, int64(0), 0} 238 | if pool.al == nil { 239 | pool.al = []string{} 240 | } 241 | pool.al = append(pool.al, addr) 242 | sort.Strings(pool.al) 243 | } 244 | 245 | func (pool *ChanConnPool) tryIncAddrConnCount(addr string) bool { 246 | pool.locker.Lock() 247 | defer pool.locker.Unlock() 248 | if pool.getAddrConnCount(addr) < pool.capIdle(addr) { 249 | if attr, ok := pool.addrs[addr]; ok { 250 | atomic.AddInt32(&attr.conncount, 1) 251 | } 252 | return true 253 | } 254 | return false 255 | } 256 | 257 | func (pool *ChanConnPool) decAddrConnCount(addr string) { 258 | if attr, ok := pool.addrs[addr]; ok { 259 | atomic.AddInt32(&attr.conncount, -1) 260 | } 261 | } 262 | 263 | func (pool *ChanConnPool) getAddrConnCount(addr string) int { 264 | if attr, ok := pool.addrs[addr]; ok { 265 | return int(attr.conncount) 266 | } 267 | return 0 268 | } 269 | 270 | func (pool *ChanConnPool) setAddrdown(addr string) { 271 | pool.alocker.Lock() 272 | defer pool.alocker.Unlock() 273 | if attr, ok := pool.addrs[addr]; ok { 274 | attr.cooldowntime = time.Now().Unix() 275 | attr.health = false 276 | attr.errcount = 0 277 | } 278 | al := []string{} 279 | for k, v := range pool.addrs { 280 | if v.health { 281 | al = append(al, k) 282 | } 283 | } 284 | sort.Strings(al) 285 | pool.al = al 286 | } 287 | 288 | func (pool *ChanConnPool) incAddrUnSafeCount(addr string) { 289 | pool.alocker.Lock() 290 | defer pool.alocker.Unlock() 291 | if attr, ok := pool.addrs[addr]; ok { 292 | attr.errcount++ 293 | } 294 | } 295 | 296 | func (pool *ChanConnPool) decAddrUnSafeCount(addr string) { 297 | pool.alocker.Lock() 298 | defer pool.alocker.Unlock() 299 | if attr, ok := pool.addrs[addr]; ok { 300 | if attr.errcount > 0 { 301 | attr.errcount-- 302 | } 303 | } 304 | } 305 | 306 | func (pool *ChanConnPool) getAddrErrCount(addr string) int64 { 307 | pool.alocker.RLock() 308 | defer pool.alocker.RUnlock() 309 | if attr, ok := pool.addrs[addr]; ok { 310 | return attr.errcount 311 | } 312 | return int64(0) 313 | } 314 | 315 | func (pool *ChanConnPool) getCooldownTime(addr string) int64 { 316 | pool.alocker.RLock() 317 | defer pool.alocker.RUnlock() 318 | if attr, ok := pool.addrs[addr]; ok { 319 | return attr.cooldowntime 320 | } 321 | return int64(0) 322 | } 323 | 324 | func (pool *ChanConnPool) getAddrHealth(addr string) bool { 325 | pool.alocker.RLock() 326 | defer pool.alocker.RUnlock() 327 | if attr, ok := pool.addrs[addr]; ok { 328 | return attr.health 329 | } 330 | return true 331 | } 332 | 333 | func (pool *ChanConnPool) getHealthyRatio() float64 { 334 | pool.alocker.RLock() 335 | defer pool.alocker.RUnlock() 336 | healthcount := 0 337 | for _, attr := range pool.addrs { 338 | if attr.health { 339 | healthcount++ 340 | } 341 | } 342 | return float64(healthcount) / float64(len(pool.addrs)) 343 | } 344 | 345 | func (pool *ChanConnPool) resetAddr(addr string) { 346 | pool.alocker.Lock() 347 | defer pool.alocker.Unlock() 348 | if attr, ok := pool.addrs[addr]; ok { 349 | attr.cooldowntime = int64(0) 350 | attr.errcount = pool.healthyThreshold - int64(MaxRetryAfterCooldown) 351 | attr.health = true 352 | } 353 | al := []string{} 354 | for k, v := range pool.addrs { 355 | if v.health { 356 | al = append(al, k) 357 | } 358 | } 359 | sort.Strings(al) 360 | pool.al = al 361 | } 362 | 363 | func (pool *ChanConnPool) getRAddr(addrs map[string]*addrAttr) (string, error) { 364 | pool.alocker.Lock() 365 | defer pool.alocker.Unlock() 366 | host_len := len(addrs) 367 | if host_len == 0 { 368 | return "", errors.New("No address available.") 369 | } else { 370 | if len(pool.al) != 0 { 371 | return pool.al[pool.r.Intn(len(pool.al))], nil 372 | } 373 | } 374 | return "", errors.New("addrs is empty.") 375 | } 376 | 377 | //checker 378 | //1.Whether unhealthy connections than the cooling cycle, if more than, the attribute the count left healthyThreshold - MaxRetryAfterCooldown health buy true cooldowntime = 0 379 | //2.Whether health attribute the count of nodes exceeds the threshold, if more than threshold value will be false health, and set up the cooling time 380 | //1.判断非健康连接是否超过了冷却周期,如果超过了,将属性count置healthyThreshold-MaxRetryAfterCooldown health置true cooldowntime=0 381 | //2.判断健康节点中的属性count是否超过阈值,如果超过阈值将health置false,并设置冷却时间点 382 | func (pool *ChanConnPool) check() { 383 | for { 384 | time.Sleep(pool.checkAddrCycle) 385 | for addr := range pool.getAllAddr() { 386 | if !pool.getAddrHealth(addr) { 387 | if time.Now().Unix()-pool.getCooldownTime(addr) > pool.maxCooldownTime { 388 | pool.resetAddr(addr) 389 | } 390 | } else { 391 | if pool.getAddrErrCount(addr) > pool.healthyThreshold { 392 | if pool.getHealthyRatio() > pool.minHealthyRatio && len(pool.al) > 1 { 393 | pool.setAddrdown(addr) 394 | } else { 395 | pool.resetAddr(addr) 396 | } 397 | } 398 | } 399 | } 400 | } 401 | } 402 | 403 | func (pool *ChanConnPool) newConn(addr string, timeout time.Duration) (interface{}, error) { 404 | for i := 0; i < 1+pool.retryTimes; i++ { 405 | client, err := pool.create(addr, timeout) 406 | if err != nil { 407 | continue 408 | } else { 409 | return client, nil 410 | } 411 | } 412 | return nil, errors.New("connect server fail.") 413 | } 414 | 415 | func (pool *ChanConnPool) getActive() int { 416 | active := pool.lenMapConn() 417 | for addr := range pool.getIdle() { 418 | active = active - pool.lenIdle(addr) 419 | } 420 | return active 421 | } 422 | 423 | func (pool *ChanConnPool) initIdle(size int) { 424 | idle := map[string]chan interface{}{} 425 | for addr := range pool.getAllAddr() { 426 | idle[addr] = make(chan interface{}, size) 427 | } 428 | pool.idle = idle 429 | } 430 | 431 | func (pool *ChanConnPool) getIdle() map[string]chan interface{} { 432 | return pool.idle 433 | } 434 | 435 | func (pool *ChanConnPool) inIdle(addr string, c interface{}) { 436 | if pool.lenIdle(addr) >= pool.capIdle(addr) { 437 | return 438 | } 439 | if queue, ok := pool.getIdle()[addr]; ok { 440 | queue <- c 441 | pool.setMapConnState(c, true) 442 | } 443 | } 444 | 445 | func (pool *ChanConnPool) outIdle(addr string) (interface{}, error) { 446 | if queue, ok := pool.getIdle()[addr]; ok { 447 | select { 448 | case client := <-queue: 449 | pool.setMapConnState(client, false) 450 | return client, nil 451 | default: 452 | cli, err := pool.newConn(addr, pool.connSvrTimeout) 453 | if err != nil { 454 | return nil, err 455 | } 456 | pool.setMapConnAddr(cli, addr, true) 457 | return cli, err 458 | } 459 | } 460 | return nil, errors.New("addr illegal.") 461 | } 462 | 463 | func (pool *ChanConnPool) lenIdle(addr string) int { 464 | if queue, ok := pool.getIdle()[addr]; ok { 465 | l := len(queue) 466 | return l 467 | } 468 | return 0 469 | } 470 | 471 | func (pool *ChanConnPool) capIdle(addr string) int { 472 | if queue, ok := pool.getIdle()[addr]; ok { 473 | c := cap(queue) 474 | return c 475 | } 476 | return 0 477 | } 478 | 479 | func (pool *ChanConnPool) reachLife(c interface{}) (reach bool) { 480 | life := pool.getConnLife(c) 481 | if pool.connLife == 0 { 482 | reach = false 483 | } else { 484 | reach = (time.Now().Unix() - life) > pool.connLife 485 | } 486 | return 487 | } 488 | 489 | func (pool *ChanConnPool) getAddrByConn(c interface{}) (string, bool) { 490 | addr, _, _, ok := pool.getMapConn(c) 491 | return addr, ok 492 | } 493 | 494 | func (pool *ChanConnPool) Get() (interface{}, error) { 495 | if addr, err := pool.getRAddr(pool.getAllAddr()); err != nil { 496 | return nil, err 497 | } else { 498 | cli, err := pool.outIdle(addr) 499 | if err != nil { 500 | pool.incAddrUnSafeCount(addr) 501 | return nil, err 502 | } else { 503 | return cli, nil 504 | } 505 | } 506 | } 507 | 508 | func (pool *ChanConnPool) Put(cli interface{}, safe bool) { 509 | if addr, _, borrow, ok := pool.getMapConn(cli); ok { 510 | if borrow { 511 | if pool.tryIncAddrConnCount(addr) { 512 | pool.setMapConnAddr(cli, addr, false) 513 | } else { 514 | pool.down(cli) 515 | pool.deleteMapConn(cli) 516 | return 517 | } 518 | } 519 | if safe { 520 | pool.decAddrUnSafeCount(addr) 521 | } else { 522 | pool.incAddrUnSafeCount(addr) 523 | } 524 | if !pool.isOpen(cli) || !safe || pool.reachLife(cli) { 525 | pool.down(cli) 526 | pool.deleteMapConn(cli) 527 | pool.decAddrConnCount(addr) 528 | } else { 529 | pool.inIdle(addr, cli) 530 | } 531 | } 532 | return 533 | } 534 | 535 | func (pool *ChanConnPool) GetHealthy() map[string]bool { 536 | healthy := map[string]bool{} 537 | for k, v := range pool.getAllAddr() { 538 | healthy[k] = v.health 539 | } 540 | return healthy 541 | } 542 | 543 | func (pool *ChanConnPool) GetUnhealthyNodeCount() (int, []string) { 544 | retCnt := 0 545 | retIp := []string{} 546 | 547 | for k, v := range pool.getAllAddr() { 548 | if !v.health { 549 | retCnt++ 550 | retIp = append(retIp, k) 551 | } 552 | } 553 | 554 | return retCnt, retIp 555 | } 556 | 557 | func (pool *ChanConnPool) GetConnCount() map[string]int { 558 | res := map[string]int{} 559 | for addr := range pool.getAllAddr() { 560 | res[addr] = pool.getAddrConnCount(addr) 561 | } 562 | return res 563 | } 564 | 565 | func (pool *ChanConnPool) GetConnAllCount() int { 566 | ret := 0 567 | for addr := range pool.getAllAddr() { 568 | ret += pool.getAddrConnCount(addr) 569 | } 570 | return ret 571 | } 572 | -------------------------------------------------------------------------------- /copool/copool.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | Golang Coroutines encapsulation, implementation coroutine pool. 17 | 对Golang的协程封装,实现协程池. 18 | */ 19 | package copool 20 | 21 | import ( 22 | "fmt" 23 | "time" 24 | 25 | "github.com/aosen/gopool" 26 | ) 27 | 28 | const ( 29 | DEFAULT_ADDR_CNT = 256 30 | DEFAULT_ADDR = "copool" 31 | DEFAULT_SIZE = 256 32 | DEFAULT_COROUTINE_LIFE = 0 33 | ) 34 | 35 | var cp *copool 36 | 37 | func init() { 38 | initCopool() 39 | } 40 | 41 | type copool struct { 42 | pool *gopool.ChanConnPool 43 | } 44 | 45 | func create(addr string, timeout time.Duration) (cor interface{}, err error) { 46 | return newCoroutine(), nil 47 | } 48 | 49 | func isOpen(c interface{}) bool { 50 | return true 51 | } 52 | 53 | func down(c interface{}) { 54 | c.(*coroutine).closeCoroutine() 55 | } 56 | 57 | func setMaxCoroutineCnt(cnt int) (err error) { 58 | cp.pool, err = gopool.NewChanConnPool(&gopool.ConnPoolReq{ 59 | Addrs: func() (addrs []string) { 60 | addrs = make([]string, DEFAULT_ADDR_CNT) 61 | for i := 0; i < DEFAULT_ADDR_CNT; i++ { 62 | addrs[i] = fmt.Sprintf("%s_%d", DEFAULT_ADDR, i) 63 | } 64 | return 65 | }(), 66 | Size: cnt, 67 | Create: create, 68 | IsOpen: isOpen, 69 | Down: down, 70 | ConnLife: DEFAULT_COROUTINE_LIFE, 71 | }) 72 | return 73 | } 74 | 75 | func initCopool() { 76 | cp = &copool{} 77 | setMaxCoroutineCnt(DEFAULT_SIZE) 78 | } 79 | 80 | func (c *copool) get() (cor *coroutine) { 81 | corer, _ := c.pool.Get() 82 | cor = corer.(*coroutine) 83 | return 84 | } 85 | 86 | func (c *copool) put(cor *coroutine) { 87 | c.pool.Put(cor, true) 88 | } 89 | 90 | type task struct { 91 | fn func(interface{}) 92 | args interface{} 93 | } 94 | 95 | func newTask(fn func(interface{}), args interface{}) (t *task) { 96 | t = &task{ 97 | fn: fn, 98 | args: args, 99 | } 100 | return 101 | } 102 | 103 | type coroutine struct { 104 | ch chan *task 105 | } 106 | 107 | func newCoroutine() (cor *coroutine) { 108 | cor = &coroutine{ 109 | ch: make(chan *task, 1), 110 | } 111 | go cor.run() 112 | return 113 | } 114 | 115 | func (cor *coroutine) run() { 116 | for { 117 | task, ok := <-cor.ch 118 | if !ok { 119 | break 120 | } 121 | task.fn(task.args) 122 | cp.put(cor) 123 | } 124 | } 125 | 126 | func (cor *coroutine) supergo(f func(interface{}), args interface{}) { 127 | cor.ch <- newTask(f, args) 128 | } 129 | 130 | func (cor *coroutine) closeCoroutine() { 131 | close(cor.ch) 132 | } 133 | 134 | func SetMaxCoroutineCnt(cnt int) error { 135 | return setMaxCoroutineCnt(cnt) 136 | } 137 | 138 | func Go(fn func(interface{}), args interface{}) { 139 | cp.get().supergo(fn, args) 140 | } 141 | 142 | func GetCoroutineCnt() int { 143 | return cp.pool.GetConnAllCount() 144 | } 145 | -------------------------------------------------------------------------------- /copool/copool_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | copool 17 | */ 18 | package copool 19 | 20 | import ( 21 | "fmt" 22 | "sync" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | const CNT = 10000 28 | 29 | type Args struct { 30 | val int 31 | wg *sync.WaitGroup 32 | } 33 | 34 | func do(args interface{}) { 35 | sum := 0 36 | it := args.(*Args) 37 | for i := it.val; i < it.val+100000; i++ { 38 | sum += i / 2 / 2 39 | } 40 | if it.wg != nil { 41 | it.wg.Done() 42 | } 43 | return 44 | } 45 | 46 | func TestSeq(t *testing.T) { 47 | now := time.Now().UnixNano() 48 | for i := 0; i < CNT; i++ { 49 | do(&Args{i, nil}) 50 | } 51 | timeCost := (time.Now().UnixNano() - now) / int64(time.Nanosecond) 52 | fmt.Println("TestSeq cost:", timeCost, "ns") 53 | } 54 | 55 | func TestGo(t *testing.T) { 56 | wg := new(sync.WaitGroup) 57 | now := time.Now().UnixNano() 58 | for i := 0; i < CNT; i++ { 59 | wg.Add(1) 60 | go do(&Args{i, wg}) 61 | } 62 | wg.Wait() 63 | timeCost := (time.Now().UnixNano() - now) / int64(time.Nanosecond) 64 | fmt.Println("TestGo cost:", timeCost, "ns") 65 | } 66 | 67 | func TestCopool(t *testing.T) { 68 | SetMaxCoroutineCnt(1) 69 | wg := new(sync.WaitGroup) 70 | now := time.Now().UnixNano() 71 | for i := 0; i < CNT; i++ { 72 | wg.Add(1) 73 | Go(do, &Args{i, wg}) 74 | } 75 | wg.Wait() 76 | timeCost := (time.Now().UnixNano() - now) / int64(time.Nanosecond) 77 | fmt.Println("TestCopool cost:", timeCost, "ns", "Coroutines Cnt:", GetCoroutineCnt()) 78 | } 79 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | Based on the channel of connection pool 17 | 基于channel的连接池 18 | */ 19 | package main 20 | 21 | import ( 22 | "errors" 23 | "net" 24 | "time" 25 | ) 26 | 27 | var addrs []string = []string{"127.0.0.1:8000", "127.0.0.1:8001", "127.0.0.1:8002", "127.0.0.1:8003"} 28 | 29 | var epool Pooler 30 | 31 | func InitExpPool() (err error) { 32 | if epool == nil { 33 | epool, err = NewChanConnPool(&ConnPoolReq{ 34 | Addrs: addrs, 35 | Create: func(addr string, timeout time.Duration) (interface{}, error) { 36 | cli, err := net.DialTimeout("tcp", addr, timeout) 37 | return cli, err 38 | }, 39 | IsOpen: func(cli interface{}) bool { 40 | if cli != nil { 41 | return true 42 | } 43 | return false 44 | }, 45 | Down: func(cli interface{}) { 46 | c := cli.(net.Conn) 47 | c.Close() 48 | }, 49 | }) 50 | return 51 | } 52 | return 53 | } 54 | 55 | func Get() (cli net.Conn, err error) { 56 | if epool == nil { 57 | err = errors.New("no init epool.") 58 | return 59 | } 60 | cli, err = epool.Get() 61 | return 62 | } 63 | 64 | func Put(cli net.Conn, safe bool) { 65 | if epool == nil { 66 | err := errors.New("no init epool.") 67 | return 68 | } 69 | epool.Put(cli, safe) 70 | } 71 | 72 | func GetHealthy() map[string]bool { 73 | if epool == nil { 74 | return nil 75 | } 76 | return epool.GetHealthy() 77 | } 78 | 79 | func GetConnCount() map[string]int { 80 | if epool == nil { 81 | return nil 82 | } 83 | return epool.GetConnCount() 84 | } 85 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | General distributed connection manager interface 17 | 通用分布式连接池管理接口 18 | */ 19 | package gopool 20 | 21 | type Pooler interface { 22 | //Get connected 23 | //获取连接 24 | Get() (interface{}, error) 25 | //Recycling connection 26 | //回收连接 27 | Put(client interface{}, safe bool) 28 | //Access to the health status of the downstream nodes 29 | //获取下游节点的健康状态 30 | GetHealthy() map[string]bool 31 | //The current number of connections for the downstream nodes 32 | //获取各下游节点当前连接数 33 | GetConnCount() map[string]int 34 | } 35 | -------------------------------------------------------------------------------- /rediscli/rediscli.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | redis client 17 | */ 18 | package rediscli 19 | 20 | import ( 21 | "errors" 22 | "strconv" 23 | "time" 24 | 25 | "github.com/aosen/gopool" 26 | "github.com/garyburd/redigo/redis" 27 | "github.com/juju/ratelimit" 28 | ) 29 | 30 | const ( 31 | DEFAULT_ADDR = "127.0.0.1:6379" 32 | DEFAULT_SIZE = 100 33 | DEFAULT_CONNTIMEOUT = 100 34 | DEFAULT_READTIMEOUT = 50 35 | DEFAULT_WRITETIMEOUT = 50 36 | DEFAULT_MAXQPS = 100000 37 | ) 38 | 39 | type Req struct { 40 | Addrs []string 41 | Size int 42 | ConnTimeOut int64 43 | ReadTimeOut int64 44 | WriteTimeOut int64 45 | MaxQps int64 46 | } 47 | 48 | func (req *Req) GetAddrs() []string { 49 | if len(req.Addrs) == 0 { 50 | return []string{DEFAULT_ADDR} 51 | } 52 | return req.Addrs 53 | } 54 | 55 | func (req *Req) GetSize() int { 56 | if req.Size <= 0 { 57 | return DEFAULT_SIZE 58 | } 59 | return req.Size 60 | } 61 | 62 | func (req *Req) GetConnTimeOut() int64 { 63 | if req.ConnTimeOut <= int64(0) { 64 | return DEFAULT_CONNTIMEOUT 65 | } 66 | return req.ConnTimeOut 67 | } 68 | 69 | func (req *Req) GetReadTimeOut() int64 { 70 | if req.ReadTimeOut <= int64(0) { 71 | return DEFAULT_READTIMEOUT 72 | } 73 | return req.ReadTimeOut 74 | } 75 | 76 | func (req *Req) GetWriteTimeOut() int64 { 77 | if req.WriteTimeOut <= int64(0) { 78 | return DEFAULT_WRITETIMEOUT 79 | } 80 | return req.WriteTimeOut 81 | } 82 | 83 | func (req *Req) GetMaxQps() int64 { 84 | if req.MaxQps <= int64(0) { 85 | return DEFAULT_MAXQPS 86 | } 87 | return req.MaxQps 88 | } 89 | 90 | type RedisCli struct { 91 | pool *gopool.ChanConnPool 92 | conntimeout time.Duration 93 | readtimeout time.Duration 94 | writetimeout time.Duration 95 | maxqps int64 96 | bucket *ratelimit.Bucket 97 | } 98 | 99 | func NewRedisCli(req *Req) (rc *RedisCli, err error) { 100 | if req == nil { 101 | err = errors.New("req is nil") 102 | } 103 | rc = &RedisCli{ 104 | conntimeout: time.Duration(req.GetConnTimeOut()) * time.Millisecond, 105 | readtimeout: time.Duration(req.GetReadTimeOut()) * time.Millisecond, 106 | writetimeout: time.Duration(req.GetWriteTimeOut()) * time.Millisecond, 107 | maxqps: req.GetMaxQps(), 108 | bucket: ratelimit.NewBucket(time.Second/time.Duration(req.GetMaxQps()), req.GetMaxQps()), 109 | } 110 | create := func(addr string, timeout time.Duration) (cli interface{}, err error) { 111 | cli, err = redis.DialTimeout("tcp", addr, timeout, rc.readtimeout, rc.writetimeout) 112 | return 113 | } 114 | isOpen := func(c interface{}) bool { 115 | if c != nil { 116 | return true 117 | } 118 | return false 119 | } 120 | down := func(c interface{}) { 121 | if cli, ok := c.(redis.Conn); ok { 122 | cli.Close() 123 | } 124 | } 125 | rc.pool, err = gopool.NewChanConnPool(&gopool.ConnPoolReq{ 126 | Addrs: req.GetAddrs(), 127 | ConnSvrTimeOut: rc.conntimeout, 128 | Size: req.GetSize(), 129 | Create: create, 130 | IsOpen: isOpen, 131 | Down: down, 132 | }) 133 | return 134 | } 135 | 136 | func (rc *RedisCli) get() (cli redis.Conn, err error) { 137 | var ( 138 | c interface{} 139 | ok bool 140 | ) 141 | if rc.bucket.TakeAvailable(1) < 1 { 142 | err = errors.New("rate limit") 143 | return 144 | } 145 | c, err = rc.pool.Get() 146 | if err != nil { 147 | return 148 | } 149 | cli, ok = c.(redis.Conn) 150 | if !ok { 151 | err = errors.New("cli is nil.") 152 | } 153 | return 154 | } 155 | 156 | func (rc *RedisCli) put(cli redis.Conn, err *error) { 157 | safe := false 158 | if *err == nil || *err == redis.ErrNil { 159 | safe = true 160 | } 161 | rc.pool.Put(cli, safe) 162 | } 163 | 164 | func (rc *RedisCli) GetHealthy() map[string]bool { 165 | return rc.pool.GetHealthy() 166 | } 167 | 168 | func (rc *RedisCli) GetUnhealthyNodeCount() (int, []string) { 169 | return rc.pool.GetUnhealthyNodeCount() 170 | } 171 | 172 | func (rc *RedisCli) GetConnCount() map[string]int { 173 | return rc.pool.GetConnCount() 174 | } 175 | 176 | func (rc *RedisCli) GetConnAllCount() int { 177 | return rc.pool.GetConnAllCount() 178 | } 179 | 180 | func (rc *RedisCli) FlushAll() (err error) { 181 | var ( 182 | cli redis.Conn 183 | ) 184 | cli, err = rc.get() 185 | if err != nil { 186 | return 187 | } 188 | defer rc.put(cli, &err) 189 | _, err = redis.String(cli.Do("FLUSHALL")) 190 | return 191 | } 192 | 193 | func (rc *RedisCli) Exists(key string) (ex bool, err error) { 194 | var ( 195 | cli redis.Conn 196 | ret int 197 | ) 198 | cli, err = rc.get() 199 | if err != nil { 200 | return 201 | } 202 | defer rc.put(cli, &err) 203 | ret, err = redis.Int(cli.Do("EXISTS", key)) 204 | if ret == 1 { 205 | ex = true 206 | return 207 | } 208 | return 209 | } 210 | 211 | func (rc *RedisCli) Expire(key string, expiresecond int) (err error) { 212 | var cli redis.Conn 213 | cli, err = rc.get() 214 | if err != nil { 215 | return 216 | } 217 | defer rc.put(cli, &err) 218 | _, err = redis.Int(cli.Do("EXPIRE", key, expiresecond)) 219 | return 220 | } 221 | 222 | func (rc *RedisCli) TTL(key string) (reply int, err error) { 223 | var cli redis.Conn 224 | cli, err = rc.get() 225 | if err != nil { 226 | return 227 | } 228 | defer rc.put(cli, &err) 229 | reply, err = redis.Int((cli.Do("TTL", key))) 230 | return 231 | } 232 | 233 | func (rc *RedisCli) Set(args ...interface{}) (err error) { 234 | var ( 235 | cli redis.Conn 236 | ) 237 | cli, err = rc.get() 238 | if err != nil { 239 | return 240 | } 241 | defer rc.put(cli, &err) 242 | _, err = redis.String((cli.Do("SET", args...))) 243 | return 244 | } 245 | 246 | func (rc *RedisCli) Get(key string) (reply string, err error) { 247 | var cli redis.Conn 248 | cli, err = rc.get() 249 | if err != nil { 250 | return 251 | } 252 | defer rc.put(cli, &err) 253 | reply, err = redis.String(cli.Do("GET", key)) 254 | return 255 | } 256 | 257 | func (rc *RedisCli) MGet(keys ...interface{}) (replys []string, err error) { 258 | var cli redis.Conn 259 | cli, err = rc.get() 260 | if err != nil { 261 | return 262 | } 263 | defer rc.put(cli, &err) 264 | replys, err = redis.Strings(cli.Do("MGET", keys...)) 265 | return 266 | } 267 | 268 | func (rc *RedisCli) Setnx(key, value string) (err error) { 269 | var ( 270 | cli redis.Conn 271 | ret int 272 | ) 273 | cli, err = rc.get() 274 | if err != nil { 275 | return 276 | } 277 | defer rc.put(cli, &err) 278 | ret, err = redis.Int((cli.Do("SETNX", key, value))) 279 | if ret == 0 { 280 | err = errors.New("exist.") 281 | } 282 | return 283 | } 284 | 285 | func (rc *RedisCli) Del(key string) (err error) { 286 | var cli redis.Conn 287 | cli, err = rc.get() 288 | if err != nil { 289 | return 290 | } 291 | defer rc.put(cli, &err) 292 | _, err = redis.Int(cli.Do("DEL", key)) 293 | return 294 | } 295 | 296 | func (rc *RedisCli) Incr(key string) (reply int, err error) { 297 | var cli redis.Conn 298 | cli, err = rc.get() 299 | if err != nil { 300 | return 301 | } 302 | defer rc.put(cli, &err) 303 | reply, err = redis.Int((cli.Do("INCR", key))) 304 | return 305 | } 306 | 307 | func (rc *RedisCli) IncrByfloat(key string, value float64) (reply string, err error) { 308 | var cli redis.Conn 309 | cli, err = rc.get() 310 | if err != nil { 311 | return 312 | } 313 | defer rc.put(cli, &err) 314 | reply, err = redis.String(cli.Do("INCRBYFLOAT", key, value)) 315 | return 316 | } 317 | 318 | func (rc *RedisCli) IncrBy(key string, value int) (reply int, err error) { 319 | var cli redis.Conn 320 | cli, err = rc.get() 321 | if err != nil { 322 | return 323 | } 324 | defer rc.put(cli, &err) 325 | reply, err = redis.Int(cli.Do("INCRBY", key, value)) 326 | return 327 | } 328 | 329 | func (rc *RedisCli) SAdd(key string, args ...interface{}) (err error) { 330 | var cli redis.Conn 331 | cli, err = rc.get() 332 | if err != nil { 333 | return 334 | } 335 | defer rc.put(cli, &err) 336 | _, err = redis.Int((cli.Do("SADD", append([]interface{}{interface{}(key)}, args...)...))) 337 | return 338 | } 339 | 340 | func (rc *RedisCli) SRem(key string, args ...interface{}) (err error) { 341 | var cli redis.Conn 342 | cli, err = rc.get() 343 | if err != nil { 344 | return 345 | } 346 | defer rc.put(cli, &err) 347 | _, err = redis.Int((cli.Do("SREM", append([]interface{}{interface{}(key)}, args...)...))) 348 | return 349 | } 350 | 351 | func (rc *RedisCli) SPop(key string) (reply string, err error) { 352 | var cli redis.Conn 353 | cli, err = rc.get() 354 | if err != nil { 355 | return 356 | } 357 | defer rc.put(cli, &err) 358 | reply, err = redis.String(cli.Do("SPOP", key)) 359 | return 360 | } 361 | 362 | func (rc *RedisCli) SCard(key string) (reply int, err error) { 363 | var cli redis.Conn 364 | cli, err = rc.get() 365 | if err != nil { 366 | return 367 | } 368 | defer rc.put(cli, &err) 369 | reply, err = redis.Int(cli.Do("SCARD", key)) 370 | return 371 | } 372 | 373 | func (rc *RedisCli) SMembers(key string) (reply []string, err error) { 374 | var cli redis.Conn 375 | cli, err = rc.get() 376 | if err != nil { 377 | return 378 | } 379 | defer rc.put(cli, &err) 380 | reply, err = redis.Strings(cli.Do("SMEMBERS", key)) 381 | return 382 | } 383 | 384 | func (rc *RedisCli) SUnionstore(dkey string, args ...interface{}) (count int64, err error) { 385 | var cli redis.Conn 386 | cli, err = rc.get() 387 | if err != nil { 388 | return 389 | } 390 | defer rc.put(cli, &err) 391 | count, err = redis.Int64((cli.Do("SUNIONSTORE", append([]interface{}{interface{}(dkey)}, args...)...))) 392 | return 393 | } 394 | 395 | //ZADD key score member [[score member] [score member] ...] 396 | func (rc *RedisCli) ZAdd(key string, ttl int64, args ...interface{}) (err error) { 397 | var cli redis.Conn 398 | cli, err = rc.get() 399 | if err != nil { 400 | return 401 | } 402 | defer rc.put(cli, &err) 403 | _, err = redis.Int((cli.Do("ZADD", append([]interface{}{interface{}(key)}, args...)...))) 404 | 405 | //默认过期时间为86400 406 | err = rc.Expire(key, int(ttl)) 407 | return 408 | } 409 | 410 | func (rc *RedisCli) ZRange(key string, start, stop int) (reply []string, err error) { 411 | var cli redis.Conn 412 | cli, err = rc.get() 413 | if err != nil { 414 | return 415 | } 416 | defer rc.put(cli, &err) 417 | reply, err = redis.Strings(cli.Do("ZRANGE", key, start, stop)) 418 | return 419 | } 420 | 421 | func (rc *RedisCli) ZRangeWithScores(key string, start, stop int) (reply map[string]string, err error) { 422 | var cli redis.Conn 423 | cli, err = rc.get() 424 | if err != nil { 425 | return 426 | } 427 | defer rc.put(cli, &err) 428 | reply, err = redis.StringMap(cli.Do("ZRANGE", key, start, stop, "WITHSCORES")) 429 | return 430 | } 431 | 432 | func (rc *RedisCli) ZRangeByScore(key string, min, max int, minopen, maxopen bool) (reply []string, err error) { 433 | var cli redis.Conn 434 | cli, err = rc.get() 435 | if err != nil { 436 | return 437 | } 438 | defer rc.put(cli, &err) 439 | minstr := strconv.FormatInt(int64(min), 10) 440 | maxstr := strconv.FormatInt(int64(max), 10) 441 | if minopen { 442 | minstr = "(" + strconv.FormatInt(int64(min), 10) 443 | } 444 | if maxopen { 445 | maxstr = "(" + strconv.FormatInt(int64(max), 10) 446 | } 447 | if 0 == min { 448 | minstr = "-inf" 449 | } 450 | if -1 == max { 451 | maxstr = "+inf" 452 | } 453 | reply, err = redis.Strings(cli.Do("ZRANGEBYSCORE", key, minstr, maxstr)) 454 | return 455 | } 456 | 457 | func (rc *RedisCli) ZRangeByScoreWithScores(key string, min, max int, minopen, maxopen bool) (reply map[string]string, err error) { 458 | var cli redis.Conn 459 | cli, err = rc.get() 460 | if err != nil { 461 | return 462 | } 463 | defer rc.put(cli, &err) 464 | minstr := strconv.FormatInt(int64(min), 10) 465 | maxstr := strconv.FormatInt(int64(max), 10) 466 | if minopen { 467 | minstr = "(" + strconv.FormatInt(int64(min), 10) 468 | } 469 | if maxopen { 470 | maxstr = "(" + strconv.FormatInt(int64(max), 10) 471 | } 472 | reply, err = redis.StringMap(cli.Do("ZRANGEBYSCORE", key, minstr, maxstr, "WITHSCORES")) 473 | return 474 | } 475 | 476 | func (rc *RedisCli) ZRem(keys ...interface{}) (reply int64, err error) { 477 | var cli redis.Conn 478 | cli, err = rc.get() 479 | if err != nil { 480 | return 481 | } 482 | defer rc.put(cli, &err) 483 | reply, err = redis.Int64(cli.Do("ZREM", keys...)) 484 | return 485 | } 486 | 487 | func (rc *RedisCli) ZRemRangeByScore(key string, min, max int64) (reply int64, err error) { 488 | var cli redis.Conn 489 | cli, err = rc.get() 490 | if err != nil { 491 | return 492 | } 493 | defer rc.put(cli, &err) 494 | minstr := strconv.FormatInt(min, 10) 495 | maxstr := strconv.FormatInt(max, 10) 496 | if 0 == min { 497 | minstr = "-inf" 498 | } 499 | if -1 == max { 500 | maxstr = "+inf" 501 | } 502 | reply, err = redis.Int64(cli.Do("ZREMRANGEBYSCORE", key, minstr, maxstr)) 503 | return 504 | } 505 | 506 | func (rc *RedisCli) ZCount(key string, min, max int64) (reply int64, err error) { 507 | var cli redis.Conn 508 | cli, err = rc.get() 509 | if err != nil { 510 | return 511 | } 512 | defer rc.put(cli, &err) 513 | minstr := strconv.FormatInt(min, 10) 514 | maxstr := strconv.FormatInt(max, 10) 515 | if 0 == min { 516 | minstr = "-inf" 517 | } 518 | if -1 == max { 519 | maxstr = "+inf" 520 | } 521 | reply, err = redis.Int64(cli.Do("ZCOUNT", key, minstr, maxstr)) 522 | return 523 | } 524 | 525 | func (rc *RedisCli) HSet(key, subkey, value string) (err error) { 526 | var cli redis.Conn 527 | cli, err = rc.get() 528 | if err != nil { 529 | return 530 | } 531 | defer rc.put(cli, &err) 532 | _, err = redis.Int(cli.Do("HSET", key, subkey, value)) 533 | return 534 | } 535 | 536 | func (rc *RedisCli) HGetall(key string) (reply map[string]string, err error) { 537 | var cli redis.Conn 538 | cli, err = rc.get() 539 | if err != nil { 540 | return 541 | } 542 | defer rc.put(cli, &err) 543 | reply, err = redis.StringMap(cli.Do("HGETALL", key)) 544 | return 545 | } 546 | 547 | func (rc *RedisCli) HGet(key, subkey string) (reply string, err error) { 548 | var cli redis.Conn 549 | cli, err = rc.get() 550 | if err != nil { 551 | return 552 | } 553 | defer rc.put(cli, &err) 554 | reply, err = redis.String(cli.Do("HGET", key, subkey)) 555 | return 556 | } 557 | 558 | func (rc *RedisCli) HKeys(key string) (replys []string, err error) { 559 | var cli redis.Conn 560 | cli, err = rc.get() 561 | if err != nil { 562 | return 563 | } 564 | defer rc.put(cli, &err) 565 | replys, err = redis.Strings(cli.Do("HKEYS", key)) 566 | return 567 | } 568 | 569 | func (rc *RedisCli) HMSet(key string, values []interface{}) (err error) { 570 | var cli redis.Conn 571 | cli, err = rc.get() 572 | if err != nil { 573 | return 574 | } 575 | defer rc.put(cli, &err) 576 | params := make([]interface{}, 0) 577 | params = append(params, key) 578 | params = append(params, values...) 579 | _, err = redis.String((cli.Do("HMSET", params...))) 580 | return 581 | } 582 | 583 | func (rc *RedisCli) HMGet(key string, fields []interface{}) (replys []string, err error) { 584 | var cli redis.Conn 585 | cli, err = rc.get() 586 | if err != nil { 587 | return 588 | } 589 | defer rc.put(cli, &err) 590 | params := make([]interface{}, 0) 591 | params = append(params, key) 592 | params = append(params, fields...) 593 | 594 | replys, err = redis.Strings((cli.Do("HMGET", params...))) 595 | return 596 | } 597 | 598 | func (rc *RedisCli) HDel(key string, fields ...interface{}) (reply int64, err error) { 599 | var cli redis.Conn 600 | cli, err = rc.get() 601 | if err != nil { 602 | return 603 | } 604 | defer rc.put(cli, &err) 605 | params := make([]interface{}, 0) 606 | params = append(params, key) 607 | params = append(params, fields...) 608 | reply, err = redis.Int64((cli.Do("HDEL", params...))) 609 | return 610 | } 611 | 612 | func (rc *RedisCli) ZScore(key, val string) (score int64, err error) { 613 | var cli redis.Conn 614 | cli, err = rc.get() 615 | if err != nil { 616 | return 617 | } 618 | defer rc.put(cli, &err) 619 | score, err = redis.Int64(cli.Do("ZSCORE", key, val)) 620 | return 621 | } 622 | -------------------------------------------------------------------------------- /rediscli/rediscli_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | redis client 17 | */ 18 | package rediscli 19 | 20 | import ( 21 | "log" 22 | "testing" 23 | ) 24 | 25 | func TestRedisCli(t *testing.T) { 26 | var ( 27 | reply string 28 | rint int 29 | rlist []string 30 | count int64 31 | ) 32 | rc, err := NewRedisCli(&Req{ 33 | Addrs: []string{"127.0.0.1:6379"}, 34 | }) 35 | if err != nil { 36 | log.Println("NewRedisCli err:", err.Error()) 37 | return 38 | } 39 | err = rc.Set("test", "helloworld") 40 | if err != nil { 41 | log.Println("Set err:", err.Error()) 42 | return 43 | } 44 | err = rc.Set("test", "helloworld", "EX", 10) 45 | if err != nil { 46 | log.Println("Set err:", err.Error()) 47 | return 48 | } 49 | reply, err = rc.Get("test") 50 | if err != nil { 51 | log.Println("Get err:", err.Error()) 52 | return 53 | } 54 | log.Println("Get Val:", reply) 55 | err = rc.Del("test") 56 | if err != nil { 57 | log.Println("Del err:", err.Error()) 58 | return 59 | } 60 | log.Println("del success.") 61 | err = rc.Setnx("test", "helloworld") 62 | if err != nil { 63 | log.Println("Setnx err:", err.Error()) 64 | return 65 | } 66 | err = rc.Expire("test", 10) 67 | if err != nil { 68 | log.Println("Expire err:", err.Error()) 69 | return 70 | } 71 | log.Println("Expire success.") 72 | rint, err = rc.Incr("test_incr") 73 | if err != nil { 74 | log.Println("Incr err:", err.Error()) 75 | return 76 | } 77 | log.Println("Incr success.", rint) 78 | reply, err = rc.IncrByfloat("test_incr_by_float", 100) 79 | if err != nil { 80 | log.Println("IncrByfloat err:", err.Error()) 81 | return 82 | } 83 | log.Println("IncrByfloat success.", reply) 84 | rint, err = rc.IncrBy("test_incrby", 12345) 85 | if err != nil { 86 | log.Println("IncrBy err:", err.Error()) 87 | return 88 | } 89 | log.Println("IncrBy success.", rint) 90 | err = rc.SAdd("test_spop", "1234567890", "2345678901") 91 | if err != nil { 92 | log.Println("SAdd err:", err.Error()) 93 | return 94 | } 95 | log.Println("SAdd success.") 96 | rlist, err = rc.SMembers("test_spop") 97 | if err != nil { 98 | log.Println("SMembers err:", err.Error()) 99 | return 100 | } 101 | log.Println("SMembers success.", rlist) 102 | reply, err = rc.SPop("test_spop") 103 | if err != nil { 104 | log.Println("SPop err:", err.Error()) 105 | return 106 | } 107 | log.Println("SPop success.", reply) 108 | count, err = rc.SUnionstore("test_sunionstore", "test_spop") 109 | if err != nil { 110 | log.Println("SUnionstore err:", err.Error()) 111 | return 112 | } 113 | log.Println("SUnionstore success.", count) 114 | log.Println("success.") 115 | err = rc.FlushAll() 116 | if err != nil { 117 | log.Println("FlushAll err:", err.Error()) 118 | return 119 | } 120 | log.Println("FlushAll success.") 121 | } 122 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 gopool Author. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | Instantiation of the connection pool configuration parameters 17 | 实例化连接池的配置参数 18 | */ 19 | package gopool 20 | 21 | import "time" 22 | 23 | const ( 24 | //The default timeout connection services 25 | //默认连接服务的超时时间 26 | DefaultConnSvrTimeOut = "100ms" 27 | //Health check time period 28 | //健康检查的时间周期 29 | DefaultCheckAddrCycle = "3s" 30 | //The default connection service retries 31 | //默认连接服务的重试次数 32 | DefaultRetryTimes = 1 33 | //Connection pool size 34 | //连接池大小 35 | DefaultSize = 20 36 | //Default health threshold, the threshold value of the node to node, unhealthy and cooling 37 | //默认健康阈值,达到阈值将节点置为不健康节点,并冷却 38 | DefaultHealthyThreshold = 20 39 | //The default maximum cooling time, seconds 40 | //默认最大冷却时长 秒 41 | DefaultMaxCooldownTime = 120 42 | //Than the default minimum health, prevent all removed, If only one downstream, does not remove 43 | //默认最小健康比, 防止全部摘除, 如果只有一个下游,则不会摘除 44 | DefaultMinHealthyRatio = 0.8 45 | //The biggest life cycle, the default connection to life cycle will be recycled 46 | //默认连接的最大生命周期, 达到生命周期会被回收 47 | DefaultConnLife = 3600 48 | ) 49 | 50 | //初始化连接池请求格式 51 | type ConnPoolReq struct { 52 | //Downstream address list 53 | //下游地址列表 54 | Addrs []string 55 | //Connection timeout of service 56 | //连接服务的超时时间 57 | ConnSvrTimeOut time.Duration 58 | //Health check time period 59 | //健康检查的时间周期 60 | CheckAddrCycle time.Duration 61 | //Connected to the downstream service retries 62 | //连接下游服务的重试次数 63 | RetryTimes int 64 | //Maximum number of connections, connection pool size 65 | //最大连接数,连接池大小 66 | Size int 67 | //Threshold, the health to nodes threshold will be set to false unhealthy node 68 | //健康阈值,达到阈值将节点置为不健康节点 false 69 | HealthyThreshold int64 70 | //The maximum cooling time. seconds 71 | //最大冷却时长 秒 72 | MaxCooldownTime int64 73 | //Than the minimum health, prevent all removed 74 | //最小健康比, 防止全部摘除 75 | MinHealthyRatio float64 76 | //Connect the biggest life cycle 77 | //连接最大生命周期 78 | ConnLife int64 79 | //Creating a connection to generate the client method 80 | //创建连接生成客户端的方法 81 | Create func(addr string, timeout time.Duration) (interface{}, error) 82 | //To determine whether a connection closed 83 | //判断连接是否关闭 84 | IsOpen func(c interface{}) bool 85 | //Close the connection 86 | //关闭连接 87 | Down func(c interface{}) 88 | } 89 | 90 | func (req *ConnPoolReq) GetConnSvrTimeOut() time.Duration { 91 | if int64(req.ConnSvrTimeOut) <= int64(0) { 92 | tm, _ := time.ParseDuration(DefaultConnSvrTimeOut) 93 | return tm 94 | } 95 | return req.ConnSvrTimeOut 96 | } 97 | 98 | func (req *ConnPoolReq) GetCheckAddrCycle() time.Duration { 99 | if int64(req.CheckAddrCycle) <= int64(0) { 100 | tm, _ := time.ParseDuration(DefaultCheckAddrCycle) 101 | return tm 102 | } 103 | return req.CheckAddrCycle 104 | } 105 | 106 | func (req *ConnPoolReq) GetRetryTimes() int { 107 | if req.RetryTimes <= 0 { 108 | return DefaultRetryTimes 109 | } 110 | return req.RetryTimes 111 | } 112 | 113 | func (req *ConnPoolReq) GetSize() int { 114 | if req.Size <= 0 { 115 | return DefaultSize 116 | } 117 | return req.Size 118 | } 119 | 120 | func (req *ConnPoolReq) GetHealthyThreshold() int64 { 121 | if req.HealthyThreshold <= 0 { 122 | return DefaultHealthyThreshold 123 | } 124 | return req.HealthyThreshold 125 | } 126 | 127 | func (req *ConnPoolReq) GetMaxCooldownTime() int64 { 128 | if req.MaxCooldownTime <= 0 { 129 | return DefaultMaxCooldownTime 130 | } 131 | return req.MaxCooldownTime 132 | } 133 | 134 | func (req *ConnPoolReq) GetMinHealthyRatio() float64 { 135 | if req.MinHealthyRatio <= 0 && req.MinHealthyRatio > 1 { 136 | return DefaultMinHealthyRatio 137 | } 138 | return req.MinHealthyRatio 139 | } 140 | 141 | func (req *ConnPoolReq) GetConnLife() int64 { 142 | if req.ConnLife <= 0 { 143 | req.ConnLife = DefaultConnLife 144 | } 145 | return req.ConnLife 146 | } 147 | --------------------------------------------------------------------------------