>  기사  >  데이터 베이스  >  Redis 명령어 사용 사례 분석

Redis 명령어 사용 사례 분석

WBOY
WBOY앞으로
2023-05-30 15:46:52655검색

문제 원인

편집자가 담당하는 애플리케이션은 관리 백그라운드 애플리케이션입니다. 권한 관리에는 Shiro 프레임워크를 사용하므로 노드가 여러 개 있으므로 분산된 Session을 사용해야 하므로 Session 정보를 저장하는 데 Redis를 사용합니다. .

Shiro는 Redis 스토리지 세션 구성 요소를 직접 제공하지 않기 때문에 Afan은 Github의 오픈 소스 구성 요소인 shiro-redis를 사용해야 했습니다.

Shiro 프레임워크는 세션이 유효한지 정기적으로 확인해야 하기 때문에 Shiro의 최하위 계층은 모든 세션 정보를 얻기 위해 SessionDAO#getActiveSessions를 호출합니다. SessionDAO#getActiveSessions 获取所有的 Session 信息。

shiro-redis 正好继承 SessionDAO 这个接口,底层使用用keys 命令查找 Redis 所有存储的 Session key。

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    }finally{
        jedis.close();
    }
    return keys;
}

找到问题原因,解决办法就比较简单了,github 上查找到解决方案,升级一下 shiro-redis 到最新版本。

在这个版本,shiro-redis 采用 scan命令代替 keys,从而修复这个问题。

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();


    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor,params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        }while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    }finally{
        jedis.close();
    }
    return keys;


}

虽然问题成功解决了,但是阿粉心里还是有点不解。

为什么keys 指令会导致其他命令执行变慢?

为什么Keys 指令查询会这么慢?

为什么Scan 指令就没有问题?

Redis 执行命令的原理

首先我们来看第一个问题,为什么keys 指令会导致其他命令执行变慢?

站在客户端的视角,执行一条命令分为三步:

  1. 发送命令

  2. 执行命令

  3. 返回结果

但是这仅仅客户端自己以为的过程,但是实际上同一时刻,可能存在很多客户端发送命令给 Redis ,而 Redis 我们都知道它采用的是单线程模型。

为了处理同一时刻所有的客户端的请求命令,Redis 内部采用了队列的方式,排队执行。

于是客户端执行一条命令实际需要四步:

  1. 发送命令

  2. 命令排队

  3. 执行命令

  4. 返回结果

由于 Redis 单线程执行命令,只能顺序从队列取出任务开始执行。

只要 3 这个过程执行命令速度过慢,队列其他任务不得不进行等待,这对外部客户端看来,Redis 好像就被阻塞一样,一直得不到响应。

所以使用 Redis 过程切勿执行需要长时间运行的指令,这样可能导致 Redis 阻塞,影响执行其他指令。

KEYS 原理

接下来开始回答第二个问题,为什么Keys 指令查询会这么慢?

回答这个问题之前,请大家回想一下 Redis 底层存储结构。

不太清楚朋友的也没关系,大家可以回看一下之前的文章「阿里面试官:HashMap 熟悉吧?好的,那就来聊聊 Redis 字典吧!」。

keys命令需要返回所有的符合给定模式 pattern 的  Redis 中键,为了实现这个目的,Redis 不得不遍历字典中 ht[0]哈希表底层数组,这个时间复杂度为 「O(N)」(N 为 Redis 中 key 所有的数量)。

即使 Redis 中的键数量很少,它仍然会有很快的执行速度。当Redis键的数量逐渐增多,达到百万、千万,甚至上亿级别时,它的执行速度会变得非常缓慢。

下面是阿粉本地做的一次实验,使用 lua 脚本往 Redis 中增加 10W 个 key,然后使用 keys 查询所有键,这个查询大概会阻塞十几秒的时间。

eval "for i=1,100000  do redis.call('set',i,i+1) end" 0

这里阿粉使用 Docker 部署 Redis,性能可能会稍差。

SCAN 原理

最后我们来看下第三个问题,为什么scan 指令就没有问题?

这是因为 scan命令采用一种黑科技-「基于游标的迭代器」

每次调用 scan 命令,Redis 都会向用户返回一个新的游标以及一定数量的 key。下次再想继续获取剩余的 key,需要将这个游标传入 scan 命令, 以此来延续之前的迭代过程。

简单来讲,scan 命令使用分页查询 redis 。

下面是一个 scan 命令的迭代过程示例:

scan 命令使用游标这种方式,巧妙将一次全量查询拆分成多次,降低查询复杂度。

虽然  scan 命令时间复杂度与 keys一样,都是 「O(N)」,但是由于 scan 命令只需要返回少量的 key,所以执行速度会很快。

最后,虽然scan 命令解决 keys

그리고 shiro-redisSessionDAO 인터페이스를 상속합니다. 맨 아래 레이어는 keys 명령을 사용하여 모든 Session는 Redis >key에 저장됩니다. <ul class=" list-paddingleft-2">rrreee<li> 문제의 원인을 찾으면 해결 방법은 비교적 간단합니다. github에서 해결 방법을 찾아 <code>shiro-redis를 최신 버전으로 업그레이드하세요.

이 버전에서는 shiro-rediskeys 대신 scan 명령을 사용하여 이 문제를 해결합니다. 🎜rrreee🎜문제는 성공적으로 해결되었지만 아펜은 여전히 ​​조금 혼란스러워했습니다. 🎜🎜 keys 지시문으로 인해 다른 명령의 실행 속도가 느려지는 이유는 무엇입니까? 🎜🎜Keys 명령 쿼리가 왜 그렇게 느린가요? 🎜🎜스캔 명령에 문제가 없는 이유는 무엇입니까? 🎜🎜Redis 명령 실행 원리🎜🎜우선 첫 번째 질문인 keys 명령으로 인해 다른 명령의 실행이 느려지는 이유는 무엇인지 살펴보겠습니다. 🎜🎜클라이언트 관점에서 명령어 실행은 세 단계로 나누어집니다. 🎜
    🎜🎜명령 보내기🎜
🎜🎜명령 실행🎜🎜🎜Return Result🎜🎜근데 이건 클라이언트가 생각하는 과정일 뿐이지만, 사실 동시에 Redis에 명령을 보내는 클라이언트도 많을 수 있고, Redis는 우리 모두가 단일 스레드 모델을 사용한다는 것을 알고 있습니다. 🎜🎜모든 클라이언트 요청 명령을 동시에 처리하기 위해 Redis는 내부적으로 대기열을 사용하여 실행을 대기열에 추가합니다. 🎜🎜따라서 클라이언트는 실제로 명령을 실행하는 네 단계가 필요합니다: 🎜
    🎜🎜명령 보내기🎜🎜🎜명령 대기열🎜🎜🎜실행 명령🎜🎜🎜결과 반환🎜
🎜Redis는 단일 스레드에서 명령을 실행하기 때문에 대기열에서 작업을 순차적으로 제거하고 실행을 시작할 수만 있습니다. 🎜🎜3인 경우 이 프로세스의 명령 실행 속도가 너무 느리고 대기열에 있는 다른 작업을 기다려야 합니다. 외부 클라이언트에게는 Redis가 차단되어 응답하지 않는 것 같습니다. 🎜🎜따라서 Redis 프로세스를 사용할 때 오랫동안 실행해야 하는 명령어를 실행하지 마세요. 이로 인해 Redis가 차단되어 다른 명령어 실행에 영향을 줄 수 있습니다. 🎜🎜KEYS 원칙🎜🎜 다음 두 번째 질문에 답해 보겠습니다. Keys 명령 쿼리가 왜 그렇게 느린가요? 🎜🎜이 질문에 답하기 전에 Redis의 기본 스토리지 구조를 기억해 보세요. 🎜🎜친구의 것을 몰라도 상관없습니다. 이전 기사인 "알리바바 인터뷰어: HashMap에 대해 잘 알고 계시나요? 자, Redis 사전에 대해 이야기해 봅시다!"를 참고하시면 됩니다. 🎜🎜keys 명령은 주어진 패턴 pattern과 일치하는 모든 Redis 중간 키를 반환해야 합니다. 이 목적을 달성하려면 Redis는 ht[0을 순회해야 합니다. ] 사전의 해시 테이블의 기본 배열입니다. 이 시간 복잡도는 "O(N)"입니다(N은 Redis의 키 수). 🎜🎜 Redis의 키 수가 적더라도 실행 속도는 빠릅니다. Redis 키 수가 점차 증가하여 수백만, 수천만, 심지어 수억에 도달하면 실행 속도가 매우 느려집니다. 🎜🎜다음은 Afen이 로컬에서 수행한 실험입니다. Lua 스크립트를 사용하여 Redis에 10W 키를 추가한 다음 keys를 사용하여 모든 키를 쿼리합니다. 이 쿼리는 약 10초 동안 차단됩니다. 🎜rrreee
🎜여기 팬은 Docker를 사용하여 Redis를 배포하므로 성능이 약간 나빠질 수 있습니다. 🎜
🎜SCAN 원리🎜🎜마지막으로 세 번째 질문을 살펴보겠습니다. 왜 scan 명령에 문제가 없는 걸까요? 🎜🎜이는 scan 명령이 검은 기술인 "커서 기반 반복자"를 사용하기 때문입니다. 🎜🎜 scan 명령이 호출될 때마다 Redis는 새로운 커서와 특정 수의 키를 사용자에게 반환합니다. 다음에 나머지 키를 계속 얻으려면 이 커서를 scan 명령에 전달하여 이전 반복 프로세스를 계속해야 합니다. 🎜🎜간단히 말하면 scan 명령은 페이징을 사용하여 redis를 쿼리합니다. 🎜🎜다음은 scan 명령의 반복 프로세스에 대한 예입니다. 🎜🎜 scan 명령은 커서를 사용하여 전체 쿼리를 여러 번 교묘하게 분할하여 쿼리 복잡성을 줄입니다. 🎜🎜scan 명령의 시간 복잡도는 keys의 시간 복잡도와 동일하지만 둘 다 "O(N)"이지만 scan에 코드> 명령은 소수의 키만 반환하면 되므로 실행 속도가 매우 빠릅니다. 🎜🎜마지막으로 scan 명령은 의 단점을 해결하지만 다른 결함도 발생합니다. 🎜🎜🎜🎜동일한 요소가 여러 번 반환될 수 있습니다. 우리 애플리케이션에는 중복 요소를 처리하는 기능이 추가되었습니다. 🎜
  • 반복 과정에서 Redis에 추가되는 요소나 삭제되는 요소가 반환될 수도 있고, 반환되지 않을 수도 있습니다.

  • 위의 결함은 개발 시 고려되어야 합니다.

    scan 외에도 redis에는 증분 반복을 위한 여러 다른 명령이 있습니다: scan以外,redis 还有其他几个用于增量迭代命令:

    • sscan:用于迭代当前数据库中的数据库键,用于解决 smembers可能产生阻塞问题

    • hscan命令用于迭代哈希键中的键值对,用于解决 hgetall 可能产生阻塞问题。

    • zscan:命令用于迭代有序集合中的元素(包括元素成员和元素分值),用于产生 zrange

      • sscan: 반복에 사용됩니다. smembers
      🎜🎜🎜의 가능한 차단 문제를 해결하는 데 사용되는 현재 데이터베이스의 데이터베이스 키 hscan 명령은 해시 키의 키-값 쌍을 반복하는 데 사용됩니다. , hgetall의 가능한 차단 문제를 해결하는 데 사용됩니다. 🎜🎜🎜🎜zscan: 이 명령은 순서가 지정된 집합(요소 멤버 및 요소 점수 포함)의 요소를 반복하는 데 사용되며 차단을 유발할 수 있는 zrange를 생성하는 데 사용됩니다. 문제. 🎜🎜🎜

      위 내용은 Redis 명령어 사용 사례 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

      성명:
      이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제