AWS Redis禁止KEY *命令,本地正常云端报错求解决方案
问题:AWS Redis环境下执行KEY *命令报错及替代方案
我在从AWS Redis缓存读取所有键时触发异常,缓存写入操作已通过日志验证成功。AWS确实禁止执行KEY *命令,原因是该命令会加载大量数据,影响Redis性能。
报错信息
redis.clients.jedis.exceptions.JedisDataException: ERR unknown command 'keys', with args beginning with: *
该代码在本地Redis服务器上可正常运行,但在AWS Redis环境中失效。
相关配置代码
package myintiative.work.config; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @Slf4j public class RedisConfig { @Value("${redis.host}") private String host; @Value("${redis.port}") private int port; @Bean public JedisConnectionFactory jedisConnectionFactory() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port); JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder(); jedisClientConfiguration.usePooling(); return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build()); } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
服务代码
package myintiative.work.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.migration.mailconnectorsendgrid.dto.CustomerContactDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.Set; import java.util.List; import java.util.stream.Collectors; @Service public class AWSRedisCacheService { @Autowired RedisTemplate<String, String> redisTemplate; ObjectMapper objectMapper; AWSRedisCacheService() { objectMapper = new ObjectMapper(); } public void writeCache(List<CustomerContactDTO> list) { System.out.println("Total Number of Records="+list.size()); System.out.println("WRITING THE CACHE!!!!!!!!!!"); String serializedData; for (CustomerContactDTO c : list) { try { serializedData = objectMapper.writeValueAsString(c); } catch (JsonProcessingException e) { e.printStackTrace(); return; } System.out.println("KEY++++++++++++" + c.getUserEmail()); redisTemplate.opsForValue().set(c.getUserEmail(), serializedData); System.out.println("ADDED++++++++++++" + redisTemplate.opsForValue().get(c.getUserEmail())); } System.out.println("COMPLETED WRITING THE CACHE!!!!!!!!!!"); } public CustomerContactDTO readCache(String key) { String retrievedData = redisTemplate.opsForValue().get(key); if (retrievedData == null) { System.out.println("No data found for " + key); return null; } CustomerContactDTO deserializedData = null; try { deserializedData = objectMapper.readValue(retrievedData, new TypeReference<>() { }); } catch (JsonProcessingException e) { e.printStackTrace(); } return deserializedData; } public void removeCacheEntry(String key) { redisTemplate.delete(key); } public void updateCache(CustomerContactDTO c) { String serializedData; try { serializedData = objectMapper.writeValueAsString(c); } catch (JsonProcessingException e) { e.printStackTrace(); return; } redisTemplate.opsForValue().set(c.getUserEmail(), serializedData); } public List<String> readFirst50Entries() { System.out.println("READING THE CACHE!!!!!!!!!!"); Set<String> keys = redisTemplate.keys("*"); System.out.println("Total Entries:" + keys.size()); List<String> keysList = keys.stream().limit(50).collect(Collectors.toList()); System.out.println("EXITING READING THE CACHE!!!!!!!!!!"); return keysList; } }
我曾尝试使用替代命令SCAN 0 MATCH "*" COUNT 1000,但Spring RedisTemplate并不直接支持该命令,实现过程对我来说过于复杂,希望得到可行的解决方案。
解决方案:使用Spring Data Redis实现SCAN命令替代KEYS
AWS ElastiCache Redis默认禁用KEYS命令,而SCAN是官方推荐的渐进式键遍历方案,以下是两种在Spring Data Redis中实现SCAN的简洁方式:
方式1:通过RedisCallback执行SCAN(兼容所有Spring Data Redis版本)
修改readFirst50Entries方法,直接调用底层连接执行SCAN:
import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.Cursor; import java.nio.charset.StandardCharsets; import java.util.ArrayList; public List<String> readFirst50Entries() { System.out.println("READING THE CACHE!!!!!!!!!!"); List<String> keysList = new ArrayList<>(); redisTemplate.execute((RedisCallback<Void>) connection -> { // 配置扫描参数:匹配所有键,每次扫描提示返回1000个(实际数量可能波动) ScanOptions options = ScanOptions.scanOptions().match("*").count(1000).build(); Cursor<byte[]> cursor = connection.scan(options); int count = 0; while (cursor.hasNext() && count < 50) { // 将字节数组转为字符串键 String key = new String(cursor.next(), StandardCharsets.UTF_8); keysList.add(key); count++; } cursor.close(); return null; }); System.out.println("Total Entries Retrieved:" + keysList.size()); System.out.println("EXITING READING THE CACHE!!!!!!!!!!"); return keysList; }
方式2:使用RedisTemplate的scan方法(Spring Data Redis 2.1+)
如果你的Spring Data Redis版本在2.1及以上,可以直接用redisTemplate.scan()获取字符串类型的Cursor,无需手动处理字节数组:
import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.Cursor; import java.io.IOException; import java.util.ArrayList; public List<String> readFirst50Entries() { System.out.println("READING THE CACHE!!!!!!!!!!"); List<String> keysList = new ArrayList<>(); ScanOptions options = ScanOptions.scanOptions().match("*").count(1000).build(); // 使用try-with-resources自动关闭Cursor,避免资源泄漏 try (Cursor<String> cursor = redisTemplate.scan(options)) { int count = 0; while (cursor.hasNext() && count < 50) { keysList.add(cursor.next()); count++; } } catch (IOException e) { e.printStackTrace(); } System.out.println("Total Entries Retrieved:" + keysList.size()); System.out.println("EXITING READING THE CACHE!!!!!!!!!!"); return keysList; }
关键说明
SCAN是渐进式遍历,每次只返回部分键,不会长时间阻塞Redis服务,符合AWS ElastiCache的性能要求COUNT参数只是提示Redis每次扫描的键数量,实际返回结果可能少于该值,无需严格匹配- 如果你的键有统一前缀(比如
customer_),可以将match("*")改为match("customer_*"),减少扫描范围,提升效率
内容的提问来源于stack exchange,提问作者Sharad Nanda
相关产品推荐
相关产品推荐

