Skip to content

Commit fd98494

Browse files
fix: 使用 SCAN 命令替代 KEYS 命令,避免大数据量时阻塞 Redis
问题: - KEYS 命令会阻塞 Redis,在大数据量场景下导致性能问题 - 多个线上故障与此相关 解决方案: - 使用 SCAN 命令替代 KEYS,非阻塞式遍历 影响文件: - sa-token-redis-template: 使用 RedisCallback + connection.scan() - sa-token-redis-template-jdk-serializer: 同上 - sa-token-redisx: 保持原有实现(redisx 库内部已使用 SCAN) - sa-token-jfinal-plugin: 使用 Jedis.scan() - sa-token-jboot-plugin: 使用 Jedis.scan() 参考: - https://redis.io/commands/scan/ - https://redis.io/commands/keys/
1 parent 0d2f2d4 commit fd98494

File tree

5 files changed

+75
-14
lines changed

5 files changed

+75
-14
lines changed

sa-token-plugin/sa-token-redis-template-jdk-serializer/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForRedisTemplate.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
import org.springframework.beans.factory.annotation.Autowired;
2121
import org.springframework.data.redis.connection.RedisConnectionFactory;
2222
import org.springframework.data.redis.core.StringRedisTemplate;
23+
import org.springframework.data.redis.core.RedisCallback;
24+
import org.springframework.data.redis.core.ScanOptions;
2325

2426
import java.util.ArrayList;
2527
import java.util.List;
26-
import java.util.Set;
2728
import java.util.concurrent.TimeUnit;
2829

2930
/**
@@ -151,8 +152,23 @@ public void updateTimeout(String key, long timeout) {
151152
*/
152153
@Override
153154
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
154-
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
155-
List<String> list = new ArrayList<>(keys);
155+
// 使用 SCAN 命令替代 KEYS,避免在大数据量时阻塞 Redis
156+
List<String> list = new ArrayList<>();
157+
String pattern = prefix + "*" + keyword + "*";
158+
159+
// 使用 RedisConnection 的 scan 方法进行非阻塞遍历
160+
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
161+
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(1000).build();
162+
try (org.springframework.data.redis.core.Cursor<byte[]> cursor = connection.scan(options)) {
163+
while (cursor.hasNext()) {
164+
list.add(new String(cursor.next()));
165+
}
166+
} catch (Exception e) {
167+
throw new RuntimeException("Redis scan error", e);
168+
}
169+
return null;
170+
});
171+
156172
return SaFoxUtil.searchList(list, start, size, sortType);
157173
}
158174

sa-token-plugin/sa-token-redis-template/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForRedisTemplate.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
import org.springframework.beans.factory.annotation.Autowired;
2121
import org.springframework.data.redis.connection.RedisConnectionFactory;
2222
import org.springframework.data.redis.core.StringRedisTemplate;
23+
import org.springframework.data.redis.core.RedisCallback;
24+
import org.springframework.data.redis.core.ScanOptions;
2325

2426
import java.util.ArrayList;
2527
import java.util.List;
26-
import java.util.Set;
2728
import java.util.concurrent.TimeUnit;
2829

2930
/**
@@ -150,8 +151,23 @@ public void updateTimeout(String key, long timeout) {
150151
*/
151152
@Override
152153
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
153-
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
154-
List<String> list = new ArrayList<>(keys);
154+
// 使用 SCAN 命令替代 KEYS,避免在大数据量时阻塞 Redis
155+
List<String> list = new ArrayList<>();
156+
String pattern = prefix + "*" + keyword + "*";
157+
158+
// 使用 RedisConnection 的 scan 方法进行非阻塞遍历
159+
stringRedisTemplate.execute((RedisCallback<Void>) connection -> {
160+
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(1000).build();
161+
try (org.springframework.data.redis.core.Cursor<byte[]> cursor = connection.scan(options)) {
162+
while (cursor.hasNext()) {
163+
list.add(new String(cursor.next()));
164+
}
165+
} catch (Exception e) {
166+
throw new RuntimeException("Redis scan error", e);
167+
}
168+
return null;
169+
});
170+
155171
return SaFoxUtil.searchList(list, start, size, sortType);
156172
}
157173

sa-token-plugin/sa-token-redisx/src/main/java/cn/dev33/satoken/dao/SaTokenDaoForRedisx.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ public void updateTimeout(String key, long timeout) {
121121

122122
/**
123123
* 搜索数据
124+
*
125+
* 注意:redisx 库的 keys() 方法内部已使用 SCAN 实现,不会阻塞 Redis
124126
*/
125127
@Override
126128
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
127129
Set<String> keys = redisBucket.keys(prefix + "*" + keyword + "*");
128130
List<String> list = new ArrayList<>(keys);
129131
return SaFoxUtil.searchList(list, start, size, sortType);
130132
}
131-
}
133+
}

sa-token-starter/sa-token-jboot-plugin/src/main/java/cn/dev33/satoken/jboot/SaTokenCacheDao.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
import io.jboot.support.redis.JbootRedisConfig;
2626
import io.jboot.utils.ConfigUtil;
2727
import redis.clients.jedis.Jedis;
28+
import redis.clients.jedis.ScanParams;
29+
import redis.clients.jedis.ScanResult;
2830

2931
import java.util.ArrayList;
3032
import java.util.List;
3133
import java.util.Map;
32-
import java.util.Set;
3334
import java.util.concurrent.ConcurrentHashMap;
3435

3536
/**
@@ -272,14 +273,24 @@ public void updateSessionTimeout(String sessionId, long timeout) {
272273

273274
@Override
274275
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
276+
// 使用 SCAN 命令替代 KEYS,避免在大数据量时阻塞 Redis
277+
List<String> list = new ArrayList<>();
278+
String pattern = prefix + "*" + keyword + "*";
279+
String cursor = ScanParams.SCAN_POINTER_START;
280+
ScanParams params = new ScanParams().match(pattern).count(1000);
281+
275282
Jedis jedis = saRedisCache.getJedis();
276283
try {
277-
Set<String> keys = jedis.keys(prefix + "*" + keyword + "*");
278-
List<String> list = new ArrayList<>(keys);
279-
return SaFoxUtil.searchList(list, start, size, sortType);
284+
do {
285+
ScanResult<String> result = jedis.scan(cursor, params);
286+
list.addAll(result.getResult());
287+
cursor = result.getCursor();
288+
} while (!cursor.equals(ScanParams.SCAN_POINTER_START));
280289
} finally {
281290
saRedisCache.returnResource(jedis);
282291
}
292+
293+
return SaFoxUtil.searchList(list, start, size, sortType);
283294
}
284295

285296

sa-token-starter/sa-token-jfinal-plugin/src/main/java/cn/dev33/satoken/jfinal/SaTokenDaoRedis.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
import com.jfinal.plugin.redis.Redis;
2323
import com.jfinal.plugin.redis.serializer.ISerializer;
2424
import redis.clients.jedis.Jedis;
25+
import redis.clients.jedis.ScanParams;
26+
import redis.clients.jedis.ScanResult;
2527

2628
import java.util.ArrayList;
2729
import java.util.List;
28-
import java.util.Set;
2930

3031
public class SaTokenDaoRedis implements SaTokenDaoBySessionFollowObject {
3132

@@ -240,8 +241,23 @@ public void updateObjectTimeout(String key, long timeout) {
240241
*/
241242
@Override
242243
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
243-
Set<String> keys = redis.keys(prefix + "*" + keyword + "*");
244-
List<String> list = new ArrayList<>(keys);
244+
// 使用 SCAN 命令替代 KEYS,避免在大数据量时阻塞 Redis
245+
List<String> list = new ArrayList<>();
246+
String pattern = prefix + "*" + keyword + "*";
247+
String cursor = ScanParams.SCAN_POINTER_START;
248+
ScanParams params = new ScanParams().match(pattern).count(1000);
249+
250+
Jedis jedis = getJedis();
251+
try {
252+
do {
253+
ScanResult<String> result = jedis.scan(cursor, params);
254+
list.addAll(result.getResult());
255+
cursor = result.getCursor();
256+
} while (!cursor.equals(ScanParams.SCAN_POINTER_START));
257+
} finally {
258+
close(jedis);
259+
}
260+
245261
return SaFoxUtil.searchList(list, start, size, sortType);
246262
}
247263

0 commit comments

Comments
 (0)