首先为什么要用mq
1应用系统服务接耦合 通过mq把同步调用改成异步调用
2实现缓存流量消峰
3 数据分发:跟应用解耦思想一致 只需要发送到mq 需要消费的服务去mq取 不需要消费的时候就自己关闭消费
缺点:
1降低了系统的可用性 mq挂了就都挂了
-》需要保证mq的高可用性
2系统复杂度变高 :怎么防止重复消费相同的消息(幂等性)、消息丢失处理、如何保证消息的顺序消费??
3一致性问题:怎么保证所有消费的系统数据是一致的?
其实共同的缺点在分布式的系统当中都是存在的 就是cap:分区容错性 一致性 可用性
**
RocketMq
**
阿里 2016年MQ中间件 阿里内部利用其处理双十一等高并发场景 可以处理万亿级别的消息
结构:
1消息从producer 先找nameserver(broker的管理者 相当于注册中心 broker要主动上报自己的信息到nameserver)去获取一个broker的地址 然后发送到 broker,consumer也从nameserv里面拿到地址 去broker取
topic :消息的种类 发送者接受者都可以发送接受一个或者多个topic
message queue:topic的分区 用于并行发送接受消息
集群:
nameserv: 无状态 每个节点信息一致,启动集群后broker会给每一个nameserv发送节点信息,所以nameserv集群内部的节点不需要同步信息(新加入一个nameserv broker会给这个新的nameserv发送节点信息!) 直接启动几个nameserv就可以
需要保存topic和broker的映射
新加一个节点也不用重启整个集群 broker会自动对新的nameserv进行上报 因为nameserv是无状态的
producer:之间也没有数据的同步 直接启动几个produer就可以 ,和nameserv保持长链接 才可以拿到topic对应的是哪台broker 和master也保持长链接 并且发送心跳
consumer:之间也没有数据的同步 直接启动几个consumer就可以,和nameserv保持长链接 定期从nameserv取topic的路由信息 和master slave也建立长链接 心跳
broker:分主从节点 master slave ,读写分离master负责读写操作(生产者只连接master 消费者连接master和slave) slave负责读操作
beaker内部有多个组 每个组(brokername相同的为一组)都有master和slave 通过id区分 brokerid为0的是主节点 非0的为slave节点 一个master可对应多个slave 一个slave只能对应一个master, 组内master 通过同步异步 与slave进行消息的同步
单master模式:broker宕机服务就不可用 不建议线上使用
多master无slave模式: 配置简单
多master多slave异步:效率高 prouder发送消息到broker后立即返回结果 broker异步进行刷盘和集群内的消息传递 有可能丢失数据!
多master多slave同步:消息安全性高 数据一致性强 丢失数据少(同步传递不会丢失数据!) 但是效率低 produer发送到broker后需要等broker同步得刷盘以及传递消息到其他组内broker进行消息同步完成后才返回给producer
broker收到消息后会自动进行持久化!
mqadmin进行集群管理
topic相关 集群相关 消息相关 消费者相关 连接相关 nameserv相关
也可以用rocketmq console
消息的发送
业务分类的标示:topic(1级) tag(2级) 发送到 对应broker的messagequeue
1)同步消息:生产者发送完后会进行阻塞 直到等到mq返回一个接受的结果才会继续执行 很可靠 用在发送重要的消息 短信通知等 (可以持久化 消费者后开启也能接受到)
发送的消息包括: topic tag 内容
2)异步消息:消息发送后不等待消息回传结果 效率高 可靠性不高 可以指定回掉函数(不能持久化 必须消费者在线的时候发送消息才能接受得到)
3)单向消息:不关心发送结果的情况下 e.g.日志发送
broker是有多个队列的(为了提高效率) 消息发送过去后broker通过轮训方式依次放到队列当中
顺序消息:(保证的是针对当个生产者所发生的内容顺序是一致的 不保证全局的生产者之间发送的消息的顺序!)
分区顺序:一个Partition(其实就是messagequeue)内所有的消息按照先进先出的顺序进行发布和消费
全局顺序:一个Topic内(包含了多个messagequeue)所有的消息按照先进先出的顺序进行发布和消费
消费者消费消息的顺序要和生产者生产消息的顺序是一致的,如何在前提条件下满足顺序性呢:保证同一个业务的消息发送到同一个队列当中即可(可以通过业务id对队列的size进行取模 来得到一个队列的index 消费者用messagelistenerorderly进行顺序消费即可 可以保证一个线程顺序消费一个业务id标示的message)!消费的时候采用一个线程去取这个队列的消息
延时消息:目前最多延迟两小时进行消费!发送者发送完后立即存储在队列里面 等延迟时间到了后消费者才进行消费
批量消息:发送者一次性发送消息的集合 sned方法发送的不再是message 而是message的list 发送的消息集合一次最好不超过4M 否则要进行消息的分割: 可以使用迭代器借口Iterator进行分割 每次next取出的是大小不超过4M的集合
过滤消息:消息发送到broker后 消费者可以根据某些条件对消息进行过滤
1)消息消费的以后可以根据tag进行过滤
2)可以通过设置某个属性的值 消费的时候拿出这个值 根据这个值的范围进行消费 ,支持sql语句和条件判断
事物消息:生产者产生的消息发送给broker是需要经过事物的提交的 否则不能被消费
发送的是半消息 到达broker后消费者是暂时不可见的
broker确认收到后调用回调函数让 producer执行一个本地的事务,commit的话提交后broker的消息就能被消费者看到进行被消费 rollback的话 broker就会把消息删除掉
如果本地事务处理出问题了没有发出commit或者rollback broker会进行回调函数的回查 让本地事物重新commit或者rollback
(半队列消息会有堆积的情况 所以控制在15次以内 , 不支持延迟消息和批量消息 可能会多次消费)
消息的接受
有两种方式 可以让broker推 也可以consumer去拉
1)负载均衡消费:每个消费者消费其中一部分消息 一起加起来消费完全部的消息 (默认)
2)广播模式消费:消费者每一个都消费相同的全部消息
消息存储
mq是有自动持久化机制的
activemq默认使用关系型数据库存储
rocketmq kafka rabbitmq默认都是直接存储到文件系统中 性能>关系型数据库
rocketmq :使用顺序写 零拷贝(NETTY也是 不需要经过用户态 文件大小1G 用于传输)
几个文件:
commitlog:
所有发送到rocketmq的消息都会存储到commitlog文件中,文件大小就是1个G,存储message queueid 和 topic consumerqueue,存满一个再来一个G,顺序写, 读是加了索引的 通过consumerqueue作为索引获得所要查的消息的位置才去commitlog精准定位位置,加快commitlog读取速度,一个messagequeue消息队列就对应一个cosummerqueue(读取到内存中)作为索引
indexfile:同样是索引文件 区别于consumerqueue的方式,通过keys(消息关键字)和时间区间来查询消息
刷盘机制
消息先写到内存pagecache当中 然后再通知刷盘线程写到磁盘中去
同步刷盘:
刷盘线程完成后才唤醒线程然后对生产者进行反馈 可靠性高 一定是刷盘成功后才返回的
异步刷盘:
写到pagecache后直接对生产者进行响应 然后开启另外一个线程进行刷盘 效率高 但是不一定刷盘成功会有数据丢失的危险
rocketmq的高可用性
除了上述的集群配置之外 从消费的角度来说:
consumer消费的时候先从master进行消费 master无法消费(不可用或者繁忙)的时候就会自动切换到slave去
producer可以向多个master进行写入 挂掉一个写另外一个来保证高可用 所以在发送消息的时候要指多个broker来保证高可用
主从复制
同步复制 :安全 吞吐量高低
异步复制:数据可能会丢失 吞度量高
一般配置异步刷盘保证吞吐量 同步主从复制保证消息的不丢失
负载均衡
producer:
轮训的策略 因为topic可以绑定不同的broker 也因为broker里面有多个队列 首先是给broker里面不同的队列进行发送 然后再跨broker进行发送来进行轮训发送
consumer:
集群模式:
多个消费者共同完成他们对应的topic下所有队列的消费 均摊 启动多个消费者即可 因为是无状态和 和nameserv和producer一样
两种方式 1 均摊负载均衡同一个broker里面的不同queue
2 均摊负载均衡不跨broker里面的queue
要让队列queue的数量 > 消费者的数量 这样每个消费者才可以工作 因为如果负载均衡的消费者已经够了 多余的不会进行工作!
广播模式:
每个消费者全部消费一遍queue 其实这种模式就不算是负载均衡了!
消息重试
为了保证消息一定会被消费掉
顺序消息:
自动就保证一定会消息重试 在消息没有被消费者反馈的时候 而且前面的消费者如果没有消费完毕 为了保证顺序性 后面的消费者不可以进行消费 所以有可能导致消费的阻塞 需要在使用顺序消息的时候监控好 避免产生生产情况下的阻塞
除了顺序消息之外的无序消息:
只对集群负载均衡消息有效 对广播消息无效 也就是广播消息不管成功还是失败只能消费一次!
默认允许重复消费16次(4小时46分钟 大于16的每次2小时) 超过之后还没有消费到的话就会进入死信队列
触发重试的机制:
返回ACtion.ReconsumeLater; null 或者抛出异常都会进行重试!
如果希望不进行重试 就在catch或者外部进行 return Action.commitmessage
可以对同一个组(groupid)下的consumer设置最大重试次数
后启动的consumer的配置会覆盖前面的配置
死信队列
超过重试次数的会被放入死信队列
死信队列中的消息:
不会再被消费者正常消费
3天后删除(和正常的消息一样) 所以要在3天内处理死信队列的消息
一个死信队列面向同一个groupid(消费者组)
处理方法:
可以让消息重新消费一次,也可以针对死信队列的消息进行消费
消息幂等姓
1通过消息id保证 但是rockatmq不保证消息id唯一
2使用业务标示 业务key, 在消费方存储key 然后每次请求的时候去查询是否存在这个key , message.setKey