편집자가 담당하는 애플리케이션은 관리 백그라운드 애플리케이션입니다. 권한 관리에는 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
指令就没有问题?
首先我们来看第一个问题,为什么keys
指令会导致其他命令执行变慢?
站在客户端的视角,执行一条命令分为三步:
发送命令
执行命令
返回结果
但是这仅仅客户端自己以为的过程,但是实际上同一时刻,可能存在很多客户端发送命令给 Redis ,而 Redis 我们都知道它采用的是单线程模型。
为了处理同一时刻所有的客户端的请求命令,Redis 内部采用了队列的方式,排队执行。
于是客户端执行一条命令实际需要四步:
发送命令
命令排队
执行命令
返回结果
由于 Redis 单线程执行命令,只能顺序从队列取出任务开始执行。
只要 3 这个过程执行命令速度过慢,队列其他任务不得不进行等待,这对外部客户端看来,Redis 好像就被阻塞一样,一直得不到响应。
所以使用 Redis 过程切勿执行需要长时间运行的指令,这样可能导致 Redis 阻塞,影响执行其他指令。
接下来开始回答第二个问题,为什么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
命令,Redis 都会向用户返回一个新的游标以及一定数量的 key。下次再想继续获取剩余的 key,需要将这个游标传入 scan 命令, 以此来延续之前的迭代过程。
简单来讲,scan
命令使用分页查询 redis 。
下面是一个 scan 命令的迭代过程示例:
scan
命令使用游标这种方式,巧妙将一次全量查询拆分成多次,降低查询复杂度。
虽然 scan
命令时间复杂度与 keys
一样,都是 「O(N)」,但是由于 scan
命令只需要返回少量的 key,所以执行速度会很快。
最后,虽然scan
命令解决 keys
shiro-redis
는 SessionDAO
인터페이스를 상속합니다. 맨 아래 레이어는 keys
명령을 사용하여 모든 Session는 Redis >key에 저장됩니다. <ul class=" list-paddingleft-2">rrreee<li> 문제의 원인을 찾으면 해결 방법은 비교적 간단합니다. github에서 해결 방법을 찾아 <code>shiro-redis
를 최신 버전으로 업그레이드하세요. 이 버전에서는 shiro-redis
가 keys
대신 scan
명령을 사용하여 이 문제를 해결합니다. 🎜rrreee🎜문제는 성공적으로 해결되었지만 아펜은 여전히 조금 혼란스러워했습니다. 🎜🎜 keys
지시문으로 인해 다른 명령의 실행 속도가 느려지는 이유는 무엇입니까? 🎜🎜Keys
명령 쿼리가 왜 그렇게 느린가요? 🎜🎜스캔
명령에 문제가 없는 이유는 무엇입니까? 🎜🎜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 중국어 웹사이트의 기타 관련 기사를 참조하세요!