Skip to content

Commit 1ce473f

Browse files
sheinbergonHeartSaVioR
authored andcommitted
Implemented a HashTag version of SCAN methods for Redis Cluster (redis#1335)
Based on http://www.paluch.biz/blog/162-iterate-over-all-keys-in-a-redis-cluster.html. This implementation supports scanning for "Hash Tag" prefixed based patterns ( i.e. {TAG}* )
1 parent dacc339 commit 1ce473f

8 files changed

Lines changed: 120 additions & 16 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
*.iml
33
*.ipr
44
*.iws
5+
nb*
56
.project
67
.settings/
78
.gradle/

src/main/java/redis/clients/jedis/BinaryJedisCluster.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Set;
1818

1919
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
20+
import redis.clients.util.JedisClusterHashTagUtil;
2021

2122
public class BinaryJedisCluster implements BinaryJedisClusterCommands,
2223
MultiKeyBinaryJedisClusterCommands, JedisClusterBinaryScriptingCommands, Closeable {
@@ -1749,7 +1750,30 @@ public List<GeoRadiusResponse> execute(Jedis connection) {
17491750
}
17501751
}.runBinary(key);
17511752
}
1753+
1754+
@Override
1755+
public ScanResult<byte[]> scan(final byte[] cursor, final ScanParams params) {
1756+
1757+
String matchPattern = null;
1758+
1759+
if (params == null || (matchPattern = params.match()) == null || matchPattern.isEmpty()) {
1760+
throw new IllegalArgumentException(BinaryJedisCluster.class.getSimpleName() + " only supports SCAN commands with non-empty MATCH patterns");
1761+
}
17521762

1763+
if (JedisClusterHashTagUtil.isClusterCompliantMatchPattern(matchPattern)) {
1764+
1765+
return new JedisClusterCommand< ScanResult<byte[]>>(connectionHandler,
1766+
maxRedirections) {
1767+
@Override
1768+
public ScanResult<byte[]> execute(Jedis connection) {
1769+
return connection.scan(cursor, params);
1770+
}
1771+
}.runBinary(SafeEncoder.encode(matchPattern));
1772+
} else {
1773+
throw new IllegalArgumentException(BinaryJedisCluster.class.getSimpleName() + " only supports SCAN commands with MATCH patterns containing hash-tags ( curly-brackets enclosed strings )");
1774+
}
1775+
}
1776+
17531777
@Override
17541778
public ScanResult<Map.Entry<byte[], byte[]>> hscan(final byte[] key, final byte[] cursor) {
17551779
return new JedisClusterCommand<ScanResult<Map.Entry<byte[], byte[]>>>(connectionHandler,

src/main/java/redis/clients/jedis/JedisCluster.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
1919

2020
import redis.clients.jedis.params.set.SetParams;
21+
import redis.clients.util.JedisClusterHashTagUtil;
22+
import redis.clients.util.SafeEncoder;
2123

2224
public class JedisCluster extends BinaryJedisCluster implements JedisClusterCommands,
2325
MultiKeyJedisClusterCommands, JedisClusterScriptingCommands {
@@ -1209,6 +1211,29 @@ public Long execute(Jedis connection) {
12091211
}.run(key);
12101212
}
12111213

1214+
@Override
1215+
public ScanResult<String> scan(final String cursor, final ScanParams params) {
1216+
1217+
String matchPattern = null;
1218+
1219+
if (params == null || (matchPattern = params.match()) == null || matchPattern.isEmpty()) {
1220+
throw new IllegalArgumentException(JedisCluster.class.getSimpleName() + " only supports SCAN commands with non-empty MATCH patterns");
1221+
}
1222+
1223+
if (JedisClusterHashTagUtil.isClusterCompliantMatchPattern(matchPattern)) {
1224+
1225+
return new JedisClusterCommand< ScanResult<String>>(connectionHandler,
1226+
maxRedirections) {
1227+
@Override
1228+
public ScanResult<String> execute(Jedis connection) {
1229+
return connection.scan(cursor, params);
1230+
}
1231+
}.runBinary(SafeEncoder.encode(matchPattern));
1232+
} else {
1233+
throw new IllegalArgumentException(JedisCluster.class.getSimpleName() + " only supports SCAN commands with MATCH patterns containing hash-tags ( curly-brackets enclosed strings )");
1234+
}
1235+
}
1236+
12121237
@Override
12131238
public ScanResult<Entry<String, String>> hscan(final String key, final String cursor) {
12141239
return new JedisClusterCommand<ScanResult<Entry<String, String>>>(connectionHandler,
Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,63 @@
11
package redis.clients.jedis;
22

3+
import java.nio.ByteBuffer;
34
import static redis.clients.jedis.Protocol.Keyword.COUNT;
45
import static redis.clients.jedis.Protocol.Keyword.MATCH;
6+
import redis.clients.jedis.Protocol.Keyword;
57

68
import java.util.ArrayList;
79
import java.util.Collection;
810
import java.util.Collections;
11+
import java.util.EnumMap;
912
import java.util.List;
13+
import java.util.Map;
1014

1115
import redis.clients.util.SafeEncoder;
1216

1317
public class ScanParams {
14-
private List<byte[]> params = new ArrayList<byte[]>();
18+
19+
private final Map<Keyword, ByteBuffer> params = new EnumMap<Keyword, ByteBuffer>(Keyword.class);
20+
1521
public final static String SCAN_POINTER_START = String.valueOf(0);
1622
public final static byte[] SCAN_POINTER_START_BINARY = SafeEncoder.encode(SCAN_POINTER_START);
1723

1824
public ScanParams match(final byte[] pattern) {
19-
params.add(MATCH.raw);
20-
params.add(pattern);
25+
params.put(MATCH, ByteBuffer.wrap(pattern));
2126
return this;
2227
}
2328

2429
public ScanParams match(final String pattern) {
25-
params.add(MATCH.raw);
26-
params.add(SafeEncoder.encode(pattern));
30+
params.put(MATCH, ByteBuffer.wrap(SafeEncoder.encode(pattern)));
2731
return this;
2832
}
2933

30-
public ScanParams count(final int count) {
31-
params.add(COUNT.raw);
32-
params.add(Protocol.toByteArray(count));
34+
public ScanParams count(final Integer count) {
35+
params.put(COUNT, ByteBuffer.wrap(Protocol.toByteArray(count)));
3336
return this;
3437
}
3538

3639
public Collection<byte[]> getParams() {
37-
return Collections.unmodifiableCollection(params);
40+
List<byte[]> paramsList = new ArrayList<byte[]>(params.size());
41+
for (Map.Entry<Keyword, ByteBuffer> param : params.entrySet()) {
42+
paramsList.add(param.getKey().raw);
43+
paramsList.add(param.getValue().array());
44+
}
45+
return Collections.unmodifiableCollection(paramsList);
46+
}
47+
48+
String match() {
49+
if (params.containsKey(MATCH)) {
50+
return new String(params.get(MATCH).array());
51+
} else {
52+
return null;
53+
}
54+
}
55+
56+
Integer count() {
57+
if (params.containsKey(COUNT)) {
58+
return params.get(COUNT).getInt();
59+
} else {
60+
return null;
61+
}
3862
}
3963
}

src/main/java/redis/clients/jedis/commands/BinaryJedisClusterCommands.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ List<GeoRadiusResponse> georadius(byte[] key, double longitude, double latitude,
263263
List<GeoRadiusResponse> georadiusByMember(byte[] key, byte[] member, double radius, GeoUnit unit,
264264
GeoRadiusParam param);
265265

266+
ScanResult<byte[]> scan(final byte[] cursor, final ScanParams params);
267+
266268
ScanResult<Map.Entry<byte[], byte[]>> hscan(byte[] key, byte[] cursor);
267269

268270
ScanResult<Map.Entry<byte[], byte[]>> hscan(byte[] key, byte[] cursor, ScanParams params);

src/main/java/redis/clients/jedis/commands/JedisClusterCommands.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ Set<String> zrevrangeByLex(final String key, final String max, final String min,
235235

236236
Long bitcount(final String key, long start, long end);
237237

238+
ScanResult<String> scan(final String cursor, final ScanParams params);
239+
238240
ScanResult<Map.Entry<String, String>> hscan(final String key, final String cursor);
239241

240242
ScanResult<String> sscan(final String key, final String cursor);

src/main/java/redis/clients/util/JedisClusterCRC16.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,7 @@ private JedisClusterCRC16(){
3636
}
3737

3838
public static int getSlot(String key) {
39-
int s = key.indexOf("{");
40-
if (s > -1) {
41-
int e = key.indexOf("}", s + 1);
42-
if (e > -1 && e != s + 1) {
43-
key = key.substring(s + 1, e);
44-
}
45-
}
39+
key = JedisClusterHashTagUtil.getHashTag(key);
4640
// optimization with modulo operator with power of 2
4741
// equivalent to getCRC16(key) % 16384
4842
return getCRC16(key) & (16384 - 1);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package redis.clients.util;
2+
3+
/**
4+
* Holds various methods/utilities to manipualte and parse redis hash-tags. See <a
5+
* href="http://redis.io/topics/cluster-spec">Cluster-Spec : Keys hash tags</a>
6+
*/
7+
public final class JedisClusterHashTagUtil {
8+
9+
private JedisClusterHashTagUtil() {
10+
throw new InstantiationError("Must not instantiate this class");
11+
}
12+
13+
public static String getHashTag(String key) {
14+
return extractHashTag(key, true);
15+
}
16+
17+
public static boolean isClusterCompliantMatchPattern(String matchPattern) {
18+
String tag = extractHashTag(matchPattern, false);
19+
return tag != null && !tag.isEmpty();
20+
}
21+
22+
private static String extractHashTag(String key, boolean returnKeyOnAbsence) {
23+
int s = key.indexOf("{");
24+
if (s > -1) {
25+
int e = key.indexOf("}", s + 1);
26+
if (e > -1 && e != s + 1) {
27+
return key.substring(s + 1, e);
28+
}
29+
}
30+
return returnKeyOnAbsence ? key : null;
31+
}
32+
}

0 commit comments

Comments
 (0)