一、使用原因
我们在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方法进行测试。
从中我们可以看出,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!");
}
}
当我们使用测试方法来执行这段代码的时候,可以发现数据被正常的传输出去了。如下图所示
至此使用Redis实现消息队列的全部过程搭建完毕。