一、简介

Redis是用C语言开发的一个高性能键值对数据库,可用于数据缓存,主要用于处理大量数据的高访问负载。

Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,spring-boot-starter-data-redis依赖于spring-data-redis 和 lettuce。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。

Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。

二、配置

依赖:

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

配置:

spring:
  redis:
    host: 127.0.0.1 # Redis服务器地址
    database: 0 # Redis数据库索引(默认为0)
    port: 6379 # Redis服务器连接端口
    password: # Redis服务器连接密码(默认为空)
    jedis:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接
    timeout: 3000ms # 连接超时时间(毫秒)

添加配置类:

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
}

我们使用了注解:@EnableCaching 来开启缓存。

三、创建Redis工具类

编写一个Redis工具类,终于添加、读取、修改、删除Redis缓存。

@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // 读取缓存
    public String get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 写入缓存
    public void set(final String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 更新缓存
    public void update(final String key, String value) {
        try {
            redisTemplate.opsForValue().getAndSet(key, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 删除缓存
    public void delete(final String key) {
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 判断某个键是否存在
    public boolean has(String key) {
        return redisTemplate.hasKey(key);
    }

    // 设置超期时间
    public boolean expire(String key, long expire) {
        return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }

    /**
     * 自增操作
     * @param delta 自增步长
     */
    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }
}

在单元测试中使用:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisTests {
    @Resource
    private RedisUtils redisUtils;

    // 插入缓存数据
    @Test
    public void set() {
        redisUtils.set("redis_key", "redis_vale");
    }

    // 读取缓存数据
    @Test
    public void get() {
        String value = redisUtils.get("redis_key");
        System.out.println(value);
    }

    // 更新缓存数据
    @Test
    public void update() {
        redisUtils.update("redis_key", "newValue");
    }

    // 删除缓存数据
    @Test
    public void delete() {
        redisUtils.delete("redis_key");
    }

    // 判断某个键是否存在
    @Test
    public void has() {
        Boolean value = redisUtils.has("redis_key");
        System.out.println(value);
    }
}

四、实例:自动根据方法生成缓存

以上是手动使用的方式,如何在查找数据库的时候自动使用缓存呢?可以加上@Cacheable注解;

@RestController
public class IndexController {
    @RequestMapping("/getUser")
    @Cacheable(value="user-key")
    public User getUser() {
        User user = new User("xiaoyu", 18, "aa123456");
        System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");
        return user;
    }
}

其中 value 的值就是缓存到 Redis 中的 key。

实体类User:

import lombok.*;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private String name;
    private int age;
    private String pass;

    @Override
    public String toString() {
        return ("name=" + this.name + ",age=" + this.age + ",pass=" + this.pass);
    }
}

注意到, User 实现了 Serializable 接口,也必须实现此接口,才能正常地存储到redis中并读取

这里是将整个方法的返回值存入了redis,所以只有第一次调用的时候回在控制台打印相应的文字,之后调用都不会打印,直接从redis中就取到了返回值。

我们可以尝试修改 new User() 中的参数,比如:

User user = new User("quanzaiyu", 20, "aa123456");

再次调用,发现结果仍然是 User(“xiaoyu”, 18, “aa123456”),说明redis并没有更新值,仍然是将之前存储的结果直接返回。

在RDM中可以看到,缓存已经存在:
Snipaste_2021-01-05_09-19-51.png

五、实例:发送验证码并存储到redis

在业务中,我们通常有使用手机号获取验证码登录的需求,验证码应该有个很短的失效时间(比如2min)。我们就可以将验证码绑定到手机号,存储到redis实现这一需求。

@Api(tags = "LoginController", description = "授权认证及登录")
@RestController
@RequestMapping(value="/login")
public class LoginController {
    @Resource
    private RedisUtils redisUtils;

    @GetMapping("/code")
    public Result code(@RequestParam String phone) {
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 6; i++) {
            sb.append(random.nextInt(10));
        }
        // 验证码绑定手机号并存储到redis
        redisUtils.set("portal:authCode:" + phone, sb.toString());
        // 设置失效时间为2min
        redisUtils.expire("portal:authCode:" + phone, 120);
        return Result.ok(sb.toString(), "获取验证码成功");
    }

    @GetMapping("/auth")
    public Result user(@RequestParam String phone, @RequestParam String code) {
        if (StringUtils.isEmpty(code)) {
            return Result.error("请输入验证码");
        }
        String realAuthCode = redisUtils.get("portal:authCode:" + phone);
        boolean result = code.equals(realAuthCode);
        if (result) {
            return Result.ok(null, "验证码校验成功");
        } else {
            return Result.error("验证码不正确");
        }
    }
}

这里只是通过随机生成6位数模拟验证码,实际开发中需要调用生成验证码的云服务实现发送验证码到指定手机。而授权接口真实环境中应该会返回一个token之类的验证信息或用户信息。

六、Session共享

分布式系统中,Session 共享有很多的解决方案,其中托管到缓存中应该是最常用的方案之一

引入依赖

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

Session 配置:

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
  • maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。

添加测试方法获取 sessionid

@RequestMapping("/uid")
String uid(HttpSession session) {
    UUID uid = (UUID) session.getAttribute("uid");
    if (uid == null) {
        uid = UUID.randomUUID();
    }
    session.setAttribute("uid", uid);
    return session.getId();
}

登录 Redis 输入 keys '*sessions*'

localhost:0>keys '*sessions*'
 1)  "spring:session:sessions:870c5229-e109-4569-933b-3be884de89c4"
 2)  "spring:session:sessions:expires:870c5229-e109-4569-933b-3be884de89c4"

870c5229-e109-4569-933b-3be884de89c4sessionId, 登录 http://localhost:8080/uid 发现会一致,就说明 Session 已经在 Redis 里面进行有效的管理了。

在多个项目中共享 Session

其实就是按照上面的步骤在另一个项目中再次配置一次,启动后自动就进行了 Session 共享。

参考资料