微服务: 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 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 spring.redis.lettuce.pool.max-idle =8 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 { MSUser redisUser = (MSUser) redisTemplate.opsForValue().get(userName); String query_user_pwd = "" ; if (null != redisUser && userPwd.equals(redisUser.getAccountName())) { log.info("Redis 中找到了 " + userName); MSUserResponseEnum rspEnum = MSUserResponseEnum.SUCCESS; user = redisUser; response.setCode(rspEnum.getCode()); response.setMsg(rspEnum.getMsg()); } else { log.info("Redis 中没有找到 " + userName); 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 { user = JSON.parseObject(JSON.toJSONString(user_map), MSUser.class); 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\":\"\"}"
参考资料
适当的赞美可以让对方做出好的改变~