1. Redis 简介
Redis 是目前业界使用最广泛的内存数据存储. 相比 Memcached,Redis 支持更丰富的数据结构, 例如 hashes, lists, sets 等, 同时支持数据持久化. 除此之外, Redis 还提供一些类数据库的特性, 比如事务, HA, 主从库. 可以说 Redis 兼具了缓存系统和数据库的一些特性, 因此有着丰富的应用场景. 本文介绍 Redis 在 Spring Boot 中两个典型的应用场景.
2. Lettuce 简介
如果在 Java 应用中使用过 Redis 缓存, 那么对 Jedis 一定不陌生, Lettuce 和 Jedis 一样, 都是连接 Redis Server 的客户端程序. Jedis 在实现上是直连 Redis Server, 多线程环境下非线程安全, 除非使用连接池, 为每个 Jedis 实例增加物理连接. Lettuce 基于 Netty 的连接实例(StatefulRedisConnection), 可以在多个线程间并发访问, 且线程安全, 满足多线程环境下的并发访问, 同时它是可伸缩的设计, 一个连接实例不够的情况也可以按需增加连接实例.
3. Spring Boot 应用中使用方式
直接通过 RedisTemplate 来使用
使用 Spring Cache 集成 Redis
通过 Spring Session 做 Session 共享
4. 工程实战
4.1 工程依赖 pom.xml 如下:
代码清单: spring-boot-Redis/pom.xml
- ***
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-Redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-Redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
spring-boot-starter-data-Redis : 在 Spring Boot 2.x 后底层不再是使用 Jedis , 而是换成了 Lettuce , 如图:
commons-pool2 : 用作 Redis 连接池, 如不引入启动会报错.
spring-session-data-Redis : Spring Session 引入, 用作共享 Session .
4.2 配置文件 application.YAML
代码清单: spring-boot-Redis/src/main/resources/application.YAML
- ***
- server:
- port: 8080
- servlet:
- session:
- timeout: 30m
- spring:
- application:
- name: spring-boot-Redis
- cache:
- # 使用了 Spring Cache 后, 能指定 spring.cache.type 就手动指定一下, 虽然它会自动去适配已有 Cache 的依赖, 但先后顺序会对 Redis 使用有影响(JCache -> EhCache -> Redis -> Guava)
- type: Redis
- Redis:
- host: 192.168.0.128
- port: 6379
- password: 123456
- # 连接超时时间(ms)
- timeout: 10000
- # Redis 默认情况下有 16 个分片, 这里配置具体使用的分片, 默认是 0
- database: 0
- lettuce:
- pool:
- # 连接池最大连接数(使用负值表示没有限制) 默认 8
- max-active: 100
- # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
- max-wait: -1
- # 连接池中的最大空闲连接 默认 8
- max-idle: 8
- # 连接池中的最小空闲连接 默认 0
- min-idle: 0
这里的配置不多解释, 需要解释的已经标注注释.
4.3 RedisTemplate 使用方式
4.3.1 创建实体类 User.java
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/model/User.java
- ***
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class User implements Serializable {
- private static final long serialVersionUID = 662692455422902539L;
- private Long id;
- private String name;
- private int age;
- }
4.3.2 自定义 RedisTemplate
默认情况下的模板只能支持 RedisTemplate<String, String> , 也就是只能存入字符串, 这在开发中是不友好的, 所以自定义模板是很有必要的, 当自定义了模板又想使用 String 存储这时候就可以使用 StringRedisTemplate 的方式, 它们并不冲突, 添加配置类 RedisCacheConfig.java , 代码如下:
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/config/RedisCacheConfig.java
- ***
- @Configuration
- @AutoConfigureAfter(RedisAutoConfiguration.class)
- public class RedisCacheConfig {
- @Bean
- public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
- RedisTemplate<String, Serializable> template = new RedisTemplate<>();
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
- template.setConnectionFactory(redisConnectionFactory);
- return template;
- }
- }
4.3.3 测试接口 UserController.java
代码清单:
- ***
- @RestController
- @Slf4j
- public class UserController {
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- RedisTemplate<String, Serializable> redisCacheTemplate;
- @Autowired
- UserService userService;
- @GetMapping("/test")
- public void test() {
- stringRedisTemplate.opsForValue().set("geekdigging", "https://www.geekdigging.com/");
- log.info("当前获取对象:{}",stringRedisTemplate.opsForValue().get("geekdigging"));
- redisCacheTemplate.opsForValue().set("geekdigging.com", new User(1L, "geekdigging", 18));
- User user = (User) redisCacheTemplate.opsForValue().get("geekdigging.com");
- log.info("当前获取对象:{}", user);
- }
- }
4.3.4 测试
启动服务, 打开浏览器访问链接: http://localhost:8080/test , 查看控制台日志打印, 如下:
2019-09-24 23:49:30.191 INFO 19108 --- [nio-8080-exec-1] c.s.s.controller.UserController : 当前获取对象: https://www.geekdigging.com/
2019-09-24 23:49:30.243 INFO 19108 --- [nio-8080-exec-1] c.s.s.controller.UserController : 当前获取对象: User(id=1, name=geekdigging, age=18)
测试成功.
4.4 使用 Spring Cache 集成 Redis
4.4.1 Spring Cache 特点
Spring 3.1 引入了激动人心的基于注释 (annotation) 的缓存 (cache) 技术, 它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 Redis), 而是一个对缓存使用的抽象, 通过在既有代码中添加少量它定义的各种 annotation, 即能够达到缓存方法的返回对象的效果.
Spring Cache 具备相当的好的灵活性, 不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition, 还提供开箱即用的缓存临时存储方案, 也支持和主流的专业缓存例如 EHCache,Redis,Guava 的集成.
基于 annotation 即可使得现有代码支持缓存
开箱即用 Out-Of-The-Box, 不用安装和部署额外第三方组件即可使用缓存
支持 Spring Express Language, 能使用对象的任何属性或者方法来定义缓存的 key 和 condition
支持 AspectJ, 并通过其实现任何方法的缓存支持
支持自定义 key 和自定义缓存管理者, 具有相当的灵活性和扩展性
4.4.2 定义接口 UserService.java
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/service/UserService.java
- ***
- public interface UserService {
- User save(User user);
- User get(Long id);
- void delete(Long id);
- }
4.4.3 接口实现 UserServiceImpl.java
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/service/impl/UserServiceImpl.java
- ***
- @Service
- @Slf4j
- public class UserServiceImpl implements UserService {
- private static final Map<Long, User> USER_MAP = new HashMap<>();
- static {
- USER_MAP.put(1L, new User(1L, "geekdigging.com", 18));
- USER_MAP.put(2L, new User(2L, "geekdigging.com", 19));
- USER_MAP.put(3L, new User(3L, "geekdigging.com", 20));
- }
- @CachePut(value = "user", key = "#user.id")
- @Override
- public User save(User user) {
- USER_MAP.put(user.getId(), user);
- log.info("进入 save 方法, 当前存储对象:{}", user);
- return user;
- }
- @Cacheable(value = "user", key = "#id")
- @Override
- public User get(Long id) {
- log.info("进入 get 方法, 当前获取对象:{}", USER_MAP.get(id));
- return USER_MAP.get(id);
- }
- @CacheEvict(value = "user", key = "#id")
- @Override
- public void delete(Long id) {
- USER_MAP.remove(id);
- log.info("进入 delete 方法, 删除成功");
- }
- }
为了方便演示数据库操作, 直接定义了一个 Map<Long, User> USER_MAP , 这里的核心就是三个注解 @Cacheable , @CachePut , @CacheEvict .
4.4.3.1 @Cacheable
根据方法的请求参数对其结果进行缓存
key: 缓存的 key, 可以为空, 如果指定要按照 SpEL 表达式编写, 如果不指定, 则缺省按照方法的所有参数进行组合(如:
- @Cacheable(value="user",key="#userName")
- )
value: 缓存的名称, 必须指定至少一个(如:
@Cacheable(value="user")
或者
- @Cacheable(value={
- "user1","use2"
- })
- )
condition: 缓存的条件, 可以为空, 使用 SpEL 编写, 返回 true 或者 false, 只有为 true 才进行缓存(如:
- @Cacheable(value = "user", key = "#id",condition = "#id < 10")
- )
- 4.4.3.2 @CachePut
根据方法的请求参数对其结果进行缓存, 和 @Cacheable 不同的是, 它每次都会触发真实方法的调用
key: 同上
value: 同上
condition: 同上
4.4.3.3 @CachEvict
根据条件对缓存进行清空
key: 同上
value: 同上
condition: 同上
allEntries: 是否清空所有缓存内容, 缺省为 false, 如果指定为 true, 则方法调用后将立即清空所有缓存(如:
- @CacheEvict(value = "user", key = "#id", allEntries = true)
- )
beforeInvocation: 是否在方法执行前就清空, 缺省为 false, 如果指定为 true, 则在方法还没有执行的时候就清空缓存, 缺省情况下, 如果方法执行抛出异常, 则不会清空缓存(如:
- @CacheEvict(value = "user", key = "#id", beforeInvocation = true)
- )
4.4.4 启动主类
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/SpringBootRedisApplication.java
- ***
- @SpringBootApplication
- @EnableCaching
- public class SpringBootRedisApplication {
- public static void main(String[] args) {
- SpringApplication.run(SpringBootRedisApplication.class, args);
- }
- }
这里需增加注解 @EnableCaching 开启 Spring Session.
4.4.5 增加测试接口
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/controller/UserController.java
- ***
- @GetMapping("/test1")
- public void test1() {
- User user = userService.save(new User(4L, "geekdigging.com", 35));
- log.info("当前 save 对象:{}", user);
- user = userService.get(1L);
- log.info("当前 get 对象:{}", user);
- userService.delete(5L);
- }
4.4.6 测试
启动服务, 打开浏览器访问链接: http://localhost:8080/test , 刷新页面, 控制台日志打印如下:
2019-09-25 00:07:21.887 INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl : 进入 save 方法, 当前存储对象: User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:07:21.897 INFO 21484 --- [nio-8080-exec-1] c.s.s.controller.UserController : 当前 save 对象: User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:07:21.899 INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl : 进入 get 方法, 当前获取对象: User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:07:21.900 INFO 21484 --- [nio-8080-exec-1] c.s.s.controller.UserController : 当前 get 对象: User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:07:21.901 INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl : 进入 delete 方法, 删除成功
再次刷新页面, 查看控制台日志:
2019-09-25 00:08:54.076 INFO 21484 --- [nio-8080-exec-7] c.s.s.service.impl.UserServiceImpl : 进入 save 方法, 当前存储对象: User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:08:54.077 INFO 21484 --- [nio-8080-exec-7] c.s.s.controller.UserController : 当前 save 对象: User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:08:54.079 INFO 21484 --- [nio-8080-exec-7] c.s.s.controller.UserController : 当前 get 对象: User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:08:54.079 INFO 21484 --- [nio-8080-exec-7] c.s.s.service.impl.UserServiceImpl : 进入 delete 方法, 删除成功
结果和我们期望的一致, 可以看到增删改查中, 查询是没有日志输出的, 因为它直接从缓存中获取的数据, 而添加, 修改, 删除都是会进入 UserServiceImpl 的方法内执行具体的业务代码.
4.5 Session 共享
4.5.1 Spring Session 简介
Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案. Spring Session 提供了集群 Session(Clustered Sessions)功能, 默认采用外置的 Redis 来存储 Session 数据, 以此来解决 Session 共享的问题.
4.5.2 启动主类 SpringBootRedisApplication.java
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/SpringBootRedisApplication.java
- ***
- @SpringBootApplication
- @EnableCaching
- @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
- public class SpringBootRedisApplication {
- public static void main(String[] args) {
- SpringApplication.run(SpringBootRedisApplication.class, args);
- }
- }
maxInactiveIntervalInSeconds: 设置 Session 失效时间, 使用 Spring Session 之后, 原 Spring Boot 配置文件 application.YAML 中的
server.session.timeout
属性不再生效.
4.5.3 增加测试接口
代码清单: spring-boot-Redis/src/main/java/com/springboot/springbootredis/controller/UserController.java
- ***
- @GetMapping("/getBlogUrl")
- public String getSessionId(HttpServletRequest request) {
- String url = (String) request.getSession().getAttribute("url");
- if (StringUtils.isEmpty(url)) {
- request.getSession().setAttribute("url", "https://www.geekdigging.com/");
- }
- log.info("获取 session 内容为: {}", request.getSession().getAttribute("url"));
- return request.getRequestedSessionId();
- }
4.5.4 测试
启动服务, 打开浏览器访问链接: http://localhost:8080/getBlogUrl , 查看 Redis 当前存储内容, 如下图:
其中 1569339180000 为失效时间, 意思是这个时间后 Session 失效, b2522824-1094-478e-a435-554a551bc8bb 为 SessionId .
4.5.6 如何在多台服务中共享 Session
按照上面的步骤在另一个项目中再次配置一次, 启动后自动就进行了 Session 共享.
5. 示例代码
示例代码 - GitHub
示例代码 - Gitee
6. 参考
- http://emacoo.cn/backend/spring-redis/
- https://blog.battcn.com/2018/05/11/springboot/v2-nosql-redis/
来源: https://www.cnblogs.com/babycomeon/p/11595609.html