SpringBoot整合Redis實(shí)現(xiàn)緩存分頁(yè)數(shù)據(jù)查詢功能
正式觀看本文之前,設(shè)想一個(gè)問(wèn)題,高并發(fā)情況下,首頁(yè)列表數(shù)據(jù)怎么做?
類似淘寶首頁(yè),這些商品是從數(shù)據(jù)庫(kù)中查出來(lái)的嗎?答案肯定不是,在高并發(fā)的情況下,數(shù)據(jù)庫(kù)是扛不住的,那么我們要怎么去扛住C端端大并發(fā)量呢,這快我們可以借助Redis,我們知道Redis是一個(gè)基于內(nèi)存的NoSQL數(shù)據(jù)庫(kù)。學(xué)過(guò)操作系統(tǒng)我們都知道,內(nèi)存要比磁盤的效率大的多,那我們Redis就是基于內(nèi)存的,而數(shù)據(jù)庫(kù)是基于磁盤的。
我們現(xiàn)在知道要用Redis去做首頁(yè)數(shù)據(jù)的分頁(yè),那么我們應(yīng)該用Redis的那種數(shù)據(jù)結(jié)構(gòu)來(lái)做呢。
Redis有5種基本的數(shù)據(jù)結(jié)構(gòu),我們這里用list類型做分頁(yè)。
在 Redis 中,List(列表)類型是按照元素的插入順序排序的字符串列表。你可以在列表的頭部(左邊)或者尾部(右部)添加新的元素。
ok,那么接下來(lái)我們就通過(guò)一個(gè)案例實(shí)操一下,首頁(yè)熱點(diǎn)數(shù)據(jù)怎么放到Redis中去查詢。
SpringBoot整合RedisTemplate這里就不做過(guò)多介紹啦,大家可以網(wǎng)上找篇博文 整合一下。
<!-- 創(chuàng)建SpringBoot項(xiàng)目加入redis的starter依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
編寫ProductService,定于數(shù)據(jù)分頁(yè)方法。
public interface ProductService { Map<String,Object> productListPage(int current, int size) throws InterruptedException; }
編寫ProductServiceImpl實(shí)現(xiàn)類。
/** * @author lixiang * @date 2023/6/18 21:01 */ @Service @Slf4j public class ProductServiceImpl implements ProductService { private static final String PRODUCT_LIST_KEY = "product:list"; private static final List<Product> PRODUCT_LIST; //模擬從數(shù)據(jù)庫(kù)中查出來(lái)的數(shù)據(jù) static { PRODUCT_LIST = new ArrayList<>(); for (int i = 1; i <= 100; i++) { Product product = new Product(); product.setId(UUID.randomUUID().toString().replace("-", "")); product.setName("商品名稱:" + i); product.setDesc("商品描述:" + i); product.setPrice(new BigDecimal(i)); product.setInventory(2); PRODUCT_LIST.add(product); } } @Autowired private RedisTemplate redisTemplate; @Override public Map<String, Object> productListPage(int current, int size) throws InterruptedException { //從緩存中拿到分頁(yè)數(shù)據(jù) List<Product> productList = getProductListByRedis(current, size); if (productList == null || productList.size() == 0) { log.info("當(dāng)前緩存中無(wú)分頁(yè)數(shù)據(jù),當(dāng)前頁(yè):" + current + ",頁(yè)大小:" + size); //從數(shù)據(jù)庫(kù)中拿到分頁(yè)數(shù)據(jù) productList = getProductListByDataSource(current, size); } Map<String, Object> resultMap = new HashMap<>(); //計(jì)算當(dāng)前總頁(yè)數(shù) int totalPage = (PRODUCT_LIST.size() + size - 1) / size; resultMap.put("total", PRODUCT_LIST.size()); resultMap.put("data", productList); resultMap.put("pages", totalPage); return resultMap; } private List<Product> getProductListByRedis(int current, int size) { log.info("從Redis取出商品信息列表,當(dāng)前頁(yè):" + current + ",頁(yè)大小:" + size); // 計(jì)算總頁(yè)數(shù) int pages = pages(size); // 起始位置 int start = current <= 0 ? 0 : (current > pages ? (pages - 1) * size : (current - 1) * size); // 終止位置 int end = start+size-1; List<Product> list = redisTemplate.opsForList().range(PRODUCT_LIST_KEY, start, end); List<Product> productList = list; return productList; } /** * 獲取商品信息集合 * * @return */ private List<Product> getProductListByDataSource(int current, int size) throws InterruptedException { //模擬從DB查詢需要300ms Thread.sleep(300); log.info("從數(shù)據(jù)庫(kù)取出商品信息列表,當(dāng)前頁(yè):" + current + ",頁(yè)大小:" + size); // 計(jì)算總頁(yè)數(shù) int pages = pages(size); // 起始位置 int start = current <= 0 ? 0 : (current > pages ? (pages - 1) * size : (current - 1) * size); //數(shù)據(jù)緩存到redis中 redisTemplate.opsForList().rightPushAll(PRODUCT_LIST_KEY, PRODUCT_LIST); //設(shè)置當(dāng)前key過(guò)期時(shí)間為1個(gè)小時(shí) redisTemplate.expire(PRODUCT_LIST_KEY,1000*60*60, TimeUnit.MILLISECONDS); return PRODUCT_LIST.stream().skip(start).limit(size).collect(Collectors.toList()); } /** * 獲取總頁(yè)數(shù) * @param size * @return */ private Integer pages(int size){ int pages = PRODUCT_LIST.size() % size == 0 ? PRODUCT_LIST.size() / size : PRODUCT_LIST.size() / size + 1; return pages; } }
ok,然后編寫controller,進(jìn)行測(cè)試。
@RestController @RequestMapping("/api/v1/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/page") public Map<String,Object> page(@RequestParam("current") int current,@RequestParam("size") int size){ Map<String, Object> stringObjectMap; try { stringObjectMap = productService.productListPage(current, size); } catch (InterruptedException e) { stringObjectMap = new HashMap<>(); } return stringObjectMap; } }
當(dāng)?shù)谝淮卧L問(wèn)的時(shí)候,先去Redis中查詢,發(fā)現(xiàn)沒(méi)有,然后就去查DB,將要緩存的數(shù)據(jù)頁(yè)放到Redis中。
第二次訪問(wèn)的時(shí)候。就直接訪問(wèn)Redis啦
通過(guò)Redis和DB查詢的對(duì)比,我們發(fā)現(xiàn)從Redis中拿出來(lái)只用了18ms,從公DB中需要300ms,由此可見(jiàn)Redis的一個(gè)強(qiáng)大之處。
那么我們觀察一下查詢邏輯,會(huì)不會(huì)有什么問(wèn)題。
public Map<String, Object> productListPage(int current, int size) throws InterruptedException { //從緩存中拿到分頁(yè)數(shù)據(jù) List<Product> productList = getProductListByRedis(current, size); if (productList == null || productList.size() == 0) { log.info("當(dāng)前緩存中無(wú)分頁(yè)數(shù)據(jù),當(dāng)前頁(yè):" + current + ",頁(yè)大小:" + size); //從數(shù)據(jù)庫(kù)中拿到分頁(yè)數(shù)據(jù) productList = getProductListByDataSource(current, size); } }
設(shè)想,假如某一時(shí)刻,Redis中的緩存失效啦,大量的請(qǐng)求,全部查到DB上,也會(huì)帶來(lái)一個(gè)災(zāi)難。所以這快又涉及到一個(gè)緩存擊穿的問(wèn)題。
解決緩存擊穿
- 方案一:永不過(guò)期
- 提前把熱點(diǎn)數(shù)據(jù)不設(shè)置過(guò)期時(shí)間,后臺(tái)異步更新緩存。
- 方案二:加互斥鎖或隊(duì)列
- 其實(shí)我理解緩存擊穿和緩存穿透差不多,所以加一個(gè)互斥鎖,讓一個(gè)線程正常請(qǐng)求數(shù)據(jù)庫(kù),其他線程等待即可(這里可以使用線程池來(lái)處理),都創(chuàng)建完緩存,讓其他線程請(qǐng)求緩存即可。
到此這篇關(guān)于SpringBoot整合Redis實(shí)現(xiàn)緩存分頁(yè)數(shù)據(jù)查詢功能的文章就介紹到這了,更多相關(guān)SpringBoot Redis數(shù)據(jù)查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis與微服務(wù)注冊(cè)的詳細(xì)過(guò)程
這篇文章主要介紹了Mybatis與微服務(wù)注冊(cè),主要包括SpringBoot整合MybatisPlus,SpringBoot整合Freeamarker以及SpringBoot整合微服務(wù)&gateway&nginx的案例代碼,需要的朋友可以參考下2023-01-01解決Mybatis映射文件mapper.xml中的注釋問(wèn)題
這篇文章主要介紹了解決Mybatis映射文件mapper.xml中的注釋問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01如何實(shí)用Java實(shí)現(xiàn)合并、拆分PDF文檔
這篇文章主要介紹了如何實(shí)用Java實(shí)現(xiàn)合并、拆分PDF文檔,處理PDF文檔時(shí),這樣的好處是對(duì)文檔的存儲(chǔ)、管理很方便。下面將通過(guò)Java程序代碼介紹具體的PDF合并、拆分的方法,需要的朋友可以參考下2019-07-07詳解在Spring MVC或Spring Boot中使用Filter打印請(qǐng)求參數(shù)問(wèn)題
這篇文章主要介紹了詳解在Spring MVC或Spring Boot中使用Filter打印請(qǐng)求參數(shù)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04javaWeb實(shí)現(xiàn)簡(jiǎn)單文件上傳
這篇文章主要為大家詳細(xì)介紹了JAVAWeb實(shí)現(xiàn)簡(jiǎn)單文件上傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06java性能優(yōu)化四種常見(jiàn)垃圾收集器匯總
這篇文章主要介紹了java性能優(yōu)化四種常見(jiàn)垃圾收集器匯總,每種垃圾收集器都有其不同的算法實(shí)現(xiàn)和步驟,下面我們簡(jiǎn)單描述下我們常見(jiàn)的四種垃圾收集器的算法過(guò)程,感興趣的同學(xué)們最好先看下以下的兩篇文章去增加理解2022-07-07java實(shí)現(xiàn)學(xué)生選課系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生選課系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02