RabbitMQ :
RabbitMQ官网介绍了,它支持六种应用场景:简单队列、工作队列、发布/订阅、路由模式、Topics主题模式、RPC,接下来分别介绍。
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.2.0</version>
</dependency>
MQ工具类
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* rabbitmq连接工具类
* @author Administrator
*
*/
public class ConnectionUtils {
/**
* 获取连接
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException{
ConnectionFactory factory = new ConnectionFactory();
// 设置服务地址
factory.setHost("127.0.0.1");
// 端口
factory.setPort(5672);
// vhost
factory.setVirtualHost("/vhost_test");
// 用户名
factory.setUsername("admin");
// 密码
factory.setPassword("123456");
return factory.newConnection();
}
/**
* 关闭连接
* @param channel
* @param con
*/
public static void close(Channel channel,Connection con){
if(channel != null){
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
if(con != null){
try {
con.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
MQ生产者
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.util.ConnectionUtils;
/**
* 简单消息队列——生产者
* @author Administrator
*
*/
public class Sender {
/**
* 队列名称
*/
private static final String QUEUE = "test_simple_queue";
public static void main(String[] args) {
Connection con = null;
Channel channel = null;
try {
// 获取连接
con = ConnectionUtils.getConnection();
// 从连接中创建通道
channel = con.createChannel();
// 声明一个队列
channel.queueDeclare(QUEUE, false, false, false, null);
// 消息内容
String msg = "simple queue hello!";
// 发送消息
channel.basicPublish("", QUEUE, null, msg.getBytes());
System.out.println("send success");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 关闭连接
ConnectionUtils.close(channel, con);
}
}
}
channel.queueDeclare()方法的作用是声明一个队列,它只在所声明的队列不存在的情况下生效,如果队列已经存在在不做任何操作,在此方法中具有如下参数:
String queue:队列名称。
boolean durable:是否持久化。队列模式是在内存中的,如果重启rabbitmq消息会丢失,如果设置为true,会保存到erlang自带的数据库,重启后可以恢复。
boolean exclusive:是否排外。作用一,连接关闭后是否自动删除当前队列;作用二,是否私有队列,如果为true,则其他通道不能访问当前队列。
boolean autoDelete:当所有消费者客户端断开连接时是否自动删除队列。
Map<String, Object> arguments:其他参数。
channel.basicPublish()方法的作用是发送消息到队列,它具有如下参数:
String exchange:交换机名称,简单队列用不到交换机,此处写""空字符串即可。
String routingKey:队列映射的路由key,此处就是队列名称。
BasicProperties props:消息的其他属性。
byte[] body:发送信息的主体。rabbitmq一般不用来发送大数据类型的消息。
消费者Recver
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.util.ConnectionUtils;
/**
* 简单队列——消费者
*
* @author Administrator
*
*/
public class Recver {
/**
* 队列名称,和生产者的队列名称必须保持一致
*/
private static final String QUEUE = "test_simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接
Connection con = ConnectionUtils.getConnection();
// 从连接中创建通道
Channel channel = con.createChannel();
// 声明队列
channel.queueDeclare(QUEUE, false, false, false, null);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
// 获取消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "utf-8");
System.out.println("接收到消息——" + msg);
}
};
// 监听队列
channel.basicConsume(QUEUE, true, consumer);
}
}
channel.basicConsume()方法的作用是监听消息队列,它具有三个参数:
- String queue:队列名称。
- boolean autoAck:是否自动应答,如果为false,需要我们手动通知服务器接收到消息了。
- Consumer callback:回调方法。
简单队列缺点
耦合度高,队列名在一端改动,另一端也要跟着改动。生产者和消费者一一对应,不支持多个消费者。
工作队列的逻辑就是队列拿到生产者的消息后会在消费者中选择一个把消息发送过去,并不是把消息同时发送给两个消费者。
工作队列消费方默认轮询方式接受消息。
// 每个消费者发送确认收到消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
// 限制发送给同一个消费者不超过1条消息
channel.basicQos(1);限制发送给同一个消费者不超过1条消息,每个消费者发送确认收到消息之前,消息队列不发送下一个消息到消费者,保证一次只处理一个消息。
添加channel.basicQos(1):保证一次只分发一条消息。
channel.basicAck(envelope.getDeliveryTag(), false):手动确认消息。false表示确认接收消息,true表示拒绝接收消息。
channel.basicConsume(QUEUE, false, consumer):设置自动应答为false。
rabbitmq不允许重新定义一个已存在的队列
kafka
Java操作Kafka
java操作kafka非常的简单,然后kafka也提供了很多缺省值,一般情况下我们不需要修改太多的参数就能使用。下面我贴出代码。
pom.xml
<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.2.0</version> </dependency>
import java.util.Properties;
import java.util.Random;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
public class Producer {
public static String topic = "duanjt_test";//定义主题
public static void main(String[] args) throws InterruptedException {
Properties p = new Properties();
p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.23.76:9092,192.168.23.77:9092");//kafka地址,多个地址用逗号分割
p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(p);
try {
while (true) {
String msg = "Hello," + new Random().nextInt(100);
ProducerRecord<String, String> record = new ProducerRecord<String, String>(topic, msg);
kafkaProducer.send(record);
System.out.println("消息发送成功:" + msg);
Thread.sleep(500);
}
} finally {
kafkaProducer.close();
}
}
}
注意:
1.kafka如果是集群,多个地址用逗号分割(,)
2.Properties的put方法,第一个参数可以是字符串,如:p.put("bootstrap.servers","192.168.23.76:9092")
3.kafkaProducer.send(record)可以通过返回的Future来判断是否已经发送到kafka,增强消息的可靠性。同时也可以使用send的第二个参数来回调,通过回调判断是否发送成功。
4.p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);设置序列化类,可以写类的全路径
import java.util.Collections; import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.serialization.StringDeserializer; public class Consumer { public static void main(String[] args) { Properties p = new Properties(); p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.23.76:9092"); p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); p.put(ConsumerConfig.GROUP_ID_CONFIG, "duanjt_test"); KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(p); kafkaConsumer.subscribe(Collections.singletonList(Producer.topic));// 订阅消息 while (true) { ConsumerRecords<String, String> records = kafkaConsumer.poll(100); for (ConsumerRecord<String, String> record : records) { System.out.println(String.format("topic:%s,offset:%d,消息:%s", // record.topic(), record.offset(), record.value())); } } } }
注意:
1.订阅消息可以订阅多个主题
2.ConsumerConfig.GROUP_ID_CONFIG表示消费者的分组,kafka根据分组名称判断是不是同一组消费者,同一组消费者去消费一个主题的数据的时候,数据将在这一组消费者上面轮询。
3.主题涉及到分区的概念,同一组消费者的个数不能大于分区数。因为:一个分区只能被同一群组的一个消费者消费。出现分区小于消费者个数的时候,可以动态增加分区。
4.注意和生产者的对比,Properties中的key和value是反序列化,而生产者是序列化。