1.订阅模式
作用类似与微信公众号,你订阅了就可以接收到消息
解读:
1.一个生产者,多个消费者。
2.每一个消费者 都有自己的队列
3.生产者没有直搂把洧息发送到队列而是发到了交换机 转发器exchange
4.每个队列都要绑定到交换机上。
5.生产者发送的消息经过交换机到达臥列 就能实现一个消息到多个消费者消费
1.2分发模式fanout
该模式下,生产者发送消息,多个消费者通过他们对应的队列获得消息,但是所获得的消息是一样的,不能指定将哪个消息给那个队列
创建一个生产者
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
----声明一个交换机
----fanout:分发模式
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
String msg = "hello exchange";
发送消息到交换机
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
channel.close();
connection.close();
}
}
创建第一个消费者
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
声明一个对列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
将队列绑定到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答 true为开启 false为关闭
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
创建第二个消费者
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
------创建一个连接
Connection connection = ConnectionUtil.getConnection();
------创建一个通道
Channel channel = connection.createChannel();
------创建一个队列,用户名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
------将队列绑定到交换机
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
------一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
------手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
------自动应答 true为开启 false为关闭
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
2.Exchange交换机
作用:一方面是接收生产者的消息,另一方面是向队列推送消息。
2.1Fanout(不处理路由健)
匿名转发。
Fanout(不处理路由健):对消息不作处理,消息不能指定发给特定的消费者
2.2Direct路由模式
交换机给队列发送消息时会在后面加一个Key,只有队列的Key和交换机的Key对应上,交换机才会把消息发给队列,如下图,交换机有error,info,warng三个Key,error的key对应c1和c2,所以带有error的key的消息就会发送给c1和c2
创建一个生产者
绑定key到交换机
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
-----声明一个交换机
-----fanout:分发模式 channel.exchangeDeclare(EXCHANGE_NAME,"direct");
String msg = "hello direct";
-----发送消息到交换机,绑定key到交换机
String key ="love";
channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());
channel.close();
connection.close();
}
}
创建第一个消费者
绑定key到交换机
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明一个对列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机,绑定key到交换机
String key ="error";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答 true为开启 false为关闭
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
创建第二个消费者
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//创建一个通道
Channel channel = connection.createChannel();
//创建一个队列,用户名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机,绑定key到交换机
String key1 = "error";
String key2 = "info";
String key3 = "love";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key1);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key2);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key3);
//一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答 true为开启 false为关闭
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
2.3Toplc exchanger主题模式
Toplc exchanger:将路由键和某模式匹配,
#匹配一个或者多个。
‘*’匹配一个。
第二个消费者:
注意我的key值: String key =“love.#”;
#代表多个,只要生产者发给交换机的key是love开头,这个消费者就能接收到
创建一个生产者:
注意我的key值:String key =“love.del”;
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"topic");//fanout:分发模式
String msg = "hello direct";
//发送消息到交换机
String key ="love.del";
channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());
channel.close();
connection.close();
}
}
创建第一个消费者
注意我的key值: String key =“love.add”;
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//声明一个对列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机
String key ="love.add";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答 true为开启 false为关闭
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
创建第二个消费者:
注意我的key值: String key =“love.#”;
#代表多个,只要生产者发给交换机的key是love开头,这个消费者就能接收到
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//创建一个通道
Channel channel = connection.createChannel();
//创建一个队列,用户名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//将队列绑定到交换机
String key = "love.#";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次应答前只发送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手动应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自动应答 true为开启 false为关闭
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
消息确认机制
在rabbitmq中我们可以通过持久化数据解决rabbitmq服务器异常的数据丢失问题。
问题:生产者将消息发送出去之后,消息到底有没有到达rabbitmq 服务器默认的情况是不知道的;
两种方式可以知道:
1.AMQP实现的事务机制
2.Confirm横式
AMQP实现的事务机制
AMQP提供了三个方法
channel.txSelect();-----开启事物:在消息发送前开启
channel.txCommit();-----提交事物:在消息发送后提交
channel.txRollback();-----事物回滚:如果有异常就回滚,处理异常
这种模式增加了服务器的请求量,使服务器处理的效率降低
public class send {
private static final String QUEUE_NAME="MXH";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建一个消息队列 QUEUE_NAME:队列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try
{
String msg = "hello,rqbbitmq你好";
channel.txSelect();-----开启事物
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
int i =1/0;
channel.txCommit();-----提交事物
}catch (Exception e)
{
channel.txRollback();-----事物回滚
System.out.println("发送异常,消息未送达");
}
channel.close();
connection.close();
}
}
Confirm横式
原理
生产者将信道设置成confirm横式,一旦信道进入confirm横式,所有在该信道上面发布的洧息都会被指很一个唯一的ID(从1开始)。一旦洧息被投递到所有匹配的队列之后,broker就会发送-一个确认给生产者(包含消息的唯一ID) .这就使得生产者知道消息已经正确到达目的队列了。如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker 固传给生产者的确认消息中deliver-tag 域包含了确认消息的序列号,此外broker也可以设置basic.ack 的multiple城,表示到这个序列号之前的所有消息都已经得到了处理。。
Confirm摸式最大的好处在于他是异步。
串行模式
channel.confirmSelect();//开启confirm模式
channel.waitForConfirms():该方法在生产者发送消息到队列后会返回true
if(channel.waitForConfirms())
{
System.out.println(“发送成功”);
}else
{
System.out.println(“发送失败”);
}
普通模式:该模式一次发送一条消息到队列,一发一回
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建一个消息队列 QUEUE_NAME:队列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try {
channel.confirmSelect();//开启confirm模式
String msg = "hello,rqbbitmq你好";
int i =1/0;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
if(channel.waitForConfirms())
{
System.out.println("发送成功");
}else
{
System.out.println("发送失败");
}
}catch (Exception e)
{
System.out.println("发送失败");
}
channel.close();
connection.close();
}
}
批量模式:该模式用for循环发送多条数据,多发一回,发完所有的消息服务器回一条是否收到消息
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建一个消息队列 QUEUE_NAME:队列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try {
channel.confirmSelect();//开启confirm模式
String msg = "hello,rqbbitmq你好";
int i =1/0;
for(int p =0;p<10;p++)
{
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
if(channel.waitForConfirms())
{
System.out.println("发送成功");
}else
{
System.out.println("发送失败");
}
}catch (Exception e)
{
System.out.println("发送失败");
}
channel.close();
connection.close();
}
}
异步模式
原理
Channel对象提供的Confirmlistener回调方法只包含deliveryTag (当前Chanel发出的消息序号)。我们需要自己为每一个Channel維护一个unconfirm的消息序号集合,每publish 一条数据,集合中元素加1.每回调一次handleAck方法,unconfirm 集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。
创建一个生产者:
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException
{
//创建一个连接
Connection connection = ConnectionUtil.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//创建一个消息队列 QUEUE_NAME:队列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.confirmSelect();//开启confirm模式
----未确认的消息标识
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
----添加通道监听
channel.addConfirmListener(new ConfirmListener()
{
@Override
-----消息发送成功
public void handleAck(long deliveryTag , boolean mulpitle) throws IOException
{
if (mulpitle) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag );
}
}
@Override
-----消息发送失败
public void handleNack(long deliveryTag , boolean mulpitle) throws IOException {
if (mulpitle) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag );
}
}
});
String msg = "hello,rqbbitmq你好";
while(true)
{
long setNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
confirmSet.add(setNo);
}
// channel.close();
// connection.close();
}
}