簡單介紹基於Redis的List實現特價商品列表功能

大雄45發表於2021-09-15
導讀 本文透過場景分析給大家介紹了基於Redis的List實現特價商品列表,本文透過例項程式碼給大家介紹的非常詳細,需要的朋友可以參考下
1、場景分析

購物平臺的特價商品列表,

商品特點:

商品有限,併發量非常的大。

考慮分頁

傳統解決方案:資料庫db,

但是在如此大的併發量的情況下,不可取。

一般會採用redis來處理。這些特價商品的資料不多,而且redis的list本身也支援分頁。是天然處理這種列表的最佳選擇解決方案。

2、分析

採用list資料,因為list資料結構有:lrange key 0 -1 可以進行資料的分頁。

127.0.0.1:6379> lpush products p1 p2 p3 p4 p5 p6 p7 p8 p9 p10
(integer) 10
127.0.0.1:6379> lrange products 0 1
1) "p10"
2) "p9"
127.0.0.1:6379> lrange products 2 3
1) "p8"
2) "p7"
127.0.0.1:6379> lrange products 4 5
1) "p6"
2) "p5"
3 、具體實現

購物平臺的熱門商品在雙11的時候,可能有100多w需要搞活動:程式需要5分鐘對特價商品進行重新整理。

3.1 ProductListService類

初始化的活動的商品資訊100個(從資料庫去查詢)

@PostContrcut使用

查詢產品列表資訊

換算的分頁的起始位置和結束位置

package com.example.service;
 
import com.example.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
 
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
/**
 * @Auther: 長頸鹿
 * @Date: 2021/08/29/18:00
 * @Description:
 */
@Service
@Slf4j
public class ProductListService {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    // 資料熱載入
    @PostConstruct
    public void initData(){
        log.info("啟動定時載入特價商品到redis的list中...");
        new Thread(() -> runCourse()).start();
    }
 
    public void runCourse() {
        while (true) {
            // 從資料庫中查詢出特價商品
            List<product> productList = this.findProductsDB();
            // 刪除原來的特價商品
            this.redisTemplate.delete("product:hot:list");
            // 把特價商品新增到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分鐘執行一次
                Thread.sleep(1000 * 60);
                log.info("定時重新整理特價商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
 
    /**
     * 資料庫中查詢特價商品
     *
     * @return
     */
    public List<product> findProductsDB() {
        //List<product> productList = productMapper.selectListHot();
        List<product> productList = new ArrayList<>();
        for (long i = 1; i <= 100; i++) {
            Product product = new Product();
            product.setId((long) new Random().nextInt(1000));
            product.setPrice((double) i);
            product.setTitle("特價商品" + (i));
            productList.add(product);
        }
        return productList;
    }
 
}
3.2 商品的資料介面的定義和展示及分頁
package com.example.controller;
 
import com.example.entity.Product;
import com.example.service.ProductListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
/**
 * @Auther: 長頸鹿
 * @Date: 2021/08/29/18:04
 * @Description:
 */
@RestController
public class ProductListController {
 
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ProductListService productListService;
 
    @GetMapping("/findProducts")
    public List<product> findProducts(int pageNo, int pageSize) {
 
        // 從那個集合去查詢
        String key = "product:hot:list";
        // 分頁的開始結束的換算
        if (pageNo <= 0) pageNo = 1;
        int start = (pageNo - 1) * pageSize;
        // 計算分頁的結束頁
        int end = start + pageSize - 1;
 
        // 根據redis的api去處理分頁查詢對應的結果
        try {
            List<product> productList = this.redisTemplate.opsForList().range(key, start, end);
            if (CollectionUtils.isEmpty(productList)) {
                //todo: 查詢資料庫,存在快取擊穿的情況,大量的併發請求進來,可能把資料庫衝
                productList = productListService.findProductsDB();
            }
            return productList;
 
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
 
}
3.3 定時任務
@Configuration      // 主要用於標記配置類,兼備Component的效果。
@EnableScheduling   // 開啟定時任務
public class SaticScheduleTask {
    // 新增定時任務
    @Scheduled(cron = "* 0/5 * * * ?")
    // 或直接指定時間間隔,例如:5秒
    // @Scheduled(fixedRate=5000)
    private void configureTasks() {
        System.err.println("執行靜態定時任務時間: " + LocalDateTime.now());
    }
}
4、解決商品列表存在的快取擊穿問題
4.1 如何引起的快取擊穿的情況
public void runCourse() {
        while (true) {
            // 從資料庫中查詢出特價商品
            List<product> productList = this.findProductsDB();
            // 刪除原來的特價商品
            this.redisTemplate.delete("product:hot:list");
            // 把特價商品新增到集合中 需要時間
            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分鐘執行一遍
                Thread.sleep(1000 * 60);
                log.info("定時重新整理特價商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

出現原因:

特價商品的資料更換需要時間,剛好特價商品還沒有放入到redis快取中。
查詢特價商品的併發量非常大,可能程式還正在寫入特價商品到快取中,這時查詢快取根本沒有資料,就會直接衝入資料庫中去查詢特價商品。可能造成資料庫沖垮。這個就叫做:快取擊穿

4.2 解決方案

主從輪詢

可以開闢兩塊redis的集合空間A和B。定時器在更新快取的時候,先更新B快取,然後再更新A快取。

一定要按照特定順序來處理。

package com.example.service;
 
import com.example.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
 
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
/**
 * @Auther: 長頸鹿
 * @Date: 2021/08/29/18:00
 * @Description:
 */
@Service
@Slf4j
public class ProductListService {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    // 資料熱載入
    @PostConstruct
    public void initData(){
        log.info("啟動定時載入特價商品到redis的list中...");
        new Thread(() -> runCourse()).start();
    }
 
    public void runCourse() {
        while (true) {
            // 從資料庫中查詢出特價商品
            List<product> productList = this.findProductsDB();
 
            // 刪除原來的特價商品
            this.redisTemplate.delete("product:hot:slave:list");
            // 把特價商品新增到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:slave:list", productList);// 刪除原來的特價商品
 
            this.redisTemplate.delete("product:hot:master:list");
            // 把特價商品新增到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:master:list", productList);
 
//            // 刪除原來的特價商品
//            this.redisTemplate.delete("product:hot:list");
//            // 把特價商品新增到集合中
//            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分鐘執行一次
                Thread.sleep(1000 * 60);
                log.info("定時重新整理特價商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
 
    /**
     * 資料庫中查詢特價商品
     *
     * @return
     */
    public List<product> findProductsDB() {
        //List<product> productList = productMapper.selectListHot();
        List<product> productList = new ArrayList<>();
        for (long i = 1; i <= 100; i++) {
            Product product = new Product();
            product.setId((long) new Random().nextInt(1000));
            product.setPrice((double) i);
            product.setTitle("特價商品" + (i));
            productList.add(product);
        }
        return productList;
    }
 
}
package com.example.controller;
 
import com.example.entity.Product;
import com.example.service.ProductListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
/**
 * @Auther: 長頸鹿
 * @Date: 2021/08/29/18:04
 * @Description:
 */
@RestController
public class ProductListController {
 
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ProductListService productListService;
 
    @GetMapping("/findProducts")
    public List<product> findProducts(int pageNo, int pageSize) {
 
        // 從那個集合去查詢
 
        String master_key = "product:hot:master:list";
        String slave_key = "product:hot:slave:list";
 
        String key = "product:hot:list";
        // 分頁的開始結束的換算
        if (pageNo <= 0) pageNo = 1;
        int start = (pageNo - 1) * pageSize;
        // 計算分頁的結束頁
        int end = start + pageSize - 1;
 
        // 根據redis的api去處理分頁查詢對應的結果
        try {
 
            List<product> productList = this.redisTemplate.opsForList().range(master_key, start, end);
 
//            List<product> productList = this.redisTemplate.opsForList().range(key, start, end);
            if (CollectionUtils.isEmpty(productList)) {
                // todo: 查詢資料庫,存在快取擊穿的情況,大量的併發請求進來,可能把資料庫衝
 
                productList = this.redisTemplate.opsForList().range(slave_key, start, end);
 
//                productList = productListService.findProductsDB();
            }
            return productList;
 
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
 
}

到此這篇關於基於Redis的List實現特價商品列表的文章就介紹到這了。

原文來自:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2792155/,如需轉載,請註明出處,否則將追究法律責任。

相關文章