微服务: SpringBoot 集成 Redis

简介

忙碌了一周,感觉只有此刻写文字的时间才是最惬意的。最近工作比较忙,文章更新的较慢还望大家多多见谅🙇‍!

在阅读下面的内容之前,希望你已经安装并且做好了学习 Spring Boot 集成 Redis 的准备了。

如果你还没有搭建 Redis 环境,Win10-安装-Redis 这篇文章或许可以帮到您。

Spring Boot 1.x 版本中默认的 Redis 客户端是 Jedis 实现的,Spring Boot 2.x 版本中默认客户端是用 Lettuce 实现的。可以从加入的依赖包中看出,Spring Boot 2.2.x 中的 spring-data-redis 仍旧包括了 Jedis 和 Lettuce,但是默认使用了 Lettuce(换句话说,如果你不想使用默认的 Lettuce 实现可以换成 Jedis 的实现),如下图:

Lettuce 和 Jedis 的都是连接 Redis Server 的客户端,简单异同点如下:

  • Jedis 在实现上是直连 Redis Server,多线程环境下非线程安全,除非使用连接池,为每个 Redis 实例增加物理连接;
  • Lettuce 是 一种可伸缩,线程安全,完全非阻塞的 Redis 客户端,多个线程可以共享一个 RedisConnection,它利用 Netty NIO 框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序;

本篇只分享在 Spring Boot 项目中如何集成 Jedis 实现的 Redis 客户端和简单使用,关于 Redis 的使用有很多应用场景,后续再做探讨和分享(Redis 确实很强大,值得我们深入学习和研究)。

加入依赖包

pom.xml 文件中添加 redis 的依赖,如下:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

运行项目后,会报如下错误:

1
2
3
4
5
6
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'redisConnectionFactory' defined in class path resource
[org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.class]: Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory]:
Factory method 'redisConnectionFactory' threw exception;
nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig

此时,需要在你的 pom 文件中添加连接池 commons-pool2 的依赖。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

Lettuce 需要依赖 Apache的 common-pool2(至少是2.2版本)提供连接池,本篇使用的版本是 2.6.0,具体可以参考 Connection-Pooling 的介绍。

配置 properties

配置 application.properties 文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ------------------------------------------
# 配置 redis for lettuce
# ------------------------------------------
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1

注意:因为我的工程中使用了多个 properties 文件用于区分不同的环境,所以你根据自己的配置文件来配置 Redis 即可。

真的是 Lettuce 嘛?

为了证明我们现在使用的是 Lettuce 实现的 Redis 客户端,我写了一段测试代码,放到登录的 MSSigninController 进行了测试(具体代码可以参考 GitHub),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@GetMapping(value = "/redisconn")
public String redis() {
log.info(redisTemplate.getValueSerializer().toString() + ", " + redisTemplate.getHashValueSerializer().toString());
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
log.info(connectionFactory.toString());
if (connectionFactory instanceof LettuceConnectionFactory) {
LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) connectionFactory;
log.info(lettuceConnectionFactory.getHostName() + ", " + lettuceConnectionFactory.getPort());
}
return connectionFactory.toString();
}

运行项目,打开 http://localhost:8080/signin/redisconn 即可看到浏览器上面显示类似如下的输出信息。

1
org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@5e91612a

同时,控制台输出如下内容:

1
2
3
4
org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@7fd8e94e,
org.springframework.data.redis.serializer.JdkSerializationRedisSerializer@7fd8e94e
org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@34212775
127.0.0.1, 6379

从上面的信息可以看出:

  • Redis 的客户端实现默认使用的是 Lettuce;
  • 默认的序列化类是 JdkSerializationRedisSerializer,下面我们可以配置 RedisTemplate 来改变默认的序列化类;

配置 RedisTemplate

关于 RedisTemplate 的配置,配置的实例代码都在 MSRedisConfig 这个类中,这里不再粘贴代码,有需要的可以直接点击 GitHub 查看。

配置完成后重新运行工程,再次访问 http://localhost:8080/signin/redisconn,控制台输出如下内容:

1
2
3
4
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer@3151bece,
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer@7248212a
org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@6bcda66f
127.0.0.1, 6379

可见,我们已经成功改变了默认的序列化类。

实例

这次的例子,仍是在原来的工程 微服务-结合MySQL实现登录注册 的基础上面改造的,后续分享的内容基本都是在这个工程上面拓展。

用户使用用户名和密码登录,首先去 Redis 里面查找,查找到了直接返回不用去 MySQL 数据库中查找了,如果没有找到再去 MySQL 数据库中查找,查找成功后存储到 Redis 中,本次分享只是为了说明如何集成 Redis以及其简单使用,流程比较简单没有考虑其他因素和优化,仅供学习使用。

修改的逻辑部分,代码贴在下面,可以结合注释看一下很简单。

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
@CrossOrigin(origins = {"*", "http://localhost:63344"})
@RequestMapping(value = "/name", method = RequestMethod.GET)
@ApiOperation(value = "用户名登录", httpMethod = "GET", notes = "用户名登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", required = true),
@ApiImplicitParam(name = "userpwd", value = "密码", required = true)
})
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
MSResponse response = new MSResponse();
MSUser user = null;
if (null == userName || null == userPwd || userName.length() <= 0 || userPwd.length() <= 0) {
MSUserResponseEnum responseEnum = MSUserResponseEnum.Login4SiginInvalidInfo;
response.setCode(responseEnum.getCode());
response.setMsg(responseEnum.getMsg());
} else {
/** 从Redis里面查找该用户 */
MSUser redisUser = (MSUser) redisTemplate.opsForValue().get(userName);
String query_user_pwd = "";
if (null != redisUser && userPwd.equals(redisUser.getAccountName())) { // Redis 里面有该用户信息
log.info("Redis 中找到了 " + userName);
MSUserResponseEnum rspEnum = MSUserResponseEnum.SUCCESS;
user = redisUser;
response.setCode(rspEnum.getCode());
response.setMsg(rspEnum.getMsg());
} else { // Redis 里面没有该用户信息
log.info("Redis 中没有找到 " + userName);
/** 查数据库的‘user’表中是否有该用户?*/
List<Map> query_users = userService.queryUserByUserName(userName);
if (query_users.isEmpty()) {// 没有该用户
MSUserResponseEnum responseEnum = MSUserResponseEnum.LoginNoSuchUser;
response.setCode(responseEnum.getCode());
response.setMsg(responseEnum.getMsg());
} else {// 有这个用户
Map user_map = query_users.get(0);
query_user_pwd = (String) user_map.get("accountPwd");
if (!query_user_pwd.equals(userPwd)) {
MSUserResponseEnum responseEnum = MSUserResponseEnum.LoginUserPwdError;
response.setCode(responseEnum.getCode());
response.setMsg(responseEnum.getMsg());
} else {
// 将查询出来的map对象使用FastJson转换为MSUser对象
user = JSON.parseObject(JSON.toJSONString(user_map), MSUser.class);
// 缓存到 Redis,使用userName作为key
String userNameKey = String.valueOf(user.getAccountName());
redisTemplate.opsForValue().set(userNameKey, user);
log.info("MySQL 中找到了 " + userName + ", 并存到 Redis 中");
MSUserResponseEnum rspEnum = MSUserResponseEnum.SUCCESS;
response.setCode(rspEnum.getCode());
response.setMsg(rspEnum.getMsg());
}
}
}
}
response.setResults(user);
return response;
}

连续两次登录请求 http://localhost:8080/signin/name?username=foobar&userpwd=foobar ,可以看到如下输出:

1
2
3
4
5
6
Redis 中没有找到 foobar
MySQL 中找到了 foobar, 并存到 Redis 中
... 省略其他日志
Redis 中找到了 foobar

打开 Redis 的 CLI,可以查询一下是否存储成功,

1
127.0.0.1:6379> get foobar

得到结果如下:

1
"{\"@class\":\"com.veryitman.user.model.MSUser\",\"userID\":1723068547,\"accountName\":\"foobar\",\"accountPwd\":\"foobar\",\"nickName\":\"foobar\",\"age\":20,\"gender\":1,\"motto\":\"\",\"phone\":\"\"}"

参考资料


适当的赞美可以让对方做出好的改变~

坚持原创技术分享!