Spring Boot EP 17:使用Redis記憶體資料庫實現快取機制
為什麼需要使用快取機制
官方網站:https://redis.io/
資料庫存放了應用系統必須要的資料,當資料越來越多、存取需求越來越大,而資料又放在相對效率較低的磁碟上,資料庫就會顯得疲態,無法滿足高I/O的需求。使用Redis作為快取,最大的好處在於速度,使用記憶體作為資料存放的載體,可想而知,速度就是比較快,再者,不是永遠都要把資料回寫至資料庫,當減少對資料庫的存取次數,也對整個系統環境有正向的影響。
從下圖來看,應用系統會先讀取Redis內的快取資料,若發現沒有想要的資料,就直接去資料庫撈取,除了應用系統使用外,也將這些資料回寫至Redis,當下次還需要的時候,就有這些資料,不需要去資料庫讀取。
實作
本篇將以讀取一段時間內的個股收盤資料為例,其流程如下圖:
步驟1:在pom.xml加入spring-boot-starter-data-redis-reactive套件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
步驟2:於application.properties增加連接Redis伺服器的設定。
# Redis資料庫索引(預設為0)
spring.redis.database=0
# Redis伺服器地址
spring.redis.host=localhost
# Redis伺服器連接端口
spring.redis.port=6379
# Redis伺服器連接密碼(預設為空)
spring.redis.password=
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閒連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閒連接
spring.redis.jedis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=1000
步驟3:在”Repositories.StockRepository.java”加入查詢股價的SQL語法。
// 從資料庫查詢收盤價,條件為股票代碼及日期
@Query(value = "SELECT CLOSE_PRICE FROM STOCK_MARKET.STOCKS_PRICE WHERE STOCK_ID=:STOCK_ID AND DATE_OF_TRADING=:DATE_OF_TRADING", nativeQuery = true)
public double getStockClosePrice(@Param("STOCK_ID") String STOCK_ID, @Param("DATE_OF_TRADING") String DATE_OF_TRADING);
步驟4:在”Controllers.StockController.java”這個控制器中加入查詢資料庫的函數與Restful API方法。
package stockmarket.jovepater.com.stockmarket.Controllers;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import stockmarket.jovepater.com.stockmarket.Classes.RspBody;
import stockmarket.jovepater.com.stockmarket.Repositories.StockRepository;
@RestController
public class StockController implements Serializable {
@Autowired
StockRepository stockRepository;
@GetMapping("stocks")
public RspBody getAllStocks() {
return new RspBody("0000", "Success", stockRepository.findAllDetails());
}
@Autowired
// 注入StringRedisTemplate類別,用來操作Redis
StringRedisTemplate stringRedisTemplate;
// 定義為Get API,並在URI帶入股票代碼、日期
@GetMapping("stock/price/{id}/{date}")
// 定義客戶端傳入股票代碼、日期
public RspBody getStockPrice(@PathVariable("id") String strId, @PathVariable("date") String strDate)
throws ParseException {
// 宣告一個存放收盤價的變數,預設為0
double doubleStockClosePrice = 0;
// 將傳入的日期(yyyyMMdd)字串轉型為Date
Date dateDate = new SimpleDateFormat("yyyyMMdd").parse(strDate);
// 從Redis查詢收盤價,並將獲得的資料存放於objStockClosePrice
Object objStockClosePrice = stringRedisTemplate.opsForHash().get(strId, strDate);
// 若objStockClosePrice不為null,表示已經從Redis取得收盤價資料
if (objStockClosePrice != null) {
// 將收盤價原本的object型態轉為double
doubleStockClosePrice = Double.valueOf(objStockClosePrice.toString());
} else {
// 若objStockClosePrice為null,表示沒有從Redis取得收盤價
// 改從資料庫查詢收盤價
doubleStockClosePrice = getStockPriceByDate(strId, dateDate);
// 將從資料庫查詢到的收盤價回寫至資料庫
stringRedisTemplate.opsForHash().putIfAbsent(strId, strDate, String.valueOf(doubleStockClosePrice));
}
if (doubleStockClosePrice > 0) {
return new RspBody("0000", "Success", doubleStockClosePrice);
} else {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
return new RspBody("0009", "Success", "The " + simpleDateFormat.format(dateDate)
+ " close price of " + strId + " is not exist.");
}
}
// 查詢資料庫中的收盤價
public double getStockPriceByDate(String StockId, Date date) {
// 將TradingDate轉為特定格式的字串
String strTradingDate = DateFormatUtils.format(date, "yyyy-MM-dd");
// 從資料庫讀取收盤價資料
double stockPrice = 0;
try {
// 將回傳的收盤資料放入變數中
stockPrice = stockRepository.getStockClosePrice(StockId, strTradingDate);
} catch (Exception ex) {
// 若資料庫中沒有收盤價,則回傳0
stockPrice = 0;
}
return stockPrice;
}
}
步驟5:透過API查詢股價。
步驟6:第一次查詢,Redis內沒有資料,必須從資料庫查詢,耗費22ms。
2022-02-25 22:40:33.163 INFO [http-nio-8080-exec-3] stockmarket.jovepater.com.stockmarket.Controllers.StockController: Time used: 22 ms
步驟7:第二次查詢,Redis內已經有資料了,耗費7ms。
2022-02-25 22:42:38.768 INFO [http-nio-8080-exec-5] stockmarket.jovepater.com.stockmarket.Controllers.StockController: Time used: 7 ms
結語
從上述可以發現,查詢效能比資料庫快了3倍 (7ms VS 22ms),當SQL查詢更複雜、資料量更大、TPS更高的情況下,這個差距會非常驚人,也就更能顯現出這樣的架構所帶來的好處了。
~ END ~