You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:25:55