Spring Boot WebFlux-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 实现缓存的更多相关文章
- spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战
		SpringBoot + Redis +SpringSession 缓存之实战 前言 前几天,从师兄那儿了解到EhCache是进程内的缓存框架,虽然它已经提供了集群环境下的缓存同步策略,这种同步仍然需 ... 
- Spring Boot (五): Redis缓存使用姿势盘点
		1. Redis 简介 Redis 是目前业界使用最广泛的内存数据存储.相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化 ... 
- Spring Boot 监听 Activemq 中的特定 topic ,并将数据通过 RabbitMq 发布出去
		1.Spring Boot 和 ActiveMQ .RabbitMQ 简介 最近因为公司的项目需要用到 Spring Boot , 所以自学了一下, 发现它与 Spring 相比,最大的优点就是减少了 ... 
- spring boot 项目从配置文件中读取maven 的pom.xml 文件标签的内容。
		需求: 将pom.xml 文件中的版本号读取到配置文件并打印到日志中. 第一步: 在pom.xml 中添加以下标签. 第二步: 将version 标签的值读取到配置文件中 这里使用 @@ 而不是 ... 
- Spring Boot 2.x基础教程:EhCache缓存的使用
		上一篇我们学会了如何使用Spring Boot使用进程内缓存在加速数据访问.可能大家会问,那我们在Spring Boot中到底使用了什么缓存呢? 在Spring Boot中通过@EnableCachi ... 
- Spring Boot 2.0 WebFlux 教程 (一) | 入门篇
		目录 一.什么是 Spring WebFlux 二.WebFlux 的优势&提升性能? 三.WebFlux 应用场景 四.选 WebFlux 还是 Spring MVC? 五.异同点 六.简单 ... 
- 实战基于Spring Boot 2的WebFlux和mLab搭建反应式Web
		Spring Framework 5带来了新的Reactive Stack非阻塞式Web框架:Spring WebFlux.作为与Spring MVC并行使用的Web框架,Spring WebFlux ... 
- Spring Boot 1.5.4集成Redis
		本文示例源码,请看这里: 如何安装与配置Redis,请看这里 首先添加起步依赖: <dependency> <groupId>org.springframework.boot& ... 
- Spring Boot 2 实践记录之 Redis 及 Session Redis 配置
		先说 Redis 的配置,在一些网上资料中,Spring Boot 的 Redis 除了添加依赖外,还要使用 XML 或 Java 配置文件做些配置,不过经过实践并不需要. 先在 pom 文件中添加 ... 
随机推荐
- Postman(接口自动化测试)
			1.Postman 接口测试参数化可能大家都非常的熟悉,但是很多人很难处理参数化后如何断言的问题,特别是当参数中出现中文时,很容易导致在 Runner 页面引入外部文件时导致中文乱码的问题,今天这篇文 ... 
- ffmpeg实践
			将mov视频解码300帧,并保存为1024:576分辨率,yuv420格式 ffmpeg -i Community_SneakAttack.mov -aspect 16:9 -vf scale=102 ... 
- 【BUAA软工】技术规格说明书
			项目 内容 班级:北航2020春软件工程 博客园班级博客 作业:技术规格说明书 技术规格说明书 宏观技术 后端 WEB服务器 WEB服务器选取的是Springboot,作为当下Java语言最主流的WE ... 
- 关于this的解析:看了就懂,忘记了随时回来看
			初识this this => 永远表示某个对象,但写在不同的地方,它表示不同的对象 全局下的this: window => 浏览器环境下的全局对象 console.log(this) =& ... 
- Flutter 2.2 现已发布!
			在本次 Google I/O 2021 大会 上,我们正式发布了 Flutter 2.2.Flutter 2.2 是我们最新版的开源工具包,可让开发者立足单个平台构建适合任何设备的精美应用.Flutt ... 
- Linux 忘记密码解决方法——RedHat
			[RedHat7.4版本] 1.将忘记密码的rhel7.4版本的虚拟机打开 2.等3秒左右出现这个画面时,用方向键,将光标移动到第二栏处,接着按"e"键 3.接在在linux16这 ... 
- 如果你想设置无人自动升级,我们推荐你将这个值修改为security,它会告诉 yum 仅仅升级修复安全问题的软件包。
			定期升级你的 CentOS 系统,是所有系统安全中最重要的措施之一.如果你不使用最新的安全补丁升级你的操作系统软件包,你将会让你的机器很容易被攻击. 如果你管理者多个 CentOS 机器,手动升级系统 ... 
- 运维常用shell脚本一(系统指标巡检、自动创建用户、跳板机)
			一.系统指标巡检脚本 #!/bin/bash menu(){ cat <<EOF +---------------------------------------------+ | 日常巡 ... 
- 危险!水很深,让叔来 —— 谈谈命令查询权责分离模式(CQRS)
			多年以前,那时我正年轻,做技术如鱼得水,甚至一度希望自己能当一辈子的一线程序员. 但是我又有两个小愿望想要达成:一个是想多挣点钱:另一个就是对项目的技术栈和架构选型能多有点主动权. 多挣点钱是因为当时 ... 
- DHCP与DHCP中继
			DHCP原理与配置 1. DHCP应用场景 2. DHCP报文类型 3. DHCP工作原理 4. IP地址获取与释放 5. DHCP中继配置 1. DHCP应用场景 在大型企业网络中,会有大量的主机或 ... 
