首頁 > 軟體

Redis分散式快取與秒殺

2023-04-04 06:01:45

一、單點Redis的問題

1、資料丟失問題

Redis資料持久化。

2、並行能力問題

大家主從叢集,實現讀寫分離。

3、故障恢復問題

利用Redis哨兵,實現健康檢測和自動恢復。

4、儲存能力問題

搭建分片叢集,利用插槽機制實現動態擴容。

二、RDB

RDB全稱Redis Database Backup file(Redis資料備份檔案),也被叫做Redis資料快照。簡單來說就是把記憶體中的所有資料都記錄到磁碟中。當Redis範例故障重啟後,從磁碟讀取快照檔案,恢復資料。
快照檔案稱為RDB檔案,預設是儲存在當前執行目錄。

Redis內部有觸發RDB的機制,可以在redis.conf檔案中找到,格式如下:

bgsave開始時會fork主程序得到子程序,子程序共用主程序的記憶體資料。完成fork後讀取記憶體資料並寫入 RDB 檔案。

fork採用的是copy-on-write技術:

  • 當主程序執行讀操作時,存取共用記憶體;
  • 當主程序執行寫操作時,則會拷貝一份資料,執行寫操作;

RDB方式bgsave的基本流程?

  1. fork主程序得到一個子程序,共用記憶體空間;
  2. 子程序讀取記憶體資料並寫入新的RDB檔案;
  3. 用新RDB檔案替換舊的RDB檔案;

RDB會在什麼時候執行?save 60 1000代表什麼含義?

  • 預設是服務停止時;
  • 代表60秒內至少執行1000次修改則觸發RDB;

RDB的缺點?

  • RDB執行間隔時間長,兩次RDB之間寫入資料有丟失的風險;
  • fork子程序、壓縮、寫出RDB檔案都比較耗時;

AOF的命令記錄的頻率也可以通過redis.conf檔案來配:

三、AOF

AOF全稱為Append Only File(追加檔案)。Redis處理的每一個寫命令都會記錄在AOF檔案,可以看做是命令紀錄檔檔案。

AOF預設是關閉的,需要修改redis.conf組態檔來開啟AOF:

AOF的命令記錄的頻率也可以通過redis.conf檔案來配:

設定項刷盤時機優點缺點
Always同步刷盤可靠性高,幾乎不丟資料效能影響大
everysec每秒刷盤效能適中最多丟失一分鐘的資料
no作業系統控制效能最好可靠性較差,可能丟失大量資料

 因為是記錄命令,AOF檔案會比RDB檔案大的多。而且AOF會記錄對同一個key的多次寫操作,但只有最後一次寫操作才有意義。通過執行bgrewriteaof命令,可以讓AOF檔案執行重寫功能,用最少的命令達到相同效果。

set id 1
set name nezha
set id 2

bgrewriteaof

mset name nezha id 2

Redis也會在觸發閾值時自動去重寫AOF檔案。閾值也可以在redis.conf中設定:

# AOF檔案比上次檔案 增長超過多少百分比則觸發重寫auto-aof-rewrite-percentage 100# AOF檔案體積最小多大以上才觸發重寫 auto-aof-rewrite-min-size 64mb 

 RDB和AOF各有自己的優缺點,如果對資料安全性要求較高,在實際開發中往往會結合兩者來使用。

RDBAOF
持久化方式定時對整個記憶體做快照記錄每一次執行的命令
資料完整性不完整,兩次備份之間會丟失相對完整,取決於刷盤策略
檔案大小會有壓縮,檔案體積小記錄命令,檔案體積很大
宕機恢復速度很快
資料恢復優先順序低,因為資料完整性不低高,因為資料完整性更高
系統資源佔用高,大量CPU和記憶體消耗低,主要是磁碟IO資源,但AOF重寫時會佔用大量CPU和記憶體資源
使用場景可以容忍數分鐘的資料丟失,追求更快的啟動速度對資料安全性要求較高常見

四、Redis優化秒殺流程

1、秒殺步驟:

  1. 查詢優惠券;
  2. 判斷秒殺商品庫存;
  3. 查詢訂單
  4. 校驗一人一單;
  5. 減庫存;
  6. 建立訂單;

2、Redis優化秒殺步驟:

  1. 新增秒殺的優惠券,將優惠券資訊儲存到Redis中;
  2. 基於Lua指令碼,判斷秒殺商品庫存,一人一單,決定使用者是否秒殺成功;
  3. 如果秒殺成功,將優惠券id、使用者id、商品id封裝到阻塞佇列中;
  4. 開啟非同步任務,不斷從阻塞佇列中讀取資訊,實現非同步下單功能;

3、秒殺的lua指令碼

 4、呼叫秒殺的lua指令碼

public Result seckillVoucher(Long voucherId) {
     Long userId = UserHolder.getUser().getId();
     long orderId = redisIdWorker.nextId("order");
     // 1.執行lua指令碼
     Long result = stringRedisTemplate.execute(
             SECKILL_SCRIPT,
             Collections.emptyList(),
             voucherId.toString(), userId.toString(), String.valueOf(orderId)
     );
     int r = result.intValue();
     // 2.判斷結果是否為0
     if (r != 0) {
         // 2.1.不為0 ,代表沒有購買資格
         return Result.fail(r == 1 ? "庫存不足" : "不能重複下單");
     }
     // 3.返回訂單id
     return Result.ok(orderId);
 }

5、通過執行緒池,操作阻塞佇列

// 執行緒池
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

/**
* 在類初始化完成後執行
*/
@PostConstruct
private void init() {
    SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}

// 阻塞佇列
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
private class OrderHandler implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                doSomething();
            } catch (Exception e) {
                log.error("處理訂單異常", e);
            }
        }
    }
}

五、基於Redis實現共用session登入

基於session實現登入

基於Redis實現共用session登入

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、獲取請求頭中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2、基於TOKEN獲取redis中的使用者
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3、判斷使用者是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5、將查詢到的hash資料轉為UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6、存在,儲存使用者資訊到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7、重新整理token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8、放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除使用者
        UserHolder.removeUser();
    }
}

 到此這篇關於Redis分散式快取與秒殺的文章就介紹到這了,更多相關Redis分散式快取秒殺內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com