mq
active mq
mq,消息中间件,它可以用来存储通信数据
订阅,发送模型
队列详情
功能是解耦合,将服务分开
同步模型
Step1 发送数据 step2 接收数据 step3 接收数据
异步模型
step1 发送数据 step2、step3去mq找数据
概念
-
消息:两台计算机之间传输的数据单位
-
消息队列:用来保存消息的容器,如果发送数据时,接收者不可用,那队列会保存消息直到成功传递
-
broker:一个实例,控制台
-
queue:只支持一次消费,消费端关注这个queue
-
topic:可以支持多次消费,只有客户端服务端同时在线才会收到,消息不会保存
-
consumer:消息消费者
- 同步消费:调用消费者的recive方法阻塞到消息到达
- 异步消费:注册一个消息监听器,消息到达时触发
消息类型
textmessage
mapmessage
bytesmessage:写的顺序要和读的顺序一致,先写的utf就要先读utf,会有切割字节流的问题,针对文件数据
streammessage
objectmessage:要实现序列化接口,同时在connectionFactory处要添加可信任的类.factory.trustPackages(list),在list里添加某个class的package名称
消费模型
消息默认是异步发送的
在没有开启事务的情况下:
producer发送持久化消息是同步的,调用send会阻塞直到broker把消息保存到磁盘并返回确认。
默认情况下,producer发送非持久化消息是异步发送的,但是在开启事务的情况下,消息都是异步发送
开启事物情况下:
当发送方法在一个事物上下文中时,被阻塞的是 commit 方法而不是 send 方法。commit 方法成功返回意味着所有的持久消息都以被写到二级存储中。
- pub/sub模型:订阅了的消费者都可以消费
- p2p模型:只有一个消费者可以消费,其他消费者不可以消费了
流程
创建链接
创建queue或者topic
创建消息创建者\消费者
发送消息/接收消息(接收消息是阻塞的)
应用场景
异步处理,有些服务不需要马上处理消息,等需要的时候再处理数据
解除耦合,两个独立的服务之间通过消息通信.如果没有解除耦合,另一端点出异常则直接返回异常了
顺序保证:mq的消息是顺序排序的,会按照顺序处理.activeMq 里面有 messageGroups 属性,可以指定 JMSXGroupID,消费者会消费指定的 JMSXGroupID。即保证了顺序性,又解决负载均衡的问题
可靠性
一个createSession可以是消费者也可以是生产者,false,auto_ack只对消费者消费数据有兴趣
只有消息被确认并且返回ack后才会清除,客户接收消息,处理消息,返回确认.
在事物会话中,事物被提交时,确认消息自动发生.
非事物会话中,消息何时被确认取决于创建时的应答模式
- Auto-ack:当消费者调用recv成功返回时,或者调用MessageListener.onmessage方法返回时,会给mq一个ack,代表消息被确认
- client-ack:消费者通过acknowledge方法确认消息.有个问题是,如果一个消费者在消费10个信息,确认第五个时,后面的5个也会被确认
- dups-ack:会话迟钝确认,不需要确认
分布式事务,二阶段提交都是靠ack机制,比如消息生产者发送一条消息阻塞住,直到这条消息被消费了才结束阻塞,又或者注册一个listener监听这条消息是否被消费.
while (i<3){
//确认,会阻塞,与accept相同
TextMessage message=(TextMessage) consumer.receive();
//System.out.println(message.getStringProperty("queue"));
System.out.println(message.getText());
//session.commit();
if(i==2){
//确认
message.acknowledge();
}
i++;
}
名词解释:
P:生产者
C:消费者
服务端:P 或者 ActiveMQ服务
客户端:ActiveMQ服务 或者 C
客户端成功接收一条消息的标志是这条消息被签收。成功接收一条消息一般包括如下三个阶段:
1.客户端接收消息;
2.客户端处理消息;
3.消息被签收。
在不带事务的 Session 中,一条消息何时和如何被签收取决于Session的设置。
1.Session.AUTO_ACKNOWLEDGE
当客户端从 receive 或 onMessage成功返回时,Session 自动签收客户端的这条消息的收条。
2.Session.CLIENT_ACKNOWLEDGE
客户端通过调用消息的 acknowledge 方法签收消息。
在带事务的 Session 中,签收自动发生在事务提交时。如果事务回滚,所有已经接收的消息将会被再次传送。
白话总结:
1、对于生产者:服务端端为P,客户端为ActiveMQ服务。 Session设置为AUTO_ACKNOWLEDGE 和CLIENT_ACKNOWLEDGE ,相对来说区别不是很大,根据情况考虑。
2、对于消费者:服务端为ActiveMQ为服务,客户端为C。 Session设置为AUTO_ACKNOWLEDGE ,接收到消息(receive 或 onMessage成功返回时),即为消费成功,然后从队列里移除该数据。不关心该数据有没有正确被处理成我们想要的结果;Session设置为CLIENT_ACKNOWLEDGE 时,必须手动调用acknowledge 方法才为消费成功,然后从队列里移除该条数据。
3、P和C的Session设置成哪种模式,互不影响。
可恢复性
系统的一部分组件失效不会影响整个系统,如果某个处理消息的进程挂掉,只要重启就可以继续消费消息
持久性
jms支持以下两种方式的提交
- persistent:指示jms provider持久保存,可以让provider不因为自己失败而丢失消息
- non:不要求持久化消息
kahadb存储:默认的存储,所有的消息都顺序添加到一个日志文件中,同时另外有一个索引文件记录指向这些日志的存储地址,还有一个事物日志用于消息的恢复操作.消息已b tree结构存储,可以快速更新
jdbc:在数据库中存储消息,异步从mq中存到db,消费者先消费mq内存中的数据,再剔除数据库中的数据
优先级
可以扰乱消费的顺序,配置文件需要指定使用优先级的目的(queue),消费端在消费消息时会优先消费优先级高的消息
过期
queue的消息默认永不过期.
当给消息设置了过期时间后,消费端在超时就无法消费了,这类消息会被放入死信队列中,需要从死信队列中取出并重新发送.死信队列不回自动清除,有堆积风险.
生产者可以监视死信队列,将死信队列里的消息取出来,重新投递,投递多少次,同时消费端要做幂等性
activeMq.DLQ
临时目的地
临时创建一个queue或者topic,它们活的时间只是这次链接保持的时间,同时只有临时消费者才能消费这条消息
事物
createSession(是否要事务)
commit();
callback();
与本地事物连在一起,发送消息时要commit不然不发送数据
消息特点
topic
- 无状态、不保存
- 不保证发布的每条数据,消费端都能获取到,只有消费者监听后才可以得到消息,否则没法得到消息
- 一个消费端可以被多个消费端消费
- 消息有过期时间
queue
- 消息落地,会保存
- 保证每条数据都会被消费端接收到
- 一条消息只能被一个消费端消费
- 消息被接收后,会通知mq,mq将消息删除
接收消息(死信队列)
接收消息时调用recive方法会阻塞,与accept相同,在IO瓶颈时有性能问题
所以改进方案1,注册一个监听器MessageListener
如果无人接收消息,死信队列数据过多,内存撑爆
producer.setTimetolive(设置消息多少时间失效,进入死信队列,保证了一些有时效性的消息),死信队列也是一个queue,它会将过期的消息从某个queue中取出放到自己的queue,所以也可以使用MessageListener监听,做消息的重新投递.
非持久化消息也可以进入死信队列,都可以配置
独占消费者
只要某个消费者接入了这个queue,那么后续所有消息都由这一个消费者消费,createQueue(“user?excluse=true”)
消息选择器
message = createMessage();
message.setProperty(“week”,1);
设置消息的元数据
createCosumer(“queue”,“week=1”),第二个参数就是分组过滤器,消费者消费元数据中week=1的消息
以分组的形式,可以负载均衡定向分发某一类型的消息.消息筛选
面试问题
如何防止消息重复发送
- *解决方法*:增加消息状态表。
- 通俗来说就是一个账本,用来记录消息的处理状态,每次处理消息之前,都去状态表中查询一次,如果已经有相同的消息存在,那么不处理,可以防止重复发送。
- 2.如何解决mq的重复消费问题
2.1 重复消费的问题会导致数据库出现脏数据,我们一般通过保证幂等性来解决这个问题
2.2 幂等性是什么。一次和多次请求某一个资源对于资源本身应该具有同样的结果。如何保证生产者重复消费数据保证幂等性
丢消息怎么办
- *解决方案*:用持久化消息【*可以使用对数据进行持久化JDBC,AMQ(日志文件),KahaDB和LevelDB*】,或者非持久化消息及时处理不要堆积,或者启动事务,启动事务后,commit()方法会负责任的等待服务器的返回,也就不会关闭连接导致消息丢失了。
持久化消息非常慢
- 默认的情况下,非持久化的消息是异步发送的,持久化的消息是同步发送的,遇到慢一点的硬盘,发送消息的速度是无法忍受的。但是在开启事务的情况下,消息都是异步发送的,效率会有2个数量级的提升。所以在发送持久化消息时,请务必开启事务模式。其实发送非持久化消息时也建议开启事务,因为根本不会影响性能。
如何防止重复消费
做消息的幂等性处理
如何保证消费顺序
queue,单消费者
消息丢失(出问题才会丢,正常不会丢)
看消息是否持久化,异步发送的消息
负载