├── LICENSE.txt ├── README.md ├── codis-client ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── codis │ │ └── nedis │ │ └── codis │ │ ├── BoundedExponentialBackoffRetryUntilElapsed.java │ │ ├── CodisProxyInfo.java │ │ └── RoundRobinNedisClientPool.java │ └── test │ └── java │ └── io │ └── codis │ └── nedis │ └── codis │ ├── TestBoundedExponentialBackoffRetryUntilElapsed.java │ ├── TestRoundRobinJedisPool.java │ └── ZooKeeperServerWapper.java ├── jedis-LICENSE.txt ├── nedis-bench ├── assembly │ ├── assembly.xml │ └── bin │ │ └── bench.sh ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── codis │ └── nedis │ └── bench │ ├── JedisBench.java │ └── NedisBench.java ├── nedis-client ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── codis │ │ └── nedis │ │ ├── AsyncCloseable.java │ │ ├── ConnectionManagement.java │ │ ├── NedisClient.java │ │ ├── NedisClientBuilder.java │ │ ├── NedisClientImpl.java │ │ ├── NedisClientPool.java │ │ ├── NedisClientPoolBuilder.java │ │ ├── NedisClientPoolImpl.java │ │ ├── PromiseConverter.java │ │ ├── exception │ │ ├── RedisResponseException.java │ │ ├── TxnAbortException.java │ │ └── TxnDiscardException.java │ │ ├── handler │ │ ├── RedisDuplexHandler.java │ │ ├── RedisRequest.java │ │ ├── RedisRequestEncoder.java │ │ ├── RedisResponseDecoder.java │ │ └── TxnRedisRequest.java │ │ ├── protocol │ │ ├── Aggregate.java │ │ ├── BitOp.java │ │ ├── BlockingListsCommands.java │ │ ├── ConnectionCommands.java │ │ ├── HashEntry.java │ │ ├── HashesCommands.java │ │ ├── HyperLogLogCommands.java │ │ ├── KeysCommands.java │ │ ├── ListsCommands.java │ │ ├── RedisCommand.java │ │ ├── RedisKeyword.java │ │ ├── ScanParams.java │ │ ├── ScanResult.java │ │ ├── ScriptingCommands.java │ │ ├── ServerCommands.java │ │ ├── SetParams.java │ │ ├── SetsCommands.java │ │ ├── SortParams.java │ │ ├── SortedSetEntry.java │ │ ├── SortedSetsCommands.java │ │ ├── StringsCommands.java │ │ ├── TransactionsCommands.java │ │ └── ZSetOpParams.java │ │ └── util │ │ ├── AbstractNedisBuilder.java │ │ ├── NedisClientHashSet.java │ │ └── NedisUtils.java │ └── test │ └── java │ └── io │ └── codis │ └── nedis │ ├── RedisServer.java │ ├── TestCommands.java │ ├── TestNedis.java │ ├── TestNedisClientImpl.java │ ├── TestUtils.java │ └── util │ └── TestNedisClientHashSet.java └── pom.xml /LICENSE.txt: -------------------------------------------------------------------------------- 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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nedis 2 | An event-driven, asynchronous redis client based on [netty](http://netty.io/) 3 | 4 | ## How to use 5 | Add this to your pom.xml. We deploy nedis to https://oss.sonatype.org. 6 | ```xml 7 | 8 | com.wandoulabs.nedis 9 | nedis-client 10 | 0.1.1 11 | 12 | ``` 13 | To use it 14 | ```java 15 | NedisClientPool nedisPool = NedisClientPoolBuilder.create().timeoutMs(5000) 16 | .remoteAddress("rediserver", 6379).build(); 17 | NedisClient nedis = NedisUtils.newPooledClient(nedisPool); 18 | nedis.set(NedisUtils.toBytes("foo"), NedisUtils.toBytes("bar")).sync(); 19 | byte[] value = nedis.get(NedisUtils.toBytes("foo")).sync().getNow(); 20 | System.out.println(NedisUtils.bytesToString(value)); 21 | nedis.close().sync(); 22 | ``` 23 | 24 | **Java7 is required to build or use nedis.** 25 | 26 | ## For codis users 27 | Add this to your pom.xml. 28 | ```xml 29 | 30 | com.wandoulabs.nedis 31 | codis-client 32 | 0.1.1 33 | 34 | ``` 35 | To use it 36 | ```java 37 | RoundRobinNedisClientPool nedisPool = RoundRobinNedisClientPool.builder() 38 | .poolBuilder(NedisClientPoolBuilder.create().timeoutMs(5000)).curatorClient("zkserver:2181", 30000) 39 | .zkProxyDir("/zk/codis/db_xxx/proxy").build().sync().getNow(); 40 | NedisClient nedis = NedisUtils.newPooledClient(nedisPool); 41 | nedis.set(NedisUtils.toBytes("foo"), NedisUtils.toBytes("bar")).sync(); 42 | byte[] value = nedis.get(NedisUtils.toBytes("foo")).sync().getNow(); 43 | System.out.println(NedisUtils.bytesToString(value)); 44 | nedis.close().sync(); 45 | ``` 46 | 47 | ## Performance 48 | Nedis is **NOT** faster than jedis, especially if you use it in a synchronized way. In nedis, the requests on the same connection will be pipelined automatically if possible, but it is still **NOT** faster than jedis if you explicitly use a pipeline when using jedis. 49 | 50 | So please use nedis with caution. 51 | 52 | If you are an expert of netty, you could try to use `EpollEventLoopGroup` and add `-Dio.netty.allocator.type=pooled`(default is `unpooled` in netty 4.0) when starting to increase performance. 53 | -------------------------------------------------------------------------------- /codis-client/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | nedis 6 | io.codis.nedis 7 | 0.2.0-SNAPSHOT 8 | 9 | io.codis.nedis 10 | codis-client 11 | 0.2.0-SNAPSHOT 12 | jar 13 | 14 | UTF-8 15 | 1.7 16 | UTF-8 17 | UTF-8 18 | UTF-8 19 | UTF-8 20 | 21 | 22 | 23 | io.codis.nedis 24 | nedis-client 25 | 26 | 27 | com.fasterxml.jackson.core 28 | jackson-databind 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 34 | 35 | org.apache.curator 36 | curator-recipes 37 | 38 | 39 | org.slf4j 40 | slf4j-simple 41 | test 42 | 43 | 44 | org.hamcrest 45 | hamcrest-library 46 | test 47 | 48 | 49 | junit 50 | junit 51 | test 52 | 53 | 54 | org.mockito 55 | mockito-core 56 | test 57 | 58 | 59 | com.google.guava 60 | guava 61 | test 62 | 63 | 64 | io.codis.nedis 65 | nedis-client 66 | test-jar 67 | test 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-source-plugin 75 | 2.4 76 | 77 | 78 | attach-sources 79 | 80 | jar-no-fork 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-javadoc-plugin 88 | 2.10.3 89 | 90 | 91 | attach-javadocs 92 | 93 | jar 94 | 95 | 96 | -Xdoclint:none 97 | 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-deploy-plugin 104 | 2.8.2 105 | 106 | 107 | deploy 108 | deploy 109 | 110 | deploy 111 | 112 | 113 | 114 | default-deploy 115 | 116 | true 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /codis-client/src/main/java/io/codis/nedis/codis/BoundedExponentialBackoffRetryUntilElapsed.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.codis; 17 | 18 | import java.util.concurrent.ThreadLocalRandom; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import org.apache.curator.RetryPolicy; 22 | import org.apache.curator.RetrySleeper; 23 | 24 | /** 25 | * Similar to {@link org.apache.curator.retry.BoundedExponentialBackoffRetry}, 26 | * but limit the retry elapsed time, not retry number. 27 | * 28 | * @author Apache9 29 | */ 30 | public class BoundedExponentialBackoffRetryUntilElapsed implements RetryPolicy { 31 | 32 | private final int baseSleepTimeMs; 33 | 34 | private final int maxSleepTimeMs; 35 | 36 | private final long maxElapsedTimeMs; 37 | 38 | /** 39 | * @param baseSleepTimeMs 40 | * initial amount of time to wait between retries 41 | * @param maxSleepTimeMs 42 | * max time in ms to sleep on each retry 43 | * @param maxElapsedTimeMs 44 | * total time in ms to retry 45 | */ 46 | public BoundedExponentialBackoffRetryUntilElapsed(int baseSleepTimeMs, int maxSleepTimeMs, 47 | long maxElapsedTimeMs) { 48 | this.baseSleepTimeMs = baseSleepTimeMs; 49 | this.maxSleepTimeMs = maxSleepTimeMs; 50 | if (maxElapsedTimeMs < 0) { 51 | this.maxElapsedTimeMs = Long.MAX_VALUE; 52 | } else { 53 | this.maxElapsedTimeMs = maxElapsedTimeMs; 54 | } 55 | } 56 | 57 | private long getSleepTimeMs(int retryCount, long elapsedTimeMs) { 58 | return Math.min( 59 | maxSleepTimeMs, 60 | (long) baseSleepTimeMs 61 | * Math.max( 62 | 1, 63 | ThreadLocalRandom.current().nextInt( 64 | 1 << Math.min(30, retryCount + 1)))); 65 | } 66 | 67 | @Override 68 | public boolean allowRetry(int retryCount, long elapsedTimeMs, RetrySleeper sleeper) { 69 | if (elapsedTimeMs >= maxElapsedTimeMs) { 70 | return false; 71 | } 72 | long sleepTimeMs = Math.min(maxElapsedTimeMs - elapsedTimeMs, 73 | getSleepTimeMs(retryCount, elapsedTimeMs)); 74 | try { 75 | sleeper.sleepFor(sleepTimeMs, TimeUnit.MILLISECONDS); 76 | } catch (InterruptedException e) { 77 | Thread.currentThread().interrupt(); 78 | return false; 79 | } 80 | return true; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /codis-client/src/main/java/io/codis/nedis/codis/CodisProxyInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.codis; 17 | 18 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | @JsonIgnoreProperties(ignoreUnknown = true) 24 | public class CodisProxyInfo { 25 | 26 | private String addr; 27 | 28 | private String state; 29 | 30 | public String getAddr() { 31 | return addr; 32 | } 33 | 34 | public void setAddr(String addr) { 35 | this.addr = addr; 36 | } 37 | 38 | public String getState() { 39 | return state; 40 | } 41 | 42 | public void setState(String state) { 43 | this.state = state; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /codis-client/src/test/java/io/codis/nedis/codis/TestBoundedExponentialBackoffRetryUntilElapsed.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.codis; 17 | 18 | import static org.junit.Assert.*; 19 | 20 | import java.util.concurrent.ThreadLocalRandom; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | import org.apache.curator.RetrySleeper; 24 | import org.junit.Test; 25 | 26 | import io.codis.nedis.codis.BoundedExponentialBackoffRetryUntilElapsed; 27 | 28 | /** 29 | * @author Apache9 30 | */ 31 | public class TestBoundedExponentialBackoffRetryUntilElapsed { 32 | 33 | private static final class FakeRetrySleeper implements RetrySleeper { 34 | 35 | public long sleepTimeMs; 36 | 37 | @Override 38 | public void sleepFor(long time, TimeUnit unit) { 39 | this.sleepTimeMs = unit.toMillis(time); 40 | } 41 | 42 | } 43 | 44 | @Test 45 | public void test() { 46 | FakeRetrySleeper sleeper = new FakeRetrySleeper(); 47 | BoundedExponentialBackoffRetryUntilElapsed r = new BoundedExponentialBackoffRetryUntilElapsed( 48 | 10, 2000, 60000); 49 | for (int i = 0; i < 100; i++) { 50 | assertTrue(r.allowRetry(i, ThreadLocalRandom.current().nextInt(60000), sleeper)); 51 | System.out.println(sleeper.sleepTimeMs); 52 | assertTrue(sleeper.sleepTimeMs <= 2000); 53 | } 54 | assertTrue(r.allowRetry(1000, 59900, sleeper)); 55 | System.out.println(sleeper.sleepTimeMs); 56 | assertTrue(sleeper.sleepTimeMs <= 100); 57 | sleeper.sleepTimeMs = -1L; 58 | assertFalse(r.allowRetry(1, 60000, sleeper)); 59 | assertEquals(-1L, sleeper.sleepTimeMs); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /codis-client/src/test/java/io/codis/nedis/codis/TestRoundRobinJedisPool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.codis; 17 | 18 | import static io.codis.nedis.TestUtils.probeFreePort; 19 | import static io.codis.nedis.TestUtils.waitUntilRedisUp; 20 | import static io.codis.nedis.util.NedisUtils.*; 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertTrue; 23 | 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.nio.file.FileVisitResult; 27 | import java.nio.file.Files; 28 | import java.nio.file.Path; 29 | import java.nio.file.SimpleFileVisitor; 30 | import java.nio.file.attribute.BasicFileAttributes; 31 | 32 | import org.apache.zookeeper.CreateMode; 33 | import org.apache.zookeeper.KeeperException; 34 | import org.apache.zookeeper.ZooDefs; 35 | import org.apache.zookeeper.ZooKeeper; 36 | import org.junit.After; 37 | import org.junit.Before; 38 | import org.junit.Test; 39 | 40 | import com.fasterxml.jackson.databind.ObjectMapper; 41 | import com.fasterxml.jackson.databind.node.ObjectNode; 42 | 43 | import io.codis.nedis.NedisClient; 44 | import io.codis.nedis.NedisClientPoolBuilder; 45 | import io.codis.nedis.RedisServer; 46 | import io.codis.nedis.codis.RoundRobinNedisClientPool; 47 | import io.codis.nedis.util.NedisUtils; 48 | 49 | /** 50 | * @author Apache9 51 | */ 52 | public class TestRoundRobinJedisPool { 53 | 54 | private ObjectMapper mapper = new ObjectMapper(); 55 | 56 | private int zkPort; 57 | 58 | private File testDir = new File(getClass().getName()); 59 | 60 | private ZooKeeperServerWapper zkServer; 61 | 62 | private int redisPort1; 63 | 64 | private RedisServer redis1; 65 | 66 | private int redisPort2; 67 | 68 | private RedisServer redis2; 69 | 70 | private NedisClient client1; 71 | 72 | private NedisClient client2; 73 | 74 | private RoundRobinNedisClientPool clientPool; 75 | 76 | private NedisClient client; 77 | 78 | private String zkPath = "/" + getClass().getName(); 79 | 80 | private void deleteDirectory(File directory) throws IOException { 81 | if (!directory.exists()) { 82 | return; 83 | } 84 | Files.walkFileTree(directory.toPath(), new SimpleFileVisitor() { 85 | 86 | @Override 87 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 88 | throws IOException { 89 | Files.delete(file); 90 | return FileVisitResult.CONTINUE; 91 | } 92 | 93 | @Override 94 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 95 | Files.delete(dir); 96 | return FileVisitResult.CONTINUE; 97 | } 98 | 99 | }); 100 | } 101 | 102 | private void addNode(String name, int port, String state) throws IOException, 103 | InterruptedException, KeeperException { 104 | ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null); 105 | try { 106 | if (zk.exists(zkPath, null) == null) { 107 | zk.create(zkPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 108 | } 109 | ObjectNode node = mapper.createObjectNode(); 110 | node.put("addr", "127.0.0.1:" + port); 111 | node.put("state", state); 112 | zk.create(zkPath + "/" + name, mapper.writer().writeValueAsBytes(node), 113 | ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 114 | } finally { 115 | zk.close(); 116 | } 117 | } 118 | 119 | private void removeNode(String name) throws InterruptedException, KeeperException, IOException { 120 | ZooKeeper zk = new ZooKeeper("localhost:" + zkPort, 5000, null); 121 | try { 122 | zk.delete(zkPath + "/" + name, -1); 123 | } finally { 124 | zk.close(); 125 | } 126 | } 127 | 128 | private void waitUntilZkUp(int port) throws InterruptedException { 129 | for (;;) { 130 | ZooKeeper zk = null; 131 | try { 132 | zk = new ZooKeeper("127.0.0.1:" + port, 5000, null); 133 | zk.getChildren("/", null); 134 | return; 135 | } catch (Exception e) { 136 | Thread.sleep(200); 137 | } finally { 138 | if (zk != null) { 139 | zk.close(); 140 | } 141 | } 142 | } 143 | } 144 | 145 | @Before 146 | public void setUp() throws Exception { 147 | deleteDirectory(testDir); 148 | testDir.mkdirs(); 149 | 150 | zkPort = probeFreePort(); 151 | zkServer = new ZooKeeperServerWapper(zkPort, testDir); 152 | zkServer.start(); 153 | waitUntilZkUp(zkPort); 154 | 155 | redisPort1 = probeFreePort(); 156 | redis1 = new RedisServer(redisPort1); 157 | redis1.start(); 158 | waitUntilRedisUp(redisPort1); 159 | 160 | redisPort2 = probeFreePort(); 161 | redis2 = new RedisServer(redisPort2); 162 | redis2.start(); 163 | waitUntilRedisUp(redisPort2); 164 | 165 | NedisClientPoolBuilder poolBuilder = NedisClientPoolBuilder.create(); 166 | 167 | client1 = NedisUtils.newPooledClient(poolBuilder.remoteAddress("localhost", redisPort1) 168 | .build()); 169 | client2 = NedisUtils.newPooledClient(poolBuilder.remoteAddress("localhost", redisPort2) 170 | .build()); 171 | 172 | addNode("node1", redisPort1, "online"); 173 | clientPool = RoundRobinNedisClientPool.builder().poolBuilder(poolBuilder) 174 | .curatorClient("localhost:" + zkPort, 30000).zkProxyDir(zkPath).build().sync() 175 | .getNow(); 176 | client = NedisUtils.newPooledClient(clientPool); 177 | } 178 | 179 | @After 180 | public void tearDown() throws IOException, InterruptedException { 181 | client.close().sync(); 182 | client1.close().sync(); 183 | client2.close().sync(); 184 | if (redis1 != null) { 185 | redis1.stop(); 186 | } 187 | if (redis2 != null) { 188 | redis2.stop(); 189 | } 190 | if (zkServer != null) { 191 | zkServer.stop(); 192 | } 193 | deleteDirectory(testDir); 194 | } 195 | 196 | @Test 197 | public void test() throws IOException, InterruptedException, KeeperException { 198 | assertTrue(client.set(toBytes("k1"), toBytes("v1")).sync().getNow().booleanValue()); 199 | assertEquals("v1", bytesToString(client1.get(toBytes("k1")).sync().getNow())); 200 | // fake node 201 | addNode("node2", 12345, "offline"); 202 | Thread.sleep(3000); 203 | assertTrue(client.set(toBytes("k2"), toBytes("v2")).sync().getNow().booleanValue()); 204 | assertEquals("v2", bytesToString(client1.get(toBytes("k2")).sync().getNow())); 205 | 206 | addNode("node3", redisPort2, "online"); 207 | Thread.sleep(3000); 208 | assertTrue(client.set(toBytes("k3"), toBytes("v3")).sync().getNow().booleanValue()); 209 | assertEquals("v3", bytesToString(client2.get(toBytes("k3")).sync().getNow())); 210 | 211 | removeNode("node1"); 212 | Thread.sleep(3000); 213 | assertTrue(client.set(toBytes("k4"), toBytes("v4")).sync().getNow().booleanValue()); 214 | assertEquals("v4", bytesToString(client2.get(toBytes("k4")).sync().getNow())); 215 | 216 | System.out.println(clientPool.numConns()); 217 | System.out.println(clientPool.numPooledConns()); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /codis-client/src/test/java/io/codis/nedis/codis/ZooKeeperServerWapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.codis; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | 21 | import org.apache.zookeeper.server.ServerCnxnFactory; 22 | import org.apache.zookeeper.server.ZooKeeperServer; 23 | import org.apache.zookeeper.server.persistence.FileTxnSnapLog; 24 | 25 | /** 26 | * @author Apache9 27 | */ 28 | public class ZooKeeperServerWapper { 29 | 30 | private volatile ServerCnxnFactory cnxnFactory; 31 | 32 | private volatile ZooKeeperServer zkServer; 33 | 34 | private volatile FileTxnSnapLog ftxn; 35 | 36 | private int port; 37 | 38 | private File baseDir; 39 | 40 | public ZooKeeperServerWapper(int port, File baseDir) throws Exception { 41 | this.port = port; 42 | this.baseDir = baseDir; 43 | } 44 | 45 | public void start() throws IOException, InterruptedException { 46 | ZooKeeperServer zkServer = new ZooKeeperServer(); 47 | FileTxnSnapLog ftxn = new FileTxnSnapLog( 48 | new File(baseDir, "zookeeper"), new File(baseDir, "zookeeper")); 49 | zkServer.setTxnLogFactory(ftxn); 50 | zkServer.setTickTime(1000); 51 | ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory(port, 52 | 100); 53 | cnxnFactory.startup(zkServer); 54 | this.cnxnFactory = cnxnFactory; 55 | this.zkServer = zkServer; 56 | this.ftxn = ftxn; 57 | } 58 | 59 | public void stop() throws IOException { 60 | cnxnFactory.shutdown(); 61 | ftxn.close(); 62 | } 63 | 64 | public boolean isRunning() { 65 | if (zkServer == null) { 66 | return false; 67 | } else { 68 | return zkServer.isRunning(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /jedis-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Jonathan Leibiusky 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /nedis-bench/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | tar.gz 5 | dir 6 | 7 | asm 8 | true 9 | 10 | 11 | assembly/bin 12 | bin 13 | 0755 14 | true 15 | 16 | 17 | 18 | 19 | lib 20 | runtime 21 | 22 | 23 | -------------------------------------------------------------------------------- /nedis-bench/assembly/bin/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JAVA_OPTS="-Xmx1g -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=65 -XX:+CMSParallelRemarkEnabled" 4 | JAVA=$JAVA_HOME/bin/java 5 | 6 | function usage() { 7 | echo "Usage: bash $0 nedis|jedis command" 8 | echo "command:" 9 | echo " start" 10 | echo " stop" 11 | exit 1 12 | } 13 | 14 | if [ $# -lt 2 ]; then 15 | usage 16 | fi 17 | 18 | if [ "$1" == nedis ]; then 19 | MAINCLASS="io.codis.nedis.bench.NedisBench" 20 | OUT_FILE=nedis.out.$(date +%y%m%d-%H%M%S) 21 | else 22 | MAINCLASS="io.codis.nedis.bench.JedisBench" 23 | OUT_FILE=jedis.out.$(date +%y%m%d-%H%M%S) 24 | fi 25 | 26 | COMMAND=$2 27 | shift 2 28 | 29 | if [ ${COMMAND} == "start" ];then 30 | # add libs to CLASSPATH 31 | for f in lib/*.jar; do 32 | CLASSPATH=${CLASSPATH}:$f; 33 | done 34 | export CLASSPATH 35 | nohup ${JAVA} ${JAVA_OPTS} ${MAINCLASS} &>${OUT_FILE} $@ & 36 | sleep 3 37 | echo 38 | # show some logs by tail 39 | tail -n 10 ${OUT_FILE} 40 | fi 41 | 42 | if [ ${COMMAND} == "stop" ];then 43 | PIDS=`ps x | grep ${MAINCLASS} | grep -v grep | grep java | awk '{print $1}'` 44 | for PID in ${PIDS};do 45 | kill -9 ${PID} 46 | echo "kill process from pid: ${PID}" 47 | done 48 | fi 49 | -------------------------------------------------------------------------------- /nedis-bench/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | nedis 6 | io.codis.nedis 7 | 0.2.0-SNAPSHOT 8 | 9 | io.codis.nedis 10 | nedis-bench 11 | 0.2.0-SNAPSHOT 12 | jar 13 | 14 | UTF-8 15 | 1.7 16 | UTF-8 17 | UTF-8 18 | UTF-8 19 | UTF-8 20 | 21 | 22 | 23 | io.codis.nedis 24 | nedis-client 25 | 26 | 27 | com.google.guava 28 | guava 29 | 30 | 31 | redis.clients 32 | jedis 33 | 2.7.3 34 | 35 | 36 | args4j 37 | args4j 38 | 2.32 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-deploy-plugin 46 | 2.8.2 47 | 48 | true 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-assembly-plugin 54 | 2.4 55 | 56 | ${project.artifactId} 57 | false 58 | 59 | assembly/assembly.xml 60 | 61 | 62 | 63 | 64 | package 65 | 66 | single 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /nedis-bench/src/main/java/io/codis/nedis/bench/JedisBench.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.bench; 17 | 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.ThreadLocalRandom; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import java.util.concurrent.atomic.AtomicLong; 24 | 25 | import org.kohsuke.args4j.CmdLineException; 26 | import org.kohsuke.args4j.CmdLineParser; 27 | import org.kohsuke.args4j.Option; 28 | 29 | import redis.clients.jedis.Jedis; 30 | import redis.clients.jedis.JedisPool; 31 | import redis.clients.jedis.JedisPoolConfig; 32 | import redis.clients.jedis.Pipeline; 33 | 34 | import com.google.common.net.HostAndPort; 35 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 36 | 37 | /** 38 | * @author Apache9 39 | */ 40 | public class JedisBench { 41 | 42 | private static class Args { 43 | 44 | @Option(name = "-c", required = true) 45 | public int conns; 46 | 47 | @Option(name = "-pl") 48 | public int pipeline; 49 | 50 | @Option(name = "-t", required = true) 51 | public int threads; 52 | 53 | @Option(name = "-m") 54 | public long minutes = TimeUnit.DAYS.toMinutes(1); 55 | 56 | @Option(name = "-r", required = true) 57 | public String redisAddr; 58 | } 59 | 60 | private static final class Worker implements Runnable { 61 | 62 | private final AtomicBoolean stop; 63 | 64 | private final AtomicLong reqCount; 65 | 66 | private final JedisPool pool; 67 | 68 | private final int pipeline; 69 | 70 | public Worker(AtomicBoolean stop, AtomicLong reqCount, JedisPool pool, int pipeline) { 71 | this.stop = stop; 72 | this.reqCount = reqCount; 73 | this.pool = pool; 74 | this.pipeline = pipeline; 75 | } 76 | 77 | private void testPipeline(byte[] key, byte[] value) { 78 | while (!stop.get()) { 79 | try (Jedis jedis = pool.getResource()) { 80 | Pipeline p = jedis.pipelined(); 81 | for (int i = 0; i < pipeline / 2; i++) { 82 | p.set(key, value); 83 | p.get(key); 84 | } 85 | p.sync(); 86 | reqCount.addAndGet(pipeline); 87 | } 88 | } 89 | } 90 | 91 | private void testNormal(byte[] key, byte[] value) { 92 | while (!stop.get()) { 93 | try { 94 | try (Jedis jedis = pool.getResource()) { 95 | jedis.set(key, value); 96 | } 97 | reqCount.incrementAndGet(); 98 | try (Jedis jedis = pool.getResource()) { 99 | jedis.get(key); 100 | } 101 | reqCount.incrementAndGet(); 102 | } catch (Throwable t) { 103 | t.printStackTrace(); 104 | System.exit(1); 105 | } 106 | } 107 | } 108 | 109 | @Override 110 | public void run() { 111 | byte[] key = new byte[4]; 112 | byte[] value = new byte[16]; 113 | ThreadLocalRandom.current().nextBytes(key); 114 | ThreadLocalRandom.current().nextBytes(value); 115 | if (pipeline > 0) { 116 | testPipeline(key, value); 117 | } else { 118 | testNormal(key, value); 119 | } 120 | } 121 | } 122 | 123 | public static void main(String[] args) throws InterruptedException { 124 | Args parsedArgs = new Args(); 125 | CmdLineParser parser = new CmdLineParser(parsedArgs); 126 | try { 127 | parser.parseArgument(args); 128 | } catch (CmdLineException e) { 129 | parser.printUsage(System.err); 130 | return; 131 | } 132 | 133 | AtomicBoolean stop = new AtomicBoolean(false); 134 | AtomicLong reqCount = new AtomicLong(0); 135 | ExecutorService executor = Executors.newFixedThreadPool(parsedArgs.threads, 136 | new ThreadFactoryBuilder().setDaemon(true).build()); 137 | HostAndPort hap = HostAndPort.fromString(parsedArgs.redisAddr); 138 | JedisPoolConfig config = new JedisPoolConfig(); 139 | config.setMaxTotal(parsedArgs.conns); 140 | config.setMaxIdle(parsedArgs.conns); 141 | JedisPool pool = new JedisPool(config, hap.getHostText(), hap.getPort()); 142 | for (int i = 0; i < parsedArgs.threads; i++) { 143 | executor.execute(new Worker(stop, reqCount, pool, parsedArgs.pipeline)); 144 | } 145 | long duration = TimeUnit.MINUTES.toNanos(parsedArgs.minutes); 146 | long startTime = System.nanoTime(); 147 | long prevTime = -1L; 148 | long prevReqCount = -1L; 149 | for (;;) { 150 | long currentTime = System.nanoTime(); 151 | if (currentTime - startTime >= duration) { 152 | stop.set(true); 153 | executor.shutdown(); 154 | if (!executor.awaitTermination(1, TimeUnit.MINUTES)) { 155 | throw new RuntimeException("Can not terminate workers"); 156 | } 157 | System.out.println(String.format("Test run %d minutes, qps: %.2f", 158 | parsedArgs.minutes, (double) reqCount.get() / (currentTime - startTime) 159 | * TimeUnit.SECONDS.toNanos(1))); 160 | pool.close(); 161 | return; 162 | } 163 | long currentReqCount = reqCount.get(); 164 | if (prevTime > 0) { 165 | System.out.println(String.format("qps: %.2f", 166 | (double) (currentReqCount - prevReqCount) / (currentTime - prevTime) 167 | * TimeUnit.SECONDS.toNanos(1))); 168 | } 169 | prevTime = currentTime; 170 | prevReqCount = currentReqCount; 171 | Thread.sleep(5000); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /nedis-bench/src/main/java/io/codis/nedis/bench/NedisBench.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.bench; 17 | 18 | import io.codis.nedis.NedisClient; 19 | import io.codis.nedis.NedisClientPoolBuilder; 20 | import io.codis.nedis.util.NedisUtils; 21 | import io.netty.util.concurrent.Future; 22 | import io.netty.util.concurrent.FutureListener; 23 | 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.Semaphore; 27 | import java.util.concurrent.ThreadLocalRandom; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | import java.util.concurrent.atomic.AtomicLong; 31 | 32 | import org.kohsuke.args4j.CmdLineException; 33 | import org.kohsuke.args4j.CmdLineParser; 34 | import org.kohsuke.args4j.Option; 35 | 36 | import com.google.common.net.HostAndPort; 37 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 38 | 39 | /** 40 | * @author Apache9 41 | */ 42 | public class NedisBench { 43 | 44 | private static class Args { 45 | 46 | @Option(name = "-c", required = true) 47 | public int conns; 48 | 49 | @Option(name = "-pl") 50 | public int pipeline; 51 | 52 | @Option(name = "-t", required = true) 53 | public int threads; 54 | 55 | @Option(name = "-m") 56 | public long minutes = TimeUnit.DAYS.toMinutes(1); 57 | 58 | @Option(name = "-r", required = true) 59 | public String redisAddr; 60 | } 61 | 62 | private static final class Worker implements Runnable { 63 | 64 | private final AtomicBoolean stop; 65 | 66 | private final AtomicLong reqCount; 67 | 68 | private final NedisClient client; 69 | 70 | private final int pipeline; 71 | 72 | public Worker(AtomicBoolean stop, AtomicLong reqCount, NedisClient client, int pipeline) { 73 | this.stop = stop; 74 | this.reqCount = reqCount; 75 | this.client = client; 76 | this.pipeline = pipeline; 77 | } 78 | 79 | private void testAsync(byte[] key, byte[] value) { 80 | final Semaphore concurrencyControl = new Semaphore(pipeline); 81 | FutureListener listener = new FutureListener() { 82 | 83 | @Override 84 | public void operationComplete(Future future) throws Exception { 85 | if (future.isSuccess()) { 86 | concurrencyControl.release(); 87 | reqCount.incrementAndGet(); 88 | } else { 89 | future.cause().printStackTrace(); 90 | System.exit(1); 91 | } 92 | } 93 | }; 94 | while (!stop.get()) { 95 | concurrencyControl.acquireUninterruptibly(2); 96 | client.set(key, value).addListener(listener); 97 | client.get(key).addListener(listener); 98 | } 99 | } 100 | 101 | private void testSync(byte[] key, byte[] value) { 102 | while (!stop.get()) { 103 | try { 104 | client.set(key, value).sync(); 105 | reqCount.incrementAndGet(); 106 | client.get(key).sync(); 107 | reqCount.incrementAndGet(); 108 | } catch (Throwable t) { 109 | t.printStackTrace(); 110 | System.exit(1); 111 | } 112 | } 113 | } 114 | 115 | @Override 116 | public void run() { 117 | byte[] key = new byte[4]; 118 | byte[] value = new byte[16]; 119 | ThreadLocalRandom.current().nextBytes(key); 120 | ThreadLocalRandom.current().nextBytes(value); 121 | if (pipeline > 0) { 122 | testAsync(key, value); 123 | } else { 124 | testSync(key, value); 125 | } 126 | } 127 | } 128 | 129 | public static void main(String[] args) throws InterruptedException { 130 | Args parsedArgs = new Args(); 131 | CmdLineParser parser = new CmdLineParser(parsedArgs); 132 | try { 133 | parser.parseArgument(args); 134 | } catch (CmdLineException e) { 135 | parser.printUsage(System.err); 136 | return; 137 | } 138 | 139 | AtomicBoolean stop = new AtomicBoolean(false); 140 | AtomicLong reqCount = new AtomicLong(0); 141 | ExecutorService executor = Executors.newFixedThreadPool(parsedArgs.threads, 142 | new ThreadFactoryBuilder().setDaemon(true).build()); 143 | HostAndPort hap = HostAndPort.fromString(parsedArgs.redisAddr); 144 | NedisClient client = NedisUtils.newPooledClient(NedisClientPoolBuilder.create() 145 | .maxPooledConns(parsedArgs.conns).remoteAddress(hap.getHostText(), hap.getPort()) 146 | .build()); 147 | for (int i = 0; i < parsedArgs.threads; i++) { 148 | executor.execute(new Worker(stop, reqCount, client, parsedArgs.pipeline)); 149 | } 150 | long duration = TimeUnit.MINUTES.toNanos(parsedArgs.minutes); 151 | long startTime = System.nanoTime(); 152 | long prevTime = -1L; 153 | long prevReqCount = -1L; 154 | for (;;) { 155 | long currentTime = System.nanoTime(); 156 | if (currentTime - startTime >= duration) { 157 | stop.set(true); 158 | executor.shutdown(); 159 | if (!executor.awaitTermination(1, TimeUnit.MINUTES)) { 160 | throw new RuntimeException("Can not terminate workers"); 161 | } 162 | System.out.println(String.format("Test run %d minutes, qps: %.2f", 163 | parsedArgs.minutes, (double) reqCount.get() / (currentTime - startTime) 164 | * TimeUnit.SECONDS.toNanos(1))); 165 | client.close().sync(); 166 | return; 167 | } 168 | long currentReqCount = reqCount.get(); 169 | if (prevTime > 0) { 170 | System.out.println(String.format("qps: %.2f", 171 | (double) (currentReqCount - prevReqCount) / (currentTime - prevTime) 172 | * TimeUnit.SECONDS.toNanos(1))); 173 | } 174 | prevTime = currentTime; 175 | prevReqCount = currentReqCount; 176 | Thread.sleep(5000); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /nedis-client/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | nedis 6 | io.codis.nedis 7 | 0.2.0-SNAPSHOT 8 | 9 | io.codis.nedis 10 | nedis-client 11 | 0.2.0-SNAPSHOT 12 | jar 13 | 14 | UTF-8 15 | 1.7 16 | UTF-8 17 | UTF-8 18 | UTF-8 19 | UTF-8 20 | 21 | 22 | 23 | io.netty 24 | netty-transport 25 | 26 | 27 | io.netty 28 | netty-handler 29 | 30 | 31 | org.apache.commons 32 | commons-lang3 33 | 34 | 35 | org.hamcrest 36 | hamcrest-library 37 | test 38 | 39 | 40 | junit 41 | junit 42 | test 43 | 44 | 45 | org.mockito 46 | mockito-core 47 | test 48 | 49 | 50 | com.google.guava 51 | guava 52 | test 53 | 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-source-plugin 60 | 2.4 61 | 62 | 63 | attach-sources 64 | 65 | jar-no-fork 66 | 67 | 68 | 69 | attach-test-sources 70 | 71 | test-jar-no-fork 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-jar-plugin 79 | 2.6 80 | 81 | 82 | 83 | test-jar 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-javadoc-plugin 91 | 2.10.3 92 | 93 | 94 | attach-javadocs 95 | 96 | jar 97 | 98 | 99 | -Xdoclint:none 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-deploy-plugin 107 | 2.8.2 108 | 109 | 110 | deploy 111 | deploy 112 | 113 | deploy 114 | 115 | 116 | 117 | default-deploy 118 | 119 | true 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/AsyncCloseable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public interface AsyncCloseable { 24 | 25 | /** 26 | * return a {@link Future} which will be notified after closed. This method always returns the 27 | * same future instance. 28 | */ 29 | Future closeFuture(); 30 | 31 | /** 32 | * close this instance. The return value is same with {@link #closeFuture()}. 33 | */ 34 | Future close(); 35 | } 36 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/ConnectionManagement.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.netty.channel.EventLoop; 19 | import io.netty.util.concurrent.Future; 20 | 21 | /** 22 | * @author Apache9 23 | */ 24 | public interface ConnectionManagement extends AsyncCloseable { 25 | /** 26 | * return previous timeout value. Possible null if the client is already closed. 27 | */ 28 | Future setTimeout(long timeoutMs); 29 | 30 | /** 31 | * return the {@link EventLoop} of the underline {@link io.netty.channel.Channel}. 32 | */ 33 | EventLoop eventLoop(); 34 | 35 | /** 36 | * return whether the client is still open. 37 | */ 38 | boolean isOpen(); 39 | 40 | /** 41 | * return the client to its pool if: 42 | *
    43 | *
  1. The client is created by a {@link NedisClientPool}.
  2. 44 | *
  3. The pool is {@code exclusive}.
  4. 45 | *
46 | */ 47 | void release(); 48 | } 49 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/NedisClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.codis.nedis.protocol.ConnectionCommands; 19 | import io.codis.nedis.protocol.HashesCommands; 20 | import io.codis.nedis.protocol.HyperLogLogCommands; 21 | import io.codis.nedis.protocol.KeysCommands; 22 | import io.codis.nedis.protocol.ListsCommands; 23 | import io.codis.nedis.protocol.ScriptingCommands; 24 | import io.codis.nedis.protocol.ServerCommands; 25 | import io.codis.nedis.protocol.SetsCommands; 26 | import io.codis.nedis.protocol.SortedSetsCommands; 27 | import io.codis.nedis.protocol.StringsCommands; 28 | import io.codis.nedis.protocol.TransactionsCommands; 29 | import io.netty.util.concurrent.Future; 30 | 31 | /** 32 | * @author Apache9 33 | */ 34 | public interface NedisClient extends ConnectionManagement, ConnectionCommands, KeysCommands, 35 | StringsCommands, ScriptingCommands, ListsCommands, SetsCommands, ServerCommands, 36 | HashesCommands, HyperLogLogCommands, SortedSetsCommands, TransactionsCommands { 37 | 38 | /** 39 | * General method to execute a redis command. 40 | */ 41 | Future execCmd(byte[] cmd, byte[]... params); 42 | } 43 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/NedisClientBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.codis.nedis.handler.RedisDuplexHandler; 19 | import io.codis.nedis.handler.RedisResponseDecoder; 20 | import io.codis.nedis.util.AbstractNedisBuilder; 21 | import io.netty.bootstrap.Bootstrap; 22 | import io.netty.channel.Channel; 23 | import io.netty.channel.ChannelFuture; 24 | import io.netty.channel.ChannelFutureListener; 25 | import io.netty.channel.ChannelInitializer; 26 | import io.netty.channel.ChannelOption; 27 | import io.netty.channel.EventLoopGroup; 28 | import io.netty.util.concurrent.Future; 29 | import io.netty.util.concurrent.Promise; 30 | 31 | import static io.codis.nedis.util.NedisUtils.DEFAULT_REDIS_PORT; 32 | 33 | import java.net.InetSocketAddress; 34 | import java.net.SocketAddress; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | /** 38 | * @author Apache9 39 | */ 40 | public class NedisClientBuilder extends AbstractNedisBuilder { 41 | 42 | private NedisClientPool pool; 43 | 44 | @Override 45 | public NedisClientBuilder group(EventLoopGroup group) { 46 | super.group(group); 47 | return this; 48 | } 49 | 50 | @Override 51 | public NedisClientBuilder channel(Class channelClass) { 52 | super.channel(channelClass); 53 | return this; 54 | } 55 | 56 | @Override 57 | public NedisClientBuilder timeoutMs(long timeoutMs) { 58 | super.timeoutMs(timeoutMs); 59 | return this; 60 | } 61 | 62 | public NedisClientBuilder belongTo(NedisClientPool pool) { 63 | this.pool = pool; 64 | return this; 65 | } 66 | 67 | public Future connect(String host) { 68 | return connect(host, DEFAULT_REDIS_PORT); 69 | } 70 | 71 | public Future connect(String inetHost, int inetPort) { 72 | return connect(new InetSocketAddress(inetHost, inetPort)); 73 | } 74 | 75 | public Future connect(SocketAddress remoteAddress) { 76 | validateGroupConfig(); 77 | Bootstrap b = new Bootstrap().group(group).channel(channelClass) 78 | .handler(new ChannelInitializer() { 79 | 80 | @Override 81 | protected void initChannel(Channel ch) throws Exception { 82 | ch.pipeline().addLast(new RedisResponseDecoder(), 83 | new RedisDuplexHandler(TimeUnit.MILLISECONDS.toNanos(timeoutMs))); 84 | } 85 | 86 | }); 87 | if (timeoutMs > 0) { 88 | b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 89 | (int) Math.min(Integer.MAX_VALUE, timeoutMs)); 90 | } 91 | ChannelFuture f = b.connect(remoteAddress); 92 | final Promise promise = f.channel().eventLoop().newPromise(); 93 | f.addListener(new ChannelFutureListener() { 94 | 95 | @Override 96 | public void operationComplete(ChannelFuture future) throws Exception { 97 | if (future.isSuccess()) { 98 | promise.trySuccess(new NedisClientImpl(future.channel(), pool)); 99 | } else { 100 | promise.tryFailure(future.cause()); 101 | } 102 | } 103 | }); 104 | return promise; 105 | } 106 | 107 | private NedisClientBuilder() {} 108 | 109 | public static NedisClientBuilder create() { 110 | return new NedisClientBuilder(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/NedisClientPool.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public interface NedisClientPool extends AsyncCloseable { 24 | 25 | /** 26 | * Acquire a {@link NedisClient} from this pool. 27 | */ 28 | Future acquire(); 29 | 30 | /** 31 | * Return a {@link NedisClient} to this pool. 32 | *

33 | * Usually you should not call this method directly, call {@link NedisClient#release()} instead. 34 | */ 35 | void release(NedisClient client); 36 | 37 | /** 38 | * Whether we should remove the {@link NedisClient} from pool when acquiring. 39 | *

40 | * If you are doing time-consuming operations(such as {@code BLPOP}) then you should set this to 41 | * {@code true} 42 | */ 43 | boolean exclusive(); 44 | 45 | /** 46 | * Total number of alive {@link NedisClient}s. 47 | *

48 | * This could be larger than {@link #numPooledConns()} if {@link #exclusive()} is {@code true}. 49 | */ 50 | int numConns(); 51 | 52 | /** 53 | * Number of {@link NedisClient}s in pool. 54 | */ 55 | int numPooledConns(); 56 | } 57 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/NedisClientPoolBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.codis.nedis.util.AbstractNedisBuilder; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.EventLoopGroup; 21 | 22 | import static io.codis.nedis.util.NedisUtils.toBytes; 23 | 24 | import java.lang.management.ManagementFactory; 25 | import java.net.InetSocketAddress; 26 | import java.net.SocketAddress; 27 | 28 | /** 29 | * @author Apache9 30 | */ 31 | public class NedisClientPoolBuilder extends AbstractNedisBuilder { 32 | 33 | private byte[] password; 34 | 35 | private int database; 36 | 37 | private byte[] clientName; 38 | 39 | private int maxPooledConns = Math.max(2, 2 * ManagementFactory.getOperatingSystemMXBean() 40 | .getAvailableProcessors()); 41 | 42 | private boolean exclusive; 43 | 44 | private SocketAddress remoteAddress; 45 | 46 | @Override 47 | public NedisClientPoolBuilder group(EventLoopGroup group) { 48 | super.group(group); 49 | return this; 50 | } 51 | 52 | @Override 53 | public NedisClientPoolBuilder channel(Class channelClass) { 54 | super.channel(channelClass); 55 | return this; 56 | } 57 | 58 | @Override 59 | public NedisClientPoolBuilder timeoutMs(long timeoutMs) { 60 | super.timeoutMs(timeoutMs); 61 | return this; 62 | } 63 | 64 | public NedisClientPoolBuilder password(String password) { 65 | this.password = toBytes(password); 66 | return this; 67 | } 68 | 69 | public NedisClientPoolBuilder database(int database) { 70 | this.database = database; 71 | return this; 72 | } 73 | 74 | public NedisClientPoolBuilder clientName(String clientName) { 75 | this.clientName = toBytes(clientName); 76 | return this; 77 | } 78 | 79 | public NedisClientPoolBuilder maxPooledConns(int maxPooledConns) { 80 | this.maxPooledConns = maxPooledConns; 81 | return this; 82 | } 83 | 84 | public NedisClientPoolBuilder exclusive(boolean exclusive) { 85 | this.exclusive = exclusive; 86 | return this; 87 | } 88 | 89 | public boolean exclusive() { 90 | return exclusive; 91 | } 92 | 93 | public NedisClientPoolBuilder remoteAddress(String host) { 94 | return remoteAddress(host, 6379); 95 | } 96 | 97 | public NedisClientPoolBuilder remoteAddress(String inetHost, int inetPort) { 98 | return remoteAddress(new InetSocketAddress(inetHost, inetPort)); 99 | } 100 | 101 | public NedisClientPoolBuilder remoteAddress(SocketAddress remoteAddress) { 102 | this.remoteAddress = remoteAddress; 103 | return this; 104 | } 105 | 106 | private void validate() { 107 | validateGroupConfig(); 108 | if (remoteAddress == null) { 109 | throw new IllegalArgumentException("remoteAddress is not set"); 110 | } 111 | } 112 | 113 | public NedisClientPoolImpl build() { 114 | validate(); 115 | return new NedisClientPoolImpl(group, channelClass, timeoutMs, remoteAddress, password, 116 | database, clientName, maxPooledConns, exclusive); 117 | } 118 | 119 | public static NedisClientPoolBuilder create() { 120 | return new NedisClientPoolBuilder(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/NedisClientPoolImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import io.codis.nedis.util.NedisClientHashSet; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.EventLoopGroup; 21 | import io.netty.util.concurrent.Future; 22 | import io.netty.util.concurrent.FutureListener; 23 | import io.netty.util.concurrent.Promise; 24 | 25 | import static io.codis.nedis.util.NedisUtils.getEventExecutor; 26 | 27 | import java.net.SocketAddress; 28 | import java.util.ArrayList; 29 | 30 | /** 31 | * @author Apache9 32 | */ 33 | public class NedisClientPoolImpl implements NedisClientPool { 34 | 35 | private final EventLoopGroup group; 36 | 37 | private final Class channelClass; 38 | 39 | private final long timeoutMs; 40 | 41 | private final SocketAddress remoteAddress; 42 | 43 | private final byte[] password; 44 | 45 | private final int database; 46 | 47 | private final byte[] clientName; 48 | 49 | private final int maxPooledConns; 50 | 51 | private final boolean exclusive; 52 | 53 | private final NedisClientHashSet pool; 54 | 55 | private final Promise closePromise; 56 | 57 | private int numConns; 58 | 59 | private boolean closed = false; 60 | 61 | public NedisClientPoolImpl(EventLoopGroup group, Class channelClass, 62 | long timeoutMs, SocketAddress remoteAddress, byte[] password, int database, 63 | byte[] clientName, int maxPooledConns, boolean exclusive) { 64 | this.group = group; 65 | this.channelClass = channelClass; 66 | this.timeoutMs = timeoutMs; 67 | this.remoteAddress = remoteAddress; 68 | this.password = password; 69 | this.database = database; 70 | this.clientName = clientName; 71 | this.maxPooledConns = maxPooledConns; 72 | this.exclusive = exclusive; 73 | this.pool = new NedisClientHashSet(maxPooledConns); 74 | this.closePromise = group.next().newPromise(); 75 | } 76 | 77 | private final class InitializeFutureListener implements FutureListener { 78 | 79 | private final Promise promise; 80 | 81 | private final NedisClientImpl client; 82 | 83 | private final State nextState; 84 | 85 | public InitializeFutureListener(Promise promise, NedisClientImpl client, 86 | State nextState) { 87 | this.promise = promise; 88 | this.client = client; 89 | this.nextState = nextState; 90 | } 91 | 92 | @Override 93 | public void operationComplete(Future future) throws Exception { 94 | if (future.isSuccess()) { 95 | initialize(promise, client, nextState); 96 | } else { 97 | promise.tryFailure(future.cause()); 98 | client.close(); 99 | } 100 | } 101 | 102 | } 103 | 104 | private enum State { 105 | AUTH, SELECT, CLIENT_SETNAME, FINISH 106 | } 107 | 108 | private void initialize(final Promise promise, final NedisClientImpl client, 109 | State state) { 110 | switch (state) { 111 | case AUTH: 112 | if (password == null) { 113 | initialize(promise, client, State.SELECT); 114 | } else { 115 | client.auth0(password).addListener( 116 | new InitializeFutureListener(promise, client, State.SELECT)); 117 | } 118 | break; 119 | case SELECT: 120 | if (database == 0) { 121 | initialize(promise, client, State.CLIENT_SETNAME); 122 | } else { 123 | client.select0(database).addListener( 124 | new InitializeFutureListener(promise, client, State.CLIENT_SETNAME)); 125 | } 126 | break; 127 | case CLIENT_SETNAME: 128 | if (clientName == null) { 129 | promise.trySuccess(client); 130 | } else { 131 | client.clientSetname0(clientName).addListener( 132 | new InitializeFutureListener(promise, client, State.FINISH)); 133 | } 134 | break; 135 | case FINISH: 136 | promise.trySuccess(client); 137 | break; 138 | } 139 | } 140 | 141 | private Future newClient() { 142 | Future f = NedisClientBuilder.create().group(group).channel(channelClass) 143 | .timeoutMs(timeoutMs).belongTo(this).connect(remoteAddress); 144 | 145 | final Promise promise = getEventExecutor(f).newPromise(); 146 | f.addListener(new FutureListener() { 147 | 148 | @Override 149 | public void operationComplete(Future future) throws Exception { 150 | if (future.isSuccess()) { 151 | initialize(promise, future.getNow(), State.AUTH); 152 | } else { 153 | promise.tryFailure(future.cause()); 154 | } 155 | } 156 | 157 | }); 158 | return promise; 159 | } 160 | 161 | private final FutureListener acquireFutureListener = new FutureListener() { 162 | 163 | @Override 164 | public void operationComplete(Future future) throws Exception { 165 | synchronized (pool) { 166 | if (future.isSuccess()) { 167 | final NedisClient client = future.getNow(); 168 | client.closeFuture().addListener(new FutureListener() { 169 | 170 | @Override 171 | public void operationComplete(Future future) throws Exception { 172 | synchronized (pool) { 173 | pool.remove(client); 174 | numConns--; 175 | if (closed && numConns == 0) { 176 | closePromise.trySuccess(null); 177 | } 178 | } 179 | } 180 | 181 | }); 182 | if (!exclusive) { 183 | if (closed) { 184 | client.close(); 185 | } else { 186 | pool.add(client); 187 | if (!pendingAcquireList.isEmpty()) { 188 | for (Promise promise: pendingAcquireList) { 189 | promise.trySuccess(client); 190 | } 191 | pendingAcquireList.clear(); 192 | // usually we do not need this any more, so trim its size. 193 | pendingAcquireList.trimToSize(); 194 | } 195 | } 196 | } 197 | } else { 198 | numConns--; 199 | if (!exclusive && numConns == 0) { 200 | // notify all pending promises that we could not get a connection. 201 | for (Promise promise: pendingAcquireList) { 202 | promise.tryFailure(future.cause()); 203 | } 204 | pendingAcquireList.clear(); 205 | } 206 | } 207 | } 208 | } 209 | }; 210 | 211 | private final ArrayList> pendingAcquireList = new ArrayList<>(); 212 | 213 | @Override 214 | public Future acquire() { 215 | synchronized (pool) { 216 | if (closed) { 217 | return group.next().newFailedFuture( 218 | new IllegalStateException("already closed")); 219 | } 220 | if (numConns < maxPooledConns) { 221 | numConns++; 222 | return newClient().addListener(acquireFutureListener); 223 | } 224 | if (!pool.isEmpty()) { 225 | NedisClient client = pool.head(exclusive); 226 | return client.eventLoop().newSucceededFuture(client); 227 | } 228 | if (exclusive) { 229 | numConns++; 230 | return newClient().addListener(acquireFutureListener); 231 | } else { 232 | // If connection is shared, then we should not create more connections than 233 | // maxPooledConns. So here we add a promise to pending queue. The promise will be 234 | // notified when there are connections in pool. 235 | Promise promise = group.next().newPromise(); 236 | pendingAcquireList.add(promise); 237 | return promise; 238 | } 239 | } 240 | } 241 | 242 | private void tryPooling(NedisClient client) { 243 | if (closed) { 244 | client.close(); 245 | return; 246 | } 247 | if (pool.contains(client)) { 248 | return; 249 | } 250 | if (pool.size() < maxPooledConns) { 251 | pool.add(client); 252 | } else { 253 | client.close(); 254 | } 255 | } 256 | 257 | @Override 258 | public void release(NedisClient client) { 259 | synchronized (pool) { 260 | if (client.isOpen()) { 261 | tryPooling(client); 262 | } 263 | } 264 | } 265 | 266 | @Override 267 | public Future close() { 268 | NedisClient[] toClose; 269 | synchronized (pool) { 270 | if (closed) { 271 | return closePromise; 272 | } 273 | closed = true; 274 | if (numConns == 0) { 275 | closePromise.trySuccess(null); 276 | return closePromise; 277 | } 278 | toClose = pool.toArray(); 279 | } 280 | for (NedisClient client: toClose) { 281 | client.close(); 282 | } 283 | return closePromise; 284 | } 285 | 286 | @Override 287 | public Future closeFuture() { 288 | return closePromise; 289 | } 290 | 291 | @Override 292 | public boolean exclusive() { 293 | return exclusive; 294 | } 295 | 296 | @Override 297 | public int numConns() { 298 | synchronized (pool) { 299 | return numConns; 300 | } 301 | } 302 | 303 | @Override 304 | public int numPooledConns() { 305 | synchronized (pool) { 306 | return pool.size(); 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/exception/RedisResponseException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.exception; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public class RedisResponseException extends IOException { 24 | 25 | private static final long serialVersionUID = 1208471190036181159L; 26 | 27 | public RedisResponseException() { 28 | super(); 29 | } 30 | 31 | public RedisResponseException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public RedisResponseException(String message) { 36 | super(message); 37 | } 38 | 39 | public RedisResponseException(Throwable cause) { 40 | super(cause); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/exception/TxnAbortException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 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 | 17 | package io.codis.nedis.exception; 18 | 19 | import java.io.IOException; 20 | 21 | /** 22 | * 23 | * @author Apache9 24 | * 25 | */ 26 | public class TxnAbortException extends IOException { 27 | 28 | private static final long serialVersionUID = -4546827177372832280L; 29 | 30 | public TxnAbortException() { 31 | super(); 32 | } 33 | 34 | public TxnAbortException(String message, Throwable cause) { 35 | super(message, cause); 36 | } 37 | 38 | public TxnAbortException(String message) { 39 | super(message); 40 | } 41 | 42 | public TxnAbortException(Throwable cause) { 43 | super(cause); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/exception/TxnDiscardException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.exception; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public class TxnDiscardException extends IOException { 24 | 25 | private static final long serialVersionUID = -1088947518802949094L; 26 | 27 | public TxnDiscardException() { 28 | super(); 29 | } 30 | 31 | public TxnDiscardException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public TxnDiscardException(String message) { 36 | super(message); 37 | } 38 | 39 | public TxnDiscardException(Throwable cause) { 40 | super(cause); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/handler/RedisDuplexHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.handler; 17 | 18 | import io.codis.nedis.exception.TxnAbortException; 19 | import io.codis.nedis.exception.TxnDiscardException; 20 | import io.codis.nedis.protocol.TransactionsCommands; 21 | import io.netty.channel.ChannelDuplexHandler; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.channel.ChannelPromise; 24 | import io.netty.handler.codec.UnsupportedMessageTypeException; 25 | import io.netty.handler.timeout.ReadTimeoutException; 26 | import io.netty.util.concurrent.Promise; 27 | 28 | import static io.codis.nedis.protocol.RedisCommand.DISCARD; 29 | import static io.codis.nedis.protocol.RedisCommand.EXEC; 30 | import static io.codis.nedis.protocol.RedisCommand.MULTI; 31 | 32 | import java.nio.channels.ClosedChannelException; 33 | import java.util.ArrayDeque; 34 | import java.util.Deque; 35 | import java.util.Iterator; 36 | import java.util.List; 37 | import java.util.concurrent.ScheduledFuture; 38 | import java.util.concurrent.TimeUnit; 39 | 40 | /** 41 | * @author Apache9 42 | */ 43 | public class RedisDuplexHandler extends ChannelDuplexHandler { 44 | 45 | private static final class Entry { 46 | 47 | public final Promise promise; 48 | 49 | public final long nanoTime; 50 | 51 | public Entry(Promise promise, long nanoTime) { 52 | this.promise = promise; 53 | this.nanoTime = nanoTime; 54 | } 55 | } 56 | 57 | private static final Entry TXN_MARKER = new Entry(null, 0); 58 | 59 | private final Deque entryQ = new ArrayDeque<>(); 60 | 61 | private long timeoutNs; 62 | 63 | private ScheduledFuture timeoutTask; 64 | 65 | private boolean inMulti; 66 | 67 | public RedisDuplexHandler(long timeoutNs) { 68 | this.timeoutNs = timeoutNs; 69 | } 70 | 71 | public long getTimeoutNs() { 72 | return timeoutNs; 73 | } 74 | 75 | public void setTimeoutNs(long timeoutNs) { 76 | this.timeoutNs = timeoutNs; 77 | } 78 | 79 | private void scheduleTimeoutTask(ChannelHandlerContext ctx) { 80 | if (timeoutNs > 0 && timeoutTask == null) { 81 | timeoutTask = ctx.executor().schedule(new TimeoutTask(ctx), timeoutNs, 82 | TimeUnit.NANOSECONDS); 83 | } 84 | } 85 | 86 | private void writeNormal(ChannelHandlerContext ctx, RedisRequest req, ChannelPromise promise) { 87 | entryQ.addLast(new Entry(req.getPromise(), System.nanoTime())); 88 | ctx.write(req.getParams(), promise); 89 | } 90 | 91 | // How to deal with txn: 92 | // When MULTI, append a TXN_MARKER to the end of entryQ after the entry of MULTI command itself. 93 | // When EXEC or DISCARD, also append a TXN_MARKER to the end of entryQ before the entry of the 94 | // command itself. 95 | // In channelRead, if the top of entryQ is a TXN_MARKER, then decode the msg and fill the 96 | // entries in entryQ until reaching the next TXN_MARKER. 97 | private void writeTxn(ChannelHandlerContext ctx, TxnRedisRequest req, ChannelPromise promise) { 98 | switch (req.getCmd()) { 99 | case MULTI: { 100 | if (inMulti) { 101 | req.getPromise().tryFailure(new IllegalStateException("Already in MULTI")); 102 | break; 103 | } 104 | inMulti = true; 105 | ctx.write(RedisRequestEncoder.encode(ctx.alloc(), MULTI.raw), promise); 106 | entryQ.addLast(new Entry(req.getPromise(), System.nanoTime())); 107 | entryQ.addLast(TXN_MARKER); 108 | break; 109 | } 110 | case EXEC: { 111 | if (!inMulti) { 112 | req.getPromise().tryFailure(new IllegalStateException("not in MULTI")); 113 | break; 114 | } 115 | ctx.write(RedisRequestEncoder.encode(ctx.alloc(), EXEC.raw), promise); 116 | inMulti = false; 117 | entryQ.addLast(TXN_MARKER); 118 | entryQ.addLast(new Entry(req.getPromise(), System.nanoTime())); 119 | break; 120 | } 121 | case DISCARD: { 122 | if (!inMulti) { 123 | req.getPromise().tryFailure(new IllegalStateException("not in MULTI")); 124 | break; 125 | } 126 | ctx.write(RedisRequestEncoder.encode(ctx.alloc(), DISCARD.raw), promise); 127 | inMulti = false; 128 | entryQ.addLast(TXN_MARKER); 129 | entryQ.addLast(new Entry(req.getPromise(), System.nanoTime())); 130 | break; 131 | } 132 | default: 133 | throw new IllegalArgumentException(req.getCmd() + " is not a transactional command"); 134 | } 135 | } 136 | 137 | @Override 138 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) 139 | throws Exception { 140 | if (msg instanceof RedisRequest) { 141 | writeNormal(ctx, (RedisRequest) msg, promise); 142 | } else if (msg instanceof TxnRedisRequest) { 143 | writeTxn(ctx, (TxnRedisRequest) msg, promise); 144 | } else { 145 | throw new UnsupportedMessageTypeException(msg, RedisRequest.class, 146 | TxnRedisRequest.class); 147 | } 148 | scheduleTimeoutTask(ctx); 149 | } 150 | 151 | @Override 152 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 153 | if (msg.equals(TransactionsCommands.QUEUED)) { 154 | // this is the reply of a command in multi, just ignore 155 | return; 156 | } 157 | Entry entry = entryQ.pollFirst(); 158 | if (entry == null) { 159 | throw new IllegalStateException("Got response " + msg + " but no one is waiting for it"); 160 | } 161 | if (entry == TXN_MARKER) { 162 | if (msg == RedisResponseDecoder.NULL_REPLY) { 163 | TxnAbortException cause = new TxnAbortException(); 164 | while ((entry = entryQ.pollFirst()) != TXN_MARKER) { 165 | entry.promise.tryFailure(cause); 166 | } 167 | } else if (msg instanceof String) { 168 | TxnDiscardException cause = new TxnDiscardException(); 169 | while ((entry = entryQ.pollFirst()) != TXN_MARKER) { 170 | entry.promise.tryFailure(cause); 171 | } 172 | } else { 173 | @SuppressWarnings("unchecked") 174 | Iterator iter = ((List) msg).iterator(); 175 | while ((entry = entryQ.pollFirst()) != TXN_MARKER) { 176 | entry.promise.trySuccess(iter.next()); 177 | } 178 | } 179 | entry = entryQ.pollFirst(); 180 | } 181 | entry.promise.trySuccess(msg); 182 | } 183 | 184 | private void failAll(Throwable cause) { 185 | for (Entry entry; (entry = entryQ.pollFirst()) != null;) { 186 | if (entry == TXN_MARKER) { 187 | continue; 188 | } 189 | entry.promise.tryFailure(cause); 190 | } 191 | } 192 | 193 | @Override 194 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 195 | if (!entryQ.isEmpty()) { // only create exception if necessary 196 | failAll(new ClosedChannelException()); 197 | } 198 | } 199 | 200 | @Override 201 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 202 | failAll(cause); 203 | ctx.close(); 204 | } 205 | 206 | private final class TimeoutTask implements Runnable { 207 | 208 | private final ChannelHandlerContext ctx; 209 | 210 | public TimeoutTask(ChannelHandlerContext ctx) { 211 | this.ctx = ctx; 212 | } 213 | 214 | @Override 215 | public void run() { 216 | if (entryQ.isEmpty() || timeoutNs <= 0) { 217 | timeoutTask = null; 218 | return; 219 | } 220 | boolean refillTxnMarker = false; 221 | if (entryQ.peekFirst() == TXN_MARKER) { 222 | entryQ.removeFirst(); 223 | refillTxnMarker = true; 224 | } 225 | if (entryQ.isEmpty()) { 226 | entryQ.addFirst(TXN_MARKER); 227 | timeoutTask = null; 228 | return; 229 | } 230 | long nextDelayNs = timeoutNs - (System.nanoTime() - entryQ.peek().nanoTime); 231 | if (nextDelayNs <= 0) { 232 | exceptionCaught(ctx, ReadTimeoutException.INSTANCE); 233 | } else { 234 | timeoutTask = ctx.executor().schedule(this, nextDelayNs, TimeUnit.NANOSECONDS); 235 | if (refillTxnMarker) { 236 | entryQ.addFirst(TXN_MARKER); 237 | } 238 | } 239 | } 240 | 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/handler/RedisRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.handler; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.util.concurrent.Promise; 20 | 21 | /** 22 | * @author Apache9 23 | */ 24 | public class RedisRequest { 25 | 26 | private final Promise promise; 27 | 28 | private final ByteBuf params; 29 | 30 | public RedisRequest(Promise promise, ByteBuf params) { 31 | this.promise = promise; 32 | this.params = params; 33 | } 34 | 35 | public Promise getPromise() { 36 | return promise; 37 | } 38 | 39 | public ByteBuf getParams() { 40 | return params; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/handler/RedisRequestEncoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.handler; 17 | 18 | import static io.codis.nedis.util.NedisUtils.toBytes; 19 | 20 | import java.util.List; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.netty.buffer.ByteBufAllocator; 24 | 25 | /** 26 | * @author Apache9 27 | */ 28 | public class RedisRequestEncoder { 29 | 30 | private static final byte[] CRLF = new byte[] { 31 | '\r', '\n' 32 | }; 33 | 34 | private final static int[] SIZE_TABLE = { 35 | 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE 36 | }; 37 | 38 | // Requires positive x 39 | private static int stringSize(int x) { 40 | for (int i = 0;; i++) { 41 | if (x <= SIZE_TABLE[i]) { 42 | return i + 1; 43 | } 44 | } 45 | } 46 | 47 | private static int paramCountSize(int paramCount) { 48 | // * + paramCount + CRLF 49 | return 1 + stringSize(paramCount) + 2; 50 | } 51 | 52 | private static int paramSize(byte[] param) { 53 | // $ + paramLength + CRLF + param + CRLF 54 | return 1 + stringSize(param.length) + 2 + param.length + 2; 55 | } 56 | 57 | private static int serializedSize(byte[] cmd, byte[][] params, byte[]... otherParams) { 58 | int size = paramCountSize(1 + params.length + otherParams.length) + paramSize(cmd); 59 | for (byte[] param: params) { 60 | size += paramSize(param); 61 | } 62 | for (byte[] param: otherParams) { 63 | size += paramSize(param); 64 | } 65 | return size; 66 | } 67 | 68 | private static int serializedSize(byte[] cmd, List params) { 69 | int size = paramCountSize(1 + params.size()) + paramSize(cmd); 70 | for (byte[] param: params) { 71 | size += paramSize(param); 72 | } 73 | return size; 74 | } 75 | 76 | private static void writeParamCount(ByteBuf buf, int paramCount) { 77 | buf.writeByte('*').writeBytes(toBytes(paramCount)).writeBytes(CRLF); 78 | } 79 | 80 | private static void writeParam(ByteBuf buf, byte[] param) { 81 | buf.writeByte('$').writeBytes(toBytes(param.length)).writeBytes(CRLF).writeBytes(param) 82 | .writeBytes(CRLF); 83 | } 84 | 85 | public static ByteBuf encode(ByteBufAllocator alloc, byte[] cmd, byte[]... params) { 86 | int serializedSize = serializedSize(cmd, params); 87 | ByteBuf buf = alloc.buffer(serializedSize, serializedSize); 88 | writeParamCount(buf, params.length + 1); 89 | writeParam(buf, cmd); 90 | for (byte[] param: params) { 91 | writeParam(buf, param); 92 | } 93 | return buf; 94 | } 95 | 96 | public static ByteBuf encode(ByteBufAllocator alloc, byte[] cmd, byte[][] headParams, 97 | byte[]... tailParams) { 98 | int serializedSize = serializedSize(cmd, headParams, tailParams); 99 | ByteBuf buf = alloc.buffer(serializedSize, serializedSize); 100 | writeParamCount(buf, headParams.length + tailParams.length + 1); 101 | writeParam(buf, cmd); 102 | for (byte[] param: headParams) { 103 | writeParam(buf, param); 104 | } 105 | for (byte[] param: tailParams) { 106 | writeParam(buf, param); 107 | } 108 | return buf; 109 | } 110 | 111 | public static ByteBuf encodeReverse(ByteBufAllocator alloc, byte[] cmd, byte[][] tailParams, 112 | byte[]... headParams) { 113 | int serializedSize = serializedSize(cmd, tailParams, headParams); 114 | ByteBuf buf = alloc.buffer(serializedSize, serializedSize); 115 | writeParamCount(buf, headParams.length + tailParams.length + 1); 116 | writeParam(buf, cmd); 117 | for (byte[] param: headParams) { 118 | writeParam(buf, param); 119 | } 120 | for (byte[] param: tailParams) { 121 | writeParam(buf, param); 122 | } 123 | return buf; 124 | } 125 | 126 | public static ByteBuf encode(ByteBufAllocator alloc, byte[] cmd, List params) { 127 | int serializedSize = serializedSize(cmd, params); 128 | ByteBuf buf = alloc.buffer(serializedSize, serializedSize); 129 | writeParamCount(buf, params.size() + 1); 130 | writeParam(buf, cmd); 131 | for (byte[] param: params) { 132 | writeParam(buf, param); 133 | } 134 | return buf; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/handler/RedisResponseDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.handler; 17 | 18 | import io.codis.nedis.exception.RedisResponseException; 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.buffer.ByteBufProcessor; 21 | import io.netty.channel.ChannelHandlerContext; 22 | import io.netty.handler.codec.ByteToMessageDecoder; 23 | 24 | import java.net.ProtocolException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import org.apache.commons.lang3.mutable.MutableBoolean; 29 | import org.apache.commons.lang3.mutable.MutableLong; 30 | 31 | /** 32 | * @author zhangduo 33 | */ 34 | public class RedisResponseDecoder extends ByteToMessageDecoder { 35 | 36 | public static final Object NULL_REPLY = new Object(); 37 | 38 | private void setReaderIndex(ByteBuf in, int index) { 39 | in.readerIndex(index == -1 ? in.writerIndex() : index + 1); 40 | } 41 | 42 | private String decodeString(ByteBuf in) throws ProtocolException { 43 | final StringBuilder buffer = new StringBuilder(); 44 | final MutableBoolean reachCRLF = new MutableBoolean(false); 45 | setReaderIndex(in, in.forEachByte(new ByteBufProcessor() { 46 | 47 | @Override 48 | public boolean process(byte value) throws Exception { 49 | if (value == '\n') { 50 | if ((byte) buffer.charAt(buffer.length() - 1) != '\r') { 51 | throw new ProtocolException("Response is not ended by CRLF"); 52 | } else { 53 | buffer.setLength(buffer.length() - 1); 54 | reachCRLF.setTrue(); 55 | return false; 56 | } 57 | } else { 58 | buffer.append((char) value); 59 | return true; 60 | } 61 | } 62 | })); 63 | return reachCRLF.booleanValue() ? buffer.toString() : null; 64 | } 65 | 66 | private int toDigit(byte b) { 67 | return b - '0'; 68 | } 69 | 70 | private Long decodeLong(ByteBuf in) throws ProtocolException { 71 | byte sign = in.readByte(); 72 | final MutableLong l; 73 | boolean negative; 74 | if (sign == '-') { 75 | negative = true; 76 | l = new MutableLong(0); 77 | } else { 78 | negative = false; 79 | l = new MutableLong(toDigit(sign)); 80 | } 81 | final MutableBoolean reachCR = new MutableBoolean(false); 82 | setReaderIndex(in, in.forEachByte(new ByteBufProcessor() { 83 | 84 | @Override 85 | public boolean process(byte value) throws Exception { 86 | if (value == '\r') { 87 | reachCR.setTrue(); 88 | return false; 89 | } else { 90 | if (value >= '0' && value <= '9') { 91 | l.setValue(l.longValue() * 10 + toDigit(value)); 92 | } else { 93 | throw new ProtocolException("Response is not ended by CRLF"); 94 | } 95 | return true; 96 | } 97 | } 98 | })); 99 | if (!reachCR.booleanValue()) { 100 | return null; 101 | } 102 | if (!in.isReadable()) { 103 | return null; 104 | } 105 | if (in.readByte() != '\n') { 106 | throw new ProtocolException("Response is not ended by CRLF"); 107 | } 108 | return negative ? -l.longValue() : l.longValue(); 109 | } 110 | 111 | private boolean decode(ByteBuf in, List out, Object nullValue) throws Exception { 112 | if (in.readableBytes() < 2) { 113 | return false; 114 | } 115 | byte b = in.readByte(); 116 | switch (b) { 117 | case '+': { 118 | String reply = decodeString(in); 119 | if (reply == null) { 120 | return false; 121 | } 122 | out.add(reply); 123 | return true; 124 | } 125 | case '-': { 126 | String reply = decodeString(in); 127 | if (reply == null) { 128 | return false; 129 | } 130 | out.add(new RedisResponseException(reply)); 131 | return true; 132 | } 133 | case ':': { 134 | Long reply = decodeLong(in); 135 | if (reply == null) { 136 | return false; 137 | } 138 | out.add(reply); 139 | return true; 140 | } 141 | case '$': { 142 | Long numBytes = decodeLong(in); 143 | if (numBytes == null) { 144 | return false; 145 | } 146 | if (numBytes.intValue() == -1) { 147 | out.add(nullValue); 148 | return true; 149 | } 150 | if (in.readableBytes() < numBytes.intValue() + 2) { 151 | return false; 152 | } 153 | if (in.getByte(in.readerIndex() + numBytes.intValue()) != '\r' 154 | || in.getByte(in.readerIndex() + numBytes.intValue() + 1) != '\n') { 155 | throw new ProtocolException("Response is not ended by CRLF"); 156 | } 157 | byte[] reply = new byte[numBytes.intValue()]; 158 | in.readBytes(reply); 159 | // skip CRLF 160 | in.skipBytes(2); 161 | out.add(reply); 162 | return true; 163 | } 164 | case '*': { 165 | Long numReplies = decodeLong(in); 166 | if (numReplies == null) { 167 | return false; 168 | } 169 | if (numReplies.intValue() == -1) { 170 | out.add(nullValue); 171 | return true; 172 | } 173 | List replies = new ArrayList<>(); 174 | for (int i = 0; i < numReplies.intValue(); i++) { 175 | if (!decode(in, replies, null)) { 176 | return false; 177 | } 178 | } 179 | out.add(replies); 180 | return true; 181 | } 182 | default: 183 | throw new ProtocolException("Unknown leading char: " + (char) b); 184 | } 185 | } 186 | 187 | @Override 188 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 189 | in.markReaderIndex(); 190 | if (!decode(in, out, NULL_REPLY)) { 191 | in.resetReaderIndex(); 192 | } 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/handler/TxnRedisRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.handler; 17 | 18 | import io.codis.nedis.protocol.RedisCommand; 19 | import io.netty.util.concurrent.Promise; 20 | 21 | /** 22 | * @author zhangduo 23 | */ 24 | public class TxnRedisRequest { 25 | 26 | private final Promise promise; 27 | 28 | private final RedisCommand cmd; 29 | 30 | public TxnRedisRequest(Promise promise, RedisCommand cmd) { 31 | this.promise = promise; 32 | this.cmd = cmd; 33 | } 34 | 35 | public Promise getPromise() { 36 | return promise; 37 | } 38 | 39 | public RedisCommand getCmd() { 40 | return cmd; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/Aggregate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | 21 | /** 22 | * @author Apache9 23 | */ 24 | public enum Aggregate { 25 | SUM, MIN, MAX; 26 | 27 | public final byte[] raw; 28 | 29 | Aggregate() { 30 | raw = name().getBytes(StandardCharsets.UTF_8); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/BitOp.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public enum BitOp { 24 | 25 | AND, OR, XOR, NOT; 26 | 27 | public final byte[] raw; 28 | 29 | private BitOp() { 30 | raw = name().getBytes(StandardCharsets.UTF_8); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/BlockingListsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * @author Apache9 24 | * @see http://redis.io/commands#list 25 | */ 26 | public interface BlockingListsCommands { 27 | 28 | Future> blpop(long timeoutSeconds, byte[]... keys); 29 | 30 | Future> brpop(long timeoutSeconds, byte[]... keys); 31 | 32 | Future brpoplpush(byte[] src, byte[] dst, long timeoutSeconds); 33 | } 34 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ConnectionCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | /** 21 | * You can not call these methods except {@code PING} if the 22 | * {@link io.codis.nedis.NedisClient} is borrowed from a 23 | * {@link io.codis.nedis.NedisClientPool}. 24 | * 25 | * @author Apache9 26 | * @see http://redis.io/commands#connection 27 | */ 28 | public interface ConnectionCommands { 29 | 30 | Future auth(byte[] password); 31 | 32 | Future echo(byte[] msg); 33 | 34 | Future ping(); 35 | 36 | Future quit(); 37 | 38 | Future select(int index); 39 | } 40 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/HashEntry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | /** 19 | * @author Apache9 20 | */ 21 | public class HashEntry { 22 | 23 | private final byte[] field; 24 | 25 | private final byte[] value; 26 | 27 | public HashEntry(byte[] field, byte[] value) { 28 | this.field = field; 29 | this.value = value; 30 | } 31 | 32 | public byte[] field() { 33 | return field; 34 | } 35 | 36 | public byte[] value() { 37 | return value; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/HashesCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * @author Apache9 25 | * @see http://redis.io/commands#hash 26 | */ 27 | public interface HashesCommands { 28 | 29 | Future hdel(byte[] key, byte[]... fields); 30 | 31 | Future hexists(byte[] key, byte[] field); 32 | 33 | Future hget(byte[] key, byte[] field); 34 | 35 | Future> hgetall(byte[] key); 36 | 37 | Future hincrby(byte[] key, byte[] field, long delta); 38 | 39 | Future hincrbyfloat(byte[] key, byte[] field, double delta); 40 | 41 | Future> hkeys(byte[] key); 42 | 43 | Future hlen(byte[] key); 44 | 45 | Future> hmget(byte[] key, byte[]... fields); 46 | 47 | Future hmset(byte[] key, Map field2Value); 48 | 49 | Future> hscan(byte[] key, ScanParams params); 50 | 51 | Future hset(byte[] key, byte[] field, byte[] value); 52 | 53 | Future hsetnx(byte[] key, byte[] field, byte[] value); 54 | 55 | Future> hvals(byte[] key); 56 | } 57 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/HyperLogLogCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | /** 21 | * @author Apache9 22 | * @see http://redis.io/commands#hyperloglog 23 | */ 24 | public interface HyperLogLogCommands { 25 | 26 | Future pfadd(byte[] key, byte[]... elements); 27 | 28 | Future pfcount(byte[]... keys); 29 | 30 | Future pfmerge(byte[] dst, byte[]... keys); 31 | } 32 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/KeysCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * {@code WAIT} is not supported yet. 24 | * 25 | * @author Apache9 26 | * @see http://redis.io/commands#generic 27 | */ 28 | public interface KeysCommands { 29 | 30 | Future del(byte[]... keys); 31 | 32 | Future dump(byte[] key); 33 | 34 | Future exists(byte[] key); 35 | 36 | Future exists(byte[]... keys); 37 | 38 | Future expire(byte[] key, long seconds); 39 | 40 | Future expireat(byte[] key, long unixTimeSeconds); 41 | 42 | Future> keys(byte[] pattern); 43 | 44 | Future migrate(byte[] host, int port, byte[] key, int dstDb, long timeoutMs); 45 | 46 | Future move(byte[] key, int db); 47 | 48 | Future persist(byte[] key); 49 | 50 | Future pexpire(byte[] key, long millis); 51 | 52 | Future pexpireat(byte[] key, long unixTimeMs); 53 | 54 | Future pttl(byte[] key); 55 | 56 | Future randomkey(); 57 | 58 | Future rename(byte[] key, byte[] newKey); 59 | 60 | Future renamenx(byte[] key, byte[] newKey); 61 | 62 | Future restore(byte[] key, int ttlMs, byte[] serializedValue, boolean replace); 63 | 64 | Future> scan(ScanParams params); 65 | 66 | Future> sort(byte[] key); 67 | 68 | Future> sort(byte[] key, SortParams params); 69 | 70 | Future sort(byte[] key, byte[] dst); 71 | 72 | Future sort(byte[] key, SortParams params, byte[] dst); 73 | 74 | Future ttl(byte[] key); 75 | 76 | Future type(byte[] key); 77 | } 78 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ListsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.nio.charset.StandardCharsets; 21 | import java.util.List; 22 | 23 | /** 24 | * @author zhangduo 25 | * @see http://redis.io/commands#list 26 | */ 27 | public interface ListsCommands extends BlockingListsCommands { 28 | 29 | Future lindex(byte[] key, long index); 30 | 31 | public enum LIST_POSITION { 32 | BEFORE, AFTER; 33 | public final byte[] raw; 34 | 35 | private LIST_POSITION() { 36 | raw = name().getBytes(StandardCharsets.UTF_8); 37 | } 38 | } 39 | 40 | Future linsert(byte[] key, LIST_POSITION where, byte[] pivot, byte[] value); 41 | 42 | Future llen(byte[] key); 43 | 44 | Future lpop(byte[] key); 45 | 46 | Future lpush(byte[] key, byte[]... values); 47 | 48 | Future lpushx(byte[] key, byte[] value); 49 | 50 | Future> lrange(byte[] key, long startInclusive, long stopInclusive); 51 | 52 | Future lrem(byte[] key, long count, byte[] value); 53 | 54 | Future lset(byte[] key, long index, byte[] value); 55 | 56 | Future ltrim(byte[] key, long startInclusive, long stopInclusive); 57 | 58 | Future rpop(byte[] key); 59 | 60 | Future rpoplpush(byte[] src, byte[] dst); 61 | 62 | Future rpush(byte[] key, byte[]... values); 63 | 64 | Future rpushx(byte[] key, byte[] value); 65 | } 66 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/RedisCommand.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public enum RedisCommand { 24 | APPEND, ASKING, AUTH, BGREWRITEAOF, BGSAVE, BITCOUNT, BITOP, BITPOS, BLPOP, BRPOP, BRPOPLPUSH, 25 | CLIENT, CLUSTER, CONFIG, DBSIZE, DEBUG, DECR, DECRBY, DEL, DISCARD, DUMP, ECHO, EVAL, EVALSHA, 26 | EXEC, EXISTS, EXPIRE, EXPIREAT, FLUSHALL, FLUSHDB, GET, GETBIT, GETRANGE, GETSET, HDEL, 27 | HEXISTS, HGET, HGETALL, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HMGET, HMSET, HSCAN, HSET, HSETNX, 28 | HVALS, INCR, INCRBY, INCRBYFLOAT, INFO, KEYS, LASTSAVE, LINDEX, LINSERT, LLEN, LPOP, LPUSH, 29 | LPUSHX, LRANGE, LREM, LSET, LTRIM, MGET, MIGRATE, MONITOR, MOVE, MSET, MSETNX, MULTI, OBJECT, 30 | PERSIST, PEXPIRE, PEXPIREAT, PFADD, PFCOUNT, PFMERGE, PING, PSETEX, PSUBSCRIBE, PTTL, PUBLISH, 31 | PUBSUB, PUNSUBSCRIBE, QUIT, RANDOMKEY, RENAME, RENAMENX, RENAMEX, RESTORE, ROLE, RPOP, 32 | RPOPLPUSH, RPUSH, RPUSHX, SADD, SAVE, SCAN, SCARD, SCRIPT, SDIFF, SDIFFSTORE, SELECT, SENTINEL, 33 | SET, SETBIT, SETEX, SETNX, SETRANGE, SHUTDOWN, SINTER, SINTERSTORE, SISMEMBER, SLAVEOF, 34 | SLOWLOG, SMEMBERS, SMOVE, SORT, SPOP, SRANDMEMBER, SREM, SSCAN, STRLEN, SUBSCRIBE, SUBSTR, 35 | SUNION, SUNIONSTORE, SYNC, TIME, TTL, TYPE, UNSUBSCRIBE, UNWATCH, WAIT, WATCH, ZADD, ZCARD, 36 | ZCOUNT, ZINCRBY, ZINTERSTORE, ZLEXCOUNT, ZRANGE, ZRANGEBYLEX, ZRANGEBYSCORE, ZRANK, ZREM, 37 | ZREMRANGEBYLEX, ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZREVRANGE, ZREVRANGEBYLEX, ZREVRANGEBYSCORE, 38 | ZREVRANK, ZSCAN, ZSCORE, ZUNIONSTORE; 39 | 40 | public final byte[] raw; 41 | 42 | RedisCommand() { 43 | raw = name().getBytes(StandardCharsets.UTF_8); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/RedisKeyword.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public enum RedisKeyword { 24 | 25 | ALPHA, ASC, BY, COUNT, DESC, EX, EXISTS, FLUSH, GET, GETNAME, KILL, LIMIT, LIST, LOAD, 26 | MATCH, NX, PX, REPLACE, RESETSTAT, REWRITE, SET, SETNAME, STORE, WITHSCORES, XX; 27 | 28 | public final byte[] raw; 29 | 30 | RedisKeyword() { 31 | raw = name().getBytes(StandardCharsets.UTF_8); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ScanParams.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import static io.codis.nedis.util.NedisUtils.toBytes; 19 | 20 | /** 21 | * @author Apache9 22 | * @see http://redis.io/commands/scan 23 | */ 24 | public class ScanParams { 25 | 26 | private static final byte[] START_CURSOR = toBytes("0"); 27 | 28 | private byte[] cursor = START_CURSOR; 29 | 30 | private byte[] pattern; 31 | 32 | private long count; 33 | 34 | public byte[] cursor() { 35 | return cursor; 36 | } 37 | 38 | public ScanParams cursor(byte[] cursor) { 39 | this.cursor = cursor != null ? cursor : START_CURSOR; 40 | return this; 41 | } 42 | 43 | public byte[] match() { 44 | return pattern; 45 | } 46 | 47 | public ScanParams match(byte[] pattern) { 48 | this.pattern = pattern; 49 | return this; 50 | } 51 | 52 | public long count() { 53 | return count; 54 | } 55 | 56 | public ScanParams count(long count) { 57 | this.count = count; 58 | return this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ScanResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import static io.codis.nedis.util.NedisUtils.toBytes; 19 | 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | /** 24 | * @author zhangduo 25 | * @see http://redis.io/commands/scan 26 | */ 27 | public class ScanResult { 28 | 29 | private static final byte[] FINISHED_CURSOR = toBytes("0"); 30 | 31 | private final byte[] cursor; 32 | 33 | private final List values; 34 | 35 | public ScanResult(byte[] cursor, List values) { 36 | this.cursor = cursor; 37 | this.values = values; 38 | } 39 | 40 | public byte[] cursor() { 41 | return cursor; 42 | } 43 | 44 | public List values() { 45 | return values; 46 | } 47 | 48 | public boolean more() { 49 | return !Arrays.equals(cursor, FINISHED_CURSOR); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ScriptingCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * @author Apache9 24 | * @see http://redis.io/commands#scripting 25 | */ 26 | public interface ScriptingCommands { 27 | 28 | Future eval(byte[] script, int numKeys, byte[]... keysvalues); 29 | 30 | Future evalsha(byte[] sha1, int numKeys, byte[]... keysvalues); 31 | 32 | Future> scriptExists(byte[]... scripts); 33 | 34 | Future scriptFlush(); 35 | 36 | Future scriptKill(); 37 | 38 | Future scriptLoad(byte[] script); 39 | } 40 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ServerCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * {@code MONITOR}, {@code SHUTDOWN} and {@code SLOWLOG} are not supported yet. 24 | * 25 | * @author Apache9 26 | * @see http://redis.io/commands#server 27 | */ 28 | public interface ServerCommands { 29 | 30 | Future bgrewriteaof(); 31 | 32 | Future bgsave(); 33 | 34 | Future clientGetname(); 35 | 36 | Future clientKill(byte[] addr); 37 | 38 | Future clientList(); 39 | 40 | Future clientSetname(byte[] name); 41 | 42 | Future> configGet(byte[] pattern); 43 | 44 | Future configResetstat(); 45 | 46 | Future configRewrite(); 47 | 48 | Future configSet(byte[] name, byte[] value); 49 | 50 | Future dbsize(); 51 | 52 | Future flushall(); 53 | 54 | Future flushdb(); 55 | 56 | Future info(); 57 | 58 | Future info(byte[] section); 59 | 60 | Future lastsave(); 61 | 62 | Future> role(); 63 | 64 | Future save(boolean save); 65 | 66 | Future slaveof(String host, int port); 67 | 68 | Future sync(); 69 | 70 | Future> time(); 71 | } 72 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/SetParams.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | /** 19 | * @author Apache9 20 | * @see http://redis.io/commands/set 21 | */ 22 | public class SetParams { 23 | 24 | private long ex; 25 | 26 | private long px; 27 | 28 | private boolean nx; 29 | 30 | private boolean xx; 31 | 32 | public SetParams setEx(long seconds) { 33 | this.ex = seconds; 34 | this.px = 0L; 35 | return this; 36 | } 37 | 38 | public SetParams setPx(long millis) { 39 | this.px = millis; 40 | this.ex = 0L; 41 | return this; 42 | } 43 | 44 | public SetParams setNx() { 45 | this.nx = true; 46 | this.xx = false; 47 | return this; 48 | } 49 | 50 | public SetParams clearNx() { 51 | this.nx = false; 52 | return this; 53 | } 54 | 55 | public SetParams setXx() { 56 | this.xx = true; 57 | this.nx = false; 58 | return this; 59 | } 60 | 61 | public SetParams clearXx() { 62 | this.xx = false; 63 | return this; 64 | } 65 | 66 | public long ex() { 67 | return ex; 68 | } 69 | 70 | public long px() { 71 | return px; 72 | } 73 | 74 | public boolean nx() { 75 | return nx; 76 | } 77 | 78 | public boolean xx() { 79 | return xx; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/SetsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.Set; 21 | 22 | /** 23 | * @author zhangduo 24 | * @see http://redis.io/commands#set 25 | */ 26 | public interface SetsCommands { 27 | 28 | Future sadd(byte[] key, byte[]... members); 29 | 30 | Future scard(byte[] key); 31 | 32 | Future> sdiff(byte[]... keys); 33 | 34 | Future sdiffstore(byte[] dst, byte[]... keys); 35 | 36 | Future> sinter(byte[]... keys); 37 | 38 | Future sinterstore(byte[] dst, byte[]... keys); 39 | 40 | Future sismember(byte[] key, byte[] member); 41 | 42 | Future> smembers(byte[] key); 43 | 44 | Future smove(byte[] src, byte[] dst, byte[] member); 45 | 46 | Future spop(byte[] key); 47 | 48 | Future srandmember(byte[] key); 49 | 50 | Future> srandmember(byte[] key, long count); 51 | 52 | Future srem(byte[] key, byte[]... members); 53 | 54 | Future> sscan(byte[] key, ScanParams params); 55 | 56 | Future> sunion(byte[]... keys); 57 | 58 | Future sunionstore(byte[] dst, byte[]... keys); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/SortParams.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import static io.codis.nedis.protocol.RedisKeyword.ALPHA; 19 | import static io.codis.nedis.protocol.RedisKeyword.ASC; 20 | import static io.codis.nedis.protocol.RedisKeyword.DESC; 21 | import static io.codis.nedis.protocol.RedisKeyword.GET; 22 | import static io.codis.nedis.util.NedisUtils.toBytes; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * @author Apache9 29 | * @see http://redis.io/commands/sort 30 | */ 31 | public class SortParams { 32 | 33 | private byte[] by; 34 | 35 | private final List limit = new ArrayList<>(2); 36 | 37 | private final List get = new ArrayList<>(); 38 | 39 | private byte[] order; 40 | 41 | private byte[] alpha; 42 | 43 | public SortParams by(byte[] pattern) { 44 | this.by = pattern; 45 | return this; 46 | } 47 | 48 | public SortParams limit(long offset, long count) { 49 | limit.clear(); 50 | limit.add(toBytes(offset)); 51 | limit.add(toBytes(count)); 52 | return this; 53 | } 54 | 55 | public SortParams get(byte[]... patterns) { 56 | for (byte[] pattern: patterns) { 57 | get.add(GET.raw); 58 | get.add(pattern); 59 | } 60 | return this; 61 | } 62 | 63 | public SortParams clearGet() { 64 | get.clear(); 65 | return this; 66 | } 67 | 68 | public SortParams asc() { 69 | order = ASC.raw; 70 | return this; 71 | } 72 | 73 | public SortParams desc() { 74 | order = DESC.raw; 75 | return this; 76 | } 77 | 78 | public SortParams clearOrder() { 79 | order = null; 80 | return this; 81 | } 82 | 83 | public SortParams alpha() { 84 | alpha = ALPHA.raw; 85 | return this; 86 | } 87 | 88 | public SortParams clearAlpha() { 89 | alpha = null; 90 | return this; 91 | } 92 | 93 | public byte[] by() { 94 | return by; 95 | } 96 | 97 | public List limit() { 98 | return limit; 99 | } 100 | 101 | public List get() { 102 | return get; 103 | } 104 | 105 | public byte[] order() { 106 | return order; 107 | } 108 | 109 | public byte[] getAlpha() { 110 | return alpha; 111 | } 112 | 113 | public SortParams clear() { 114 | by = null; 115 | limit.clear(); 116 | get.clear(); 117 | order = null; 118 | alpha = null; 119 | return this; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/SortedSetEntry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | /** 19 | * @author Apache9 20 | */ 21 | public class SortedSetEntry { 22 | 23 | private final byte[] member; 24 | 25 | private final double score; 26 | 27 | public SortedSetEntry(byte[] member, double score) { 28 | this.member = member; 29 | this.score = score; 30 | } 31 | 32 | public byte[] member() { 33 | return member; 34 | } 35 | 36 | public double score() { 37 | return score; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/SortedSetsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * @author Apache9 25 | * @see http://redis.io/commands#sorted_set 26 | */ 27 | public interface SortedSetsCommands { 28 | 29 | Future zadd(byte[] key, double score, byte[] member); 30 | 31 | Future zadd(byte[] key, Map member2Score); 32 | 33 | Future zcard(byte[] key); 34 | 35 | Future zcount(byte[] key, byte[] min, byte[] max); 36 | 37 | Future zincrby(byte[] key, double delta, byte[] member); 38 | 39 | Future zinterstore(byte[] dst, byte[]... keys); 40 | 41 | Future zinterstore(byte[] dst, ZSetOpParams params); 42 | 43 | Future zlexcount(byte[] key, byte[] min, byte[] max); 44 | 45 | Future> zrange(byte[] key, long startInclusive, long stopInclusive); 46 | 47 | Future> zrangeWithScores(byte[] key, long startInclusive, 48 | long stopInclusive); 49 | 50 | Future> zrangebylex(byte[] key, byte[] min, byte[] max); 51 | 52 | Future> zrangebylex(byte[] key, byte[] min, byte[] max, long offset, long count); 53 | 54 | Future> zrangebyscore(byte[] key, byte[] min, byte[] max); 55 | 56 | Future> zrangebyscore(byte[] key, byte[] min, byte[] max, long offset, long count); 57 | 58 | Future> zrangebyscoreWithScores(byte[] key, byte[] min, byte[] max); 59 | 60 | Future> zrangebyscoreWithScores(byte[] key, byte[] min, byte[] max, 61 | long offset, long count); 62 | 63 | Future zrank(byte[] key, byte[] member); 64 | 65 | Future zrem(byte[] key, byte[]... members); 66 | 67 | Future zremrangebylex(byte[] key, byte[] min, byte[] max); 68 | 69 | Future zremrangebyrank(byte[] key, long startInclusive, long stopInclusive); 70 | 71 | Future zremrangebyscore(byte[] key, byte[] min, byte[] max); 72 | 73 | Future> zrevrange(byte[] key, long startInclusive, long stopInclusive); 74 | 75 | Future> zrevrangeWithScores(byte[] key, long startInclusive, 76 | long stopInclusive); 77 | 78 | Future> zrevrangebylex(byte[] key, byte[] max, byte[] min); 79 | 80 | Future> zrevrangebylex(byte[] key, byte[] max, byte[] min, long offset, long count); 81 | 82 | Future> zrevrangebyscore(byte[] key, byte[] max, byte[] min); 83 | 84 | Future> zrevrangebyscore(byte[] key, byte[] max, byte[] min, long offset, 85 | long count); 86 | 87 | Future> zrevrangebyscoreWithScores(byte[] key, byte[] max, byte[] min); 88 | 89 | Future> zrevrangebyscoreWithScores(byte[] key, byte[] max, byte[] min, 90 | long offset, long count); 91 | 92 | Future zrevrank(byte[] key, byte[] member); 93 | 94 | Future> zscan(byte[] key, ScanParams params); 95 | 96 | Future zscore(byte[] key, byte[] member); 97 | 98 | Future zunionstore(byte[] dst, byte[]... keys); 99 | 100 | Future zunionstore(byte[] dst, ZSetOpParams params); 101 | } 102 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/StringsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import java.util.List; 19 | 20 | import io.netty.util.concurrent.Future; 21 | 22 | /** 23 | * @author Apache9 24 | * @see http://redis.io/commands#string 25 | */ 26 | public interface StringsCommands { 27 | 28 | Future append(byte[] key, byte[] value); 29 | 30 | Future bitcount(byte[] key); 31 | 32 | Future bitcount(byte[] key, long startInclusive, long endInclusive); 33 | 34 | Future bitop(BitOp op, byte[] dst, byte[]... keys); 35 | 36 | Future bitpos(byte[] key, boolean bit); 37 | 38 | Future bitpos(byte[] key, boolean bit, long startInclusive); 39 | 40 | Future bitpos(byte[] key, boolean bit, long startInclusive, long endInclusive); 41 | 42 | Future decr(byte[] key); 43 | 44 | Future decrby(byte[] key, long delta); 45 | 46 | Future get(byte[] key); 47 | 48 | Future getbit(byte[] key, long offset); 49 | 50 | Future getrange(byte[] key, long startInclusive, long endInclusive); 51 | 52 | Future getset(byte[] key, byte[] value); 53 | 54 | Future incr(byte[] key); 55 | 56 | Future incrby(byte[] key, long delta); 57 | 58 | Future incrbyfloat(byte[] key, double delta); 59 | 60 | Future> mget(byte[]... keys); 61 | 62 | Future mset(byte[]... keysvalues); 63 | 64 | Future msetnx(byte[]... keysvalues); 65 | 66 | Future set(byte[] key, byte[] value); 67 | 68 | Future set(byte[] key, byte[] value, SetParams params); 69 | 70 | Future setbit(byte[] key, long offset, boolean bit); 71 | 72 | Future setrange(byte[] key, long offset, byte[] value); 73 | 74 | Future strlen(byte[] key); 75 | } 76 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/TransactionsCommands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import io.netty.util.concurrent.Future; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * @author Apache9 24 | * @see http://redis.io/commands#transactions 25 | */ 26 | public interface TransactionsCommands { 27 | 28 | public static final String QUEUED = "QUEUED"; 29 | 30 | Future discard(); 31 | 32 | Future> exec(); 33 | 34 | Future multi(); 35 | 36 | Future unwatch(); 37 | 38 | Future watch(byte[]... keys); 39 | } 40 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/protocol/ZSetOpParams.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.protocol; 17 | 18 | import static io.codis.nedis.util.NedisUtils.toBytes; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Apache9 25 | * @see http://redis.io/commands/zinterstore 26 | * @see http://redis.io/commands/zunionstore 27 | */ 28 | public class ZSetOpParams { 29 | 30 | private final List keys = new ArrayList<>(); 31 | 32 | private final List weights = new ArrayList<>(); 33 | 34 | private Aggregate aggregate; 35 | 36 | public ZSetOpParams clear() { 37 | keys.clear(); 38 | weights.clear(); 39 | aggregate = null; 40 | return this; 41 | } 42 | 43 | public ZSetOpParams key(byte[] key) { 44 | if (!weights.isEmpty()) { 45 | throw new IllegalArgumentException(); 46 | } 47 | keys.add(key); 48 | return this; 49 | } 50 | 51 | public ZSetOpParams keyWithWeight(byte[] key, double weight) { 52 | if (!keys.isEmpty() && weights.isEmpty()) { 53 | throw new IllegalArgumentException(); 54 | } 55 | keys.add(key); 56 | weights.add(toBytes(weight)); 57 | return this; 58 | } 59 | 60 | public ZSetOpParams aggregate(Aggregate aggregate) { 61 | this.aggregate = aggregate; 62 | return this; 63 | } 64 | 65 | public List keys() { 66 | return keys; 67 | } 68 | 69 | public List weights() { 70 | return weights; 71 | } 72 | 73 | public Aggregate aggregate() { 74 | return aggregate; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/util/AbstractNedisBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.util; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.EventLoopGroup; 20 | 21 | import static io.codis.nedis.util.NedisUtils.defaultEventLoopConfig; 22 | 23 | import org.apache.commons.lang3.tuple.Pair; 24 | 25 | /** 26 | * Common configurations: 27 | *
    28 | *
  • EventLoopGroup
  • 29 | *
  • ChannelClass
  • 30 | *
  • TimeoutMs
  • 31 | *
32 | * 33 | * @author Apache9 34 | */ 35 | public class AbstractNedisBuilder { 36 | 37 | protected EventLoopGroup group; 38 | 39 | protected Class channelClass; 40 | 41 | protected long timeoutMs; 42 | 43 | public AbstractNedisBuilder group(EventLoopGroup group) { 44 | this.group = group; 45 | return this; 46 | } 47 | 48 | public EventLoopGroup group() { 49 | return group; 50 | } 51 | 52 | public AbstractNedisBuilder channel(Class channelClass) { 53 | this.channelClass = channelClass; 54 | return this; 55 | } 56 | 57 | public AbstractNedisBuilder timeoutMs(long timeoutMs) { 58 | this.timeoutMs = timeoutMs; 59 | return this; 60 | } 61 | 62 | public final void validateGroupConfig() { 63 | if (group == null && channelClass != null) { 64 | throw new IllegalArgumentException("group is null but channel is not"); 65 | } 66 | if (channelClass == null && group != null) { 67 | throw new IllegalArgumentException("channel is null but group is not"); 68 | } 69 | if (group == null) { 70 | Pair> defaultEventLoopConfig = defaultEventLoopConfig(); 71 | group = defaultEventLoopConfig.getLeft(); 72 | channelClass = defaultEventLoopConfig.getRight(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/util/NedisClientHashSet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.util; 17 | 18 | import io.codis.nedis.NedisClient; 19 | 20 | /** 21 | * An identity LinkedHashSet. 22 | * 23 | * @author Apache9 24 | */ 25 | public class NedisClientHashSet { 26 | 27 | private static final float LOAD_FACTOR = 0.75f; 28 | 29 | private static final int MAXIMUM_CAPACITY = 1 << 30; 30 | 31 | private static final class Entry { 32 | 33 | final NedisClient value; 34 | 35 | Entry next; 36 | 37 | Entry before; 38 | 39 | Entry after; 40 | 41 | public Entry(NedisClient value, Entry next) { 42 | this.value = value; 43 | this.next = next; 44 | } 45 | 46 | public void remove() { 47 | before.after = after; 48 | after.before = before; 49 | } 50 | 51 | public void addBefore(Entry existingEntry) { 52 | after = existingEntry; 53 | before = existingEntry.before; 54 | before.after = this; 55 | after.before = this; 56 | } 57 | } 58 | 59 | private final Entry[] table; 60 | 61 | private Entry header; 62 | 63 | private int size; 64 | 65 | private static final int tableSizeFor(int cap) { 66 | int n = cap - 1; 67 | n |= n >>> 1; 68 | n |= n >>> 2; 69 | n |= n >>> 4; 70 | n |= n >>> 8; 71 | n |= n >>> 16; 72 | return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 73 | } 74 | 75 | private int indexFor(NedisClient client) { 76 | int h = System.identityHashCode(client); 77 | h = h ^ (h >>> 16); 78 | return h & (table.length - 1); 79 | } 80 | 81 | public NedisClientHashSet(int size) { 82 | table = new Entry[tableSizeFor((int) (size / LOAD_FACTOR))]; 83 | header = new Entry(null, null); 84 | header.before = header.after = header; 85 | } 86 | 87 | public boolean isEmpty() { 88 | return size == 0; 89 | } 90 | 91 | public int size() { 92 | return size; 93 | } 94 | 95 | public boolean add(NedisClient value) { 96 | int i = indexFor(value); 97 | for (Entry e = table[i]; e != null; e = e.next) { 98 | if (e.value == value) { 99 | return false; 100 | } 101 | } 102 | Entry old = table[i]; 103 | Entry e = new Entry(value, old); 104 | table[i] = e; 105 | e.addBefore(header); 106 | size++; 107 | return true; 108 | } 109 | 110 | public boolean contains(NedisClient value) { 111 | if (isEmpty()) { 112 | return false; 113 | } 114 | int i = indexFor(value); 115 | for (Entry e = table[i]; e != null; e = e.next) { 116 | if (e.value == value) { 117 | return true; 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | public boolean remove(NedisClient value) { 124 | if (isEmpty()) { 125 | return false; 126 | } 127 | int i = indexFor(value); 128 | Entry prev = table[i]; 129 | Entry e = prev; 130 | 131 | while (e != null) { 132 | Entry next = e.next; 133 | if (value == e.value) { 134 | size--; 135 | if (prev == e) { 136 | table[i] = next; 137 | } else { 138 | prev.next = next; 139 | } 140 | e.remove(); 141 | return true; 142 | } 143 | prev = e; 144 | e = next; 145 | } 146 | 147 | return false; 148 | } 149 | 150 | public NedisClient head(boolean remove) { 151 | if (isEmpty()) { 152 | return null; 153 | } 154 | Entry e = header.after; 155 | if (remove) { 156 | remove(e.value); 157 | } else { 158 | e.remove(); 159 | e.addBefore(header); 160 | } 161 | return e.value; 162 | } 163 | 164 | public NedisClient[] toArray() { 165 | NedisClient[] arr = new NedisClient[size]; 166 | Entry e = header.after; 167 | for (int i = 0; i < size; i++) { 168 | arr[i] = e.value; 169 | e = e.after; 170 | } 171 | return arr; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /nedis-client/src/main/java/io/codis/nedis/util/NedisUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.util; 17 | 18 | import io.codis.nedis.AsyncCloseable; 19 | import io.codis.nedis.ConnectionManagement; 20 | import io.codis.nedis.NedisClient; 21 | import io.codis.nedis.NedisClientPool; 22 | import io.codis.nedis.protocol.BlockingListsCommands; 23 | import io.codis.nedis.protocol.TransactionsCommands; 24 | import io.netty.channel.Channel; 25 | import io.netty.channel.EventLoopGroup; 26 | import io.netty.channel.nio.NioEventLoopGroup; 27 | import io.netty.channel.socket.nio.NioSocketChannel; 28 | import io.netty.util.concurrent.EventExecutor; 29 | import io.netty.util.concurrent.Future; 30 | import io.netty.util.concurrent.FutureListener; 31 | import io.netty.util.concurrent.Promise; 32 | 33 | import java.lang.reflect.InvocationHandler; 34 | import java.lang.reflect.InvocationTargetException; 35 | import java.lang.reflect.Method; 36 | import java.lang.reflect.Proxy; 37 | import java.nio.charset.StandardCharsets; 38 | import java.util.Arrays; 39 | import java.util.Comparator; 40 | import java.util.Map; 41 | import java.util.Set; 42 | import java.util.TreeMap; 43 | import java.util.TreeSet; 44 | 45 | import javax.naming.OperationNotSupportedException; 46 | 47 | import org.apache.commons.lang3.tuple.Pair; 48 | 49 | /** 50 | * @author Apache9 51 | */ 52 | public class NedisUtils { 53 | 54 | public static final int DEFAULT_REDIS_PORT = 6379; 55 | 56 | public static byte[] toBytes(double value) { 57 | return toBytes(Double.toString(value)); 58 | } 59 | 60 | public static byte[] toBytes(int value) { 61 | return toBytes(Integer.toString(value)); 62 | } 63 | 64 | public static byte[] toBytes(long value) { 65 | return toBytes(Long.toString(value)); 66 | } 67 | 68 | private static final byte[] TRUE = new byte[] { 69 | '1' 70 | }; 71 | 72 | private static final byte[] FALSE = new byte[] { 73 | '0' 74 | }; 75 | 76 | public static byte[] toBytes(boolean value) { 77 | return value ? TRUE : FALSE; 78 | } 79 | 80 | public static byte[] toBytes(String value) { 81 | return value.getBytes(StandardCharsets.UTF_8); 82 | } 83 | 84 | public static byte[] toBytesExclusive(double value) { 85 | return toBytes("(" + Double.toString(value)); 86 | } 87 | 88 | public static String bytesToString(byte[] value) { 89 | return new String(value, StandardCharsets.UTF_8); 90 | } 91 | 92 | public static double bytesToDouble(byte[] value) { 93 | return Double.parseDouble(bytesToString(value)); 94 | } 95 | 96 | public static byte[][] toParamsReverse(byte[][] tailParams, byte[]... headParams) { 97 | return toParams(headParams, tailParams); 98 | } 99 | 100 | public static byte[][] toParams(byte[][] headParams, byte[]... tailParams) { 101 | byte[][] params = Arrays.copyOf(headParams, headParams.length + tailParams.length); 102 | System.arraycopy(tailParams, 0, params, headParams.length, tailParams.length); 103 | return params; 104 | } 105 | 106 | public static EventExecutor getEventExecutor(Future future) { 107 | Class clazz = future.getClass(); 108 | for (;;) { 109 | try { 110 | Method method = clazz.getDeclaredMethod("executor"); 111 | method.setAccessible(true); 112 | return (EventExecutor) method.invoke(future); 113 | } catch (NoSuchMethodException e) {} catch (SecurityException | IllegalAccessException 114 | | InvocationTargetException e) { 115 | throw new RuntimeException(e); 116 | } 117 | clazz = clazz.getSuperclass(); 118 | if (clazz == null) { 119 | return null; 120 | } 121 | } 122 | } 123 | 124 | private static final class Invoker implements InvocationHandler { 125 | 126 | private final NedisClientPool pool; 127 | 128 | public Invoker(NedisClientPool pool) { 129 | this.pool = pool; 130 | } 131 | 132 | private void resetTimeout(final NedisClient client, long previousTimeoutMs) { 133 | client.setTimeout(previousTimeoutMs).addListener(new FutureListener() { 134 | 135 | @Override 136 | public void operationComplete(Future future) throws Exception { 137 | if (future.getNow() != null) { 138 | client.release(); 139 | } 140 | } 141 | 142 | }); 143 | } 144 | 145 | @SuppressWarnings({ 146 | "unchecked", "rawtypes" 147 | }) 148 | private void callBlocking(final NedisClient client, Method method, Object[] args, 149 | final long previousTimeoutMs, final Promise promise) throws IllegalAccessException, 150 | InvocationTargetException { 151 | ((Future) method.invoke(client, args)).addListener(new FutureListener() { 152 | 153 | @Override 154 | public void operationComplete(Future future) throws Exception { 155 | if (future.isSuccess()) { 156 | promise.trySuccess(future.getNow()); 157 | } else { 158 | promise.tryFailure(future.cause()); 159 | } 160 | resetTimeout(client, previousTimeoutMs); 161 | } 162 | }); 163 | } 164 | 165 | private void setInfiniteTimeout(final NedisClient client, final Method method, 166 | final Object[] args, @SuppressWarnings("rawtypes") final Promise promise) { 167 | client.setTimeout(0L).addListener(new FutureListener() { 168 | 169 | @Override 170 | public void operationComplete(Future future) throws Exception { 171 | // will not fail, but could be null 172 | Long previousTimeoutMs = future.get(); 173 | if (previousTimeoutMs == null) { 174 | promise.tryFailure(new IllegalStateException("already closed")); 175 | } else { 176 | callBlocking(client, method, args, previousTimeoutMs.longValue(), promise); 177 | } 178 | } 179 | }); 180 | } 181 | 182 | @SuppressWarnings({ 183 | "unchecked", "rawtypes" 184 | }) 185 | private void call(final NedisClient client, Method method, Object[] args, 186 | final Promise promise) throws IllegalAccessException, InvocationTargetException { 187 | ((Future) method.invoke(client, args)).addListener(new FutureListener() { 188 | 189 | @Override 190 | public void operationComplete(Future future) throws Exception { 191 | if (future.isSuccess()) { 192 | promise.trySuccess(future.getNow()); 193 | } else { 194 | promise.tryFailure(future.cause()); 195 | } 196 | client.release(); 197 | } 198 | }); 199 | } 200 | 201 | @Override 202 | public Object invoke(Object proxy, final Method method, final Object[] args) 203 | throws Throwable { 204 | // delegate Object methods like toString, hashCode, etc. 205 | if (method.getDeclaringClass().equals(Object.class)) { 206 | return method.invoke(this, args); 207 | } 208 | if (method.getDeclaringClass().equals(AsyncCloseable.class)) { 209 | return method.invoke(pool, args); 210 | } 211 | if (method.getDeclaringClass().equals(ConnectionManagement.class)) { 212 | throw new OperationNotSupportedException( 213 | "Can not call connection related methods on pooled client"); 214 | } 215 | if (method.getDeclaringClass().equals(TransactionsCommands.class)) { 216 | throw new OperationNotSupportedException( 217 | "Can not call transaction related methods on pooled client"); 218 | } 219 | Future clientFuture = pool.acquire(); 220 | @SuppressWarnings("rawtypes") 221 | final Promise promise = getEventExecutor(clientFuture).newPromise(); 222 | clientFuture.addListener(new FutureListener() { 223 | 224 | @Override 225 | public void operationComplete(Future future) throws Exception { 226 | if (future.isSuccess()) { 227 | if (method.getDeclaringClass().equals(BlockingListsCommands.class)) { 228 | setInfiniteTimeout(future.getNow(), method, args, promise); 229 | } else { 230 | call(future.getNow(), method, args, promise); 231 | } 232 | } else { 233 | promise.tryFailure(future.cause()); 234 | } 235 | } 236 | }); 237 | return promise; 238 | } 239 | 240 | } 241 | 242 | /** 243 | * Return a {@link NedisClient} which does acquire-execute-release automatically. You do not 244 | * need to call release when using the returned {@link NedisClient}. 245 | */ 246 | public static NedisClient newPooledClient(NedisClientPool pool) { 247 | return (NedisClient) Proxy.newProxyInstance(NedisClient.class.getClassLoader(), 248 | new Class[] { 249 | NedisClient.class 250 | }, new Invoker(pool)); 251 | } 252 | 253 | public static final Comparator BYTES_COMPARATOR = new Comparator() { 254 | 255 | @Override 256 | public int compare(byte[] o1, byte[] o2) { 257 | for (int i = 0, j = 0; i < o1.length && j < o2.length; i++, j++) { 258 | int a = (o1[i] & 0xff); 259 | int b = (o2[j] & 0xff); 260 | if (a != b) { 261 | return a - b; 262 | } 263 | } 264 | return o1.length - o2.length; 265 | } 266 | }; 267 | 268 | public static Map newBytesKeyMap() { 269 | return new TreeMap(BYTES_COMPARATOR); 270 | } 271 | 272 | public static Set newBytesSet() { 273 | return new TreeSet(BYTES_COMPARATOR); 274 | } 275 | 276 | private static Pair> DEFAULT_EVENT_LOOP_CONFIG; 277 | 278 | public static synchronized Pair> defaultEventLoopConfig() { 279 | if (DEFAULT_EVENT_LOOP_CONFIG == null) { 280 | DEFAULT_EVENT_LOOP_CONFIG = Pair.>of( 281 | new NioEventLoopGroup(), NioSocketChannel.class); 282 | } 283 | return DEFAULT_EVENT_LOOP_CONFIG; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /nedis-client/src/test/java/io/codis/nedis/RedisServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * @author Apache9 22 | */ 23 | public class RedisServer { 24 | 25 | private final ProcessBuilder builder; 26 | 27 | private Process process; 28 | 29 | public RedisServer(int port) { 30 | builder = new ProcessBuilder().command("redis-server", "--port", Long.toString(port)) 31 | .inheritIO(); 32 | } 33 | 34 | public void start() throws IOException { 35 | process = builder.start(); 36 | } 37 | 38 | public void stop() { 39 | if (process != null) { 40 | process.destroy(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /nedis-client/src/test/java/io/codis/nedis/TestNedis.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import static io.codis.nedis.TestUtils.cleanRedis; 19 | import static io.codis.nedis.TestUtils.probeFreePort; 20 | import static io.codis.nedis.TestUtils.waitUntilRedisUp; 21 | import static io.codis.nedis.protocol.RedisCommand.GET; 22 | import static io.codis.nedis.util.NedisUtils.bytesToString; 23 | import static io.codis.nedis.util.NedisUtils.toBytes; 24 | import static org.hamcrest.CoreMatchers.instanceOf; 25 | import static org.hamcrest.CoreMatchers.is; 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertFalse; 28 | import static org.junit.Assert.assertNull; 29 | import static org.junit.Assert.assertThat; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | import io.codis.nedis.NedisClient; 33 | import io.codis.nedis.NedisClientPool; 34 | import io.codis.nedis.NedisClientPoolBuilder; 35 | import io.codis.nedis.exception.RedisResponseException; 36 | import io.codis.nedis.exception.TxnAbortException; 37 | import io.codis.nedis.exception.TxnDiscardException; 38 | import io.codis.nedis.util.NedisUtils; 39 | import io.netty.handler.timeout.ReadTimeoutException; 40 | import io.netty.util.concurrent.Future; 41 | 42 | import java.io.IOException; 43 | import java.net.InetSocketAddress; 44 | import java.util.List; 45 | import java.util.concurrent.ExecutionException; 46 | 47 | import org.junit.After; 48 | import org.junit.AfterClass; 49 | import org.junit.BeforeClass; 50 | import org.junit.Test; 51 | 52 | /** 53 | * @author Apache9 54 | */ 55 | public class TestNedis { 56 | 57 | private static int PORT; 58 | 59 | private static RedisServer REDIS; 60 | 61 | private NedisClientPool pool; 62 | 63 | @BeforeClass 64 | public static void setUp() throws IOException, InterruptedException { 65 | PORT = probeFreePort(); 66 | REDIS = new RedisServer(PORT); 67 | REDIS.start(); 68 | waitUntilRedisUp(PORT); 69 | } 70 | 71 | @AfterClass 72 | public static void tearDownAfterClass() throws InterruptedException { 73 | REDIS.stop(); 74 | } 75 | 76 | @After 77 | public void tearDown() throws InterruptedException, IOException { 78 | if (pool != null) { 79 | pool.close(); 80 | } 81 | cleanRedis(PORT); 82 | } 83 | 84 | @Test 85 | public void test() throws InterruptedException, ExecutionException { 86 | pool = NedisClientPoolBuilder.create() 87 | .remoteAddress(new InetSocketAddress("127.0.0.1", PORT)).clientName("test").build(); 88 | NedisClient client = NedisUtils.newPooledClient(pool); 89 | System.out.println(client.toString()); 90 | Future pingFuture = client.ping(); 91 | for (int i = 0; i < 1000; i++) { 92 | Future setFuture = client.set(toBytes("foo"), toBytes("bar" + i)); 93 | assertTrue(setFuture.sync().getNow()); 94 | assertEquals("bar" + i, bytesToString(client.get(toBytes("foo")).sync().getNow())); 95 | } 96 | assertEquals("PONG", pingFuture.sync().getNow()); 97 | assertEquals(null, client.get(toBytes("bar")).sync().getNow()); 98 | 99 | NedisClient pipelineClient = pool.acquire().sync().getNow(); 100 | Future incrFuture = pipelineClient.incr(toBytes("num")); 101 | Future incrByFuture = pipelineClient.incrby(toBytes("num"), 2L); 102 | Future decrFuture = pipelineClient.decr(toBytes("num")); 103 | Future decrByFuture = pipelineClient.decrby(toBytes("num"), 2L); 104 | assertEquals(1L, incrFuture.sync().getNow().longValue()); 105 | assertEquals(3L, incrByFuture.sync().getNow().longValue()); 106 | assertEquals(2L, decrFuture.sync().getNow().longValue()); 107 | assertEquals(0L, decrByFuture.sync().getNow().longValue()); 108 | pipelineClient.release(); 109 | 110 | client.mset(toBytes("a1"), toBytes("b1"), toBytes("a2"), toBytes("b2")).sync(); 111 | 112 | List resp = client.mget(toBytes("a1"), toBytes("a2"), toBytes("a3")).sync() 113 | .getNow(); 114 | assertEquals(3, resp.size()); 115 | assertEquals("b1", bytesToString(resp.get(0))); 116 | assertEquals("b2", bytesToString(resp.get(1))); 117 | assertEquals(null, resp.get(2)); 118 | 119 | assertEquals(pool.numConns(), pool.numPooledConns()); 120 | int numConns = pool.numConns(); 121 | Throwable error = client.execCmd(GET.raw).await().cause(); 122 | error.printStackTrace(); 123 | assertTrue(error instanceof RedisResponseException); 124 | 125 | // this error does not cause a connection closing. 126 | assertEquals(numConns, pool.numConns()); 127 | assertEquals(numConns, pool.numPooledConns()); 128 | 129 | client.close().sync(); 130 | 131 | assertEquals(0, pool.numPooledConns()); 132 | assertEquals(0, pool.numConns()); 133 | } 134 | 135 | @Test 136 | public void testTimeout() throws InterruptedException { 137 | pool = NedisClientPoolBuilder.create() 138 | .remoteAddress(new InetSocketAddress("127.0.0.1", PORT)).database(1).build(); 139 | NedisClient client = pool.acquire().sync().getNow(); 140 | Thread.sleep(1000); 141 | assertEquals(1, pool.numPooledConns()); 142 | assertEquals(1, pool.numConns()); 143 | assertEquals(0L, client.setTimeout(100).sync().getNow().longValue()); 144 | Future future = client.blpop(1, toBytes("foo")).await(); 145 | assertFalse(future.isSuccess()); 146 | assertTrue(future.cause() instanceof ReadTimeoutException); 147 | Thread.sleep(1000); 148 | assertEquals(0, pool.numPooledConns()); 149 | assertEquals(0, pool.numConns()); 150 | } 151 | 152 | @Test 153 | public void testBlockingCommands() throws InterruptedException { 154 | pool = NedisClientPoolBuilder.create() 155 | .remoteAddress(new InetSocketAddress("127.0.0.1", PORT)).timeoutMs(100) 156 | .exclusive(true).build(); 157 | NedisClient client = NedisUtils.newPooledClient(pool); 158 | Future> brpopFuture = client.brpop(100, toBytes("foo")); 159 | Thread.sleep(1000); 160 | assertFalse(brpopFuture.isDone()); 161 | client.lpush(toBytes("foo"), toBytes("bar")); 162 | List brpopResp = brpopFuture.sync().getNow(); 163 | assertEquals(2, brpopResp.size()); 164 | assertEquals("foo", bytesToString(brpopResp.get(0))); 165 | assertEquals("bar", bytesToString(brpopResp.get(1))); 166 | 167 | Future> blpopFuture = client.blpop(100, toBytes("a1")); 168 | Future brpoplpushFuture = client.brpoplpush(toBytes("a2"), toBytes("a1"), 100); 169 | Thread.sleep(1000); 170 | assertFalse(blpopFuture.isDone()); 171 | assertFalse(brpoplpushFuture.isDone()); 172 | client.lpush(toBytes("a2"), toBytes("b")); 173 | 174 | List blpopResp = blpopFuture.sync().getNow(); 175 | assertEquals(2, blpopResp.size()); 176 | assertEquals("a1", bytesToString(blpopResp.get(0))); 177 | assertEquals("b", bytesToString(blpopResp.get(1))); 178 | 179 | assertTrue(brpoplpushFuture.isDone()); 180 | assertEquals("b", bytesToString(brpoplpushFuture.getNow())); 181 | } 182 | 183 | private void testTxn(NedisClient txnClient, NedisClient chkClient) throws InterruptedException { 184 | Future multiFuture = txnClient.multi(); 185 | Future setFuture1 = txnClient.set(toBytes("k1"), toBytes("v1")); 186 | Future setFuture2 = txnClient.set(toBytes("k2"), toBytes("v2")); 187 | Thread.sleep(1000); 188 | assertFalse(setFuture1.isDone()); 189 | assertFalse(setFuture2.isDone()); 190 | assertFalse(chkClient.exists(toBytes("k1")).sync().getNow().booleanValue()); 191 | assertFalse(chkClient.exists(toBytes("k2")).sync().getNow().booleanValue()); 192 | List execResult = txnClient.exec().sync().getNow(); 193 | assertTrue(multiFuture.isDone()); 194 | assertTrue(setFuture1.getNow().booleanValue()); 195 | assertTrue(setFuture2.getNow().booleanValue()); 196 | assertEquals(2, execResult.size()); 197 | assertEquals("OK", execResult.get(0).toString()); 198 | assertEquals("OK", execResult.get(1).toString()); 199 | 200 | multiFuture = txnClient.multi(); 201 | setFuture1 = txnClient.set(toBytes("k1"), toBytes("v3")); 202 | setFuture2 = txnClient.set(toBytes("k2"), toBytes("v4")); 203 | Thread.sleep(1000); 204 | assertFalse(setFuture1.isDone()); 205 | assertFalse(setFuture2.isDone()); 206 | assertEquals("v1", bytesToString(chkClient.get(toBytes("k1")).sync().getNow())); 207 | assertEquals("v2", bytesToString(chkClient.get(toBytes("k2")).sync().getNow())); 208 | txnClient.discard().sync(); 209 | assertTrue(multiFuture.isDone()); 210 | assertTrue(setFuture1.isDone()); 211 | assertTrue(setFuture2.isDone()); 212 | assertThat(setFuture1.cause(), is(instanceOf(TxnDiscardException.class))); 213 | assertThat(setFuture2.cause(), is(instanceOf(TxnDiscardException.class))); 214 | assertEquals("v1", bytesToString(chkClient.get(toBytes("k1")).sync().getNow())); 215 | assertEquals("v2", bytesToString(chkClient.get(toBytes("k2")).sync().getNow())); 216 | 217 | Future watchFuture = txnClient.watch(toBytes("k1")); 218 | multiFuture = txnClient.multi(); 219 | setFuture1 = txnClient.set(toBytes("k1"), toBytes("v3")); 220 | execResult = txnClient.exec().sync().getNow(); 221 | assertTrue(watchFuture.isDone()); 222 | assertTrue(multiFuture.isDone()); 223 | assertTrue(setFuture1.getNow().booleanValue()); 224 | assertEquals(1, execResult.size()); 225 | assertEquals("OK", execResult.get(0).toString()); 226 | assertEquals("v3", bytesToString(chkClient.get(toBytes("k1")).sync().getNow())); 227 | 228 | txnClient.watch(toBytes("k1")).sync(); 229 | multiFuture = txnClient.multi(); 230 | setFuture1 = txnClient.set(toBytes("k1"), toBytes("v4")); 231 | assertTrue(chkClient.set(toBytes("k1"), toBytes("v1")).sync().getNow().booleanValue()); 232 | execResult = txnClient.exec().sync().getNow(); 233 | assertTrue(watchFuture.isDone()); 234 | assertTrue(multiFuture.isDone()); 235 | assertTrue(setFuture1.isDone()); 236 | assertThat(setFuture1.cause(), is(instanceOf(TxnAbortException.class))); 237 | assertNull(execResult); 238 | assertEquals("v1", bytesToString(chkClient.get(toBytes("k1")).sync().getNow())); 239 | } 240 | 241 | @Test 242 | public void testTxn() throws InterruptedException { 243 | pool = NedisClientPoolBuilder.create() 244 | .remoteAddress(new InetSocketAddress("127.0.0.1", PORT)).exclusive(true).build(); 245 | NedisClient client = pool.acquire().sync().getNow(); 246 | NedisClient client2 = pool.acquire().sync().getNow(); 247 | try { 248 | testTxn(client, client2); 249 | } finally { 250 | client.release(); 251 | client2.release(); 252 | } 253 | } 254 | 255 | @Test 256 | public void testInfiniteWaitWhenClosing() throws InterruptedException { 257 | pool = NedisClientPoolBuilder.create() 258 | .remoteAddress(new InetSocketAddress("127.0.0.1", PORT)).build(); 259 | pool.close().sync(); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /nedis-client/src/test/java/io/codis/nedis/TestNedisClientImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 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:// 9 | import org.junit.Test; 10 | www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package io.codis.nedis; 19 | 20 | import static org.junit.Assert.*; 21 | import static org.hamcrest.CoreMatchers.*; 22 | import java.io.IOException; 23 | import java.util.Set; 24 | 25 | import org.junit.Test; 26 | import org.mockito.asm.AnnotationVisitor; 27 | import org.mockito.asm.Attribute; 28 | import org.mockito.asm.ClassReader; 29 | import org.mockito.asm.ClassVisitor; 30 | import org.mockito.asm.FieldVisitor; 31 | import org.mockito.asm.Label; 32 | import org.mockito.asm.MethodVisitor; 33 | import org.mockito.asm.Opcodes; 34 | 35 | import com.google.common.collect.ImmutableSet; 36 | import com.google.common.collect.Sets; 37 | 38 | import io.codis.nedis.NedisClientImpl; 39 | 40 | /** 41 | * At least make sure that we call the right commands. 42 | * 43 | * @author Apache9 44 | */ 45 | public class TestNedisClientImpl { 46 | 47 | private static final Set EXCLUDE_METHODS = ImmutableSet.builder() 48 | .add("setTimeout").add("eventLoop").add("isOpen").add("release").add("closeFuture") 49 | .add("close").add("execCmd").add("execCmd0").add("execScanCmd").add("execTxnCmd") 50 | .add("exec").add("auth").add("quit").add("select").add("clientSetname").build(); 51 | 52 | private static final ClassVisitor CV = new ClassVisitor() { 53 | 54 | @Override 55 | public void visitSource(String source, String debug) {} 56 | 57 | @Override 58 | public void visitOuterClass(String owner, String name, String desc) {} 59 | 60 | @Override 61 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, 62 | String[] exceptions) { 63 | if ((Opcodes.ACC_PUBLIC & access) == 0 || EXCLUDE_METHODS.contains(name) 64 | || name.contains("<")) { 65 | return null; 66 | } 67 | if (name.endsWith("0")) { 68 | name = name.substring(0, name.length() - 1); 69 | } 70 | int index = -1; 71 | for (int i = 0; i < name.length(); i++) { 72 | if (Character.isUpperCase(name.charAt(i))) { 73 | index = i; 74 | break; 75 | } 76 | } 77 | if (index < 0) { 78 | return new FindEnumVisitor(name, name.toUpperCase(), null); 79 | } else { 80 | String cmd = name.substring(0, index).toUpperCase(); 81 | return new FindEnumVisitor(name, cmd, name.substring(index).toUpperCase()); 82 | } 83 | } 84 | 85 | @Override 86 | public void visitInnerClass(String name, String outerName, String innerName, int access) {} 87 | 88 | @Override 89 | public FieldVisitor visitField(int access, String name, String desc, String signature, 90 | Object value) { 91 | return null; 92 | } 93 | 94 | @Override 95 | public void visitEnd() {} 96 | 97 | @Override 98 | public void visitAttribute(Attribute attr) {} 99 | 100 | @Override 101 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 102 | return null; 103 | } 104 | 105 | @Override 106 | public void visit(int version, int access, String name, String signature, String superName, 107 | String[] interfaces) {} 108 | }; 109 | 110 | private static final class FindEnumVisitor implements MethodVisitor { 111 | 112 | private final String method; 113 | 114 | private final String cmd; 115 | 116 | private final Set visitedCmds = Sets.newHashSet(); 117 | 118 | private final String keyword; 119 | 120 | private final Set visitedKeywords = Sets.newHashSet(); 121 | 122 | public FindEnumVisitor(String method, String cmd, String keyword) { 123 | this.method = method; 124 | this.cmd = cmd; 125 | this.keyword = keyword; 126 | } 127 | 128 | @Override 129 | public AnnotationVisitor visitAnnotationDefault() { 130 | return null; 131 | } 132 | 133 | @Override 134 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 135 | return null; 136 | } 137 | 138 | @Override 139 | public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 140 | boolean visible) { 141 | return null; 142 | } 143 | 144 | @Override 145 | public void visitAttribute(Attribute attr) {} 146 | 147 | @Override 148 | public void visitCode() {} 149 | 150 | @Override 151 | public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {} 152 | 153 | @Override 154 | public void visitInsn(int opcode) {} 155 | 156 | @Override 157 | public void visitIntInsn(int opcode, int operand) {} 158 | 159 | @Override 160 | public void visitVarInsn(int opcode, int var) {} 161 | 162 | @Override 163 | public void visitTypeInsn(int opcode, String type) {} 164 | 165 | @Override 166 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { 167 | if (opcode != Opcodes.GETSTATIC) { 168 | return; 169 | } 170 | if (owner.endsWith("RedisCommand")) { 171 | visitedCmds.add(name); 172 | } else if (owner.endsWith("RedisKeyword")) { 173 | visitedKeywords.add(name); 174 | } 175 | } 176 | 177 | @Override 178 | public void visitMethodInsn(int opcode, String owner, String name, String desc) {} 179 | 180 | @Override 181 | public void visitJumpInsn(int opcode, Label label) {} 182 | 183 | @Override 184 | public void visitLabel(Label label) {} 185 | 186 | @Override 187 | public void visitLdcInsn(Object cst) {} 188 | 189 | @Override 190 | public void visitIincInsn(int var, int increment) {} 191 | 192 | @Override 193 | public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {} 194 | 195 | @Override 196 | public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {} 197 | 198 | @Override 199 | public void visitMultiANewArrayInsn(String desc, int dims) {} 200 | 201 | @Override 202 | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {} 203 | 204 | @Override 205 | public void visitLocalVariable(String name, String desc, String signature, Label start, 206 | Label end, int index) {} 207 | 208 | @Override 209 | public void visitLineNumber(int line, Label start) {} 210 | 211 | @Override 212 | public void visitMaxs(int maxStack, int maxLocals) {} 213 | 214 | @Override 215 | public void visitEnd() { 216 | assertThat(method + " miss command enum", visitedCmds, 217 | is((Set) Sets.newHashSet(cmd))); 218 | if (keyword != null) { 219 | assertThat(method + " miss keyword enum", visitedKeywords, hasItem(keyword)); 220 | } 221 | } 222 | } 223 | 224 | @Test 225 | public void assertCommandEnum() throws IOException { 226 | ClassReader cr = new ClassReader(NedisClientImpl.class.getName()); 227 | cr.accept(CV, ClassReader.SKIP_DEBUG); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /nedis-client/src/test/java/io/codis/nedis/TestUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis; 17 | 18 | import static io.codis.nedis.util.NedisUtils.toBytes; 19 | 20 | import java.io.IOException; 21 | import java.net.ServerSocket; 22 | import java.util.List; 23 | 24 | import io.codis.nedis.NedisClient; 25 | import io.codis.nedis.NedisClientBuilder; 26 | 27 | /** 28 | * @author Apache9 29 | */ 30 | public class TestUtils { 31 | 32 | public static final double ERROR = 1E-4; 33 | 34 | public static int probeFreePort() throws IOException { 35 | try (ServerSocket ss = new ServerSocket(0)) { 36 | ss.setReuseAddress(true); 37 | return ss.getLocalPort(); 38 | } 39 | } 40 | 41 | public static void cleanRedis(int port) throws IOException, InterruptedException { 42 | NedisClient client = NedisClientBuilder.create().timeoutMs(100).connect("127.0.0.1", port) 43 | .sync().getNow(); 44 | try { 45 | List keys = client.keys(toBytes("*")).sync().getNow(); 46 | if (!keys.isEmpty()) { 47 | client.del(keys.toArray(new byte[0][])).sync(); 48 | } 49 | } finally { 50 | client.close().sync(); 51 | } 52 | 53 | } 54 | 55 | public static void waitUntilRedisUp(int port) throws InterruptedException { 56 | for (;;) { 57 | NedisClient client = null; 58 | try { 59 | client = NedisClientBuilder.create().timeoutMs(100).connect("127.0.0.1", port) 60 | .sync().getNow(); 61 | if ("PONG".equals(client.ping().sync().getNow())) { 62 | break; 63 | } 64 | } catch (Exception e) { 65 | Thread.sleep(200); 66 | } finally { 67 | if (client != null) { 68 | client.close().sync(); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /nedis-client/src/test/java/io/codis/nedis/util/TestNedisClientHashSet.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 CodisLabs. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.codis.nedis.util; 17 | 18 | import static org.junit.Assert.*; 19 | import static org.mockito.Mockito.*; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import org.junit.Test; 25 | 26 | import io.codis.nedis.NedisClient; 27 | import io.codis.nedis.util.NedisClientHashSet; 28 | 29 | /** 30 | * @author Apache9 31 | */ 32 | public class TestNedisClientHashSet { 33 | 34 | @Test 35 | public void test() { 36 | int cap = 1000; 37 | List list = new ArrayList<>(); 38 | NedisClientHashSet set = new NedisClientHashSet(cap); 39 | assertTrue(set.isEmpty()); 40 | assertEquals(0, set.size()); 41 | for (int i = 0; i < cap; i++) { 42 | NedisClient client = mock(NedisClient.class); 43 | list.add(client); 44 | assertEquals(i, set.size()); 45 | assertFalse(set.contains(client)); 46 | assertTrue(set.add(client)); 47 | assertEquals(i + 1, set.size()); 48 | assertTrue(set.contains(client)); 49 | assertFalse(set.add(client)); 50 | assertEquals(i + 1, set.size()); 51 | } 52 | assertFalse(set.isEmpty()); 53 | for (NedisClient client: list) { 54 | assertTrue(set.contains(client)); 55 | assertTrue(set.remove(client)); 56 | assertEquals(cap - 1, set.size()); 57 | assertFalse(set.contains(client)); 58 | assertTrue(set.add(client)); 59 | assertEquals(cap, set.size()); 60 | } 61 | for (NedisClient client: list) { 62 | assertSame(client, set.head(false)); 63 | assertEquals(cap, set.size()); 64 | } 65 | for (NedisClient client: list) { 66 | assertSame(client, set.head(false)); 67 | assertEquals(cap, set.size()); 68 | } 69 | int expectedSize = cap; 70 | for (NedisClient client: list) { 71 | assertSame(client, set.head(true)); 72 | expectedSize--; 73 | assertEquals(expectedSize, set.size()); 74 | } 75 | assertTrue(set.isEmpty()); 76 | for (NedisClient client: list) { 77 | assertTrue(set.add(client)); 78 | } 79 | NedisClient[] clients = set.toArray(); 80 | assertEquals(cap, clients.length); 81 | for (int i = 0; i < cap; i++) { 82 | assertSame(list.get(i), clients[i]); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | io.codis.nedis 6 | nedis 7 | 0.2.0-SNAPSHOT 8 | pom 9 | nedis 10 | Redis client based on netty 11 | https://github.com/CodisLabs/nedis 12 | 13 | scm:git:ssh://github.com:CodisLabs/nedis.git 14 | scm:git:ssh://github.com:CodisLabs/nedis.git 15 | ssh://github.com:CodisLabs/nedis.git 16 | HEAD 17 | 18 | 19 | nedis-client 20 | codis-client 21 | nedis-bench 22 | 23 | 24 | UTF-8 25 | 1.7 26 | UTF-8 27 | UTF-8 28 | UTF-8 29 | UTF-8 30 | 4.0.35.Final 31 | 32 | 33 | 34 | 35 | io.codis.nedis 36 | nedis-client 37 | ${project.version} 38 | 39 | 40 | io.codis.nedis 41 | nedis-client 42 | test-jar 43 | ${project.version} 44 | 45 | 46 | com.fasterxml.jackson.core 47 | jackson-databind 48 | 2.7.0 49 | 50 | 51 | org.slf4j 52 | slf4j-api 53 | 1.7.12 54 | 55 | 56 | org.slf4j 57 | slf4j-simple 58 | 1.7.12 59 | 60 | 61 | org.apache.curator 62 | curator-recipes 63 | 2.9.1 64 | 65 | 66 | io.netty 67 | netty-transport 68 | ${netty.version} 69 | 70 | 71 | io.netty 72 | netty-handler 73 | ${netty.version} 74 | 75 | 76 | org.apache.commons 77 | commons-lang3 78 | 3.4 79 | 80 | 81 | org.hamcrest 82 | hamcrest-library 83 | 1.3 84 | 85 | 86 | junit 87 | junit 88 | 4.12 89 | 90 | 91 | org.mockito 92 | mockito-core 93 | 1.10.19 94 | 95 | 96 | com.google.guava 97 | guava 98 | 18.0 99 | 100 | 101 | 102 | 103 | 104 | ossrh 105 | https://oss.sonatype.org/content/repositories/snapshots 106 | 107 | 108 | ossrh 109 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-compiler-plugin 117 | 3.3 118 | 119 | ${java.version} 120 | ${java.version} 121 | ${app.encoding} 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-gpg-plugin 127 | 1.5 128 | 129 | 130 | sign-artifacts 131 | verify 132 | 133 | sign 134 | 135 | 136 | 137 | 138 | 139 | org.sonatype.plugins 140 | nexus-staging-maven-plugin 141 | 1.6.5 142 | true 143 | 144 | ossrh 145 | https://oss.sonatype.org/ 146 | false 147 | 148 | 149 | 150 | org.codehaus.mojo 151 | findbugs-maven-plugin 152 | 3.0.2 153 | 154 | true 155 | target/findbugs 156 | target/findbugs 157 | 158 | 159 | 160 | org.jacoco 161 | jacoco-maven-plugin 162 | 0.7.4.201502262128 163 | 164 | 165 | default-prepare-agent 166 | 167 | prepare-agent 168 | 169 | 170 | 171 | default-report 172 | prepare-package 173 | 174 | report 175 | 176 | 177 | 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-surefire-plugin 182 | 2.18.1 183 | 184 | always 185 | true 186 | ${argLine} -Dfile.encoding=UTF-8 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-surefire-report-plugin 192 | 2.18.1 193 | 194 | true 195 | 196 | 197 | 198 | org.apache.maven.plugins 199 | maven-deploy-plugin 200 | 2.8.2 201 | 202 | 203 | deploy 204 | deploy 205 | 206 | deploy 207 | 208 | 209 | 210 | default-deploy 211 | 212 | true 213 | 214 | 215 | 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-site-plugin 220 | 3.4 221 | 222 | 223 | 224 | org.codehaus.mojo 225 | findbugs-maven-plugin 226 | 227 | 228 | org.jacoco 229 | jacoco-maven-plugin 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | Apache License 239 | http://www.apache.org/licenses/LICENSE-2.0 240 | repo 241 | 242 | 243 | 244 | 245 | apache9 246 | Apache9 247 | palomino219@gmail.com 248 | 249 | developer 250 | 251 | +8 252 | 253 | 254 | 255 | --------------------------------------------------------------------------------