简介

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

在阅读下面的内容之前,希望你已经安装并且做好了学习 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 的实现),如下图:

boot redis客户端 spring spring boot redis lettuce_mysql


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

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

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

加入依赖包

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

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

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 的依赖。

<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 文件,如下:

# ------------------------------------------
# 配置 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),如下:

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 即可看到浏览器上面显示类似如下的输出信息。

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

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

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,控制台输出如下内容:

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以及其简单使用,流程比较简单没有考虑其他因素和优化,仅供学习使用。

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

@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 ,可以看到如下输出:

Redis 中没有找到 foobar
MySQL 中找到了 foobar, 并存到 Redis 中

... 省略其他日志

Redis 中找到了 foobar

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

127.0.0.1:6379> get foobar

得到结果如下:

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