Fork me on GitHub

SpringBoot2.0集成Redis详解及踩过的坑(Could not get a resource from the pool)

SpringBoot2.0集成Redis

首先安装的过程就不提了。上一个项目的redis是配置在Windows下的,集成很简单,也没有做什么配置。这次为了进行测试,装在了linux下。在SpringBoot集成的过程中遇到了一些小坑,分享一下。

pom文件中添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

<!--引入Json依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

可以看到除了Redis我还加入了JSON的依赖,这是因为我之后读取出来的数据要转换成JSON串的格式,数据可读,方便开发。

application.properties文件中添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
#redis配置
#Linux主机地址
spring.redis.host=192.168.78.131
#端口
spring.redis.port=6379
#超时时间,ms
spring.redis.timeout=3000
#连接池的最大连接数
spring.redis.jedis.pool.max-active=10
#链接的最大等待时间ms
spring.redis.jedis.pool.max-wait=3000
#连接池中的最大的等待数量
spring.redis.jedis.pool.max-idle=10

在SpringBoot2.0的版本中timeout变成了Duration类型的,从其源码中我们可以看一下是怎么定义的:

1
2
3
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}

所以正常的写法应该是:

1
spring.redis.timeout=3000ms

但是这样也带来了一个问题,下面再说这个问题是什么,这里先按照初始的定义,不会报错的

正常的人可能还会在配置文件中添加redis的密码的配置,没有密码的话就不要添加这个配置,就算默认为空也会导致出现错误,不能实现OAuth认证,要是有密码的话就添加上并写上自己的密码。

1
spring.redis.password=

Redis的自定义初始化

首先写redisConfig的文件,读取application.properties文件中yml的配置

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
package com.springboot.SecKill.redis;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;


/**
* Redis的配置
* @author WilsonSong
* @date 2018/8/1/001
*/
//作为组件扫描进来
@Component
//读取配置文件
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class RedisConfig {
private String host; //主机
private int port; //端口
private int timeout; //超时时间

@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive; //连接池最大线程数

@Value("${spring.redis.jedis.pool.max-wait}")
private long maxWait; //等待时间

@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;//最大空闲连接

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}


public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public int getTimeout() {
return timeout;
}

public void setTimeout(int timeout) {
this.timeout = timeout;
}

public int getMaxActive() {
return maxActive;
}

public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}

public long getMaxWait() {
return maxWait;
}

public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}

public int getMaxIdle() {
return maxIdle;
}

public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
}

上面用了两种方式来读取application.properties中的配置,一种是直接设置@ConfigurationProperties(prefix = “spring.redis”),然后变量名与application.properties中的变量名一样,这样就可以读出来了,然后像max-wait这种变量名没法定义啊,所以又用了@Value(“${spring.redis.jedis.pool.max-idle}”)这种注解的方式来读取,当然你可以全部影注解的方式来读取,注意过程中的每一个变量的基本数据类型定义准确。maxWait和timeout这两个本来都是Duration类型的,但是这里分别写成long和int类型的。一会儿再解释这个问题。

过程中需要从redis连接池中获取redis服务,所以这里初始化jedisPool的配置

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
package com.springboot.SecKill.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
* @author WilsonSong
* @date 2018/8/1/001
*/
@Service
public class RedisPoolFactory {

@Autowired
RedisConfig redisConfig;
/**
* redis连接池的一些配置
* @return
*/
@Bean
public JedisPool JedisPoolFactory(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getMaxIdle());
poolConfig.setMaxTotal(redisConfig.getMaxActive());
poolConfig.setMaxWaitMillis((redisConfig.getMaxWait()));

JedisPool jedisPool = new JedisPool(poolConfig, redisConfig.getHost(),redisConfig.getPort(),redisConfig.getTimeout());
return jedisPool;
}

}

这里解释下maxWait和timeout这两个数据类型的问题。首先

1
poolConfig.setMaxWaitMillis((redisConfig.getMaxWait()));

用到了maxWait这个变量,看一下setMaxWaitMillis()函数的源码

1
2
3
public void setMaxWaitMillis(long maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}

maxWaitMillis这个变量是long类型的,所以执勤才那么定义。

同理

1
JedisPool jedisPool = new JedisPool(poolConfig, redisConfig.getHost(),redisConfig.getPort(),redisConfig.getTimeout());

这里面有用到redisConfig.getTimeout(),看一下JedisPool是怎么初始化的

1
2
3
public JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout) {
this(poolConfig, host, port, timeout, (String)null, 0, (String)null);
}

可以看到timeout是int类型的,你要一开始定义成Duration类型的,然后可能过程中会涉及到数据类型的强制准换,会不会报错不知道,有兴趣的可以试一下。

最后就是初始化redis的方法如get,set等等

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
package com.springboot.SecKill.redis;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;



/**
* @author WilsonSong
* @date 2018/8/1
*/
@Service
public class RedisService {
private static final Logger logger = LoggerFactory.getLogger(RedisService.class);

@Autowired
JedisPool jedisPool;


public <T> T get(KeyPrefix prefix, String key, Class<T> clazz){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
//生成real key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = String2Bean(str, clazz);
return t;
}catch (Exception e){
logger.error("redis连接池异常"+e.getMessage());
return null;
}finally {
if (jedis != null){
jedis.close();
}
}
}

public <T> boolean set(KeyPrefix prefix,String key, T value){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
String value_new = Bean2String(value);
if (value_new == null || value_new.length() <0){
return false;
}

//生成real key
String realKey = prefix.getPrefix() + key;
//过期时间
int seconds = prefix.expireSeconds();
if (seconds <= 0){
jedis.set(realKey, value_new);
}else {
jedis.setex(realKey,seconds,value_new);
}

return true;
}catch (Exception e){
logger.error("redis连接池异常"+e.getMessage());
return false;
}finally {
if (jedis != null){
jedis.close();
}
}
}

//key 是否存在
public <T> Boolean exists(KeyPrefix prefix, String key){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
//生成real key
String realKey = prefix.getPrefix() + key;
return jedis.exists(realKey);

}catch (Exception e){
logger.error("redis连接池异常"+e.getMessage());
return null;
}finally {
if (jedis != null){
jedis.close();
}
}
}

//增加key对应的值
public <T> Long incr(KeyPrefix prefix, String key){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
//生成real key
String realKey = prefix.getPrefix() + key;
return jedis.incr(realKey);
}catch (Exception e){
logger.error("redis连接池异常"+e.getMessage());
return null;
}finally {
if (jedis != null){
jedis.close();
}
}
}

//减少key对应的对象的值
public <T> Long decr(KeyPrefix prefix, String key){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
//生成real key
String realKey = prefix.getPrefix() + key;
return jedis.decr(realKey);
}catch (Exception e){
logger.error("redis连接池异常"+e.getMessage());
return null;
}finally {
if (jedis != null){
jedis.close();
}
}
}

//bean对象准换为String
private <T> String Bean2String(T value) {
if (value == null){
return null;
}
Class<?> clazz = value.getClass();
if (clazz == int.class || clazz == Integer.class){
return ""+value;
}else if (clazz == String.class){
return (String)value;
}else if (clazz == long.class || clazz == Long.class){
return ""+value;
}else {
return JSON.toJSONString(value);
}

}

//String转换为bean
private <T> T String2Bean(String str, Class<T> clazz) {
if (str == null || str.length() <0 || clazz == null){
return null;
}

if (clazz == int.class || clazz == Integer.class){
return (T)Integer.valueOf(str);
}else if (clazz == String.class){
return (T)str;
}else if (clazz == long.class || clazz == Long.class){
return (T)Long.valueOf(str);
}else {
return JSON.toJavaObject(JSON.parseObject(str),clazz);
}
}
}

最后在controller中编写一个方法实现对service层的调用即可,就不赘述了。

踩过的坑

运行程序发现报出异常

Could not get a resource from the pool。

其实也就是在执行

jedis = jedisPool.getResource();这一步时出错了

  1. 首先第一种可能是你的redis服务没开启,检查下。
  2. 确认开启了之后就修改你的redis.conf文件,把bind 127.0.0.1改成bind 0.0.0.0,

​ 也就是把redis的访问权限有只能本机访问改成所有的都能访问。

  1. 最后可能的原因有你的Linux的防火墙没有开放Redis的端口,可以配置打开,也可以直接关掉防火墙,我这里直接关掉了Linux的防火墙。

我用的是centos7

永久关闭防火墙的方法

1
2
3
4
//临时关闭
systemctl stop firewalld
//禁止开机启动
systemctl disable firewalld

这样你再测试下,要是还不能用还有别的原因,可以自己上网去看一下具体怎么解决。

本文标题:SpringBoot2.0集成Redis详解及踩过的坑(Could not get a resource from the pool)

文章作者:WilsonSong

发布时间:2018年08月02日 - 09:08

最后更新:2018年08月16日 - 20:08

原始链接:https://songwell1024.github.io/2018/08/02/Redis/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------