Spring Boot WebFlux-07——WebFlux 中 Redis 實現緩存


第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 實現緩存案例,工程如圖:

file

目錄核心如下:

  • 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

file

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

file

再請求一次,獲取城市信息會發現數據獲取的耗時快了很多,服務端 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 對響應式的支持操作,以及緩存更新策略及實際應用小例子。

單擊這里查看源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM