diff --git a/pom.xml b/pom.xml index 30503f001..b0a6988d6 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,13 @@ redis.clients jedis 3.6.0 + true + + + io.lettuce + lettuce-core + 6.2.3.RELEASE + true @@ -38,7 +45,7 @@ org.apache.shiro shiro-core - 1.10.0 + 1.11.0 diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java new file mode 100644 index 000000000..d95d56696 --- /dev/null +++ b/src/main/java/org/crazycake/shiro/LettuceRedisClusterManager.java @@ -0,0 +1,332 @@ +package org.crazycake.shiro; + +import io.lettuce.core.*; +import io.lettuce.core.cluster.ClusterClientOptions; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; +import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands; +import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; +import io.lettuce.core.cluster.api.sync.RedisClusterCommands; +import io.lettuce.core.cluster.models.partitions.ClusterPartitionParser; +import io.lettuce.core.cluster.models.partitions.Partitions; +import io.lettuce.core.codec.ByteArrayCodec; +import io.lettuce.core.support.ConnectionPoolSupport; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.crazycake.shiro.exception.PoolException; + +import java.time.Duration; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * @author Teamo + * @since 2022/05/19 + */ +public class LettuceRedisClusterManager implements IRedisManager { + + /** + * Comma-separated list of "host:port" pairs to bootstrap from. This represents an + * "initial" list of cluster nodes and is required to have at least one entry. + */ + private List nodes; + + /** + * Default value of count. + */ + private static final int DEFAULT_COUNT = 100; + + /** + * timeout for RedisClient try to connect to redis server, not expire time! unit seconds. + */ + private Duration timeout = RedisURI.DEFAULT_TIMEOUT_DURATION; + + /** + * Redis database. + */ + private int database = 0; + + /** + * Redis password. + */ + private String password; + + /** + * Whether to enable async. + */ + private boolean isAsync = true; + + /** + * The number of elements returned at every iteration. + */ + private int count = DEFAULT_COUNT; + + /** + * genericObjectPoolConfig used to initialize GenericObjectPoolConfig object. + */ + private GenericObjectPoolConfig> genericObjectPoolConfig = new GenericObjectPoolConfig<>(); + + /** + * GenericObjectPool. + */ + private volatile GenericObjectPool> genericObjectPool; + + /** + * ClusterClientOptions used to initialize RedisClient. + */ + private ClusterClientOptions clusterClientOptions = ClusterClientOptions.create(); + + private void initialize() { + if (genericObjectPool == null) { + synchronized (LettuceRedisClusterManager.class) { + if (genericObjectPool == null) { + RedisClusterClient redisClusterClient = RedisClusterClient.create(getClusterRedisURI()); + redisClusterClient.setOptions(clusterClientOptions); + StatefulRedisClusterConnection connect = redisClusterClient.connect(new ByteArrayCodec()); + genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> connect, genericObjectPoolConfig); + } + } + } + } + + private StatefulRedisClusterConnection getStatefulConnection() { + if (genericObjectPool == null) { + initialize(); + } + try { + return genericObjectPool.borrowObject(); + } catch (Exception e) { + throw new PoolException("Could not get a resource from the pool", e); + } + } + + private List getClusterRedisURI() { + Objects.requireNonNull(nodes, "nodes must not be null!"); + return nodes.stream().map(node -> { + String[] hostAndPort = node.split(":"); + RedisURI.Builder builder = RedisURI.builder() + .withHost(hostAndPort[0]) + .withPort(Integer.parseInt(hostAndPort[1])) + .withDatabase(database) + .withTimeout(timeout); + if (password != null) { + builder.withPassword(password.toCharArray()); + } + return builder.build(); + }).collect(Collectors.toList()); + } + + @Override + public byte[] get(byte[] key) { + if (key == null) { + return null; + } + byte[] value = null; + try (StatefulRedisClusterConnection connection = getStatefulConnection()) { + if (isAsync) { + RedisAdvancedClusterAsyncCommands async = connection.async(); + value = LettuceFutures.awaitOrCancel(async.get(key), timeout.getSeconds(), TimeUnit.SECONDS); + } else { + RedisAdvancedClusterCommands sync = connection.sync(); + value = sync.get(key); + } + } + return value; + } + + @Override + public byte[] set(byte[] key, byte[] value, int expire) { + if (key == null) { + return null; + } + try (StatefulRedisClusterConnection connection = getStatefulConnection()) { + if (isAsync) { + RedisAdvancedClusterAsyncCommands async = connection.async(); + if (expire > 0) { + async.set(key, value, SetArgs.Builder.ex(expire)); + } else { + async.set(key, value); + } + } else { + RedisAdvancedClusterCommands sync = connection.sync(); + if (expire > 0) { + sync.set(key, value, SetArgs.Builder.ex(expire)); + } else { + sync.set(key, value); + } + } + } + return value; + } + + @Override + public void del(byte[] key) { + try (StatefulRedisClusterConnection connection = getStatefulConnection()) { + if (isAsync) { + RedisAdvancedClusterAsyncCommands async = connection.async(); + async.del(key); + } else { + RedisAdvancedClusterCommands sync = connection.sync(); + sync.del(key); + } + } + } + + @Override + public Long dbSize(byte[] pattern) { + AtomicLong dbSize = new AtomicLong(0L); + + try (StatefulRedisClusterConnection connection = getStatefulConnection()) { + if (isAsync) { + RedisAdvancedClusterAsyncCommands async = connection.async(); + Partitions parse = ClusterPartitionParser.parse(LettuceFutures.awaitOrCancel(async.clusterNodes(), timeout.getSeconds(), TimeUnit.SECONDS)); + + parse.forEach(redisClusterNode -> { + RedisClusterAsyncCommands clusterAsyncCommands = async.getConnection(redisClusterNode.getNodeId()); + + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + while (!scanCursor.isFinished()) { + scanCursor = LettuceFutures.awaitOrCancel(clusterAsyncCommands.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS); + dbSize.addAndGet(scanCursor.getKeys().size()); + } + }); + } else { + RedisAdvancedClusterCommands sync = connection.sync(); + Partitions parse = ClusterPartitionParser.parse(sync.clusterNodes()); + + parse.forEach(redisClusterNode -> { + RedisClusterCommands clusterCommands = sync.getConnection(redisClusterNode.getNodeId()); + + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + while (!scanCursor.isFinished()) { + scanCursor = clusterCommands.scan(scanCursor, scanArgs); + dbSize.addAndGet(scanCursor.getKeys().size()); + } + }); + } + } + return dbSize.get(); + } + + @Override + public Set keys(byte[] pattern) { + Set keys = new HashSet<>(); + + try (StatefulRedisClusterConnection connection = getStatefulConnection()) { + if (isAsync) { + RedisAdvancedClusterAsyncCommands async = connection.async(); + Partitions parse = ClusterPartitionParser.parse(LettuceFutures.awaitOrCancel(async.clusterNodes(), timeout.getSeconds(), TimeUnit.SECONDS)); + + parse.forEach(redisClusterNode -> { + RedisClusterAsyncCommands clusterAsyncCommands = async.getConnection(redisClusterNode.getNodeId()); + + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + while (!scanCursor.isFinished()) { + scanCursor = LettuceFutures.awaitOrCancel(clusterAsyncCommands.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS); + keys.addAll(scanCursor.getKeys()); + } + }); + } else { + RedisAdvancedClusterCommands sync = connection.sync(); + Partitions parse = ClusterPartitionParser.parse(sync.clusterNodes()); + + parse.forEach(redisClusterNode -> { + RedisClusterCommands clusterCommands = sync.getConnection(redisClusterNode.getNodeId()); + + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + while (!scanCursor.isFinished()) { + scanCursor = clusterCommands.scan(scanCursor, scanArgs); + keys.addAll(scanCursor.getKeys()); + } + }); + } + } + return keys; + } + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public ClusterClientOptions getClusterClientOptions() { + return clusterClientOptions; + } + + public void setClusterClientOptions(ClusterClientOptions clusterClientOptions) { + this.clusterClientOptions = clusterClientOptions; + } + + public Duration getTimeout() { + return timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public int getDatabase() { + return database; + } + + public void setDatabase(int database) { + this.database = database; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isAsync() { + return isAsync; + } + + public void setIsAsync(boolean isAsync) { + this.isAsync = isAsync; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public GenericObjectPoolConfig> getGenericObjectPoolConfig() { + return genericObjectPoolConfig; + } + + public void setGenericObjectPoolConfig(GenericObjectPoolConfig> genericObjectPoolConfig) { + this.genericObjectPoolConfig = genericObjectPoolConfig; + } + + public GenericObjectPool> getGenericObjectPool() { + return genericObjectPool; + } + + public void setGenericObjectPool(GenericObjectPool> genericObjectPool) { + this.genericObjectPool = genericObjectPool; + } +} diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisManager.java new file mode 100644 index 000000000..5913ad259 --- /dev/null +++ b/src/main/java/org/crazycake/shiro/LettuceRedisManager.java @@ -0,0 +1,90 @@ +package org.crazycake.shiro; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.codec.ByteArrayCodec; +import io.lettuce.core.support.ConnectionPoolSupport; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.crazycake.shiro.common.AbstractLettuceRedisManager; +import org.crazycake.shiro.exception.PoolException; + +/** + * Singleton lettuce redis + * + * @author Teamo + * @since 2022/05/18 + */ +public class LettuceRedisManager extends AbstractLettuceRedisManager { + + /** + * Redis server host. + */ + private String host = "localhost"; + + /** + * Redis server port. + */ + private int port = RedisURI.DEFAULT_REDIS_PORT; + + /** + * GenericObjectPool. + */ + private volatile GenericObjectPool> genericObjectPool; + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void initialize() { + if (genericObjectPool == null) { + synchronized (LettuceRedisManager.class) { + if (genericObjectPool == null) { + RedisClient redisClient = RedisClient.create(createRedisURI()); + redisClient.setOptions(getClientOptions()); + GenericObjectPoolConfig genericObjectPoolConfig = getGenericObjectPoolConfig(); + genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(new ByteArrayCodec()), genericObjectPoolConfig); + } + } + } + } + + private RedisURI createRedisURI() { + RedisURI.Builder builder = RedisURI.builder() + .withHost(getHost()) + .withPort(getPort()) + .withDatabase(getDatabase()) + .withTimeout(getTimeout()); + String password = getPassword(); + if (password != null) { + builder.withPassword(password.toCharArray()); + } + return builder.build(); + } + + @Override + protected StatefulRedisConnection getStatefulConnection() { + if (genericObjectPool == null) { + initialize(); + } + try { + return genericObjectPool.borrowObject(); + } catch (Exception e) { + throw new PoolException("Could not get a resource from the pool", e); + } + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java b/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java new file mode 100644 index 000000000..c3308ab08 --- /dev/null +++ b/src/main/java/org/crazycake/shiro/LettuceRedisSentinelManager.java @@ -0,0 +1,121 @@ +package org.crazycake.shiro; + +import io.lettuce.core.ReadFrom; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.codec.ByteArrayCodec; +import io.lettuce.core.masterreplica.MasterReplica; +import io.lettuce.core.masterreplica.StatefulRedisMasterReplicaConnection; +import io.lettuce.core.support.ConnectionPoolSupport; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.crazycake.shiro.common.AbstractLettuceRedisManager; +import org.crazycake.shiro.exception.PoolException; + +import java.util.List; +import java.util.Objects; + +/** + * @author Teamo + * @since 2022/05/19 + */ +public class LettuceRedisSentinelManager extends AbstractLettuceRedisManager { + private static final String DEFAULT_MASTER_NAME = "mymaster"; + + private String masterName = DEFAULT_MASTER_NAME; + + private List nodes; + + private String sentinelPassword; + + private ReadFrom readFrom = ReadFrom.REPLICA_PREFERRED; + + /** + * GenericObjectPool. + */ + private volatile GenericObjectPool> genericObjectPool; + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void initialize() { + if (genericObjectPool == null) { + synchronized (LettuceRedisSentinelManager.class) { + if (genericObjectPool == null) { + RedisURI redisURI = this.createSentinelRedisURI(); + RedisClient redisClient = RedisClient.create(redisURI); + redisClient.setOptions(getClientOptions()); + StatefulRedisMasterReplicaConnection connect = MasterReplica.connect(redisClient, new ByteArrayCodec(), redisURI); + connect.setReadFrom(readFrom); + GenericObjectPoolConfig genericObjectPoolConfig = getGenericObjectPoolConfig(); + genericObjectPool = ConnectionPoolSupport.createGenericObjectPool(() -> connect, genericObjectPoolConfig); + } + } + } + } + + @Override + protected StatefulRedisMasterReplicaConnection getStatefulConnection() { + if (genericObjectPool == null) { + initialize(); + } + try { + return genericObjectPool.borrowObject(); + } catch (Exception e) { + throw new PoolException("Could not get a resource from the pool", e); + } + } + + private RedisURI createSentinelRedisURI() { + Objects.requireNonNull(nodes, "nodes must not be null!"); + + RedisURI.Builder builder = RedisURI.builder(); + for (String node : nodes) { + String[] hostAndPort = node.split(":"); + + RedisURI.Builder sentinelBuilder = RedisURI.Builder.redis(hostAndPort[0], Integer.parseInt(hostAndPort[1])); + + if (sentinelPassword != null) { + sentinelBuilder.withPassword(sentinelPassword.toCharArray()); + } + + builder.withSentinel(sentinelBuilder.build()); + } + + String password = getPassword(); + if (password != null) { + builder.withPassword(password.toCharArray()); + } + return builder.withSentinelMasterId(masterName).withDatabase(getDatabase()).build(); + } + + public String getMasterName() { + return masterName; + } + + public void setMasterName(String masterName) { + this.masterName = masterName; + } + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public String getSentinelPassword() { + return sentinelPassword; + } + + public void setSentinelPassword(String sentinelPassword) { + this.sentinelPassword = sentinelPassword; + } + + public ReadFrom getReadFrom() { + return readFrom; + } + + public void setReadFrom(ReadFrom readFrom) { + this.readFrom = readFrom; + } +} diff --git a/src/main/java/org/crazycake/shiro/RedisClusterManager.java b/src/main/java/org/crazycake/shiro/RedisClusterManager.java index a27326975..8b7ef6098 100644 --- a/src/main/java/org/crazycake/shiro/RedisClusterManager.java +++ b/src/main/java/org/crazycake/shiro/RedisClusterManager.java @@ -93,7 +93,7 @@ public void del(byte[] key) { @Override public Long dbSize(byte[] pattern) { - Long dbSize = 0L; + long dbSize = 0L; Map clusterNodes = getJedisCluster().getClusterNodes(); Iterator> nodeIt = clusterNodes.entrySet().iterator(); while (nodeIt.hasNext()) { @@ -115,7 +115,7 @@ public Set keys(byte[] pattern) { while (nodeIt.hasNext()) { Map.Entry node = nodeIt.next(); Set nodeKeys = getKeysFromClusterNode(node.getValue(), pattern); - if (nodeKeys == null || nodeKeys.size() == 0) { + if (nodeKeys.size() == 0) { continue; } keys.addAll(nodeKeys); diff --git a/src/main/java/org/crazycake/shiro/RedisManager.java b/src/main/java/org/crazycake/shiro/RedisManager.java index 180cb9a05..eef3fb435 100644 --- a/src/main/java/org/crazycake/shiro/RedisManager.java +++ b/src/main/java/org/crazycake/shiro/RedisManager.java @@ -17,7 +17,7 @@ public class RedisManager extends WorkAloneRedisManager implements IRedisManager private int database = Protocol.DEFAULT_DATABASE; - private JedisPool jedisPool; + private volatile JedisPool jedisPool; private void init() { if (jedisPool == null) { diff --git a/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java b/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java new file mode 100644 index 000000000..44ac427bb --- /dev/null +++ b/src/main/java/org/crazycake/shiro/common/AbstractLettuceRedisManager.java @@ -0,0 +1,238 @@ +package org.crazycake.shiro.common; + +import io.lettuce.core.*; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.sync.RedisCommands; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.crazycake.shiro.IRedisManager; + +import java.time.Duration; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @author Teamo + * @since 2022/05/19 + */ +public abstract class AbstractLettuceRedisManager implements IRedisManager { + + /** + * Default value of count. + */ + private static final int DEFAULT_COUNT = 100; + + /** + * timeout for RedisClient try to connect to redis server, not expire time! unit seconds. + */ + private Duration timeout = RedisURI.DEFAULT_TIMEOUT_DURATION; + + /** + * Redis database. + */ + private int database = 0; + + /** + * Redis password. + */ + private String password; + + /** + * Whether to enable async. + */ + private boolean isAsync = true; + + /** + * The number of elements returned at every iteration. + */ + private int count = DEFAULT_COUNT; + + /** + * ClientOptions used to initialize RedisClient. + */ + private ClientOptions clientOptions = ClientOptions.create(); + + /** + * genericObjectPoolConfig used to initialize GenericObjectPoolConfig object. + */ + @SuppressWarnings("rawtypes") + private GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig<>(); + + /** + * Get a stateful connection. + * + * @return T + */ + @SuppressWarnings("rawtypes") + protected abstract StatefulRedisConnection getStatefulConnection(); + + public Duration getTimeout() { + return timeout; + } + + public void setTimeout(Duration timeout) { + this.timeout = timeout; + } + + public int getDatabase() { + return database; + } + + public void setDatabase(int database) { + this.database = database; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isAsync() { + return isAsync; + } + + public void setIsAsync(boolean isAsync) { + this.isAsync = isAsync; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public ClientOptions getClientOptions() { + return clientOptions; + } + + public void setClientOptions(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public GenericObjectPoolConfig getGenericObjectPoolConfig() { + return genericObjectPoolConfig; + } + + public void setGenericObjectPoolConfig(GenericObjectPoolConfig genericObjectPoolConfig) { + this.genericObjectPoolConfig = genericObjectPoolConfig; + } + + @Override + @SuppressWarnings("unchecked") + public byte[] get(byte[] key) { + if (key == null) { + return null; + } + byte[] value = null; + try (StatefulRedisConnection connect = getStatefulConnection()) { + if (isAsync) { + RedisAsyncCommands async = connect.async(); + RedisFuture redisFuture = async.get(key); + value = LettuceFutures.awaitOrCancel(redisFuture, timeout.getSeconds(), TimeUnit.SECONDS); + } else { + RedisCommands sync = connect.sync(); + value = sync.get(key); + } + } + return value; + } + + @Override + @SuppressWarnings({"unchecked"}) + public byte[] set(byte[] key, byte[] value, int expire) { + if (key == null) { + return null; + } + try (StatefulRedisConnection connect = getStatefulConnection()) { + if (isAsync) { + RedisAsyncCommands async = connect.async(); + if (expire > 0) { + async.set(key, value, SetArgs.Builder.ex(expire)); + } else { + async.set(key, value); + } + } else { + RedisCommands sync = connect.sync(); + if (expire > 0) { + sync.set(key, value, SetArgs.Builder.ex(expire)); + } else { + sync.set(key, value); + } + } + } + return value; + } + + @Override + @SuppressWarnings("unchecked") + public void del(byte[] key) { + try (StatefulRedisConnection connect = getStatefulConnection()) { + if (isAsync) { + RedisAsyncCommands async = connect.async(); + async.del(key); + } else { + RedisCommands sync = connect.sync(); + sync.del(key); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public Long dbSize(byte[] pattern) { + long dbSize = 0L; + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + try (StatefulRedisConnection connect = getStatefulConnection()) { + while (!scanCursor.isFinished()) { + scanCursor = getKeyScanCursor(connect, scanCursor, scanArgs); + dbSize += scanCursor.getKeys().size(); + } + } + return dbSize; + } + + @Override + @SuppressWarnings("unchecked") + public Set keys(byte[] pattern) { + Set keys = new HashSet<>(); + KeyScanCursor scanCursor = new KeyScanCursor<>(); + scanCursor.setCursor(ScanCursor.INITIAL.getCursor()); + ScanArgs scanArgs = ScanArgs.Builder.matches(pattern).limit(count); + try (StatefulRedisConnection connect = getStatefulConnection()) { + while (!scanCursor.isFinished()) { + scanCursor = getKeyScanCursor(connect, scanCursor, scanArgs); + keys.addAll(scanCursor.getKeys()); + } + } + return keys; + } + + /** + * get scan cursor result + * + * @param connect connection + * @param scanCursor scan cursor + * @param scanArgs scan param + * @return KeyScanCursor + */ + private KeyScanCursor getKeyScanCursor(final StatefulRedisConnection connect, + KeyScanCursor scanCursor, + ScanArgs scanArgs) { + if (isAsync) { + RedisAsyncCommands async = connect.async(); + scanCursor = LettuceFutures.awaitOrCancel(async.scan(scanCursor, scanArgs), timeout.getSeconds(), TimeUnit.SECONDS); + } else { + RedisCommands sync = connect.sync(); + scanCursor = sync.scan(scanCursor, scanArgs); + } + return scanCursor; + } +} diff --git a/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java b/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java index 7352f6432..5c3ee3a98 100644 --- a/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java +++ b/src/main/java/org/crazycake/shiro/common/WorkAloneRedisManager.java @@ -117,9 +117,7 @@ public Long dbSize(byte[] pattern) { do { scanResult = jedis.scan(cursor, params); List results = scanResult.getResult(); - for (byte[] result : results) { - dbSize++; - } + dbSize += results.size(); cursor = scanResult.getCursorAsBytes(); } while (scanResult.getCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0); } finally { @@ -133,6 +131,7 @@ public Long dbSize(byte[] pattern) { * @param pattern key pattern * @return key set */ + @Override public Set keys(byte[] pattern) { Set keys = new HashSet(); Jedis jedis = getJedis(); diff --git a/src/main/java/org/crazycake/shiro/exception/PoolException.java b/src/main/java/org/crazycake/shiro/exception/PoolException.java new file mode 100644 index 000000000..9e5de632b --- /dev/null +++ b/src/main/java/org/crazycake/shiro/exception/PoolException.java @@ -0,0 +1,26 @@ +package org.crazycake.shiro.exception; + +/** + * @author Teamo + * @since 2022/05/18 + */ +public class PoolException extends RuntimeException { + /** + * Constructs a new LettucePoolException instance. + * + * @param msg the detail message. + */ + public PoolException(String msg) { + super(msg); + } + + /** + * Constructs a new LettucePoolException instance. + * + * @param msg the detail message. + * @param cause the nested exception. + */ + public PoolException(String msg, Throwable cause) { + super(msg, cause); + } +}