第07课:WebFlux 中 Redis 实现缓存

前言

首先,补充下上一篇的内容,RedisTemplate 实现操作 Redis,但操作是同步的,不是 Reactive 的。自然,支持 Reactive 的操作类为 ReactiveRedisTemplate,下面我们写个小案例。

ReactiveRedisTemplate

在上一篇工程中,新建 CityWebFluxReactiveController 类,路由为 /city2 开头。

@RestController
@RequestMapping(value = "/city2")
public class CityWebFluxReactiveController { @Autowired
private ReactiveRedisTemplate reactiveRedisTemplate; @GetMapping(value = "/{id}")
public Mono<City> findCityById(@PathVariable("id") Long id) {
String key = "city_" + id;
ReactiveValueOperations<String, City> operations = reactiveRedisTemplate.opsForValue();
Mono<City> city = operations.get(key);
return city;
} @PostMapping
public Mono<City> saveCity(@RequestBody City city) {
String key = "city_" + city.getId();
ReactiveValueOperations<String, City> operations = reactiveRedisTemplate.opsForValue();
return operations.getAndSet(key, city);
} @DeleteMapping(value = "/{id}")
public Mono<Long> deleteCity(@PathVariable("id") Long id) {
String key = "city_" + id;
return reactiveRedisTemplate.delete(key);
}
}
  • 写法和以前保持一致,@Autowired 注入 ReactiveRedisTemplate 对象。
  • ReactiveValueOperations 是 String(或 value)的操作视图,操作视图还有 ReactiveHashOperations、ReactiveListOperations、ReactiveSetOperations 和 ReactiveZSetOperations 等。
  • 不一样的是,操作视图 set 方法是操作 City 对象,但可以 get 回 Mono 或者 Flux 对象。

结构

回到这个工程,新建一个工程编写整合 Redis 实现缓存案例,工程如图:

目录核心如下:

  • pom.xml maven 配置
  • application.properties 配置文件
  • domain 实体类
  • dao mongodb数据操作层
  • handler 业务层,本文要点
  • controller 控制层

单击这里查看源代码

控制层 CityWebFluxController

代码如下:

@RestController
@RequestMapping(value = "/city")
public class CityWebFluxController { @Autowired
private CityHandler cityHandler; @GetMapping(value = "/{id}")
public Mono<City> findCityById(@PathVariable("id") Long id) {
return cityHandler.findCityById(id);
} @GetMapping()
public Flux<City> findAllCity() {
return cityHandler.findAllCity();
} @PostMapping()
public Mono<City> saveCity(@RequestBody City city) {
return cityHandler.save(city);
} @PutMapping()
public Mono<City> modifyCity(@RequestBody City city) {
return cityHandler.modifyCity(city);
} @DeleteMapping(value = "/{id}")
public Mono<Long> deleteCity(@PathVariable("id") Long id) {
return cityHandler.deleteCity(id);
}
}

CityHandler 业务层

目前,@Cacheable 等注解形式实现缓存没有很好的集成,二者 Mono / Flux 对象没有实现 Serializable,无法通过默认序列化器,解决方式是需要自定义序列化,这里通过手动方式与 Redis 手动集成,并实现缓存策略。

参考《缓存更新的套路》,缓存更新的模式有四种:Cache aside、Read through、Write through、Write behind caching。

这里使用的是 Cache Aside 策略,从三个维度(摘自耗子叔叔博客):

  • 失效:应用程序先从 Cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从 Cache 中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

代码如下:

@Component
public class CityHandler { private static final Logger LOGGER = LoggerFactory.getLogger(CityHandler.class); @Autowired
private RedisTemplate redisTemplate; private final CityRepository cityRepository; @Autowired
public CityHandler(CityRepository cityRepository) {
this.cityRepository = cityRepository;
} public Mono<City> save(City city) {
return cityRepository.save(city);
} public Mono<City> findCityById(Long id) { // 从缓存中获取城市信息
String key = "city_" + id;
ValueOperations<String, City> operations = redisTemplate.opsForValue(); // 缓存存在
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
City city = operations.get(key); LOGGER.info("CityHandler.findCityById() : 从缓存中获取了城市 >> " + city.toString());
return Mono.create(cityMonoSink -> cityMonoSink.success(city));
} // 从 MongoDB 中获取城市信息
Mono<City> cityMono = cityRepository.findById(id); if (cityMono == null)
return cityMono; // 插入缓存
cityMono.subscribe(cityObj -> {
operations.set(key, cityObj);
LOGGER.info("CityHandler.findCityById() : 城市插入缓存 >> " + cityObj.toString());
}); return cityMono;
} public Flux<City> findAllCity() {
return cityRepository.findAll().cache();
} public Mono<City> modifyCity(City city) { Mono<City> cityMono = cityRepository.save(city); // 缓存存在,删除缓存
String key = "city_" + city.getId();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key); LOGGER.info("CityHandler.modifyCity() : 从缓存中删除城市 ID >> " + city.getId());
} return cityMono;
} public Mono<Long> deleteCity(Long id) { cityRepository.deleteById(id); // 缓存存在,删除缓存
String key = "city_" + id;
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key); LOGGER.info("CityHandler.deleteCity() : 从缓存中删除城市 ID >> " + id);
} return Mono.create(cityMonoSink -> cityMonoSink.success(id));
}
}

首先这里注入了 RedisTemplate 对象,联想到 Spring 的 JdbcTemplate ,RedisTemplate 封装了 RedisConnection,具有连接管理,序列化和 Redis 操作等功能,还有针对 String 的支持对象 StringRedisTemplate。

回到更新缓存的逻辑。

a. findCityById 获取城市逻辑:

  • 如果缓存存在,从缓存中获取城市信息;
  • 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存。

b. deleteCity 删除 / modifyCity 更新城市逻辑:

  • 如果缓存存在,删除;
  • 如果缓存不存在,不操作。

运行工程

一个操作 Redis 工程就开发完毕了,下面运行工程验证下。使用 IDEA 右侧工具栏,点击 Maven Project Tab,点击使用下 Maven 插件的 install 命令;或者使用命令行的形式,在工程根目录下,执行 Maven 清理和安装工程的指令:

cd springboot-webflux-7-redis-cache
mvn clean install

在控制台中看到成功的输出:

... 省略
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:30 min
[INFO] Finished at: 2018-10-15T10:00:54+08:00
[INFO] Final Memory: 31M/174M
[INFO] ------------------------------------------------------------------------

在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式,可以在控制台看到成功运行的输出:

... 省略
2018-04-10 08:43:39.932 INFO 2052 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-04-10 08:43:39.935 INFO 2052 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-04-10 08:43:39.960 INFO 2052 --- [ main] org.spring.springboot.Application : Started Application in 6.547 seconds (JVM running for 9.851)

打开 POST MAN 工具,开发必备。进行下面操作:

新增城市信息 POST http://127.0.0.1:8080/city

获取城市信息 GET http://127.0.0.1:8080/city/2

再请求一次,获取城市信息会发现数据获取的耗时快了很多,服务端 Console 输出的日志:

2017-04-13 18:29:00.273  INFO 13038 --- [nio-8080-exec-1] findCityById() : 城市插入缓存 >> City{id=12, provinceId=3, cityName='三亚', description='水好,天蓝'}
2017-04-13 18:29:03.145 INFO 13038 --- [nio-8080-exec-2] findCityById() : 从缓存中获取了城市 >> City{id=12, provinceId=3, cityName='三亚', description='水好,天蓝'}

可见,第一次是从数据库 MongoDB 获取数据,并插入缓存,第二次直接从缓存中取。

更新 / 删除城市信息,这两种操作中,如果缓存有对应的数据,则删除缓存。服务端 Console 输出的日志:

2017-04-13 18:29:52.248  INFO 13038 --- [nio-8080-exec-9] deleteCity() : 从缓存中删除城市 ID >> 12

总结

这一讲,主要补充了 Redis 对响应式的支持操作,以及缓存更新策略及实际应用小例子。

单击这里查看源代码

Spring Boot WebFlux-07——WebFlux 中 Redis 实现缓存的更多相关文章

  1. spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战

    SpringBoot + Redis +SpringSession 缓存之实战 前言 前几天,从师兄那儿了解到EhCache是进程内的缓存框架,虽然它已经提供了集群环境下的缓存同步策略,这种同步仍然需 ...

  2. Spring Boot (五): Redis缓存使用姿势盘点

    1. Redis 简介 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化 ...

  3. Spring Boot 监听 Activemq 中的特定 topic ,并将数据通过 RabbitMq 发布出去

    1.Spring Boot 和 ActiveMQ .RabbitMQ 简介 最近因为公司的项目需要用到 Spring Boot , 所以自学了一下, 发现它与 Spring 相比,最大的优点就是减少了 ...

  4. spring boot 项目从配置文件中读取maven 的pom.xml 文件标签的内容。

    需求: 将pom.xml 文件中的版本号读取到配置文件并打印到日志中. 第一步: 在pom.xml 中添加以下标签. 第二步: 将version 标签的值读取到配置文件中 这里使用 @@  而不是  ...

  5. Spring Boot 2.x基础教程:EhCache缓存的使用

    上一篇我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问.可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢? 在Spring Boot中通过@EnableCachi ...

  6. Spring Boot 2.0 WebFlux 教程 (一) | 入门篇

    目录 一.什么是 Spring WebFlux 二.WebFlux 的优势&提升性能? 三.WebFlux 应用场景 四.选 WebFlux 还是 Spring MVC? 五.异同点 六.简单 ...

  7. 实战基于Spring Boot 2的WebFlux和mLab搭建反应式Web

    Spring Framework 5带来了新的Reactive Stack非阻塞式Web框架:Spring WebFlux.作为与Spring MVC并行使用的Web框架,Spring WebFlux ...

  8. Spring Boot 1.5.4集成Redis

    本文示例源码,请看这里: 如何安装与配置Redis,请看这里 首先添加起步依赖: <dependency> <groupId>org.springframework.boot& ...

  9. Spring Boot 2 实践记录之 Redis 及 Session Redis 配置

    先说 Redis 的配置,在一些网上资料中,Spring Boot 的 Redis 除了添加依赖外,还要使用 XML 或 Java 配置文件做些配置,不过经过实践并不需要. 先在 pom 文件中添加 ...

随机推荐

  1. android手势识别

    一.简单手势识别: 简单手势识别分为以下几个步骤: 1.手指触碰屏幕时,触发MotionEvent事件: 2.该事件被OnTouchListener监听,可在它的onTouch()方法中获得该Moti ...

  2. Spring随堂笔记

    Spring 1.IoC控制反转 IoC特点不用new 就可以初始化类: 控制反转的英文名叫 Ioc(Inversion of Control) ,依赖注入英文名叫DI(Dependency Inje ...

  3. Day015 异常处理机制

    异常处理机制 抛出异常 捕获异常 异常处理的五个关键字 try:监控一个代码块,有异常就能通过catch捕获 catch(想要捕获的异常类型):捕获想要捕获的异常,catch代码块的代码只有在捕获到异 ...

  4. Codeforces Round #712 (Div. 2)

    A. Déjà Vu 题意:就是问能否加上字母a,使得字符串不中心对称 思路:只有一种情况不能加入,就是全部是a,剩下的都可以满足,找a的位置就找哪个字母不是a,然后让它的对称位置是新加的这个a 代码 ...

  5. BUA软件工程个人博客作业

    写在前面 项目 内容 所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航) 作业要求 个人博客作业 课程目标 培养软件开发能力 本作业对实现目标的具体作用 阅读教材,了解软件工程,并比较各 ...

  6. Jmeter(一) - 从入门到精通 - 环境搭建(详解教程)

    1.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静态和动态资源的性能,例如:静态文件, ...

  7. Azure DevOps(一)利用Azure DevOps Pipeline 构建应用程序镜像到AWS ECR

    一,引言 最近项目上让开始学习AWS,作为一名合格的开发人员,当然也是学会利用Azure DevOps Pipeline 将应用程序部署到 AWS ECS(完全托管的容器编排服务).我们要学会将应用程 ...

  8. 使用CSS样式的三种方法

    一.内联样式 内联样式通过style属性来设置,属性值可以任意的CSS样式. 1 <!DOCTYPE html> 2 <html lang="en"> 3 ...

  9. awk sed grep 常用命令

    文本间隔 在每一行后面增加一空行: sed G sed G v1 >v2 在每一行后面增加两行空行: sed 'G;G' 将第一个脚本所产生的所有空行删除(即删除所有偶数行): sed 'n;d ...

  10. 关于typedef的用法总结-(转自Bigcoder)

    不管实在C还是C++代码中,typedef这个词都不少见,当然出现频率较高的还是在C代码中.typedef与#define有些相似,但更多的是不同,特别是在一些复杂的用法上,就完全不同了,看了网上一些 ...