Redisson中zset之scan命令使用

需求背景:第三方接口返回数据量太大,直接返回给前端容易导致页面崩溃,还不想存入数据库中,那么缓存是一个不错的选择,redis,es,都可以,此处尝试使用redis中的scan命令实现数据的模糊匹配搜索;

1、导入redisson依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.7</version>
</dependency>

2、注入spring

@Configuration
public class RedisConfig {
	@Value("${spring.redis.host:127.0.0.1}")
    private String host;
    @Value("${spring.redis.port:6379}")
    private int port;
    @Value("${spring.redis.timeout:0}")
    private int timeout;
    @Value("${spring.redis.password:#{null}}")
    private String password;


    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.setTransportMode(TransportMode.NIO);
        config.useSingleServer()
            .setAddress("redis://" + host + ":" + port)
            .setPassword(password)
            .setTimeout(timeout)
            // 重试次数3
            .setRetryAttempts(3)
            // 失败重试间隔1s
            .setRetryInterval(1000)
            // 心跳检测间隔1s
            .setPingConnectionInterval(1000);
        config.setCodec(new JsonJacksonCodec());
        return Redisson.create(config);
    }

 /**
     * 创建redisson缓存工厂
     *
     * @param client
     * @return
     */
    @Bean(name = "redissonConnectionFactory")
    public RedissonConnectionFactory getFactory(RedissonClient client) {
        var redissonConnectionFactory = new RedissonConnectionFactory(client);
        return redissonConnectionFactory;
    }

}

3、redisson工具类封装

@Component
@Slf4j
public class RedissonUtils {
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public boolean isExists(String key) {
        var bucket = redissonClient.getBucket(key);
        return bucket.isExists();
    }

   /**
     * 对value模糊搜索
     *
     * @param key   zset key
     * @param value 搜索条件
     * @return 前100条
     */
    public List<String> getRelateCustomerWord(String key, String value) {
        var scoredSortedSet = redissonClient.getScoredSortedSet(key);
        if (scoredSortedSet.isEmpty()) {
            return Lists.newArrayList();
        }
        // 无搜索条件,默认返回100条
        if (StringUtil.isBlank(value)) {
            var entries = scoredSortedSet.entryRange(0, 99);
            return entries.stream().map(v -> v.getValue().toString()).collect(Collectors.toList());
        }
        // 模糊匹配value,获取100条 
        var iterator = scoredSortedSet.iterator("*" + value + "*", 100);
        List<String> res = Lists.newArrayList();
        while (iterator.hasNext()) {
            var next = iterator.next();
            res.add(String.valueOf(next));
        }
        return res;
    }
	/**
     * 添加redisson分布式锁, 防止并发执行
     *
     * @param lockKey   锁名称
     * @param waitTime  获取锁的等待时间 单位:秒
     * @param leaseTime 持有锁时间 单位:秒
     * @param process   执行器
     * @return
     */
    public Object lockExecute(String lockKey, long waitTime, long leaseTime, RedissonLockProcess process) throws Exception {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 加锁
            boolean haveLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            // 获取锁
            if (haveLock) {
                try {
                    return process.execute();
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            } else {
                log.info("未获取到锁{},请重试", lockKey);
                throw new Exception("未获取到锁" + lockKey);
            }
        } catch (InterruptedException e) {
            log.info("尝试获取锁失败{}", lockKey, e);
            throw new Exception("尝试获取锁失败" + lockKey);
        }
    }
}
// 	函数式接口执行器
@FunctionalInterface
public interface RedissonLockProcess {
    Object execute() throws MpException;
}

zset中使用iterator对scan命令进行封装,查看redisson源码可知
在这里插入图片描述

4、项目实际应用

@Autowired
private RestTemplateClient restTemplateClient;
// 获取可用颜色下拉数据
public List<String> getAcceptedColors(ShopAuthParam param) throws Exception {
	var cacheColorKey = "color_zset";
      var isExists = redissonUtils.isExists(cacheColorKey);
      // 存在
      if (isExists) {
          // cache qry
          return redissonUtils.getRelateCustomerWord(cacheColorKey, param.getKeyword());
      } else {
      	// 此处使用分布式锁,保证数据只有一份,lockExecute方法在上边的工具类RedissonUtils已经封装好
          return (List<String>) redissonUtils.lockExecute("lock:color", 3, 10, () -> {
              // 再次判断是否已经添加进缓存
              var dataExist = redissonUtils.isExists(cacheColorKey);
              if (dataExist) {
                    // cache qry
          		    return redissonUtils.getRelateCustomerWord(cacheColorKey, param.getKeyword());
              } else {
                    // todo 从第三方接口获取
                    List<String> res = xxx;
                    // 存入redis zset
			        var scoredSortedSet = redissonClient.getScoredSortedSet(cacheColorKey);
			        var collect = res.stream().collect(Collectors.toMap(d -> (Object)d, v -> 0.0));
			        scoredSortedSet.addAll(collect);
			        // cache 12 hours
			        scoredSortedSet.expire(12, TimeUnit.HOURS);
			        return res.stream()
			            .filter(data -> data.contains(param.getKeyword()))
			            .limit(100).collect(Collectors.toList());
              }
          });
     }
 }

使用scoredSortedSet 而不使用SortedSet,LexSortedSet两种,是因为想增加分数累加功能,当使用某一下拉数据一次,就累加1,也就是在数据被使用后执行

scoredSortedSet.addScore(data, scoredSortedSet.getScore(data) + 1);

这样当用户使用某一下拉数据次数越多,他下次查询时显示的就越靠前;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LuciferWWP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值