消息队列rabbitmq

AMQP:advanced message queuing protocol

协议模型

  • publisher
  • consumer
  • broker
  • virtual host
  • exchange
  • message queue
  • binding
  • channel
channel:
几乎所有的操作都在channel中进行,客户端可建立多个channel,每个channel代表一个会话任务
message:
由properties和body组成。properties可以对消息进行修饰,比如消息的优先级、延迟等高级特性;
body就是消息体的内容
virtual host:
逻辑隔离,一个virtual host可以有若干个exchange和queue,同一个virtual host不能有相同名称
的exchange和queue
exchange:
接受消息,根据路由键转发消息到绑定的队列

binding:
exchange和queue中的虚拟连接,binding中可以包含routing key

routing key:
queue:

端口号

amqp:5672
cluster:25672
management:15672

入门demo

生产端

connectionfactory,setHost、port、virtualHost
connection
connection.createChannel
channel.basicPublish(exchange,routingKey,props,body)

channel.close
connection.close

消费端

// exclusive:独占,这个队列只有我一个channel可监听
// autoDelete:队列脱离exchange,是否自动删除
channel.queueDeclare(queue,durable,exclusive,autoDelete,arguments)
QueuingConsumer queueingConsumer = new QueueingConsumer(channel)
// autoAck:是否自动签收
channel.basicConsume(queueName,autoAck,queueingConsumer)
// 阻塞获取消息
Delivery delivery = queueingConsumer.nextDelivery()
delivery.getBody()
delivery.getProperties()

// Envelop envelop = delivery.getEnvelop();

binding

exchange和queue之间连接关系,
exchange和exchange之间也可以
binding中可以包含routingKey或者参数

queue

durability
auto delete:最后一个监听被移除,是否自动删除

message

properties和body组成,
可以自定义属性,使用headers,
常用属性:delivery model
其他属性:
content_Type、content_encoding、priority
correlation_id、

Map headsMap = new ...
headsMap.put("my1","111")
AMQP.BasicProperties = 链式编程
	.deliveryMode(2) // 2为持久化消息
	.contentEncoding("utf-8")
	.expiration("15000") // 过期时间,15秒
	.headers(headsMap) //自定义的属性
	.build()

exchange

  • exchange类型
direct、topic、fanout、headers

channel.exchangeDeclare(exchangeName,exchangeType,...)
channel.queueBind(queueName,exchangeName,routingKey)

topicExchange:可以使用通配符进行模糊匹配
# :一个或多个词,
* :一个词

fanoutExchange:不处理任何路由键,只需简单地将队列绑定到交换机上
Topic类型:
消费者这边:
routingKey = "user.#"
channel.queueBind(queueName,exchangeName,routingKey)

生产者这边:
routingKey = "user.save"
channel.basicPublish(exchangeName,routingKey...,msg)
durability:是否需要持久化
autoDelete:当最后一个绑定到exchange的队列删除后,自动删除该exchange

如何保证百分之百消息投递成功

什么是生产端的可靠性投递?
1.消息成功发出
2.MQ节点的成功接收
3.发送端收到broker的确认应答
4.完善的消息补偿机制

  • 解决方案
1.消息落库,对消息状态进行打标
2.消息延迟投递,二次确认,回调检查
消息落库,对消息状态进行打标:
1.消息先不发出去,先进行数据入库
2.发送消息到broker
3.broker confirm,消息队列返回确认消息
4.producer端异步监听第三步的返回确认消息
6.监听到消息成功消费,修改数据库中消息状态

可能出的状况:
producer5分钟都没收到确认:那么使用分布式定时任务进行一次重新发送

重新发送次数大于3次,那么我们定为消息发送失败,要人工进行去补偿,
消息延迟投递,二次确认,回调检查:
1.多了一个callbackservice
2.下游服务消费完消息后,向一个队列里发送确认,callback服务会监听这条消息
3.监听到后会将这条消息持久化到数据库

上游服务发送第一条消息之后,会接着发送第二条消息,这条消息用于询问callbackservice下游服务是否成功消费
5.callbackservice监听第二条询问消息,然后告诉上游服务,成功消费了

可能状况:
询问之后没有发现成功消费,那么重新再次发送两条消息,直到成功为止

confirm消息确认机制

channel.confirmSelect() // 消息投递模式:消息确认模式
channel.addConfirmListener(new ConfirmListener() {
	HandlerNack...{
		确认消息未成功送达
	}
	handlerack...{
		确认消息成功送达
	}
}) 

// 还有一种可能就是返回的确认丢失,需要一个分布式定时任务重新发送未成功的消息

return listener,用于处理一些不可路由的消息

mandatory:为true,监听器会接受到路由不可达消息,然后人工后续处理,为false,broker端自动删除该消息

channel.addReturnListener(new ReturnListener(){
	HandlerReturn(...){

	}
})

幂等性,如何避免消息的重复消费

update count = count -1 ,version = version+1
where version = 1

解决方案:

1.数据库去重机制
2.Redis,判断是否已经存在

消费端自定义监听

consumer.nextDelivery太low

channel.basicConsume(queueName,true,new MyConsumer(channel))
MyConsumer extends DefaultConsumer {
	构造(channel)

	handlerDelivery(consumerTag,Envelop,properties,body){
	}
}

消费端限流

qos(服务质量保证):非自动确认消息的前提下,如果一定数目的消息未被确认前,不进行消费新的消息。

//prefetchSize:消息大小,消费端一般不做限制,生产端会做
//prefetchCount:一次最多处理多少条消息,一般设置为1,一旦有N个消息还没ack,该consumer将block住
//global:true在channel级别,false在consumer级别
// prefetchSize和global这两项,rabbitmq还没实现,prefetchCount在no_ask=false时生效,必须手工签收,而不是自动签收
void basicQoS(unit prefetchSize,prefetchCount,global)
channel.basicQos(0:不限制消息大小,prefetchCount=1false:应用到consume级别)
// autoAck = false关闭自动签收
channel.basicConsume(...autoAck = false ...)
修改MyConsumer:
channel.basicAck(envelop.getDeliveryTag(),false:不批量签收)

ACK、NACk,与重回队列

NACK:让broker重新投递,达到次数后手工ack,业务异常进行日志记录,然后人工补偿。

若consumer宕机没有ack,那么broker会等consumer恢复之后重新投递消息,或给下一个consumer消费

  • 重回队列:对没有处理成功的消息,递回给broker,broker会将这条消息放到队列末尾重投递
    实际应用中,会关闭重回队列
// requeue是否重回队列,false,不重新投递,抛弃此条消息
channel.basicNAck(...requeue)

TTL:time to live

  • 消息过期时间,这个消息不管到哪个队列的过期时间
  • 队列的过期时间:任意消息到这个队列后的过期时间

死信队列-DLX-deadLetterExchange

当一个消息,变成死信,它能重新被publish到另一个exchange,这个exchange就是DLX

  • 消息变为死信的几种情况
消息被拒绝,并且requeue=false
ttl过期
队列达到最大长度
设置死信队列的exchange叫做dlx.exchange和queue
然后绑定
消费者指定死信队列:
arguments.put("x-dead-letter-exchange","dlx.exchange")
channel.queueDeclare(queueName,true,false,false,arguments)

你可能感兴趣的:(技术)