基於spring boot+redis Stream 實現一個消息隊列

消息隊列添加消息和消費確認以及刪除消息

    /**
     * 消息隊列添加消息
     * @param message 隊列存儲消息
     * @param queueKey 隊列
     */
    public void addMessageBlockingQueue(String message,String queueKey){
        Record<String,String> record = StreamRecords.objectBacked(message).withStreamKey(queueKey);
        redisTemplate.opsForStream().add(record);
    }

    /**
     * 消息隊列消費確認
     * @param queueKey 消息隊列key
     * @param group 分組名稱
     * @param recordId 消息id
     * @return 成功或者失敗
     */
    public boolean messageQueueConsumptionAck(String queueKey,String group,RecordId recordId){
        try {
            Long result = redisTemplate.opsForStream().acknowledge(queueKey, group, recordId);
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    /**
     * 消息隊列刪除消息
     * @param queueKey 消息隊列key
     * @param recordId 消息id
     * @return
     */
    public boolean messageQueueConsumptionDelField(String queueKey, RecordId recordId){
        try {
            Long result = redisTemplate.opsForStream().delete(queueKey, recordId);
            if (SUCCESS.equals(result)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

創建消費者監聽處理類:

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component()
public class RedisStreamListener implements StreamListener<String, ObjectRecord<String,String>> {

    @Override
    public void onMessage(ObjectRecord<String, String> message) {
        log.info(message.toString());
		// 消息消費ack確認
        redisService.messageQueueConsumptionAck("key", "group", "recordId");
		// 消費完成消息直接刪除
		redisService.messageQueueConsumptionDelField("key", "recordId");
    }
}

創建消費者監聽類的訂閱配置:

import io.lettuce.core.RedisException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
import org.springframework.data.redis.stream.Subscription;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Slf4j
@RequiredArgsConstructor
@Configuration
public class Config {

    private final StringRedisTemplate redisTemplate;

    private final StreamListener<String, ObjectRecord<String, String>> streamListener;

    @Bean
    public Subscription subscription(RedisConnectionFactory factory) {
        checkGroup();
        // 創建Stream消息監聽容器配置
        StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> options = StreamMessageListenerContainer
                .StreamMessageListenerContainerOptions
                .builder()
                // 設置阻塞時間
                .pollTimeout(Duration.ofSeconds(1))
                // 配置消息類型
                .targetType(String.class)
                .build();
        // 創建Stream消息監聽容器
        StreamMessageListenerContainer<String, ObjectRecord<String, String>> listenerContainer = StreamMessageListenerContainer.create(factory, options);
        // 設置消費手動提交配置
        Subscription subscription = listenerContainer.receive(
                // 設置消費者分組和名稱
                Consumer.from("group", "consumer-1"),
                // 設置訂閱Stream的key和獲取偏移量,以及消費處理類
                StreamOffset.create("key", ReadOffset.lastConsumed()),
                streamListener);
        // 監聽容器啓動
        listenerContainer.start();
        return subscription;
    }

    /**
     * 由於訂閱需要先有stream,先做下檢查
     */
    private void checkGroup() {
        // 創建需要校驗的分組List
        List<String> consumers = new ArrayList<>();
        consumers.add("group");
        StreamInfo.XInfoGroups infoGroups = null;
        try {
            // 獲取Stream的所有組信息
            infoGroups = redisTemplate.opsForStream().groups(BaseConstant.SEND_MESSAGE_QUEUE_KEY);
        } catch (RedisSystemException | RedisException | InvalidDataAccessApiUsageException ex) {
            log.error("group key not exist or commend error", ex);
        }

        // 遍歷校驗分組是否存在
        for (String consumer : consumers) {
            boolean consumerExist = false;
            if (Objects.nonNull(infoGroups)) {
                if (infoGroups.stream().anyMatch(t -> Objects.equals(consumer, t.groupName()))) {
                    consumerExist = true;
                }
            }
            // 創建不存在的分組
            if (!consumerExist) {
                redisTemplate.opsForStream().createGroup("key", consumer);
            }
        }

    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章