使用java scan 大量的redis的key

1、redis配置

import com.soul.data.lettuce.config.AbstractSpringDataRedisConfig;
import com.soul.data.redis.core.StringRedisTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Component
@PropertySource("redis.properties")
public class RedisConfig extends AbstractSpringDataRedisConfig {

    @Value("${user.version.redis.host}")
    private String host;

    @Value("${user.version.redis.port}")
    private int port;

    @Value("${user.version.redis.password}")
    private String password;

    @Bean(name = "userVersionRedisTemplate")
    public StringRedisTemplate userVersionRedisTemplate() {
        return redisTemplateChangedCommandTimeout(host, port, password);
    }

    protected LettuceConnectionFactory redisConnectionFactoryChangedCommandTimeout(String hostName, Integer port, String password) {
        RedisStandaloneConfiguration redisConf = new RedisStandaloneConfiguration();
        redisConf.setHostName(hostName);
        redisConf.setPort(port);
        redisConf.setPassword(RedisPassword.of(password));
        //命令3秒超时(默认60秒)
        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofSeconds(3))
                .poolConfig(defaultPoolConfig())
                .build();
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisConf, lettuceClientConfiguration);
        lettuceConnectionFactory.afterPropertiesSet();
        return lettuceConnectionFactory;
    }

    protected StringRedisTemplate redisTemplateChangedCommandTimeout(String hostName, Integer port, String password) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(this.redisConnectionFactoryChangedCommandTimeout(hostName, port, password));
        template.afterPropertiesSet();
        return template;
    }

    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return defaultKeyGenerator();
    }

}

2、scan redis的数据并设置过期时间

import com.soul.data.redis.core.RedisTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

@RestController
public class ExpiredController implements EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(ExpiredController.class);

    public static int EXPIRE_SECOND = 130 * 24 * 60 * 60;

    @Resource(name = "userVersionRedisTemplate")
    private RedisTemplate<String, String> userVersionRedisTemplate;

    @RequestMapping("/expired")
    public String expired(HttpServletRequest request, Integer count) {
        count = Objects.isNull(count) ? 5000 : count;
        String remoteHost = request.getRemoteHost();
        if (!"127.0.0.1".equals(remoteHost)) {
            return "0";
        }

        logger.info("expire data,count:{}", count);
        String keyPattern = "u:v:*";
        Integer result = doExpired(userVersionRedisTemplate, keyPattern, count);
        logger.info("expire finished,keyPattern:{},count:{},result:{}", keyPattern, count, result);
        return String.valueOf(result);
    }

    private Integer doExpired(RedisTemplate<String, String> redisTemplate, String keyPattern, Integer count) {
        Integer total = redisTemplate.execute(new RedisCallback<Integer>() {
            @Override
            public Integer doInRedis(RedisConnection connection) throws DataAccessException {
                Integer res = 0;
                ScanOptions.ScanOptionsBuilder builder = new ScanOptions.ScanOptionsBuilder();
                ScanOptions scanOptions = builder.match(keyPattern)
                        .count(count).build();

                Cursor<byte[]> cursor = connection.scan(scanOptions);
                long cursorId = cursor.getCursorId();
                long position = cursor.getPosition();
                logger.info("scan data,keyPattern:{},count:{},cursorId:{} position:{}", keyPattern, count, cursorId, position);

                while (cursor.hasNext()) {
                    //紧急情况如redis支持不住,通过apollo改成true,退出循环
                    boolean interrupt = env.getProperty("interrupt", Boolean.class, Boolean.FALSE);
                    if (interrupt) {
                        logger.info("aborted program,keyPattern:{},count:{},cursorId:{} position:{},res:{}", keyPattern, count, cursorId, position, res);
                        return res;
                    }

                    byte[] bytes = cursor.next();
                    Long expireSecond = connection.ttl(bytes);
                    boolean result = false;
                    if (expireSecond == null || expireSecond.intValue() == -1) {
                        result = connection.expire(bytes, EXPIRE_SECOND);
                        res++;
                        if (res % 10000 == 0) {
                            try {
                                Thread.sleep(20);
                                logger.info("thread sleep 20ms");
                            } catch (InterruptedException e) {
                                logger.error("thread sleep error", e);
                            }
                        }
                    }

                    if (res != 0 && res % 1000 == 0) {
                        logger.info("expire result:{},key:{},res:{}", result, new String(bytes), res);
                    }
                }

                try {
                    cursor.close();
                } catch (Exception e) {
                    logger.error("close cursor error", e);
                }
                return res;
            }
        });

        return total;
    }

    private Environment env;

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中通过 Redis SCAN 命令可以获取所有的 key,SCAN 命令可以配合游标(cursor)进行分批次获取,以避免一次性获取过多的 key 导致性能问题。以下是使用 Jedis 和 Lettuce 两个 Redis 客户端库中的方法实现的示例代码: 使用 Jedis 客户端库: ```java // 创建 Jedis 实例 Jedis jedis = new Jedis("localhost", 6379); // 初始化游标 String cursor = ScanParams.SCAN_POINTER_START; // 创建 ScanParams 实例 ScanParams params = new ScanParams().count(1000); // 循环扫描所有 key while (true) { // 执行 SCAN 命令 ScanResult<String> result = jedis.scan(cursor, params); // 获取当前批次的游标和 key 列表 cursor = result.getStringCursor(); List<String> keys = result.getResult(); // 对 key 列表进行处理 for (String key : keys) { System.out.println(key); } // 如果游标为 0,则说明已经扫描完成 if (cursor.equals(ScanParams.SCAN_POINTER_START)) { break; } } // 关闭 Jedis 连接 jedis.close(); ``` 使用 Lettuce 客户端库: ```java // 创建 RedisClient 实例 RedisClient client = RedisClient.create("redis://localhost"); // 创建 RedisConnection 实例 RedisConnection<String, String> connection = client.connect(); // 初始化游标 String cursor = ScanCursor.INITIAL.getValue(); // 创建 ScanArgs 实例 ScanArgs args = ScanArgs.Builder.limit(1000); // 循环扫描所有 key while (true) { // 执行 SCAN 命令 RedisAdvancedClusterAsyncCommands<String, String> asyncCommands = connection.async(); RedisFuture<ScanResult<String>> future = asyncCommands.scan(cursor, args); ScanResult<String> result = future.get(); // 获取当前批次的游标和 key 列表 cursor = result.getCursor(); List<String> keys = result.getResult(); // 对 key 列表进行处理 for (String key : keys) { System.out.println(key); } // 如果游标为 0,则说明已经扫描完成 if (cursor.equals(ScanCursor.INITIAL.getValue())) { break; } } // 关闭 RedisConnection 连接 connection.close(); // 关闭 RedisClient 连接 client.shutdown(); ``` 需要注意的是,SCAN 命令会消耗一定的性能,因此需要根据实际情况来调整游标的大小和扫描的频率。同时,SCAN 命令是一个近似算法,可能会漏扫或者重复扫描一些 key,因此在实际使用中需要进行进一步的判断和处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值