一:上篇大概介绍了rabbitMQ的基本信息和简单的发送接收,这篇文章说一下进阶一点的,也是在项目中运用的,仅供参考,代码复制可用。废话不多说,直接上代码。
二:实际上生产者发送消息,并不是直接和消息队列接触的,它是和交换机EXCHANGE交互,交换机负责将消息分发到绑定的各个消息队列。消费者想要消费消息,要先绑定交换机和相应的消息队列,也就是QUEUE。
1:生产者代码。代码读取一个文件,按行读取,按行发送。
//定义队列名称
private final static String QUEUE_NAME = "appreportdata_800021";
//定义交换机名称
private final static String EXCHANGE_NAME = "projectcode_exchange_direct";
public static void send(String content) {
ConnectionFactory factory = null;
Connection connection = null;
Channel channel = null;
try {
factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
connection = factory.newConnection();
channel.basicQos(1);
channel = connection.createChannel();
//绑定交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct",true);
//订阅消息
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//绑定队列
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "app_800021");
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
String rc1 = "";
String rc="utf-8";
try {
if(rc.equals("")){
rc1 = "GB2312";
}else{
rc1 = rc;
}
// reader = new BufferedReader(new FileReader(file));
File file = new File("文件路径");
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),rc1));
String tempString = null;
// 一次读入一行,直到读入null为文件结束
while ((tempString = reader.readLine()) != null) {
// 显示行号
System.out.println("line "+ tempString);
sb.append(tempString);
channel.confirmSelect();
channel.basicPublish(EXCHANGE_NAME, "app_800021", MessageProperties.PERSISTENT_BASIC, tempString.getBytes("UTF-8"));
channel.waitForConfirmsOrDie();
//sb.append("\n\r");
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
//System.out.println("已经发送消息....." + content);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
if (channel != null) {
// 关闭资源
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
2:消费者代码
//队列名称
String QUEUE_NAME="";
ConnectionFactory factory = null;
Connection connection = null;
Channel channel = null;
//交换机名称
private String EXCHANGE_NAME = "";
//路由键
private String ROUTE_KEY = "";
QUEUE_NAME = "appreportdata_800021";
EXCHANGE_NAME="projectcode_exchange_direct";
factory = new ConnectionFactory();
factory.setHost("ip");
try {
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTE_KEY);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
//获取队列中的内容
String content = new String(body, "UTF-8");
if (content != null && !("").equals(content)) {
if (!"".equals(tableName) && tableName != null && content.contains(tableName)) {
flag = false;
}
//消费信息逻辑处理,
//接收消息回复ack
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
//设置第二个参数为false 当noAck=false时,RabbitMQ会等待消费者显式发回
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException e) {
//e.printStackTrace();
} catch (TimeoutException e) {
}
生产消费端代码基本框架就是像这些,具体业务逻辑,可以具体处理。
3:这里还有一些问题,消费端消费信息的时候是线程阻塞的,没有消息的时候消费端是一直等着的,也就是说程序是一直在public void handleDelivery这个方法里停着,监听着rabbitMQ服务上相应的消息发布。这个是被动式的接受消息,那么问题来了,当接收的信息是sql语句的时候,接收是一条一条接收的,但是接收一条就进行一次数据库操作,显然是效率非常低的,这时会想着把数据存储在临时list里,批量操作,那么问题又来了,批量操作是有条件的,比如说达到500条的时候进行一次数据库操作,但是消费端发送700条,会有200条达不到条件,不会进行批量入库操作,但是此时程序是阻塞的,判断条件什么的不管用的,只会等着达到500条的时候才会批量操作入库,但是数据要求及时入库,这时就不行了。但是问题还是要解决的。
4:解决上面问题,rabbitMQ还有一种获取消息的方式,这是主动式的拉取数据,但是没有消息的时候会抛异常的。代码如下
GetResponse response = null;
factory = new ConnectionFactory();
factory.setHost(ConfParser.host);
factory.setAutomaticRecoveryEnabled(true);
factory.setNetworkRecoveryInterval(10);// 设置 每10s ,重试一次
factory.setTopologyRecoveryEnabled(false);// 设置不重新声明交换器,队列等信息。
try {
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTE_KEY);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
public void getMessageAndDoIt() {
boolean autoAck = false;
while (true) {
try {
response = channel.basicGet(QUEUE_NAME, autoAck);
byte[] body = response.getBody();
content = new String(body, "UTF-8");
sqlList.add(content);
//每500条数据提交一次
if (sqlList.size()==500) {
//执行入库动作
doActionSql();
}
long deliveryTag = response.getEnvelope().getDeliveryTag();
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
//正在执行接收消息任务的时候,channel出现异常,直接执行入库任务,保证sqlList内的数据不丢失
if (sqlList.size()>0) {
//执行入库动作
doActionSql();
}
try {
Thread.sleep(1000*60*2);
} catch ( InterruptedException e1) {
//休眠状态下连接断的话,休眠前数据已经全部进入入库步骤,不需考虑
e.printStackTrace();
}
}
}
}
大概说一下业务逻辑,关键就是这个 GetResponse 这个方法,response = channel.basicGet(QUEUE_NAME, autoAck);用死循环一直去消息队列里获取数据,当没有数据的时候会抛异常,这时候就可以在有异常的时候知道消息队列没有数据了,没有达到批量上限的数据也可以在异常处理里做入库操作了。完美解决。
代码仅供参考,如有不对的地方敬请谅解和指正。希望能给大家带来帮助。