一、使用原因

我们在JAVA开发的过程中有的时候我们会有这样一个需求:两个进程之间进行交互。这也就涉及到了一个消息队列的概念。

他主要是用于让请求方(生产者)将其需要告知被请求方(消费者,订阅者)的消息放入一个中间件中,被请求方将会去监听这个中间件来获得需要的信息。

我们知道,现如今的消息队列产品有很多。例如:RabbitMq,ZeroMq,ActiveMq,Redis等等。

最近我在进行一次小型开发的时候,发现我需要让两个服务器之间进行交流。当然我们最简单的方法也可以让被请求方去暴露一个接口。请求方去写一个Http请求来进行调用。但是这样并不是很有趣。所以我阅读了相关知识,想去试一试消息队列。

一开始我尝试使用了RabbitMq,但是我的机器原因,他的运行效率并不是很高,经常会有延迟。

而后我想起了他的定义:他本身支持许多协议,如:AMQP,XMPP,SMTP,STOMP。这导致他直接变成了一个重量型框架。

这时我想起了Redis。

Redis作为一个有名的Nosql程序。他支持将信息放入内存中等待处理。那么这也就是实现消息队列的原理。

 

二、简要描述

在RabbitMq中,其本身的结构大致可以理解为:

1、生产者将数据放入指定的Exchange

2、Exchange将信息根据其内部设定放入指定的Queue

3、消费者连接Queue获取数据

在Redis中有所不同:

1、生产者将数据放入Channel

2、消费者从Channel中取出数据

从中我们可以看出RabbitMq的功能明显更为强大,其可以更简易的定制Exchange的各种设定

但是我自己的项目并没有那么高的要求,只会给服务器增加更多的负担。所以Redis已经足够使用了。

 

三、具体实现

1、导入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
	<version>1.5.3.RELEASE</version>
</dependency>

2、编写配置文件

在Springboot中可以使用yml格式的配置文件进行配置,其具体配置如下

server:
  port: 8096
spring:
  redis:
    host: "自己的Redis主机"
    port: 6379
    password: ""
    pool:
        //最大空闲连接数
      max-idle: 100
        //最大连接数
      max-active: 100
        //最小空闲连接数
      min-idle: 1
        //最大等待毫秒数(-1为不确定)
      max-wait: -1

3、代码实现

首先我们要将刚才在配置文件里写好的数据加载到Spring中

@Component
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisBean {

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

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

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

    @Value("${spring.redis.pool.max-idle:100}")
    private int maxIdle;

    @Value("${spring.redis.pool.max-active:100}")
    private int maxActive;

    @Value("${spring.redis.pool.min-idle:1}")
    private int minIdle;

    @Value("${spring.redis.pool.max-wait:-1}")
    private int maxWait;

}

其中需要注意,如果想要使用@ConfigurationProperties注解或者使用@Data注解,需要如下依赖

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>

接下来我们要根据配置去将Jedis注入容器中。

@Configuration
public class RedisConfig {

    @Autowired
    private RedisBean redisBean;

    @Bean
    public JedisPool getJedisPool() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(redisBean.getMaxIdle());
        config.setMaxTotal(redisBean.getMaxActive());
        config.setMaxWaitMillis(redisBean.getMaxWait());

        String password = redisBean.getPassword();
        if (password == null || "".equals(password)) {
            password = null;
        }
        //10000为超时时间
        JedisPool pool = new JedisPool(config, redisBean.getHost(), redisBean.getPort(),
                10000,
                password);
        System.out.println(">> redis初始化");
        return pool;
    }

}

我们现在拥有了JedisPool实例了,接下来要开始正式实现消息队列了。

Jedis为我们暴露了一个subscribe接口。用来监听指定channel的数据。将其放到一个继承JedisPubSub的处理类中进行处理。

我们接到了数据需要进行处理,所以我们需要一个数据处理类。这个数据处理类需要继承JedisPubSub,重写onMessage方法。

@Component
public class MqHandler extends JedisPubSub {

    @Override
    public void onMessage(String channel, String message) {
        System.out.println(">> 接收到了来自 " + channel + " 的消息: " + message);
    }


}

好了,我们这下拥有数据处理类了,这里我写的比较简单,在这一层中大家可以继承任何的业务类,让其数据可以被处理。

我们现在将处理类与channel绑定在一起

@Component
public class MqThread extends Thread {


    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private MqHandler mqHandler;

    @Override
    public void run() {
        Jedis jedis = jedisPool.getResource();
        jedis.subscribe(mqHandler, "channel1", "channel2");
    }


}

我们希望Springboot在启动的时候自动运行这个子进程,所以我们可以这样做。

编写一个实现了ApplicationRunner接口的类。

//继承ApplicationRunner的内容会在项目启动时自动运行
@Component
public class ActiveThread implements ApplicationRunner {

    @Autowired
    private MqThread mqThread;

    @Override
    public void run(ApplicationArguments args) {
        mqThread.start();
    }
}

这时我们的子进程将会在Springboot被启动时自动运行。

好了我们现在进行一下测试

我们用两个端口去启动相同的项目。8096-8097。

此时我们调用redis的publish方法进行测试。

springboot 获取redis监控数据 springboot监听redis消息队列_redis

springboot 获取redis监控数据 springboot监听redis消息队列_Mq_02

springboot 获取redis监控数据 springboot监听redis消息队列_Mq_03

从中我们可以看出,redis的publish功能是将数据放入channel,然后分发到每一个监听该channel的进程。

类似于RabbitMq的fanout机制。

在我们发布消息之后出现的数字表示当前监听该channel的进程数量。

那么我们对于接收端的编写就此完成。

 

发送端的代码在Redis的配置方面与接收端完全相同,所以不再赘述。我只将发布数据的部分展现出来。

此处使用了Spring的Test方法。

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

	@Autowired
	RedisConfig redisConfig;

	@Test
	public void publishData() {
		Jedis jedis = redisConfig.getJedisPool().getResource();
		jedis.publish("channel1", "message!");
	}

}

当我们使用测试方法来执行这段代码的时候,可以发现数据被正常的传输出去了。如下图所示

springboot 获取redis监控数据 springboot监听redis消息队列_Redis_04

至此使用Redis实现消息队列的全部过程搭建完毕。