您的位置:首页 > 数据库 > Redis

【Redis】spring-data-redis与JedisPool的区别、使用ShardedJedisPool与spring集成的实现及一致性哈希分析

2017-05-23 10:22 686 查看


一、Redis与spring的整合一般分为spring-data-redis整合和JedisPool整合,先看看两者的区别

1)、引用的依赖不同:

spring-data-redis使用的依赖如下:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
1
2
3
4
5
1
2
3
4
5

JedisPool使用的依赖如下:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
1
2
3
4
5
6
7
1
2
3
4
5
6
7

2)、管理Jedis实例方式、操作redis服务的不同:

spring-data-redis:

通过org.springframework.data.redis.connection.jedis.JedisConnectionFactory来管理,即通过工厂类管理,然后通过配置的模版bean,操作redis服务,代码段中充斥大量与业务无关的模版片段代码,代码冗余,不易维护,比如像下面的代码:
@Autowired
protected RedisTemplate<Serializable, Serializable> redisTemplate;

public void saveUser(final User user) {
redisTemplate.execute(new RedisCallback<Object>() {

@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
connection.set(redisTemplate.getStringSerializer().serialize("user.uid." + user.getId()),
redisTemplate.getStringSerializer().serialize(user.getName()));
return null;
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public User getUser(final long id) {
return redisTemplate.execute(new RedisCallback<User>() {
@Override
public User doInRedis(RedisConnection connection) throws DataAccessException {
byte[] key = redisTemplate.getStringSerializer().serialize("user.uid." + id);
if (connection.exists(key)) {
byte[] value = connection.get(key);
String name = redisTemplate.getStringSerializer().deserialize(value);
User user = new User();
user.setName(name);
user.setId(id);
return user;
}
return null;
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

JedisPool方式:

通过redis.clients.jedis.JedisPool来管理,即通过池来管理,通过池对象获取jedis实例,然后通过jedis实例直接操作redis服务,剔除了与业务无关的冗余代码,如下面的代码片段:
@Autowired
private JedisPool jedisPool;

@Override
public String save(String key,String val) {
Jedis jedis = jedisPool.getResource();
return jedis.set(key, val);

}
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10

从工厂类到池的方式变化,就相当于mybatis连接MySQL方变化是一样的,代码变得更简洁,维护也更容易了。

spring-data-redis的集成方式可以查看 java之redis篇(spring-data-redis整合) 这边博文。

但是本文与spring集成并未直接采用JedisPool,而是采用了 ShardedJedisPool ,为什么呢?

因为ShardedJedisPool可以通过一致性哈希实现分布式存储

shared一致性哈希采用以下方案:

1、Redis服务器节点划分:将每台服务器节点采用hash算法划分为160个虚拟节点(可以配置划分权重)

2、将划分虚拟节点采用TreeMap存储

3、对每个Redis服务器的物理连接采用LinkedHashMap存储

4、对Key or KeyTag 采用同样的hash算法,然后从TreeMap获取大于等于键hash值得节点,取最邻近节点存储;当key的hash值大于虚拟节点hash值得最大值时,存入第一个虚拟节点sharded采用的hash算法:MD5 和 MurmurHash两种;默认采用64位的MurmurHash算法;

更为深入解释可以查看 Jedis分片连接池(分布式) 、 jedis源码中ShardedJedis实现sharding文章。

使用非分片的JedisPool和使用分片的ShardedJedisPool示例可以查看 Java内存数据库实践之深入浅出Redis - Jedis分布式(Sharding/Sharded) 这篇文章。


二、使用ShardedJedisPool与spring集成

集成参照:https://my.oschina.net/u/866380/blog/521658

相关依赖jar包
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.2.6.RELEASE</spring.version>
</properties>

<dependencies>

<!-- spring begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
<type>jar</type>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
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
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

Spring 配置文件spring-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!-- 开启注解驱动 -->
<context:annotation-config></context:annotation-config>

<!-- 引入属性文件 -->
<context:property-placeholder location="classpath:spring-redis.properties" ignore-unresolvable="true" />

<!-- 配置redis数据库连接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.pool.maxActive}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="minIdle" value="${redis.pool.minIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWait}" />
<property name="lifo" value="${redis.pool.lifo}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
</bean>

<!-- 配置redis共享连接池,业务层主要通过该bean访问redis服务器 -->
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1">
<list>
<bean class="redis.clients.jedis.JedisShardInfo">
<constructor-arg index="0" value="${redis.uri.0}" />
</bean>
</list>
</constructor-arg>
</bean>

</beans>
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
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

几个注意的点:

(1)如果你有多个数据源需要通过
<context:property-placeholder>
管理,且不愿意放在一个配置文件里,那么一定要加上ignore-unresolvable=“true”

(2)注意新版的(具体从哪个版本开始不清楚,有兴趣可以查一下)JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码。

(3)ShardedJedisPool有多种构造函数,选择你需要的(具体看源码),示例中只初始化了一个分片,并使用了通过指定host的构造器(具体格式见下文),如果有集群,在下增加新的即可。

(4)当然,你的spring核心配置文件中得有扫描组件。

(5)客户端分片使用ShardedJedisPool,设置DB和超时时间

redis连接池配置文件spring-redis.properties
#最大分配的对象数
redis.pool.maxActive=10
#最大能够保持idel状态的对象数
redis.pool.maxIdle=10
#最小空闲的对象数。2.5.1以上版本有效
redis.pool.minIdle=8
#当池内没有返回对象时,最大等待时间
redis.pool.maxWait=1000
#是否启用Lifo。如果不设置,默认为true。2.5.1以上版本有效
redis.pool.lifo=false
#当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=false

## REDIS URI
## 使用DB0库
##redis://用户名:密码@host:port/库,其中用户名随意填写的
redis.uri.0=redis://testRedis:foobared@119.254.166.136:6379/0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意:redis uir的格式为 redis://用户名:密码@host:port/库,其中用户名随意填写的,如果没有用户名和密码,格式为redis://:@host:port/库,使用这种方式,配置内容少,还能自定义db index,非常适合开发、测试和线上环境的切换

代码实现

(1)推荐大家使用统一的类来管理Jedis实例的生成和回收,参考代码如下:JedisDataSourceImpl
@Repository("jedisDS")
public class JedisDataSourceImpl implements JedisDataSource {
private static final Logger LOG = LoggerFactory.getLogger(JedisDataSourceImpl.class);

@Autowired
private ShardedJedisPool shardedJedisPool;

@Override
public ShardedJedis getRedisClient() {
ShardedJedis shardJedis = null;
try {
shardJedis = shardedJedisPool.getResource();
return shardJedis;
} catch (Exception e) {
LOG.error("[JedisDS] getRedisClent error:" + e.getMessage());
if (null != shardJedis)
shardJedis.close();
}
return null;
}

@Override
public void returnResource(ShardedJedis shardedJedis) {
shardedJedis.close();
}

@Override
public void returnResource(ShardedJedis shardedJedis, boolean broken) {
shardedJedis.close();
}

}
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
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

这里要注意的是Jedis实例的回收,从jedis2.6开始,原returnResource方式已经提示在后续版本中不再支持,所以不建议大家再用ShardedJedisPool里的returnResource和retureBrokenResource方法,虽然在2.7中还支持(毕竟是因为这两个方法存在漏洞)。

(2)编写具体的Jedis操作类(片断):RedisClientTemplate
@Repository("redisClientTemplate")
public class RedisClientTemplate {
private static final Logger log = LoggerFactory.getLogger(RedisClientTemplate.class);

@Autowired
private JedisDataSource redisDataSource;

public void disconnect() {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
shardedJedis.disconnect();
}

/**
* 设置单个值
*
* @param key
* @param value
* @return
*/
public String set(String key, String value) {
String result = null;

ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.set(key, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

/**
* 获取单个值
*
* @param key
* @return
*/
public String get(String key) {
String result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}

boolean broken = false;
try {
result = shardedJedis.get(key);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public Boolean exists(String key) {
Boolean result = false;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.exists(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public String type(String key) {
String result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.type(key);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

/**
* 在某段时间后失效
*
* @param key
* @param seconds
* @return
*/
public Long expire(String key, int seconds) {
Long result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.expire(key, seconds);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

/**
* 在某个时间点失效
*
* @param key
* @param unixTime
* @return
*/
public Long expireAt(String key, long unixTime) {
Long result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.expireAt(key, unixTime);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public Long ttl(String key) {
Long result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.ttl(key);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public boolean setbit(String key, long offset, boolean value) {

ShardedJedis shardedJedis = redisDataSource.getRedisClient();
boolean result = false;
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.setbit(key, offset, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public boolean getbit(String key, long offset) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
boolean result = false;
if (shardedJedis == null) {
return result;
}
boolean broken = false;

try {
result = shardedJedis.getbit(key, offset);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public long setRange(String key, long offset, String value) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
long result = 0;
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.setrange(key, offset, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}

public String getRange(String key, long startOffset, long endOffset) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
String result = null;
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.getrange(key, startOffset, endOffset);

} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}
}
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
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

(3)好了,接下来在你的业务代码里加载RedisClientTemplate.class就可以了。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-redis.xml")
public class RedisTest{
@Autowired
private RedisClientTemplate redisClientTemplate ;

@Test
public void testSave(){
String key = "10086";
String val = "中国移动";
String r = redisClientTemplate .save(key,val);
System.out.println(r);
}

@Test
public void testFind(){
String key = "10086";
String result = redisClientTemplate .find(key);
System.out.println(result);
}

@Test public void testDel(){
String key = "10086";
long l = redisClientTemplate .delete(key);
System.out.println(l);
}
}
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
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

补充:

1、shardedjedispool分片实现集群与jediscluster实现集群区别以及jediscluster实现集群可以查看 分布式缓存技术redis学习系列(七)——spring整合jediscluster 

2、shardedjedispool分片集群与jediscluster集群同样不具有高可用特性,但基于shardedjedispool的ShardedJedisSentinelPool可以实现集群高可用,redis服务一台master挂机,sentinel哨兵进行主从切换之后,客户端程序自动完成同步的主从切换。 

具体的实现方式就是通过sentinel的sentinelGetMasterAddrByName(masterName);(实际执行sentinel的get-master-addr-by-name命令)获取哨兵监控的所有master节点;然后用一个MasterListener启用一个线程,利用redis的订阅与发布机制,订阅+switch-master频道,时时获取redis服务端主从切换的信息。 

实现原理和源码分析可以查看 基于Redis Sentinel的Redis集群(主从&Sharding)高可用方案 相关源码地址位于 sharded-jedis-sentinel-pool 


http://sailorls.iteye.com/blog/1604702 http://blog.csdn.net/javaloveiphone/article/details/52355180
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐