├── .gitignore
├── .project
├── .reviewboardrc
├── codeformat.xml
├── pom.xml
├── releasenotes.txt
└── src
├── main
└── java
│ └── com
│ └── yeahmobi
│ └── yedis
│ ├── async
│ ├── AsyncOperation.java
│ ├── JedisPoolExecutor.java
│ ├── OperationResult.java
│ └── ThreadLocalHolder.java
│ ├── atomic
│ ├── AtomConfig.java
│ └── Yedis.java
│ ├── common
│ ├── ServerInfo.java
│ ├── YedisException.java
│ ├── YedisNetworkException.java
│ └── YedisTimeoutException.java
│ ├── group
│ ├── ConfigChangeListener.java
│ ├── DefaultConfigManager.java
│ ├── GroupConfig.java
│ ├── GroupYedis.java
│ ├── MasterSlaveConfigManager.java
│ ├── ReadMode.java
│ └── ZookeeperConfigManager.java
│ ├── loadbalance
│ ├── LoadBalancer.java
│ ├── RandomLoadBalancer.java
│ └── RoundRobinLoadBalancer.java
│ ├── pipeline
│ ├── PipelineJedisPool.java
│ ├── ShardedYedisPipeline.java
│ └── YedisPipeline.java
│ ├── shard
│ ├── AbstractShardingStrategy.java
│ ├── DummyShardingStrategy.java
│ ├── HashCodeComputingStrategy.java
│ ├── ShardedYedis.java
│ ├── ShardingAlgorithm.java
│ ├── ShardingStrategy.java
│ ├── ShardingStrategyFactory.java
│ └── SimpleHashingShardingStrategy.java
│ └── util
│ ├── RetribleExecutor.java
│ └── SleepStrategy.java
└── test
├── java
└── com
│ └── yeahmobi
│ └── yedis
│ ├── base
│ ├── DoubleServerYedisTestBase.java
│ ├── YedisBaseTest.java
│ └── YedisTestBase.java
│ ├── example
│ ├── GroupUseCase.java
│ ├── GroupUseCaseWithZK.java
│ ├── PerformanceTest.java
│ ├── ShardedYedisPipelineCase.java
│ └── YedisCase.java
│ ├── group
│ ├── GroupYedisHashTest.java
│ ├── GroupYedisKeyTest.java
│ ├── GroupYedisListTest.java
│ ├── GroupYedisSetTest.java
│ ├── GroupYedisStringTest.java
│ └── GroupYedisTest.java
│ ├── loadbalance
│ ├── RandomLoadBalancerTest.java
│ └── RoundRobinLoadBalancerTest.java
│ ├── shard
│ ├── AbstractShardedYedisTest.java
│ ├── DefaultHashCodeCoputingStrategy.java
│ ├── ShardedYedisHashTest.java
│ ├── ShardedYedisKeyTest.java
│ ├── ShardedYedisListTest.java
│ ├── ShardedYedisSetTest.java
│ ├── ShardedYedisStringTest.java
│ └── ShardedYedisTest.java
│ └── util
│ └── RetribleExecutorTest.java
└── resources
└── logback.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # temp folder #
2 | ######################
3 | target/
4 | bin/
5 | .settings/
6 | .metadata/
7 | lib/
8 | classes/
9 | genimage/
10 | .svn/
11 | # Compiled source #
12 | ###################
13 | .classpath
14 | *.com
15 | *.class
16 | *.dll
17 | *.exe
18 | *.o
19 | *.so
20 |
21 | # Packages #
22 | ############
23 | # it's better to unpack these files and commit the raw source
24 | # git has its own built in compression methods
25 | *.7z
26 | *.dmg
27 | *.gz
28 | *.iso
29 | *.jar
30 | *.rar
31 | *.tar
32 | *.zip
33 |
34 | # Logs and databases #
35 | ######################
36 | *.log
37 |
38 | # OS generated files #
39 | ######################
40 | .DS_Store
41 | .DS_Store?
42 | ._*
43 | .Spotlight-V100
44 | .Trashes
45 | Icon?
46 | ehthumbs.db
47 | Thumbs.db
48 |
49 | # eclipse generated files #
50 | ######################
51 | #.project
52 | #.classpath
53 |
54 | # jupiter generated files #
55 | ######################
56 | .jupiter
57 | *.review
58 | */review/
59 |
60 | # temp file #
61 | ######################
62 | *~
63 | !/lib
64 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | yedis
4 | NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.
5 |
6 |
7 |
8 | org.eclipse.jdt.core.javabuilder
9 |
10 |
11 |
12 | org.eclipse.jdt.core.javanature
13 |
14 |
--------------------------------------------------------------------------------
/.reviewboardrc:
--------------------------------------------------------------------------------
1 | REVIEWBOARD_URL = "http://rb.dy/"
2 | REPOSITORY = "platform"
3 |
--------------------------------------------------------------------------------
/codeformat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | com.yeahmobi.yedis
4 | yedis
5 | 0.4.0-beta
6 | 4.0.0
7 | yedis v${project.version}
8 |
9 |
10 | clojars.org
11 | http://clojars.org/repo
12 |
13 |
14 |
15 |
16 | yeahmobi-releases
17 | http://172.20.0.77:8081/nexus/content/repositories/releases/
18 |
19 |
20 |
21 |
22 | ch.qos.logback
23 | logback-classic
24 | test
25 |
26 |
27 | org.slf4j
28 | slf4j-api
29 |
30 |
31 | redis.clients
32 | jedis
33 |
34 |
35 | org.apache.curator
36 | curator-framework
37 |
38 |
39 | com.alibaba
40 | fastjson
41 |
42 |
43 | com.google.guava
44 | guava
45 |
46 |
47 | redis.embedded
48 | embedded-redis
49 | test
50 |
51 |
52 | junit
53 | junit
54 | test
55 |
56 |
57 | org.easymock
58 | easymock
59 | test
60 |
61 |
62 |
63 |
64 |
65 | ch.qos.logback
66 | logback-classic
67 | 1.0.12
68 |
69 |
70 | redis.embedded
71 | embedded-redis
72 | 0.3
73 |
74 |
75 | com.google.guava
76 | guava
77 | 18.0
78 |
79 |
80 | org.easymock
81 | easymock
82 | 2.4
83 |
84 |
85 | org.easymock
86 | easymockclassextension
87 | 2.4
88 |
89 |
90 | junit
91 | junit
92 | 4.8.1
93 |
94 |
95 | org.slf4j
96 | slf4j-api
97 | 1.6.1
98 |
99 |
100 | org.slf4j
101 | slf4j-log4j12
102 | 1.6.1
103 |
104 |
105 | redis.clients
106 | jedis
107 | 2.5.2
108 |
109 |
110 | org.apache.curator
111 | curator-framework
112 | 2.6.0
113 |
114 |
115 | com.alibaba
116 | fastjson
117 | 1.1.41
118 |
119 |
120 |
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-compiler-plugin
126 | 3.1
127 |
128 | 1.6
129 | 1.6
130 | UTF-8
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-source-plugin
136 | 2.3
137 |
138 |
139 |
140 | jar
141 |
142 |
143 |
144 |
145 |
146 | org.apache.maven.plugins
147 | maven-resources-plugin
148 | 2.6
149 |
150 | UTF-8
151 |
152 |
153 |
154 | org.apache.maven.plugins
155 | maven-eclipse-plugin
156 | 2.9
157 |
158 | true
159 | true
160 | true
161 |
162 |
163 |
164 | org.apache.maven.plugins
165 | maven-surefire-report-plugin
166 | 2.17
167 |
168 |
169 | org.codehaus.mojo
170 | cobertura-maven-plugin
171 | 2.6
172 |
173 |
174 | org.jacoco
175 | jacoco-maven-plugin
176 | 0.6.3.201306030806
177 |
178 |
179 |
180 | prepare-agent
181 |
182 |
183 |
184 | report
185 | prepare-package
186 |
187 | report
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | org.codehaus.mojo
198 | cobertura-maven-plugin
199 | 2.6
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/releasenotes.txt:
--------------------------------------------------------------------------------
1 | Version 0.1 (2014.09.13)
2 |
3 | 1.配置动态化
4 | 可以从 zookeeper 获取服务器列表;列表发生变化时,能自动重建新的连接,自动切换。
5 | 2.读写分离
6 | 4种读写分离策略 (默认是 SlavePreferred )
7 | Master:写操作和读操作都访问 master。
8 | MasterPreferred:写操作访问 master;读操作访问 master,但当 master 不可用时,则访问 slave。
9 | Slave:写操作访问 master;读操作访问 slave,slave 不可用时读调用会报错。
10 | SlavePreferred:写操作访问 master;读操作访问 slave,但当 slave 都不可用时,则访问 master。
11 | 3.读库负载均衡
12 | 策略 (默认是 RoundRobin,暂不支持权重 )
13 | RoundRobin
14 | Random
15 |
16 | Version 0.3 (2014.11.05)
17 | 1. 支持Sharding
18 | 现在只支持简单hash算法
19 |
20 | Version 0.4 (2015.03.02)
21 | 1. 支持 pipeline
22 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/async/AsyncOperation.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.async;
2 |
3 | import java.util.concurrent.Callable;
4 |
5 | import redis.clients.jedis.Jedis;
6 |
7 | public abstract class AsyncOperation implements Callable> {
8 |
9 | public abstract OperationResult execute(Jedis jedis);
10 |
11 | @Override
12 | public OperationResult call() throws Exception {
13 | Jedis jedis = ThreadLocalHolder.jedisHolder.get();
14 | return execute(jedis);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/async/JedisPoolExecutor.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.async;
2 |
3 | import java.util.concurrent.ExecutionException;
4 | import java.util.concurrent.Future;
5 | import java.util.concurrent.FutureTask;
6 | import java.util.concurrent.LinkedBlockingQueue;
7 | import java.util.concurrent.atomic.AtomicBoolean;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import redis.clients.jedis.Jedis;
14 | import redis.clients.jedis.exceptions.JedisConnectionException;
15 |
16 | import com.yeahmobi.yedis.atomic.AtomConfig;
17 | import com.yeahmobi.yedis.util.SleepStrategy;
18 |
19 | public class JedisPoolExecutor {
20 |
21 | private static final Logger logger = LoggerFactory.getLogger(JedisPoolExecutor.class);
22 |
23 | private static final String nameFormat = "YedisPool(%s)-Client%d";
24 |
25 | private final AtomicInteger count = new AtomicInteger(1);
26 |
27 | private final AtomicBoolean shutdown = new AtomicBoolean(false);
28 |
29 | private final Worker[] workers;
30 |
31 | private final AtomicInteger workerIndex = new AtomicInteger(0);
32 |
33 | private AtomConfig config;
34 |
35 | public JedisPoolExecutor(AtomConfig config) {
36 | if (config == null) {
37 | throw new IllegalArgumentException("Argument cannot be null.");
38 | }
39 |
40 | this.config = config;
41 |
42 | this.workers = new Worker[config.getThreadPoolSize()];
43 | }
44 |
45 | public void start() {
46 | for (int i = 0; i < this.workers.length; i++) {
47 | this.workers[i] = new Worker();
48 | this.workers[i].start();
49 | }
50 | }
51 |
52 | public void shutdown() {
53 | if (shutdown.compareAndSet(false, true)) {
54 | for (Worker worker : workers) {
55 | worker.close();
56 | }
57 | }
58 | }
59 |
60 | public boolean isShutdown() {
61 | return shutdown.get();
62 | }
63 |
64 | private void checkClose() {
65 | if (shutdown.get()) {
66 | throw new IllegalStateException("It is already closed.");
67 | }
68 | }
69 |
70 | public Future> submit(AsyncOperation opr) {
71 | checkClose();
72 | if (opr == null) throw new NullPointerException();
73 | FutureTask> ftask = new FutureTask>(opr);
74 |
75 | // 选择一个work, task入队
76 | workers[nextIndex()].addTask(ftask);
77 |
78 | return ftask;
79 | }
80 |
81 | private int nextIndex() {
82 | return Math.abs(workerIndex.getAndIncrement() % workers.length);
83 | }
84 |
85 | @SuppressWarnings("rawtypes")
86 | private class Worker extends Thread {
87 |
88 | private LinkedBlockingQueue queue = new LinkedBlockingQueue();
89 |
90 | private Jedis jedis;
91 |
92 | private SleepStrategy sleepStrategy = new SleepStrategy();
93 |
94 | public Worker() {
95 | super(String.format(nameFormat, config.getHost() + ":" + config.getPort(), count.getAndIncrement()));
96 | this.setDaemon(true);
97 | }
98 |
99 | public void close() {
100 | FutureTask task;
101 | // 将queue中的 task拿出来,cancle掉
102 | while ((task = queue.poll()) != null) {
103 | task.cancel(true);
104 | }
105 | this.interrupt();
106 | if (jedis != null) {
107 | jedis.close();
108 | }
109 | logger.info("Closed from " + config.getHost() + ":" + config.getPort());
110 | }
111 |
112 | public void addTask(FutureTask task) {
113 | queue.add(task);
114 | }
115 |
116 | @Override
117 | public void run() {
118 | while (!shutdown.get()) {
119 | try {
120 | checkJedis();
121 |
122 | FutureTask task = queue.take();
123 | if (!task.isCancelled()) {
124 | ThreadLocalHolder.jedisHolder.set(jedis);
125 | task.run();
126 | task.get();// 尝试获取结果,以便获取task是否有异常,如果有异常,需要关闭jedis
127 | }
128 | } catch (InterruptedException e) {
129 | // 如果被中断,按逻辑会继续检查shutdown (Yedis超时cancle时也会走到此中断的逻辑)
130 | } catch (ExecutionException e) {
131 | // 此处仅仅是为了判断task是否有jedis的网络异常
132 | Throwable cause = e.getCause();
133 | if (cause instanceof JedisConnectionException) {
134 | // 网络问题,关闭
135 | if (jedis != null) {
136 | jedis.close();
137 | }
138 | }
139 | } catch (JedisConnectionException e) {
140 | // 创建jedis时遇到网络问题
141 | logger.error("Error when connecting to " + config.getHost() + ":" + config.getPort()
142 | + " in YedisExecutor: " + e.getMessage());
143 | // 网络问题,关闭
144 | if (jedis != null) {
145 | jedis.close();
146 | sleepStrategy.sleep();
147 | }
148 | } catch (RuntimeException e) {
149 | // 遇到task运行时异常,则记log (实际上FutureTask的run方法不会抛出任何异常)
150 | logger.error("Error when task running in YedisExecutor", e);
151 | } catch (Error e) {
152 | // 遇到task运行时异常,则记log(实际上FutureTask的run方法不会抛出任何异常)
153 | logger.error("Error when task running in YedisExecutor", e);
154 | } catch (Throwable e) {
155 | // 遇到task运行时异常,则记log(实际上FutureTask的run方法不会抛出任何异常)
156 | logger.error("Error when task running in YedisExecutor", e);
157 | } finally {
158 | ThreadLocalHolder.jedisHolder.remove();
159 | }
160 | }
161 | }
162 |
163 | private void checkJedis() {
164 | if (jedis == null || !jedis.isConnected()) {
165 | createJedis();
166 | }
167 | }
168 |
169 | private void createJedis() {
170 | jedis = new Jedis(config.getHost(), config.getPort(), config.getSocketTimeout());
171 | jedis.select(config.getDatabase());
172 | logger.info("Connected to " + config.getHost() + ":" + config.getPort());
173 | sleepStrategy.reset();
174 | }
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/async/OperationResult.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.async;
2 |
3 | public class OperationResult {
4 |
5 | private T v;
6 |
7 | public OperationResult(T v) {
8 | this.v = v;
9 | }
10 |
11 | public T getValue() {
12 | return v;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/async/ThreadLocalHolder.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.async;
2 |
3 | import redis.clients.jedis.Jedis;
4 |
5 |
6 | public class ThreadLocalHolder {
7 |
8 | public static final ThreadLocal jedisHolder = new ThreadLocal();
9 |
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/atomic/AtomConfig.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.atomic;
2 |
3 | import redis.clients.jedis.JedisPoolConfig;
4 |
5 | import com.yeahmobi.yedis.common.ServerInfo;
6 |
7 | public class AtomConfig implements Cloneable {
8 |
9 | private static final int DEFAULT_PORT = 6379;
10 |
11 | private static final int DEFAULT_MAX_POOL_SIZE = 20;
12 |
13 | private static final int DEFAULT_MIN_IDLE = 0;
14 |
15 | private static final int DEFAULT_MAX_IDLE = -1;//unlimited
16 |
17 | private static final long DEFAULT_MAX_WAIT_MILLIS = 1000;
18 |
19 | // host和port
20 | private ServerInfo serverInfo;
21 |
22 | // 数据库
23 | private int database = 0;
24 |
25 | // redis的密码
26 | private String password;
27 |
28 | // Jedis底层connection的timeout
29 | private int socketTimeout = 100;
30 |
31 | // 控制所有Yedis操作的超时
32 | private long timeout = 100;
33 |
34 | private int threadPoolSize = 5;
35 |
36 | private String clientName;
37 |
38 | private JedisPoolConfig pipelinePoolConfig = new JedisPoolConfig();
39 | {
40 | pipelinePoolConfig.setMaxTotal(DEFAULT_MAX_POOL_SIZE);
41 | pipelinePoolConfig.setMaxIdle(DEFAULT_MAX_IDLE);
42 | pipelinePoolConfig.setMinIdle(DEFAULT_MIN_IDLE);
43 | pipelinePoolConfig.setMaxWaitMillis(DEFAULT_MAX_WAIT_MILLIS);
44 | }
45 |
46 | public AtomConfig() {
47 |
48 | }
49 |
50 | public AtomConfig(String host) {
51 | serverInfo = new ServerInfo();
52 | serverInfo.setHost(host);
53 | serverInfo.setPort(DEFAULT_PORT);
54 | }
55 |
56 | public AtomConfig(String host, int port) {
57 | serverInfo = new ServerInfo();
58 | serverInfo.setHost(host);
59 | serverInfo.setPort(port);
60 | }
61 |
62 | public ServerInfo getServerInfo() {
63 | return serverInfo;
64 | }
65 |
66 | public void setServerInfo(ServerInfo serverInfo) {
67 | this.serverInfo = serverInfo;
68 | }
69 |
70 | public String getHost() {
71 | return serverInfo.getHost();
72 | }
73 |
74 | public void setHost(String host) {
75 | serverInfo.setHost(host);
76 | }
77 |
78 | public int getPort() {
79 | return serverInfo.getPort();
80 | }
81 |
82 | public void setPort(int port) {
83 | serverInfo.setPort(port);
84 | }
85 |
86 | public int getDatabase() {
87 | return database;
88 | }
89 |
90 | public void setDatabase(int database) {
91 | this.database = database;
92 | }
93 |
94 | public String getPassword() {
95 | return password;
96 | }
97 |
98 | public void setPassword(String password) {
99 | this.password = password;
100 | }
101 |
102 | public long getTimeout() {
103 | return timeout;
104 | }
105 |
106 | public void setTimeout(long timeout) {
107 | this.timeout = timeout;
108 | }
109 |
110 | public int getSocketTimeout() {
111 | return socketTimeout;
112 | }
113 |
114 | public void setSocketTimeout(int socketTimeout) {
115 | this.socketTimeout = socketTimeout;
116 | }
117 |
118 | public int getThreadPoolSize() {
119 | return threadPoolSize;
120 | }
121 |
122 | public void setThreadPoolSize(int threadPoolSize) {
123 | this.threadPoolSize = threadPoolSize;
124 | }
125 |
126 | public String getClientName() {
127 | return clientName;
128 | }
129 |
130 | public void setClientName(String clientName) {
131 | this.clientName = clientName;
132 | }
133 |
134 | public JedisPoolConfig getPipelinePoolConfig() {
135 | return pipelinePoolConfig;
136 | }
137 |
138 | public void setPipelinePoolConfig(JedisPoolConfig pipelinePoolConfig) {
139 | this.pipelinePoolConfig = pipelinePoolConfig;
140 | }
141 |
142 | @Override
143 | public String toString() {
144 | return "AtomConfig [serverInfo=" + serverInfo + ", database=" + database + ", password=" + password
145 | + ", socketTimeout=" + socketTimeout + ", timeout=" + timeout + ", threadPoolSize=" + threadPoolSize
146 | + ", clientName=" + clientName + ", pipelinePoolConfig=" + pipelinePoolConfig + "]";
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/common/ServerInfo.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.common;
2 |
3 | public class ServerInfo {
4 |
5 | private String host;
6 | private int port;
7 |
8 | public ServerInfo() {
9 | }
10 |
11 | public ServerInfo(String host, int port) {
12 | super();
13 | this.host = host;
14 | this.port = port;
15 | }
16 |
17 | public String getHost() {
18 | return host;
19 | }
20 |
21 | public void setHost(String host) {
22 | this.host = host;
23 | }
24 |
25 | public int getPort() {
26 | return port;
27 | }
28 |
29 | public void setPort(int port) {
30 | this.port = port;
31 | }
32 |
33 | public String generateKey() {
34 | return host + "_" + port;
35 | }
36 |
37 | @Override
38 | public boolean equals(Object obj) {
39 | if (obj == null) {
40 | return false;
41 | }
42 | if (getClass() != obj.getClass()) {
43 | return false;
44 | }
45 | final ServerInfo other = (ServerInfo) obj;
46 | if ((this.host == null) ? (other.host != null) : !this.host.equals(other.host)) {
47 | return false;
48 | }
49 | if (this.port != other.port) {
50 | return false;
51 | }
52 | return true;
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | int hash = 3;
58 | hash = 41 * hash + host.hashCode();
59 | hash = 41 * hash + this.port;
60 | return hash;
61 | }
62 |
63 | @Override
64 | public String toString() {
65 | return String.format("ServerInfo [host=%s, port=%s]", host, port);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/common/YedisException.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.common;
2 |
3 | public class YedisException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = -6731964051519025372L;
6 |
7 | public YedisException() {
8 | }
9 |
10 | public YedisException(String message) {
11 | super(message);
12 | }
13 |
14 | public YedisException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public YedisException(Throwable cause) {
19 | super(cause);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/common/YedisNetworkException.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.common;
2 |
3 | public class YedisNetworkException extends YedisException {
4 |
5 | private static final long serialVersionUID = 1936628372356991877L;
6 |
7 | public YedisNetworkException() {
8 | }
9 |
10 | public YedisNetworkException(String message) {
11 | super(message);
12 | }
13 |
14 | public YedisNetworkException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public YedisNetworkException(Throwable cause) {
19 | super(cause);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/common/YedisTimeoutException.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.common;
2 |
3 | public class YedisTimeoutException extends YedisException {
4 |
5 | private static final long serialVersionUID = -7454647511869528468L;
6 |
7 | public YedisTimeoutException() {
8 | }
9 |
10 | public YedisTimeoutException(String message) {
11 | super(message);
12 | }
13 |
14 | public YedisTimeoutException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | public YedisTimeoutException(Throwable cause) {
19 | super(cause);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/ConfigChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | public interface ConfigChangeListener {
4 |
5 | void onChanged(MasterSlaveConfigManager configManager);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/DefaultConfigManager.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | import java.util.List;
4 |
5 | import com.yeahmobi.yedis.common.ServerInfo;
6 |
7 | /**
8 | * @author atell
9 | */
10 | public class DefaultConfigManager implements MasterSlaveConfigManager {
11 |
12 | private ServerInfo writeSeverInfo;
13 |
14 | private List readSeverInfoList;
15 |
16 | public DefaultConfigManager(ServerInfo writeSeverInfo, List readSeverInfoList) {
17 | this.writeSeverInfo = writeSeverInfo;
18 | this.readSeverInfoList = readSeverInfoList;
19 | }
20 |
21 | @Override
22 | public ServerInfo getMasterServerInfo() {
23 | return this.writeSeverInfo;
24 | }
25 |
26 | @Override
27 | public List getSlaveServerInfos() {
28 | return this.readSeverInfoList;
29 | }
30 |
31 | @Override
32 | public void addListener(ConfigChangeListener listener) {
33 | }
34 |
35 | @Override
36 | public void close() {
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/GroupConfig.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.apache.curator.RetryPolicy;
7 |
8 | import redis.clients.jedis.JedisPoolConfig;
9 |
10 | import com.yeahmobi.yedis.atomic.AtomConfig;
11 | import com.yeahmobi.yedis.common.ServerInfo;
12 | import com.yeahmobi.yedis.common.YedisException;
13 | import com.yeahmobi.yedis.loadbalance.LoadBalancer;
14 | import com.yeahmobi.yedis.loadbalance.LoadBalancer.Type;
15 |
16 | public class GroupConfig {
17 |
18 | private static final int DEFAULT_MAX_POOL_SIZE = 20;
19 |
20 | private static final int DEFAULT_MIN_IDLE = 0;
21 |
22 | private static final int DEFAULT_MAX_IDLE = -1;//unlimited
23 |
24 | private static final long DEFAULT_MAX_WAIT_MILLIS = 1000;
25 |
26 | // 读库负载均衡
27 | private LoadBalancer.Type loadBalancerType = Type.ROUND_ROBIN;
28 |
29 | private MasterSlaveConfigManager masterSlaveConfigManager;
30 |
31 | // 数据库
32 | private int database = 0;
33 |
34 | // redis的密码
35 | private String password;
36 |
37 | // Jedis底层connection的timeout
38 | private int socketTimeout = 100;
39 |
40 | // 控制所有Yedis操作的超时
41 | private long timeout = 100;
42 |
43 | private int threadPoolSize = 5;
44 |
45 | private String clientName;
46 |
47 | private ReadMode readMode = ReadMode.SLAVEPREFERRED;
48 |
49 | private JedisPoolConfig pipelinePoolConfig = new JedisPoolConfig();
50 | {
51 | pipelinePoolConfig.setMaxTotal(DEFAULT_MAX_POOL_SIZE);
52 | pipelinePoolConfig.setMaxIdle(DEFAULT_MAX_IDLE);
53 | pipelinePoolConfig.setMinIdle(DEFAULT_MIN_IDLE);
54 | pipelinePoolConfig.setMaxWaitMillis(DEFAULT_MAX_WAIT_MILLIS);
55 | }
56 |
57 | public GroupConfig(ServerInfo writeSeverInfo, List readSeverInfoList) {
58 | this.masterSlaveConfigManager = new DefaultConfigManager(writeSeverInfo, readSeverInfoList);
59 | }
60 |
61 | /**
62 | * 通过zookeeper获取 Master/Slave的动态配置
63 | *
64 | * @param clusterName 集群名称,对应的zookeeper的路径是 /yedis/failover/[clusterName]
65 | * @param zkUrl zookeeper的连接字符串
66 | */
67 | public GroupConfig(String clusterName, String zkUrl) {
68 | try {
69 | this.masterSlaveConfigManager = new ZookeeperConfigManager(clusterName, zkUrl);
70 | } catch (Exception e) {
71 | throw new YedisException(e.getMessage(), e);
72 | }
73 | }
74 |
75 | /**
76 | * 通过zookeeper获取 Master/Slave的动态配置
77 | *
78 | * @param rootpath zookeeper的根路径
79 | * @param clusterName 集群名称,对应的zookeeper的路径是 [rootpath]/[clusterName]
80 | * @param zkUrl zookeeper的连接字符串
81 | */
82 | public GroupConfig(String rootPath, String clusterName, String zkUrl) {
83 | try {
84 | this.masterSlaveConfigManager = new ZookeeperConfigManager(rootPath, clusterName, zkUrl);
85 | } catch (Exception e) {
86 | throw new YedisException(e.getMessage(), e);
87 | }
88 | }
89 |
90 | /**
91 | * 通过zookeeper获取 Master/Slave的动态配置
92 | *
93 | * @param rootpath zookeeper的根路径
94 | * @param clusterName 集群名称,对应的zookeeper的路径是 [rootpath]/[clusterName]
95 | * @param zkUrl zookeeper的连接字符串
96 | * @param sessionTimeoutMs zookeeper的会话超时
97 | * @param connectionTimeoutMs zookeeper的连接超时
98 | * @param retryPolicy zookeeper的重试策略
99 | */
100 | public GroupConfig(String rootPath, String clusterName, String zkUrl, int sessionTimeoutMs,
101 | int connectionTimeoutMs, RetryPolicy retryPolicy) {
102 | try {
103 | this.masterSlaveConfigManager = new ZookeeperConfigManager(rootPath, clusterName, zkUrl, sessionTimeoutMs,
104 | connectionTimeoutMs, retryPolicy);
105 | } catch (Exception e) {
106 | throw new YedisException(e.getMessage(), e);
107 | }
108 | }
109 |
110 | public ServerInfo getMasterServerInfo() {
111 | return masterSlaveConfigManager.getMasterServerInfo();
112 | }
113 |
114 | public List getSlaveServerInfoList() {
115 | return masterSlaveConfigManager.getSlaveServerInfos();
116 | }
117 |
118 | public void addListener(ConfigChangeListener listener) {
119 | masterSlaveConfigManager.addListener(listener);
120 | }
121 |
122 | public LoadBalancer.Type getLoadBalancerType() {
123 | return loadBalancerType;
124 | }
125 |
126 | public void setLoadBalancerType(LoadBalancer.Type loadBalancerType) {
127 | this.loadBalancerType = loadBalancerType;
128 | }
129 |
130 | public int getDatabase() {
131 | return database;
132 | }
133 |
134 | public void setDatabase(int database) {
135 | this.database = database;
136 | }
137 |
138 | public MasterSlaveConfigManager getMasterSlaveConfigManager() {
139 | return masterSlaveConfigManager;
140 | }
141 |
142 | public void setMasterSlaveConfigManager(MasterSlaveConfigManager masterSlaveConfigManager) {
143 | this.masterSlaveConfigManager = masterSlaveConfigManager;
144 | }
145 |
146 | public String getPassword() {
147 | return password;
148 | }
149 |
150 | public void setPassword(String password) {
151 | this.password = password;
152 | }
153 |
154 | public int getSocketTimeout() {
155 | return socketTimeout;
156 | }
157 |
158 | public void setSocketTimeout(int socketTimeout) {
159 | this.socketTimeout = socketTimeout;
160 | }
161 |
162 | public long getTimeout() {
163 | return timeout;
164 | }
165 |
166 | public void setTimeout(long timeout) {
167 | this.timeout = timeout;
168 | }
169 |
170 | public int getThreadPoolSize() {
171 | return threadPoolSize;
172 | }
173 |
174 | public void setThreadPoolSize(int threadPoolSize) {
175 | this.threadPoolSize = threadPoolSize;
176 | }
177 |
178 | public String getClientName() {
179 | return clientName;
180 | }
181 |
182 | public void setClientName(String clientName) {
183 | this.clientName = clientName;
184 | }
185 |
186 | public ReadMode getReadMode() {
187 | return readMode;
188 | }
189 |
190 | public void setReadMode(ReadMode readMode) {
191 | this.readMode = readMode;
192 | }
193 |
194 | public List getSlaveAtomConfigs() {
195 | List serverInfos = this.getSlaveServerInfoList();
196 | if (serverInfos != null) {
197 | List atomConfigs = new ArrayList(serverInfos.size());
198 | for (ServerInfo serverInfo : serverInfos) {
199 | AtomConfig atomConfig = new AtomConfig();
200 | atomConfig.setClientName(clientName);
201 | atomConfig.setDatabase(database);
202 | atomConfig.setPassword(password);
203 | atomConfig.setServerInfo(serverInfo);
204 | atomConfig.setSocketTimeout(socketTimeout);
205 | atomConfig.setThreadPoolSize(threadPoolSize);
206 | atomConfig.setTimeout(timeout);
207 | atomConfig.setPipelinePoolConfig(pipelinePoolConfig);
208 | atomConfigs.add(atomConfig);
209 | }
210 | return atomConfigs;
211 | }
212 | return null;
213 |
214 | }
215 |
216 | public AtomConfig getMasterAtomConfig() {
217 | ServerInfo serverInfo = this.getMasterServerInfo();
218 | if (serverInfo != null) {
219 | AtomConfig atomConfig = new AtomConfig();
220 | atomConfig.setClientName(clientName);
221 | atomConfig.setDatabase(database);
222 | atomConfig.setPassword(password);
223 | atomConfig.setServerInfo(serverInfo);
224 | atomConfig.setSocketTimeout(socketTimeout);
225 | atomConfig.setThreadPoolSize(threadPoolSize);
226 | atomConfig.setTimeout(timeout);
227 | atomConfig.setPipelinePoolConfig(pipelinePoolConfig);
228 | return atomConfig;
229 | }
230 | return null;
231 | }
232 |
233 | public JedisPoolConfig getPipelinePoolConfig() {
234 | return pipelinePoolConfig;
235 | }
236 |
237 | @Override
238 | public String toString() {
239 | return "GroupConfig [loadBalancerType=" + loadBalancerType + ", masterSlaveConfigManager="
240 | + masterSlaveConfigManager + ", database=" + database + ", password=" + password + ", socketTimeout="
241 | + socketTimeout + ", timeout=" + timeout + ", threadPoolSize=" + threadPoolSize + ", clientName="
242 | + clientName + ", readMode=" + readMode + ", pipelinePoolConfig=" + pipelinePoolConfig + "]";
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/MasterSlaveConfigManager.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | import java.util.List;
4 |
5 | import com.yeahmobi.yedis.common.ServerInfo;
6 |
7 | public interface MasterSlaveConfigManager {
8 |
9 | ServerInfo getMasterServerInfo();
10 |
11 | List getSlaveServerInfos();
12 |
13 | void addListener(ConfigChangeListener listener);
14 |
15 | void close();
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/ReadMode.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | public enum ReadMode {
4 | /**
5 | * Master:写操作和读操作都访问 master。
6 | */
7 | MASTER,
8 | /**
9 | * MasterPreferred:写操作访问 master;读操作访问 master,但当 master 不可用时,则访问 slave。
10 | */
11 | MASTERPREFERRED,
12 | /**
13 | * Slave:写操作访问 master;读操作访问 slave,slave 不可用时读调用会报错。
14 | */
15 | SLAVE,
16 | /**
17 | * SlavePreferred:写操作访问 master;读操作访问 slave,但当 slave 都不可用时,则访问 master
18 | */
19 | SLAVEPREFERRED;
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/group/ZookeeperConfigManager.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.group;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.ArrayList;
5 | import java.util.Collections;
6 | import java.util.Comparator;
7 | import java.util.Iterator;
8 | import java.util.List;
9 |
10 | import org.apache.curator.RetryPolicy;
11 | import org.apache.curator.framework.CuratorFramework;
12 | import org.apache.curator.framework.CuratorFrameworkFactory;
13 | import org.apache.curator.framework.api.CuratorWatcher;
14 | import org.apache.curator.retry.BoundedExponentialBackoffRetry;
15 | import org.apache.zookeeper.WatchedEvent;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 |
19 | import com.alibaba.fastjson.JSON;
20 | import com.google.common.base.Splitter;
21 | import com.google.common.base.Strings;
22 | import com.google.common.base.Verify;
23 | import com.yeahmobi.yedis.common.ServerInfo;
24 |
25 | /**
26 | * @author atell
27 | */
28 | public class ZookeeperConfigManager implements MasterSlaveConfigManager, CuratorWatcher {
29 |
30 | private final static Logger logger = LoggerFactory.getLogger(ZookeeperConfigManager.class);
31 |
32 | private static final int DEFAULT_MAX_SLEEP_TIME = 30000;
33 | private static final int DEFAULT_BASE_SLEEP_TIME = 500;
34 |
35 | private static final int DEFAULT_SESSION_TIMEOUT_MS = 60 * 1000;
36 | private static final int DEFAULT_CONNECTION_TIMEOUT_MS = 15 * 1000;
37 |
38 | private static final String NAMESPACE = "yedis/failover";
39 |
40 | private final CuratorFramework client;
41 |
42 | private final String clusterName;
43 |
44 | private List listeners = new ArrayList();
45 |
46 | private ServerInfo masterSeverInfo;
47 |
48 | private List slaveSeverInfos;
49 |
50 | public ZookeeperConfigManager(String rootPath, String clusterName, String zkUrl, int sessionTimeoutMs,
51 | int connectionTimeoutMs, RetryPolicy retryPolicy) throws Exception {
52 | this.clusterName = clusterName;
53 |
54 | // 构造并启动zk client
55 | if (rootPath == null) {
56 | rootPath = NAMESPACE;
57 | }
58 | this.client = CuratorFrameworkFactory.builder().connectString(zkUrl).sessionTimeoutMs(sessionTimeoutMs).connectionTimeoutMs(connectionTimeoutMs).namespace(rootPath).retryPolicy(retryPolicy).build();
59 | client.start();
60 |
61 | // 获取并解析failoverStr,内容如:{"master":"172.20.0.53:6379","slaves":["172.20.0.55:6379","172.20.0.56:6379"],"unavailable":[]}
62 | byte[] data = client.getData().forPath(clusterName);
63 | Failover failover = parseFailoverStr(data);
64 | this.masterSeverInfo = parseServerInfo(failover.getMaster());
65 | this.slaveSeverInfos = parseSlaves(failover.getSlaves());
66 |
67 | // 设置zk监听
68 | client.getData().usingWatcher(this).inBackground().forPath(clusterName);
69 |
70 | }
71 |
72 | private Failover parseFailoverStr(byte[] data) throws Exception, UnsupportedEncodingException {
73 | String failoverStr = new String(data, "UTF-8");
74 | Failover failover = JSON.parseObject(failoverStr, Failover.class);
75 | logger.info("cluster config is:" + failoverStr);
76 | return failover;
77 | }
78 |
79 | public ZookeeperConfigManager(String clusterName, String zkUrl, int sessionTimeoutMs, int connectionTimeoutMs)
80 | throws Exception {
81 | this(null, clusterName, zkUrl, sessionTimeoutMs, connectionTimeoutMs,
82 | new BoundedExponentialBackoffRetry(DEFAULT_BASE_SLEEP_TIME, DEFAULT_MAX_SLEEP_TIME, Integer.MAX_VALUE));
83 | }
84 |
85 | public ZookeeperConfigManager(String rootPath, String clusterName, String zkUrl, int sessionTimeoutMs,
86 | int connectionTimeoutMs) throws Exception {
87 | this(rootPath, clusterName, zkUrl, sessionTimeoutMs, connectionTimeoutMs,
88 | new BoundedExponentialBackoffRetry(DEFAULT_BASE_SLEEP_TIME, DEFAULT_MAX_SLEEP_TIME, Integer.MAX_VALUE));
89 | }
90 |
91 | public ZookeeperConfigManager(String clusterName, String zkUrl, RetryPolicy retryPolicy) throws Exception {
92 | this(null, clusterName, zkUrl, DEFAULT_SESSION_TIMEOUT_MS, DEFAULT_CONNECTION_TIMEOUT_MS, retryPolicy);
93 | }
94 |
95 | public ZookeeperConfigManager(String rootPath, String clusterName, String zkUrl, RetryPolicy retryPolicy)
96 | throws Exception {
97 | this(rootPath, clusterName, zkUrl, DEFAULT_SESSION_TIMEOUT_MS, DEFAULT_CONNECTION_TIMEOUT_MS, retryPolicy);
98 | }
99 |
100 | public ZookeeperConfigManager(String clusterName, String zkUrl) throws Exception {
101 | this(null, clusterName, zkUrl, DEFAULT_SESSION_TIMEOUT_MS, DEFAULT_CONNECTION_TIMEOUT_MS,
102 | new BoundedExponentialBackoffRetry(DEFAULT_BASE_SLEEP_TIME, DEFAULT_MAX_SLEEP_TIME, Integer.MAX_VALUE));
103 | }
104 |
105 | public ZookeeperConfigManager(String rootPath, String clusterName, String zkUrl) throws Exception {
106 | this(rootPath, clusterName, zkUrl, DEFAULT_SESSION_TIMEOUT_MS, DEFAULT_CONNECTION_TIMEOUT_MS,
107 | new BoundedExponentialBackoffRetry(DEFAULT_BASE_SLEEP_TIME, DEFAULT_MAX_SLEEP_TIME, Integer.MAX_VALUE));
108 | }
109 |
110 | private List parseSlaves(List slaves) {
111 | Verify.verify(slaves != null);
112 |
113 | List list = new ArrayList();
114 | if (slaves != null) {
115 | for (String str : slaves) {
116 | ServerInfo serverInfo = parseServerInfo(str);
117 | list.add(serverInfo);
118 | }
119 | }
120 | return list;
121 | }
122 |
123 | private ServerInfo parseServerInfo(String nodeStr) {
124 | try {
125 | Verify.verify(!Strings.isNullOrEmpty(nodeStr));
126 |
127 | Iterable split = Splitter.on(':').split(nodeStr);
128 | Iterator iterator = split.iterator();
129 |
130 | boolean hasNext = iterator.hasNext();
131 | Verify.verify(hasNext);
132 |
133 | String host = iterator.next();
134 | hasNext = iterator.hasNext();
135 | Verify.verify(hasNext);
136 |
137 | int port = Integer.parseInt(iterator.next());
138 |
139 | return new ServerInfo(host, port);
140 |
141 | } catch (Exception e) {
142 | throw new IllegalArgumentException("Error node string:" + nodeStr);
143 | }
144 | }
145 |
146 | @Override
147 | public ServerInfo getMasterServerInfo() {
148 | return this.masterSeverInfo;
149 | }
150 |
151 | @Override
152 | public List getSlaveServerInfos() {
153 | return this.slaveSeverInfos;
154 | }
155 |
156 | @Override
157 | public void addListener(ConfigChangeListener listener) {
158 | this.listeners.add(listener);
159 | }
160 |
161 | @Override
162 | public void close() {
163 | client.close();
164 | }
165 |
166 | public static class Failover {
167 |
168 | private String master;
169 | private List slaves;
170 |
171 | public String getMaster() {
172 | return master;
173 | }
174 |
175 | public void setMaster(String master) {
176 | this.master = master;
177 | }
178 |
179 | public List getSlaves() {
180 | return slaves;
181 | }
182 |
183 | public void setSlaves(List slaves) {
184 | this.slaves = slaves;
185 | }
186 |
187 | }
188 |
189 | @Override
190 | public void process(WatchedEvent event) throws Exception {
191 | logger.info("Config changed.");
192 |
193 | try {
194 | // 获取最新配置
195 | ServerInfo masterSeverInfo = null;
196 | List slaveSeverInfos = null;
197 | byte[] data = client.getData().forPath(clusterName);
198 | Failover failover = parseFailoverStr(data);
199 | masterSeverInfo = parseServerInfo(failover.getMaster());
200 | slaveSeverInfos = parseSlaves(failover.getSlaves());
201 |
202 | // 计算是否配置发生变更
203 | boolean changed = false;
204 | if (!this.masterSeverInfo.equals(masterSeverInfo)) {
205 | this.masterSeverInfo = masterSeverInfo;
206 | changed = true;
207 | }
208 | if (!equals(this.slaveSeverInfos, slaveSeverInfos)) {
209 | this.slaveSeverInfos = slaveSeverInfos;
210 | changed = true;
211 | }
212 |
213 | // 通知listener
214 | if (changed && listeners != null && listeners.size() > 0) {
215 | for (ConfigChangeListener listener : listeners) {
216 | try {
217 | listener.onChanged(this);
218 | } catch (Exception e) {
219 | logger.error("Config changed, but error occurred when invoke this listener.", e);
220 | }
221 | }
222 | }
223 | } catch (Exception e) {
224 | logger.error("Received watch event from zookeeper, but error occurred, so nothing changed.", e);
225 | } finally {
226 | // 再次监听
227 | client.getData().usingWatcher(this).inBackground().forPath(clusterName);
228 | }
229 |
230 | }
231 |
232 | private boolean equals(List list1, List list2) {
233 | if (list1 == null && list2 == null) {
234 | return true;
235 | } else if (list1 == null || list2 == null) {
236 | return false;
237 | } else if (list1.size() != list2.size()) {
238 | return false;
239 | }
240 |
241 | Comparator super ServerInfo> comparator = new Comparator() {
242 |
243 | @Override
244 | public int compare(ServerInfo o1, ServerInfo o2) {
245 | String c1 = o1.getHost() + o1.getPort();
246 | String c2 = o2.getHost() + o2.getPort();
247 | return c1.compareTo(c2);
248 | }
249 |
250 | };
251 | Collections.sort(list1, comparator);
252 | Collections.sort(list2, comparator);
253 |
254 | return list1.equals(list2);
255 | }
256 |
257 | public String getClusterName() {
258 | return clusterName;
259 | }
260 |
261 | }
262 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/loadbalance/LoadBalancer.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.loadbalance;
2 |
3 | import com.yeahmobi.yedis.atomic.Yedis;
4 |
5 | /**
6 | * @author atell
7 | */
8 | public interface LoadBalancer {
9 |
10 | Yedis route();
11 |
12 | Type getType();
13 |
14 | public enum Type {
15 | RANDOM, ROUND_ROBIN
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/loadbalance/RandomLoadBalancer.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.loadbalance;
2 |
3 | import java.util.List;
4 | import java.util.Random;
5 |
6 | import com.yeahmobi.yedis.atomic.Yedis;
7 |
8 | public class RandomLoadBalancer implements LoadBalancer {
9 |
10 | private Type type = LoadBalancer.Type.RANDOM;
11 | private List yedisList;
12 | private int listSize;
13 |
14 | private final Random random = new Random();
15 |
16 | public RandomLoadBalancer(List yedisList) {
17 | if (yedisList == null || yedisList.size() <= 0) {
18 | throw new IllegalArgumentException("yedisList cannot be null or empty.");
19 | }
20 | this.yedisList = yedisList;
21 | this.listSize = yedisList.size();
22 | }
23 |
24 | @Override
25 | public Yedis route() {
26 | return yedisList.get(random.nextInt(listSize));
27 | }
28 |
29 | @Override
30 | public Type getType() {
31 | return type;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/loadbalance/RoundRobinLoadBalancer.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.loadbalance;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | import com.yeahmobi.yedis.atomic.Yedis;
7 |
8 | public class RoundRobinLoadBalancer implements LoadBalancer {
9 |
10 | private final Type type = LoadBalancer.Type.ROUND_ROBIN;
11 | private final List yedisList;
12 | private final int listSize;
13 |
14 | private final AtomicInteger index = new AtomicInteger(-1);
15 |
16 | public RoundRobinLoadBalancer(List yedisList) {
17 | if (yedisList == null || yedisList.size() <= 0) {
18 | throw new IllegalArgumentException("yedisList cannot be null or empty.");
19 | }
20 | this.yedisList = yedisList;
21 | this.listSize = yedisList.size();
22 | }
23 |
24 | @Override
25 | public Yedis route() {
26 | return yedisList.get(getIndex());
27 | }
28 |
29 | private int getIndex() {
30 | return Math.abs(index.incrementAndGet() % listSize);
31 | }
32 |
33 | @Override
34 | public Type getType() {
35 | return type;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/pipeline/PipelineJedisPool.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.pipeline;
2 |
3 | import redis.clients.jedis.Jedis;
4 | import redis.clients.jedis.JedisPool;
5 |
6 | import com.yeahmobi.yedis.atomic.AtomConfig;
7 |
8 | public class PipelineJedisPool {
9 |
10 | private final JedisPool jedisPool;
11 |
12 | private final AtomConfig config;
13 |
14 | public PipelineJedisPool(AtomConfig config) {
15 | if (config == null) {
16 | throw new IllegalArgumentException("Argument cannot be null.");
17 | }
18 |
19 | this.config = config;
20 | this.jedisPool = new JedisPool(config.getPipelinePoolConfig(), config.getHost(), config.getPort(),
21 | config.getSocketTimeout(), config.getPassword(), config.getDatabase());
22 | }
23 |
24 | public AtomConfig getConfig() {
25 | return config;
26 | }
27 |
28 | public Jedis getJedis() {
29 | return this.jedisPool.getResource();
30 | }
31 |
32 | public void returnBrokenJedis(Jedis resource) {
33 | jedisPool.returnBrokenResource(resource);
34 | }
35 |
36 | public void returnJedis(Jedis resource) {
37 | jedisPool.returnResource(resource);
38 | }
39 |
40 | public void destroy() {
41 | jedisPool.destroy();
42 | }
43 |
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/yeahmobi/yedis/pipeline/ShardedYedisPipeline.java:
--------------------------------------------------------------------------------
1 | package com.yeahmobi.yedis.pipeline;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Set;
8 |
9 | import redis.clients.jedis.BinaryClient.LIST_POSITION;
10 | import redis.clients.jedis.BitPosParams;
11 | import redis.clients.jedis.Response;
12 | import redis.clients.jedis.SortingParams;
13 | import redis.clients.jedis.Tuple;
14 |
15 | import com.yeahmobi.yedis.group.GroupYedis;
16 | import com.yeahmobi.yedis.shard.ShardingStrategy;
17 |
18 | public class ShardedYedisPipeline {
19 |
20 | private HashMap pipelineInfos = new HashMap();
21 |
22 | private ShardingStrategy shardingStrategy;
23 |
24 | private List responseInfos = new ArrayList();
25 |
26 | static class PipelineInfo {
27 |
28 | YedisPipeline yedisPipeline;
29 | int indexCount = 0;
30 | }
31 |
32 | static class ResponseInfo {
33 |
34 | // indexCount指向当前pipeline的response的位置
35 | YedisPipeline yedisPipeline;
36 | // indexCount指向当前pipeline的response的位置
37 | int index;
38 |
39 | public ResponseInfo(YedisPipeline yedisPipeline, int index) {
40 | super();
41 | this.yedisPipeline = yedisPipeline;
42 | this.index = index;
43 | }
44 | }
45 |
46 | public ShardedYedisPipeline(ShardingStrategy shardingStrategy) {
47 | super();
48 | this.shardingStrategy = shardingStrategy;
49 | }
50 |
51 | private YedisPipeline route(byte[] key) {
52 | GroupYedis groupYedis = shardingStrategy.route(key);
53 | return getAndPut(groupYedis);
54 | }
55 |
56 | private YedisPipeline route(String key) {
57 | GroupYedis groupYedis = shardingStrategy.route(key);
58 | return getAndPut(groupYedis);
59 | }
60 |
61 | private YedisPipeline getAndPut(GroupYedis groupYedis) {
62 | PipelineInfo pipelineInfo = pipelineInfos.get(groupYedis);
63 | if (pipelineInfo == null) {
64 | pipelineInfo = new PipelineInfo();
65 | pipelineInfo.yedisPipeline = groupYedis.pipelined();
66 | pipelineInfos.put(groupYedis, pipelineInfo);
67 | }
68 |
69 | ResponseInfo responseInfo = new ResponseInfo(pipelineInfo.yedisPipeline, pipelineInfo.indexCount);
70 | responseInfos.add(responseInfo);
71 |
72 | pipelineInfo.indexCount++;
73 |
74 | return pipelineInfo.yedisPipeline;
75 | }
76 |
77 | public void sync() {
78 | List errorMsg = new ArrayList();
79 | for (PipelineInfo pipelineInfo : pipelineInfos.values()) {
80 | YedisPipeline pipeline = pipelineInfo.yedisPipeline;
81 | try {
82 | pipeline.sync();
83 | } catch (Exception e) {
84 | // 如果某个YedisPipeline 有异常,记录起来
85 | errorMsg.add(String.format("Pipeline(%s:%s) error message is " + e.getMessage(),
86 | pipeline.getConfig().getHost(), pipeline.getConfig().getPort()));
87 | }
88 | }
89 | if (errorMsg.size() > 0) {
90 | throw new RuntimeException("Some pipelines error, detail is: " + errorMsg.toString());
91 | }
92 | }
93 |
94 | @SuppressWarnings("unchecked")
95 | public List