基于redis stream结构的RedisStreamUtil工具类

        这个基于redis stream 的工具类是基于RedisClient的客户端创建的,因为当初开始创建的时候考虑到可能需要分布式缓存,所以也创建了一个Redisson,但没用上。

1.目前几个其他的客户端优缺点在哪:

        1、redisTemplate是基于某个具体实现的再封装,比如说springBoot1.x时,具体实现是jedis;而到了springBoot2.x时,具体实现变成了lettuce。封装的好处就是隐藏了具体的实现,使调用更简单,但是有人测试过jedis效率要10-30倍的高于redisTemplate的执行效率,所以单从执行效率上来讲,jedis完爆redisTemplate。redisTemplate的好处就是基于springBoot自动装配的原理,使得整合redis时比较简单。
        2、jedis作为老牌的redis客户端,采用同步阻塞式IO,采用线程池时是线程安全的。优点是简单、灵活、api全面,缺点是某些redis高级功能需要自己封装,同时低版本的Jedis不支持stream操作,注意检查版本!!
        3、lettuce作为新式的redis客户端,基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis高级功能,例如集群、哨兵、管道等,缺点是api抽象,学习成本高。lettuce好是好,但是jedis比他生得早。
        4、redission作为redis的分布式客户端,同样基于netty采用异步非阻塞式IO,是线程安全的,优点是提供了很多redis的分布式操作和高级功能,缺点是api抽象,学习成本高,同时Redisson对字符串操作的支持较差,只是简单需求没必要用这个。

2.那用RedisClient的理由:


        它是Spring Data Redis 中的一个核心接口。提供了一种高级抽象来与 Redis 数据库进行交互。通过连接工厂(ConnectionFactory)创建与 Redis 的连接。提供了一些高级操作,如事务处理和发布/订阅。因为需要用到消息推送消费,所以选择用RedisClient。

3.那我们平常最常用的RedisTemplate和RedisClient区别又在哪?

RedisTemplate和RedisClient都是Spring Data Redis的一部分,它们提供了一种Java对象操作Redis的模板类。RedisTemplate是RedisTemplate类,它继承自AbstractRedisTemplate类,主要封装了Redis的字符串类型操作相关的内容,包括序列化、反序列化、存储、读取等。RedisTemplate中提供了对Redis的CRUD操作的一些封装,使用起来比较简单,你只需要注入一个Redis连接对象(如Jedis连接对象),就可以通过各种方法(如读写数据的方法)直接对Redis进行操作了。

RedisClient是RedisClientInterface的子接口,Spring Data Redis对Redis的访问核心是RedisTemplate,而RedisTemplate底层实现是通过RedisConnection连接Redis进行操作的。

总之,RedisTemplate和RedisClient都是Spring Data Redis的一部分,它们提供了一种Java对象操作Redis的模板类。其中,RedisTemplate主要封装了Redis的字符串类型操作相关的内容,提供了对Redis的CRUD操作的一些封装;而RedisClient是RedisClientInterface的子接口,底层实现是通过RedisConnection连接Redis进行操作的。

4.上代码:

public class RedisStreamUtil {

    private RedisCommands<String, String> redisCommands;
    private RedissonClient redissonClient;
    @Value("${spring.redis.host}") String host;
    @Value("${spring.redis.port}") int port;
    @Value("${spring.redis.password}") String password;

    // 创建一个本地缓存
    @Autowired
    CacheManager caffeineManage;

    // 配置redisCommands
    public RedisStreamUtil(@Value("${spring.redis.host}") String host,
                           @Value("${spring.redis.port}") int port,
                           @Value("${spring.redis.password}") String password) {
        RedisURI redisUri = RedisURI.create(host, port);
        redisUri.setPassword(password);
        RedisClient redisClient = RedisClient.create(redisUri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        this.redisCommands = connection.sync();
        this.redisCommands.select(15);
//        this.redisCommands.setTimeout(Duration.ofDays(0));

        // Redisson配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        config.useSingleServer().setPassword(password);
        // 创建Redisson客户端
        redissonClient = Redisson.create(config);
    }

    // 创建一个 RedisCommands 实例
    public RedisCommands<String, String> getRedisCommands() {
        return this.redisCommands;
    }



//在redisclient中,重复进行同一个创建消费组命令,会报一个消费者已存在的错误,在某些redis客户端是不会报这个错误的,所以这里需要进行一个判断

    //1.生成消费者组
    public void generateGroup(String consumerGroup, String streamKey) {
        //当不存在consumerGroup这个消费者组时
        if (isAliveGroup(consumerGroup, streamKey)) {
            // 创建消费者组,偏移量为0
            redisCommands.xgroupCreate(XReadArgs.StreamOffset.from(streamKey, "0-0"), consumerGroup);
        }
    }


    //2.生成消息并进行消费
    public Boolean consumeAndProcessMessages(String streamKey, String consumerGroup, Map<String, String> dataMap) {
        boolean result = true;

        // 生成消息并写入Redis Stream
        generateMessageAndWriteToStream(streamKey, dataMap);

        // 消费消息
        result = consumeMessageFromStream(streamKey, consumerGroup);

        return result;
    }
    //生成消息并写入Redis Stream
    public void generateMessageAndWriteToStream(String streamKey, Map<String, String> dataMap) {
        redisCommands.xadd(streamKey, dataMap);
    }

    // 消费消息
    public Boolean consumeMessageFromStream(String streamKey, String consumerGroup) {
        List<StreamMessage<String, String>> myConsumer = redisCommands.xreadgroup(
                Consumer.from(consumerGroup, "Consumer"),
//阻塞等待为无限等待,最大读取数量为10000,>意思是从最新消息开始读取
                XReadArgs.Builder.count(10000).block(Duration.ofDays(0)),
                XReadArgs.StreamOffset.from(streamKey, ">")

        );
        if (myConsumer.isEmpty()) {
            throw new EmptyStackException();
        }

        for (StreamMessage<String, String> entry : myConsumer) {
            String id = entry.getId();
            Map<String, String> data = entry.getBody();
            for (Map.Entry<String, String> a : data.entrySet()) {
//业务需要,这里进行的是一个对本地缓存的删除
                deleteLocalDatabase(a.getValue());
            }
//对消息进行消费确认
            redisCommands.xack(streamKey, consumerGroup, id);
//删除这条消息
//            long xdel = redisCommands.xdel(streamKey, id.toString());
//            if(xdel!=1){
//                return false;
//            }
        }
        return true;
    }

    //这个方法就是直接返回消费流中值
    public String consumeApplicationMessageFromStream(String streamKey, String consumerGroup) {
        System.out.println("等待中");
        List<StreamMessage<String, String>> myConsumer = redisCommands.xreadgroup(
                Consumer.from(consumerGroup, "Consumer"),
                XReadArgs.Builder.count(10000).block(Duration.ZERO),
                XReadArgs.StreamOffset.from(streamKey, ">")
        );
        if (myConsumer.isEmpty()) {
            return null;
        }
        String value = null;
        for (StreamMessage<String, String> entry : myConsumer) {
            String id = entry.getId();
            Map<String, String> data = entry.getBody();
            for (Map.Entry<String, String> a : data.entrySet()) {
                 value = a.getValue();
            }
            redisCommands.xack(streamKey, consumerGroup, id);
            long xdel = redisCommands.xdel(streamKey, id);
            if(xdel!=1){
                return null;
            }
        }
        return value;
    }


    //判断流是否存在
    private boolean isStreamExists(String streamKey) {
        Long exists = redisCommands.exists(streamKey);
        return exists != null && exists > 0;
    }




//但在redisclient中,没有一个直接判断流中是否存在某个消费组的命令,所以需要通过获取流中streamkey中消费者组信息来间接获取其中的消费组名称,这里因为定的逻辑只需要一个消费者组,所以可以直接对返回的数据进行分割取值,如果有多个消费者组,可以反其道行之,尝试删除一个不存在的消费者组中的消费者,如果消费者组不存在,则该命令会返回一个错误,如果报错,就证明这个消费者组不存在哈哈


    //判断流中是否已存在消费者组
    public Boolean isAliveGroup(String consumerGroup, String streamKey) {
        // 1. 如果流不存在,创建流
        if (!isStreamExists(streamKey)) {
            HashMap<String, String> temp = new HashMap<>();
//这一部分判断流是否存在貌似不太需要,重复创建流和重复创建消费组一样,有的客户端不会报错
//因为没有办法创建一个空流,所以需要往里加一个消息初始化后再删除这个消息
            //1.1对流进行初始化
            temp.put("start", "stratvlaue");
            String xadd = redisCommands.xadd(streamKey, temp);
            redisCommands.xdel(streamKey, xadd);
        }

        //2.  获得流中的所有消费组
        List<Object> response = null;
        try {
            response = redisCommands.xinfoGroups(streamKey);
        } catch (RedisCommandExecutionException e) {
            // 处理命令执行异常
            return false;
        }
        if (response.isEmpty()) {
            return true; // 没有消费者组,返回true
        }
        //3.判断消费组是否已重复
        String str = response.get(0).toString();
        str = str.substring(1, str.length() - 1);
        String[] values = str.split(", ");

        List<String> list = new ArrayList<>();
            //3.1将返回的数据中的消费者组取出来
        for (String value : values) {
            list.add(value.trim()); // 添加到集合中,并去除首尾的空格
        }
        return !list.contains(consumerGroup);
    }

//当需要加锁操作时可以用这个方法,重写run逻辑即可
    public void protectedOperation(String lockKey, Runnable operation) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock();
            operation.run();
        } finally {
            lock.unlock();
        }
    }

    // 根据id 删除Redis缓存
    public void deleteRedisCache(String key) {
        redisCommands.del(key);
    }


    //根据key删除本地缓存
    public  Boolean deleteLocalDatabase(String id) {
        //连接的指定的缓存实例
        //这个缓存实例是自己指定生成的
        //其他场景生成了本地缓存,指定存在这个实例中,所以删除时需要指定删除哪个实例中的缓存
        Cache dicData = caffeineManage.getCache("dicData");
        //删除对应key
        boolean b = dicData.evictIfPresent(id);
        return b;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值