一、Redis与spring的整合一般分为spring-data-redis整合和JedisPool整合,先看看两者的区别
1)、引用的依赖不同:
spring-data-redis使用的依赖如下:
< groupId > org.springframework.data
groupId >
< artifactId > spring-data-redis
artifactId >
< version > 1.0.2.RELEASE
version >
JedisPool使用的依赖如下:
< groupId > redis.clients
groupId >
< artifactId > jedis
artifactId >
2)、管理Jedis实例方式、操作redis服务的不同:
spring-data-redis:
通过org.springframework.data.redis.connection.jedis.JedisConnectionFactory来管理,即通过工厂类管理,然后通过配置的模版bean,操作redis服务,代码段中充斥大量与业务无关的模版片段代码,代码冗余,不易维护,比如像下面的代码:
protected RedisTemplate redisTemplate;
public
void
saveUser
(
final
User user) {
redisTemplate.execute(
new RedisCallback() {
public
Object
doInRedis
(RedisConnection connection)
throws
DataAccessException {
connection.set(redisTemplate.getStringSerializer().serialize(
"user.uid." + user.getId()),
redisTemplate.getStringSerializer().serialize(user.getName()));
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public
User
getUser
(
final
long
id) {
return redisTemplate.execute(
new RedisCallback() {
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);
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
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服务,剔除了与业务无关的冗余代码,如下面的代码片段:
private JedisPool jedisPool;
public
String
save
(String key,String val) {
Jedis jedis = jedisPool.getResource();
return jedis.set(key, val);
从工厂类到池的方式变化,就相当于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包
< project.build.sourceEncoding > UTF-8
project.build.sourceEncoding >
< spring.version > 4.2.6.RELEASE
spring.version >
< groupId > org.springframework
groupId >
< artifactId > spring-context
artifactId >
< version > ${spring.version}
version >
< groupId > org.springframework
groupId >
< artifactId > spring-context-support
artifactId >
< version > ${spring.version}
version >
< groupId > org.springframework
groupId >
< artifactId > spring-test
artifactId >
< version > ${spring.version}
version >
< groupId > redis.clients
groupId >
< artifactId > jedis
artifactId >
< artifactId > junit
artifactId >
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
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"
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" />
< 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 id = "shardedJedisPool" class = "redis.clients.jedis.ShardedJedisPool" >
< constructor-arg index = "0" ref = "jedisPoolConfig" />
< constructor-arg index = "1" >
< bean class = "redis.clients.jedis.JedisShardInfo" >
< constructor-arg index = "0" value = "${redis.uri.0}" />
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
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)如果你有多个数据源需要通过
管理,且不愿意放在一个配置文件里,那么一定要加上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
redis
.pool
.maxWait =
1000
#是否启用Lifo。如果不设置,默认为true。2.5.1以上版本有效
#当调用borrow Object方法时,是否进行有效性检查
redis
.pool
.testOnBorrow =
false
##redis://用户名:密码@host:port/库,其中用户名随意填写的
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
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
public
class JedisDataSourceImpl implements JedisDataSource {
private
static
final Logger LOG = LoggerFactory.getLogger(JedisDataSourceImpl.class);
private ShardedJedisPool shardedJedisPool;
public
ShardedJedis
getRedisClient
() {
ShardedJedis shardJedis =
null ;
shardJedis = shardedJedisPool.getResource();
LOG.error(
"[JedisDS] getRedisClent error:" + e.getMessage());
public
void
returnResource
(ShardedJedis shardedJedis) {
public
void
returnResource
(ShardedJedis shardedJedis,
boolean
broken) {
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
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);
private JedisDataSource redisDataSource;
public
void
disconnect
() {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
shardedJedis.disconnect();
public
String
set
(String key, String value) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.set(key, value);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
String
get
(String key) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.get(key);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
Boolean
exists
(String key) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.exists(key);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
String
type
(String key) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.type(key);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
Long
expire
(String key,
int
seconds) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.expire(key, seconds);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
Long
expireAt
(String key,
long
unixTime) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.expireAt(key, unixTime);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
Long
ttl
(String key) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.ttl(key);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
boolean
setbit
(String key,
long
offset,
boolean
value) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.setbit(key, offset, value);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
boolean
getbit
(String key,
long
offset) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.getbit(key, offset);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
long
setRange
(String key,
long
offset, String value) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.setrange(key, offset, value);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
public
String
getRange
(String key,
long
startOffset,
long
endOffset) {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis ==
null ) {
result = shardedJedis.getrange(key, startOffset, endOffset);
log.error(e.getMessage(), e);
redisDataSource.returnResource(shardedJedis, broken);
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
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" )
private RedisClientTemplate redisClientTemplate ;
String r = redisClientTemplate .save(key,val);
String result = redisClientTemplate .find(key);
System.out.println(result);
@Test
public
void
testDel
() {
long l = redisClientTemplate .delete(key);
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
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