10. SDR 阻塞队列实现生产者消费者模式

Redis 默认的发布订阅模式, 当有消息产生时, 所有订阅该频道channel的客户端都会接收到消息。 有时我们希望一个消息只能被一个客户端消费,也就是类似于生产者消费者模式,我们可以借助于redis 的阻塞队列来实现. 借助于redis 阻塞队列实现的生产者消费者模式,有以下特点:

  • 一条消息只能被一个客户端消费,在并发应用中也是如此
  • 当队列中没有消息时,队列会处于阻塞状态. 当有消息后,重新开始消费.

1. 消费者开发

1.1 消费线程

  • 定义一个守护线程,用于从redis 阻塞队列中获取消息

public class ConsumerDemonThread implements Runnable {


    // redis 操作模板
    private StringRedisTemplate stringRedisTemplate;


    // 阻塞队列名称
    private String QUEUE_NAME = "block-queue";

    public ConsumerDemonThread(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public void run() {

        while (true) {

            try {
                // 从redis 阻塞队列读取消息
                String message = readMessageFromBlockingQueue();

                // 执行消息任务
                doMessageTask(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * @Description: 执行消息任务
     * @param message 消息
     * @author: zongf
     * @time:  2019-03-13 14:10:43
     */
    private void doMessageTask(String message){
        if (message != null) {
            System.out.println(Thread.currentThread().getName() + "-开始消费消息: " + message);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * @Description: 从阻塞队列中读取消息
     * @return: String 消息
     * @author: zongf
     * @time:  2019-03-13 14:11:15
     */
    private String readMessageFromBlockingQueue(){

       // 阻塞读取redis 队列
       Object resultList = stringRedisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {

                StringRedisConnection redisConnection = (StringRedisConnection) connection;

                // 阻塞读入redis 队列, 永不超时.
                return ((StringRedisConnection) connection).bLPop(Integer.MAX_VALUE, QUEUE_NAME);
            }
        });

       // 格式化读取到的结果
       List<String> list = (List<String>) resultList;

       // 如果获取结果为null, 正常不会出现这种情况
        if (list == null || list.size() < 2) {
            return null;
        }

        // 读取返回的list 结构时[队列名称, value] 结构, 而并非时单独的value.
        if (QUEUE_NAME.equals(list.get(0))) {
            return list.get(1);
        }

        return null;
    }
}

1.2 守护线程启动器

  • @Component 将启动器注册为Spring 的一个bean
  • @PostConstruct 定义项目启动时执行的初始化方法
@Component
public class ConsumerDemonThreadPoolStarter {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 项目启动时, 启动守护线程池
    @PostConstruct
    public void init(){

        int corePoolSize = 5;
        int maxPoolSize = 5;
        int keepAliveTime = 3000;
        int threadNum = 5;

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingDeque<>(Integer.MAX_VALUE));

        for (int i = 0; i < threadNum; i++) {
            threadPoolExecutor.execute(new ConsumerDemonThread(stringRedisTemplate));
        }
    }
}

2. 生产者开发

生产者开发相对简单,就调用list相关的API向list中push 消息即可.

2.1 java 进行生产者测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerTest {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    public void test() throws InterruptedException {

        ListOperations<String, String> ops = redisTemplate.opsForList();

        for (int i = 65; i < 90; i++) {
            char c = (char) i;
            ops.leftPush("block-queue", "" + c);

            Thread.sleep(1000);
        }
    }
}

2.2 redis-cli 命令行

127.0.0.1:6379> LPUSH block-queue A
(integer) 1
127.0.0.1:6379> LPUSH block-queue B
(integer) 1

2.3 测试结果

pool-1-thread-5-开始消费消息: A
pool-1-thread-4-开始消费消息: B
pool-1-thread-1-开始消费消息: C
pool-1-thread-2-开始消费消息: D
pool-1-thread-3-开始消费消息: E
pool-1-thread-5-开始消费消息: K
pool-1-thread-4-开始消费消息: L
pool-1-thread-1-开始消费消息: M
pool-1-thread-2-开始消费消息: N
pool-1-thread-3-开始消费消息: O
pool-1-thread-5-开始消费消息: U
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值