L'application dont l'éditeur est responsable est une application de gestion en arrière-plan. Le framework Shiro est utilisé pour la gestion des autorisations. Puisqu'il existe plusieurs nœuds, des sessions distribuées doivent être utilisées. , donc Redis est utilisé ici pour stocker les informations de session .
Étant donné que Shiro ne fournit pas directement le composant Session de stockage Redis, Afan a dû utiliser shiro-redis, un composant open source de Github.
Étant donné que le framework Shiro doit vérifier régulièrement si la session est valide, la couche inférieure de Shiro appellera SessionDAO#getActiveSessions
pour obtenir toutes les informations de session. 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
hérite simplement de l'interface SessionDAO
La couche inférieure utilise la commande keys
pour trouver tous les éléments. stocké dans la clé Redis code>Session
. shiro-redis
vers la dernière version. Dans cette version, shiro-redis
utilise la commande scan
au lieu de keys
pour résoudre ce problème. #🎜🎜#rrreee#🎜🎜#Bien que le problème ait été résolu avec succès, Ah Fen était encore un peu confus. #🎜🎜##🎜🎜#Pourquoi la directive keys
ralentit-elle l'exécution des autres commandes ? #🎜🎜##🎜🎜#Pourquoi la requête de la commande Keys
est-elle si lente ? #🎜🎜##🎜🎜#Pourquoi n'y a-t-il aucun problème avec la commande Scan
? #🎜🎜##🎜🎜#Le principe d'exécution des commandes Redis#🎜🎜##🎜🎜#Tout d'abord, regardons la première question, pourquoi l'instruction keys
provoque-t-elle l'exécution d'autres commandes Ralentissez? #🎜🎜##🎜🎜# Du point de vue du client, l'exécution d'une commande est divisée en trois étapes : #🎜🎜#Keys
est-elle si lente ? #🎜🎜##🎜🎜#Avant de répondre à cette question, veuillez repenser à la structure de stockage sous-jacente de Redis. #🎜🎜##🎜🎜# Peu importe si vous ne le connaissez pas bien. Vous pouvez revenir sur l'article précédent « Alibaba Interviewer : Connaissez-vous HashMap ? D'accord, parlons du dictionnaire Redis ! #🎜🎜##🎜🎜# La commande keys
doit renvoyer toutes les clés Redis qui correspondent au modèle pattern
donné. Afin d'atteindre cet objectif, Redis doit parcourir le modèle. dictionnaire ht[0]Le tableau sous-jacent de la table de hachage, cette complexité temporelle est "O(N)" (N est le nombre de clés dans Redis). #🎜🎜##🎜🎜#Même si le nombre de clés dans Redis est petit, il aura toujours une vitesse d'exécution rapide. Lorsque le nombre de clés Redis augmente progressivement et atteint des millions, des dizaines de millions, voire des centaines de millions, sa vitesse d'exécution deviendra très lente. #🎜🎜##🎜🎜#Ce qui suit est une expérience réalisée par Ah Fen localement. Utilisez le script lua pour ajouter des clés 10W à Redis, puis utilisez keys
pour interroger toutes les clés. une douzaine de secondes. #🎜🎜#rrreee#🎜🎜#Ici, le fan utilise Docker pour déployer Redis, et les performances peuvent être légèrement pires. #🎜🎜##🎜🎜#Principe du SCAN#🎜🎜##🎜🎜#Enfin, regardons la troisième question, pourquoi n'y a-t-il aucun problème avec la commande
scan
? #🎜🎜##🎜🎜#C'est parce que la commande scan
utilise une technologie noire - "itérateur basé sur un curseur". #🎜🎜##🎜🎜#A chaque fois que la commande scan
est appelée, Redis renverra un nouveau curseur et un certain nombre de clés à l'utilisateur. Si vous souhaitez continuer à obtenir les clés restantes la prochaine fois, vous devez passer ce curseur à la commande scan pour continuer le processus d'itération précédent. #🎜🎜##🎜🎜#Pour faire simple, la commande scan
utilise la pagination pour interroger Redis. #🎜🎜##🎜🎜#Ce qui suit est un exemple du processus itératif de la commande scan : #🎜🎜##🎜🎜# La commande scan
utilise des curseurs pour diviser intelligemment une requête complète en plusieurs fois, réduisant ainsi la complexité des requêtes. #🎜🎜##🎜🎜#Bien que la complexité temporelle de la commande scan
soit la même que celle des clés
, les deux sont "O(N)", mais en raison de La commande scan
n'a besoin de renvoyer qu'un petit nombre de clés, la vitesse d'exécution sera donc très rapide. #🎜🎜##🎜🎜#Enfin, bien que la commande scan
résolve le problème des clés
, elle introduit également quelques autres défauts : #🎜🎜##🎜🎜## 🎜🎜 ##🎜🎜#Le même élément peut être retourné plusieurs fois, ce qui nécessite que notre application ajoute la fonction de gestion des éléments répétés. #🎜🎜#Pendant le processus d'itération, les éléments qui sont ajoutés à Redis, ou les éléments qui sont supprimés, peuvent ou non être renvoyés.
Les défauts ci-dessus doivent être pris en compte dans notre développement.
En plus de scan
, redis dispose de plusieurs autres commandes pour l'itération incrémentielle : scan
以外,redis 还有其他几个用于增量迭代命令:
sscan
:用于迭代当前数据库中的数据库键,用于解决 smembers
可能产生阻塞问题
hscan
命令用于迭代哈希键中的键值对,用于解决 hgetall
可能产生阻塞问题。
zscan
:命令用于迭代有序集合中的元素(包括元素成员和元素分值),用于产生 zrange
sscan
: utilisé pour itérer les clés de la base de données dans la base de données actuelle, utilisé pour résoudre l'éventuel problème de blocage des smembers
hscan
est utilisée pour itérer les paires clé-valeur dans la clé de hachage et est utilisée pour résoudre l'éventuel problème de blocage de hgetall
. #🎜🎜##🎜🎜##🎜🎜##🎜🎜#zscan
: La commande est utilisée pour itérer les éléments de l'ensemble ordonné (y compris les membres des éléments et les scores des éléments), et est utilisée pour générer zrange
peut provoquer des problèmes de blocage. #🎜🎜##🎜🎜##🎜🎜#Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!