├── .gitignore
├── README.md
├── jthreadhighcpu.sh
├── src
├── test
│ └── java
│ │ ├── redis
│ │ └── clients
│ │ │ └── jedis
│ │ │ ├── ShardedJedisSentinelPoolSpringTest.java
│ │ │ └── ShardedJedisSentinelPoolTest.java
│ │ └── redis.xml
└── main
│ └── java
│ └── redis
│ └── clients
│ └── jedis
│ └── ShardedJedisSentinelPool.java
├── pom.xml
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | target
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Sharded jedis sentinel Pool
2 | ===========================
3 | ### Redis HA by using Redis Sentinel
4 | * Requires Jedis2.2.2 or later;
5 | * You can read [the blog](http://warm-breeze.iteye.com/blog/2020413) to understand and use it.
6 |
--------------------------------------------------------------------------------
/jthreadhighcpu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | LOG_FILE="/home/work/local/mop/jcpu.log";
4 | JSTACK_FILE="/home/work/local/mop/jstack.log";
5 |
6 | PID="$1";
7 | shift;
8 | i=0;
9 | j="$1";
10 | if [ -z "${j}" ]; then
11 | j=5;
12 | fi
13 |
14 | ps -mp ${PID} -o THREAD,tid,time | sort -rn > ${LOG_FILE};
15 | jstack ${PID} > ${JSTACK_FILE};
16 |
17 | for LINE in `cat ${LOG_FILE}|gawk -F '-' '{print $5}'|gawk -F ' ' '{print $1}'`
18 | do
19 | i=$(($i+1));
20 | if (($i>$j)); then
21 | break;
22 | fi;
23 | XPID=`printf "%x\n" ${LINE}`;
24 | echo ${XPID};
25 | grep -A 10 "0x${XPID}" ${JSTACK_FILE};
26 | done;
27 |
--------------------------------------------------------------------------------
/src/test/java/redis/clients/jedis/ShardedJedisSentinelPoolSpringTest.java:
--------------------------------------------------------------------------------
1 | package redis.clients.jedis;
2 |
3 | import junit.framework.TestCase;
4 |
5 | import org.springframework.context.ApplicationContext;
6 | import org.springframework.context.support.ClassPathXmlApplicationContext;
7 |
8 | import redis.clients.jedis.exceptions.JedisConnectionException;
9 |
10 | public class ShardedJedisSentinelPoolSpringTest extends TestCase {
11 |
12 | public void testX() throws Exception {
13 |
14 | ApplicationContext ac = new ClassPathXmlApplicationContext("redis.xml");
15 | ShardedJedisSentinelPool pool = (ShardedJedisSentinelPool) ac.getBean("shardedJedisPool");
16 |
17 | ShardedJedis j = null;
18 | for (int i = 0; i < 100; i++) {
19 | try {
20 | j = pool.getResource();
21 | j.set("KEY: " + i, "" + i);
22 | System.out.print(i);
23 | System.out.print(" ");
24 | Thread.sleep(500);
25 | pool.returnResource(j);
26 | } catch (JedisConnectionException e) {
27 | System.out.print("x");
28 | i--;
29 | Thread.sleep(1000);
30 | }
31 | }
32 |
33 | System.out.println("");
34 |
35 | for (int i = 0; i < 100; i++) {
36 | try {
37 | j = pool.getResource();
38 | assertEquals(j.get("KEY: " + i), "" + i);
39 | System.out.print(".");
40 | Thread.sleep(500);
41 | pool.returnResource(j);
42 | } catch (JedisConnectionException e) {
43 | System.out.print("x");
44 | i--;
45 | Thread.sleep(1000);
46 | }
47 | }
48 |
49 | pool.destroy();
50 | }
51 | }
--------------------------------------------------------------------------------
/src/test/java/redis/clients/jedis/ShardedJedisSentinelPoolTest.java:
--------------------------------------------------------------------------------
1 | package redis.clients.jedis;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.Set;
7 |
8 | import junit.framework.TestCase;
9 |
10 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
11 |
12 | import redis.clients.jedis.exceptions.JedisConnectionException;
13 |
14 | public class ShardedJedisSentinelPoolTest extends TestCase {
15 |
16 | public void testX() throws Exception {
17 |
18 | GenericObjectPoolConfig config = new GenericObjectPoolConfig();
19 |
20 | List masters = new ArrayList();
21 | masters.add("shard1");
22 | masters.add("shard2");
23 |
24 | Set sentinels = new HashSet();
25 | sentinels.add("192.168.109.212:26379");
26 | sentinels.add("192.168.109.215:26379");
27 |
28 | ShardedJedisSentinelPool pool = new ShardedJedisSentinelPool(masters, sentinels, config, 60000);
29 |
30 | ShardedJedis jedis = null;
31 | try {
32 | jedis = pool.getResource();
33 | // do somethind...
34 | // ...
35 | } finally {
36 | if (jedis != null) pool.returnResource(jedis);
37 | pool.destroy();
38 | }
39 |
40 | ShardedJedis j = null;
41 | for (int i = 0; i < 100; i++) {
42 | try {
43 | j = pool.getResource();
44 | j.set("KEY: " + i, "" + i);
45 | System.out.print(i);
46 | System.out.print(" ");
47 | Thread.sleep(500);
48 | pool.returnResource(j);
49 | } catch (JedisConnectionException e) {
50 | System.out.print("x");
51 | i--;
52 | Thread.sleep(1000);
53 | }
54 | }
55 |
56 | System.out.println("");
57 |
58 | for (int i = 0; i < 100; i++) {
59 | try {
60 | j = pool.getResource();
61 | assertEquals(j.get("KEY: " + i), "" + i);
62 | System.out.print(".");
63 | Thread.sleep(500);
64 | pool.returnResource(j);
65 | } catch (JedisConnectionException e) {
66 | System.out.print("x");
67 | i--;
68 | Thread.sleep(1000);
69 | }
70 | }
71 |
72 | pool.destroy();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | redis.clients
5 | sharded-jedis-sentinel-pool
6 | 1.0-SNAPSHOT
7 | jar
8 | sharded-jedis-sentinel-pool
9 | http://maven.apache.org
10 |
11 |
12 | warmbreeze
13 | warmbreeze
14 | coolbreezezhj@gmail.com
15 |
16 | developer
17 |
18 | +8
19 |
20 |
21 |
22 | 3.2.4.RELEASE
23 | UTF-8
24 |
25 |
26 |
27 | redis.clients
28 | jedis
29 | 2.4.0
30 |
31 |
32 | commons-pool
33 | commons-pool
34 | 1.6
35 |
36 |
37 | org.apache.commons
38 | commons-pool2
39 | 2.0
40 | jar
41 | compile
42 |
43 |
44 | junit
45 | junit
46 | 3.8.1
47 | test
48 |
49 |
50 | org.springframework
51 | spring-core
52 | ${spring.version}
53 |
54 |
55 | org.springframework
56 | spring-context
57 | ${spring.version}
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/test/java/redis.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 | shard1
20 | shard2
21 |
22 |
23 |
24 |
25 | 192.168.109.212:26379
26 | 192.168.109.215:26379
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/src/main/java/redis/clients/jedis/ShardedJedisSentinelPool.java:
--------------------------------------------------------------------------------
1 | package redis.clients.jedis;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collections;
6 | import java.util.HashMap;
7 | import java.util.HashSet;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Set;
11 | import java.util.concurrent.atomic.AtomicBoolean;
12 | import java.util.logging.Logger;
13 | import java.util.regex.Pattern;
14 |
15 | import org.apache.commons.pool2.PooledObject;
16 | import org.apache.commons.pool2.PooledObjectFactory;
17 | import org.apache.commons.pool2.impl.DefaultPooledObject;
18 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
19 |
20 | import redis.clients.jedis.exceptions.JedisConnectionException;
21 | import redis.clients.util.Hashing;
22 | import redis.clients.util.Pool;
23 |
24 | public class ShardedJedisSentinelPool extends Pool {
25 |
26 | public static final int MAX_RETRY_SENTINEL = 10;
27 |
28 | protected final Logger log = Logger.getLogger(getClass().getName());
29 |
30 | protected GenericObjectPoolConfig poolConfig;
31 |
32 | protected int timeout = Protocol.DEFAULT_TIMEOUT;
33 |
34 | private int sentinelRetry = 0;
35 |
36 | protected String password;
37 |
38 | protected int database = Protocol.DEFAULT_DATABASE;
39 |
40 | protected Set masterListeners = new HashSet();
41 |
42 | private volatile List currentHostMasters;
43 |
44 | public ShardedJedisSentinelPool(List masters, Set sentinels) {
45 | this(masters, sentinels, new GenericObjectPoolConfig(),
46 | Protocol.DEFAULT_TIMEOUT, null, Protocol.DEFAULT_DATABASE);
47 | }
48 |
49 | public ShardedJedisSentinelPool(List masters, Set sentinels, String password) {
50 | this(masters, sentinels, new GenericObjectPoolConfig(),
51 | Protocol.DEFAULT_TIMEOUT, password);
52 | }
53 |
54 | public ShardedJedisSentinelPool(final GenericObjectPoolConfig poolConfig, List masters, Set sentinels) {
55 | this(masters, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT, null,
56 | Protocol.DEFAULT_DATABASE);
57 | }
58 |
59 | public ShardedJedisSentinelPool(List masters, Set sentinels,
60 | final GenericObjectPoolConfig poolConfig, int timeout,
61 | final String password) {
62 | this(masters, sentinels, poolConfig, timeout, password,
63 | Protocol.DEFAULT_DATABASE);
64 | }
65 |
66 | public ShardedJedisSentinelPool(List masters, Set sentinels,
67 | final GenericObjectPoolConfig poolConfig, final int timeout) {
68 | this(masters, sentinels, poolConfig, timeout, null,
69 | Protocol.DEFAULT_DATABASE);
70 | }
71 |
72 | public ShardedJedisSentinelPool(List masters, Set sentinels,
73 | final GenericObjectPoolConfig poolConfig, final String password) {
74 | this(masters, sentinels, poolConfig, Protocol.DEFAULT_TIMEOUT,
75 | password);
76 | }
77 |
78 | public ShardedJedisSentinelPool(List masters, Set sentinels,
79 | final GenericObjectPoolConfig poolConfig, int timeout,
80 | final String password, final int database) {
81 | this.poolConfig = poolConfig;
82 | this.timeout = timeout;
83 | this.password = password;
84 | this.database = database;
85 |
86 | List masterList = initSentinels(sentinels, masters);
87 | initPool(masterList);
88 | }
89 |
90 | public void destroy() {
91 | for (MasterListener m : masterListeners) {
92 | m.shutdown();
93 | }
94 |
95 | super.destroy();
96 | }
97 |
98 | public List getCurrentHostMaster() {
99 | return currentHostMasters;
100 | }
101 |
102 | private void initPool(List masters) {
103 | if (!equals(currentHostMasters, masters)) {
104 | StringBuffer sb = new StringBuffer();
105 | for (HostAndPort master : masters) {
106 | sb.append(master.toString());
107 | sb.append(" ");
108 | }
109 | log.info("Created ShardedJedisPool to master at [" + sb.toString() + "]");
110 | List shardMasters = makeShardInfoList(masters);
111 | initPool(poolConfig, new ShardedJedisFactory(shardMasters, Hashing.MURMUR_HASH, null));
112 | currentHostMasters = masters;
113 | }
114 | }
115 |
116 | private boolean equals(List currentShardMasters, List shardMasters) {
117 | if (currentShardMasters != null && shardMasters != null) {
118 | if (currentShardMasters.size() == shardMasters.size()) {
119 | for (int i = 0; i < currentShardMasters.size(); i++) {
120 | if (!currentShardMasters.get(i).equals(shardMasters.get(i))) return false;
121 | }
122 | return true;
123 | }
124 | }
125 | return false;
126 | }
127 |
128 | private List makeShardInfoList(List masters) {
129 | List shardMasters = new ArrayList();
130 | for (HostAndPort master : masters) {
131 | JedisShardInfo jedisShardInfo = new JedisShardInfo(master.getHost(), master.getPort(), timeout);
132 | jedisShardInfo.setPassword(password);
133 |
134 | shardMasters.add(jedisShardInfo);
135 | }
136 | return shardMasters;
137 | }
138 |
139 | private List initSentinels(Set sentinels, final List masters) {
140 |
141 | Map masterMap = new HashMap();
142 | List shardMasters = new ArrayList();
143 |
144 | log.info("Trying to find all master from available Sentinels...");
145 |
146 | for (String masterName : masters) {
147 | HostAndPort master = null;
148 | boolean fetched = false;
149 |
150 | while (!fetched && sentinelRetry < MAX_RETRY_SENTINEL) {
151 | for (String sentinel : sentinels) {
152 | final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
153 |
154 | log.fine("Connecting to Sentinel " + hap);
155 |
156 | try {
157 | Jedis jedis = new Jedis(hap.getHost(), hap.getPort());
158 | master = masterMap.get(masterName);
159 | if (master == null) {
160 | List hostAndPort = jedis.sentinelGetMasterAddrByName(masterName);
161 | if (hostAndPort != null && hostAndPort.size() > 0) {
162 | master = toHostAndPort(hostAndPort);
163 | log.fine("Found Redis master at " + master);
164 | shardMasters.add(master);
165 | masterMap.put(masterName, master);
166 | fetched = true;
167 | jedis.disconnect();
168 | break;
169 | }
170 | }
171 | } catch (JedisConnectionException e) {
172 | log.warning("Cannot connect to sentinel running @ " + hap + ". Trying next one.");
173 | }
174 | }
175 |
176 | if (null == master) {
177 | try {
178 | log.severe("All sentinels down, cannot determine where is "
179 | + masterName + " master is running... sleeping 1000ms, Will try again.");
180 | Thread.sleep(1000);
181 | } catch (InterruptedException e) {
182 | e.printStackTrace();
183 | }
184 | fetched = false;
185 | sentinelRetry++;
186 | }
187 | }
188 |
189 | // Try MAX_RETRY_SENTINEL times.
190 | if (!fetched && sentinelRetry >= MAX_RETRY_SENTINEL) {
191 | log.severe("All sentinels down and try " + MAX_RETRY_SENTINEL + " times, Abort.");
192 | throw new JedisConnectionException("Cannot connect all sentinels, Abort.");
193 | }
194 | }
195 |
196 | // All shards master must been accessed.
197 | if (masters.size() != 0 && masters.size() == shardMasters.size()) {
198 |
199 | log.info("Starting Sentinel listeners...");
200 | for (String sentinel : sentinels) {
201 | final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":")));
202 | MasterListener masterListener = new MasterListener(masters, hap.getHost(), hap.getPort());
203 | masterListeners.add(masterListener);
204 | masterListener.start();
205 | }
206 | }
207 |
208 | return shardMasters;
209 | }
210 |
211 | private HostAndPort toHostAndPort(List getMasterAddrByNameResult) {
212 | String host = getMasterAddrByNameResult.get(0);
213 | int port = Integer.parseInt(getMasterAddrByNameResult.get(1));
214 |
215 | return new HostAndPort(host, port);
216 | }
217 |
218 | /**
219 | * PoolableObjectFactory custom impl.
220 | */
221 | protected static class ShardedJedisFactory implements PooledObjectFactory {
222 | private List shards;
223 | private Hashing algo;
224 | private Pattern keyTagPattern;
225 |
226 | public ShardedJedisFactory(List shards, Hashing algo, Pattern keyTagPattern) {
227 | this.shards = shards;
228 | this.algo = algo;
229 | this.keyTagPattern = keyTagPattern;
230 | }
231 |
232 | public PooledObject makeObject() throws Exception {
233 | ShardedJedis jedis = new ShardedJedis(shards, algo, keyTagPattern);
234 | return new DefaultPooledObject(jedis);
235 | }
236 |
237 | public void destroyObject(PooledObject pooledShardedJedis) throws Exception {
238 | final ShardedJedis shardedJedis = pooledShardedJedis.getObject();
239 | for (Jedis jedis : shardedJedis.getAllShards()) {
240 | try {
241 | try {
242 | jedis.quit();
243 | } catch (Exception e) {
244 |
245 | }
246 | jedis.disconnect();
247 | } catch (Exception e) {
248 |
249 | }
250 | }
251 | }
252 |
253 | public boolean validateObject(PooledObject pooledShardedJedis) {
254 | try {
255 | ShardedJedis jedis = pooledShardedJedis.getObject();
256 | for (Jedis shard : jedis.getAllShards()) {
257 | if (!shard.ping().equals("PONG")) {
258 | return false;
259 | }
260 | }
261 | return true;
262 | } catch (Exception ex) {
263 | return false;
264 | }
265 | }
266 |
267 | public void activateObject(PooledObject p) throws Exception {
268 |
269 | }
270 |
271 | public void passivateObject(PooledObject p) throws Exception {
272 |
273 | }
274 | }
275 |
276 | protected class JedisPubSubAdapter extends JedisPubSub {
277 | @Override
278 | public void onMessage(String channel, String message) {
279 | }
280 |
281 | @Override
282 | public void onPMessage(String pattern, String channel, String message) {
283 | }
284 |
285 | @Override
286 | public void onPSubscribe(String pattern, int subscribedChannels) {
287 | }
288 |
289 | @Override
290 | public void onPUnsubscribe(String pattern, int subscribedChannels) {
291 | }
292 |
293 | @Override
294 | public void onSubscribe(String channel, int subscribedChannels) {
295 | }
296 |
297 | @Override
298 | public void onUnsubscribe(String channel, int subscribedChannels) {
299 | }
300 | }
301 |
302 | protected class MasterListener extends Thread {
303 |
304 | protected List masters;
305 | protected String host;
306 | protected int port;
307 | protected long subscribeRetryWaitTimeMillis = 5000;
308 | protected Jedis jedis;
309 | protected AtomicBoolean running = new AtomicBoolean(false);
310 |
311 | protected MasterListener() {
312 | }
313 |
314 | public MasterListener(List masters, String host, int port) {
315 | this.masters = masters;
316 | this.host = host;
317 | this.port = port;
318 | }
319 |
320 | public MasterListener(List masters, String host, int port,
321 | long subscribeRetryWaitTimeMillis) {
322 | this(masters, host, port);
323 | this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis;
324 | }
325 |
326 | public void run() {
327 |
328 | running.set(true);
329 |
330 | while (running.get()) {
331 |
332 | jedis = new Jedis(host, port);
333 |
334 | try {
335 | jedis.subscribe(new JedisPubSubAdapter() {
336 | @Override
337 | public void onMessage(String channel, String message) {
338 | log.fine("Sentinel " + host + ":" + port + " published: " + message + ".");
339 |
340 | String[] switchMasterMsg = message.split(" ");
341 |
342 | if (switchMasterMsg.length > 3) {
343 |
344 | int index = masters.indexOf(switchMasterMsg[0]);
345 | if (index >= 0) {
346 | HostAndPort newHostMaster = toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4]));
347 | List newHostMasters = new ArrayList();
348 | for (int i = 0; i < masters.size(); i++) {
349 | newHostMasters.add(null);
350 | }
351 | Collections.copy(newHostMasters, currentHostMasters);
352 | newHostMasters.set(index, newHostMaster);
353 |
354 | initPool(newHostMasters);
355 | } else {
356 | StringBuffer sb = new StringBuffer();
357 | for (String masterName : masters) {
358 | sb.append(masterName);
359 | sb.append(",");
360 | }
361 | log.fine("Ignoring message on +switch-master for master name "
362 | + switchMasterMsg[0]
363 | + ", our monitor master name are ["
364 | + sb + "]");
365 | }
366 |
367 | } else {
368 | log.severe("Invalid message received on Sentinel "
369 | + host
370 | + ":"
371 | + port
372 | + " on channel +switch-master: "
373 | + message);
374 | }
375 | }
376 | }, "+switch-master");
377 |
378 | } catch (JedisConnectionException e) {
379 |
380 | if (running.get()) {
381 | log.severe("Lost connection to Sentinel at " + host
382 | + ":" + port
383 | + ". Sleeping 5000ms and retrying.");
384 | try {
385 | Thread.sleep(subscribeRetryWaitTimeMillis);
386 | } catch (InterruptedException e1) {
387 | e1.printStackTrace();
388 | }
389 | } else {
390 | log.fine("Unsubscribing from Sentinel at " + host + ":"
391 | + port);
392 | }
393 | }
394 | }
395 | }
396 |
397 | public void shutdown() {
398 | try {
399 | log.fine("Shutting down listener on " + host + ":" + port);
400 | running.set(false);
401 | // This isn't good, the Jedis object is not thread safe
402 | jedis.disconnect();
403 | } catch (Exception e) {
404 | log.severe("Caught exception while shutting down: " + e.getMessage());
405 | }
406 | }
407 | }
408 | }
409 |
--------------------------------------------------------------------------------