欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Springboot如何優(yōu)雅高效的清除Redis中的業(yè)務(wù)key

 更新時間:2025年05月07日 08:54:50   作者:血小濺  
這篇文章主要為大家詳細介紹了Springboot如何優(yōu)雅高效的清除Redis中的業(yè)務(wù)key,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

1、問題背景

云服務(wù)運維工程師聯(lián)系我說老系統(tǒng)有個服務(wù)連接redis集群實例使用keys命令導(dǎo)致實例夯住了并給我截了個圖。然后剛開始我是挺懵逼的,同事跟我說在某個服務(wù)中,我去找了找壓根沒有,后來我仔細想了想,并看了運維老師提供的圖,我想到了方法找對應(yīng)的應(yīng)用進程。以下是排查及解決過程。

2、如何找到對應(yīng)的應(yīng)用進程

根據(jù)下面的圖,我們可以看到,redis集群的服務(wù)端口為9000,客戶端連接分配的客戶端本地通信端口【本地端口只是一個臨時標(biāo)識,用于客戶端與 Redis 之間的通信,通常是由操作系統(tǒng)在每次創(chuàng)建新連接時自動分配的,并不會影響連接的實際功能。】為39720,那么我們就可以通過netstat命令來查找對應(yīng)的應(yīng)用進程了。

2.1、使用netstat查找進程

進入應(yīng)用部署的服務(wù)器,使用如下netstat命令查找進程,如下圖,從下圖我們可以看出,進程是個java進程,進程號為15817

netstat -anlp |grep 9000 |grep EST |grep 39720

2.2、使用jps命令查看應(yīng)用名稱

使用jps命令查看java進程對應(yīng)的應(yīng)用名稱,通過命令我們可以看出

jps -l |grep 15817

3、問題代碼及原因分析

3.1、查找問題代碼

根據(jù)步驟2我們找到了對應(yīng)的應(yīng)用,下面我們就可以通過redis中的key關(guān)鍵詞YZ_MULTI_DIAG搜索代碼了,然后找到了如下圖的代碼,確實使用了keys命令。

private void cleanCache(String toUserId) {
    Set<String> keys = stringRedisTemplate.keys("YZ_MULTI_DIAG:" + toUserId + "*");
    stringRedisTemplate.delete(keys);
}

3.2、原因分析

keys 命令在 Redis 中遍歷所有的鍵,是一個阻塞操作,尤其是當(dāng) Redis 數(shù)據(jù)量大時,可能會導(dǎo)致 Redis 實例卡住或響應(yīng)變慢。在 Redis 中,keys 命令用于查找與給定模式匹配的所有鍵,它會掃描整個數(shù)據(jù)庫,并返回符合條件的所有鍵。這個命令在某些情況下會導(dǎo)致 Redis 實例“夯住”或變得非常緩慢,原因如下:

3.2.1、 阻塞和性能影響

  • keys 命令需要遍歷 Redis 實例中所有的鍵,無論數(shù)據(jù)庫中有多少個鍵。對于存儲大量鍵的 Redis 實例來說,keys 命令會消耗大量的 CPU 和內(nèi)存資源,因為它必須檢查每個鍵,并將結(jié)果返回給客戶端。
  • 如果有大量的鍵,keys 命令可能會導(dǎo)致 Redis 被阻塞,直到命令完成執(zhí)行。在此期間,Redis 無法處理其他客戶端請求,這可能會導(dǎo)致延遲或服務(wù)中斷。

3.2.2、 不適合生產(chǎn)環(huán)境

  • 在生產(chǎn)環(huán)境中,通常不建議使用 keys 命令,特別是在有大量鍵值對的情況下。keys 命令的性能是 O(N),其中 N 是數(shù)據(jù)庫中鍵的數(shù)量。這意味著數(shù)據(jù)庫中鍵越多,執(zhí)行時間就越長,負載越重。
  • 更適合使用 scan 命令,它是增量式的,并不會一次性返回所有匹配的鍵,而是通過多次迭代逐步獲取。這使得 Redis 在掃描鍵時不會被完全阻塞。

3.2.3、 其他客戶端請求的影響

  • 由于 keys 命令會導(dǎo)致 Redis 掃描整個鍵空間,它會占用 Redis 實例的 CPU 和內(nèi)存資源,這可能導(dǎo)致其他客戶端請求的響應(yīng)時間延遲,甚至阻塞其他操作,導(dǎo)致整個 Redis 實例性能下降。
  • 在 Redis 集群環(huán)境中,keys 命令會對集群的每個節(jié)點進行全局掃描,可能會對整個集群的性能產(chǎn)生影響。

4、優(yōu)化方案

  • 使用 scan 命令替代 keys 命令。scan 命令是增量的,可以分批次掃描鍵,避免一次性操作導(dǎo)致的阻塞。
  • 如果需要列出鍵,盡量使用特定的鍵模式(例如,前綴)來限制掃描的范圍,避免掃描整個數(shù)據(jù)庫。
  • 在生產(chǎn)環(huán)境中,應(yīng)該避免在高負載期間使用 keys 命令。

優(yōu)化后的代碼如下,使用類似分頁概念進行批量刪除。

private void cleanCache(String toUserId) {
    String pattern = "YZ_MULTI_DIAG:" + toUserId + "*";
    ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).count(100).build();
    stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
        String cursor = "0"; // 初始游標(biāo)
        try {
            do {
                // 使用SCAN命令分頁獲取匹配的鍵
                Cursor<byte[]> scanCursor = connection.scan(scanOptions);
                List<byte[]> keysToDelete = new ArrayList<>();
                while (scanCursor.hasNext()) {
                    keysToDelete.add(scanCursor.next());
                    // 分批刪除,避免內(nèi)存占用過高
                    if (keysToDelete.size() >= 100) {
                        connection.del(keysToDelete.toArray(new byte[0][]));
                        keysToDelete.clear();
                    }
                }
                // 刪除剩余的鍵
                if (!keysToDelete.isEmpty()) {
                    connection.del(keysToDelete.toArray(new byte[0][]));
                }
                cursor = scanCursor.getCursorId() + ""; // 更新游標(biāo)
            } while (!"0".equals(cursor)); // 如果游標(biāo)為0,表示掃描結(jié)束
        } catch (Exception e) {
            log.error("Error while scanning and deleting Redis keys with pattern: {}", pattern, e);
        }
        return null;
    });
}

5、測試驗證

5.1、編寫測試類

新增測試類,代碼如下,新增100個key,然后按照每個批次10個進行刪除測試,代碼如下

package com.jianjang.zhgl.person.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.ActiveProfiles;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: zhgl_server
 * @description: 緩存清理測試類
 * @author: Jian Jang
 * @create: 2025-05-06 11:25:51
 * @blame ZHSF Team
 */

@Slf4j
@ActiveProfiles("local")
@SpringBootTest
public class RedisCleanCacheTest {
    /**
     * 測試key
     */
    private final static String TEST_KEY = "TEST_KEY:";
    private final static String BIZ_KEY = "userId";
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Test
    public void addCache() {
        for (int i = 0; i < 100; i++) {
            stringRedisTemplate.opsForValue().set(TEST_KEY+BIZ_KEY+i, "value" + i);
        }
    }
    @Test
    public void cleanCache() {
        cleanCache(BIZ_KEY, 10);
    }


    /**
     * 清除緩存內(nèi)容
     *
     * @param redisKey
     * @param batchSize
     */
    private void cleanCache(String redisKey, int batchSize) {
        String pattern = TEST_KEY + redisKey + "*";
        ScanOptions scanOptions = ScanOptions.scanOptions().match(pattern).count(batchSize).build();
        stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
            String cursor = "0"; // 初始游標(biāo)
            try {
                do {
                    // 使用SCAN命令分頁獲取匹配的鍵
                    Cursor<byte[]> scanCursor = connection.scan(scanOptions);
                    List<byte[]> keysToDelete = new ArrayList<>();
                    while (scanCursor.hasNext()) {
                        keysToDelete.add(scanCursor.next());
                        // 分批刪除,避免內(nèi)存占用過高
                        if (keysToDelete.size() >= batchSize) {
                            connection.del(keysToDelete.toArray(new byte[0][]));
                            keysToDelete.clear();
                        }
                    }
                    // 刪除剩余的鍵
                    if (!keysToDelete.isEmpty()) {
                        connection.del(keysToDelete.toArray(new byte[0][]));
                    }
                    cursor = scanCursor.getCursorId() + ""; // 更新游標(biāo)
                } while (!"0".equals(cursor)); // 如果游標(biāo)為0,表示掃描結(jié)束
            } catch (Exception e) {
                log.error("Error while scanning and deleting Redis keys with pattern: {}", pattern, e);
            }
            return null;
        });
    }
}

5.2、測試新增

執(zhí)行新增測試方法后,新增成功,如下圖,

5.3、測試批量刪除

執(zhí)行批量刪除方法后,刪除成功,如下圖,100個TEST_KEY已被清除。

到此這篇關(guān)于Springboot如何優(yōu)雅高效的清除Redis中的業(yè)務(wù)key的文章就介紹到這了,更多相關(guān)Springboot清除Redis業(yè)務(wù)key內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論