消息中间件RocketMQ(一):核心知识与集群搭建(含分布式消息事务)

前言:本文为原创 若有错误欢迎评论!

  • 安装参考我的博客:https://blog.csdn.net/weixin_43934607/article/details/100538881
  • 整合SpringBoot参考我的博客:https://blog.csdn.net/weixin_43934607/article/details/100115270

一.主从模式搭

  • 准备两台虚拟机

1.主从模式搭建

  1. 先进入bin 启动nameserver:

    nohup sh mqnamesrv &

  2. 进入每个机子的:/conf/2m-2s-async/

    cd /conf/2m-2s-async/

  3. 主节点编辑broker-a.properties 或 broker-b.properties

    vi broker-a.properties

    增加:namesrvAddr=192.168.159.129:9876;192.168.159.130:9876
    修改:brokerClusterName=myCluster
    
  4. 从节点编辑broker-a-s.properties 或 broker-b-s.properties

    vi broker-a-s.properties

    增加:namesrvAddr=192.168.159.129:9876;192.168.159.130:9876
    修改:brokerClusterName=myCluster
    

    注意:一个机子只用给集群配置

    1. namesrvAddr:相当于注册中心 集群中不用所有节点都启动nameserver 格式为:ip1:端口;ip2:端口
    2. brokerClusterName:通过nameservere找到连接的节点 然后通过这个名字用来区分是否是一个集群
  5. 进入bin目录

    1. 主节点启动不带’-s‘的配置文件

      nohup sh mqbroker -c …/conf/2m-2s-async/broker-a.properties &

    2. 从节点启动带’-s‘的配置文件

      nohup sh mqbroker -c …/conf/2m-2s-async/broker-a-s.properties &

  6. 修改可视化管控台配置和使用时的改变

    1. 进入rockemq-console-ng.jar的/BOOT-INF/classes 修改application.properties的

      namesrvAddr=192.168.159.129:9876;192.168.159.130:9876
      
    2. 在应用里给DefaultMQProducer和DefaultMQConsumer设置nameserver的时候 也是多个ip地址的形式:

      (192.168.159.129:9876;192.168.159.130:9876)
      

2.消费问题

  • 主节点宕机之后 可以从从节点消费 主节点再次上线之后会读取从节点的偏移量 保证不会重复消费
  • 注意:slave有一定滞后性 在主节点宕机后可能有少量消息丢失 主节点重新上线后未同步的消息可以恢复消费

3.broker如何配置主从

  • 一个master可以对应多个slave

    • 通过brokerName(不是brokerClusterName)来匹配
    • 通过不同brokerId(主:0,从:1、2、3递增)来对应主从
    • 因为/conf/2m-2s-async/ 的brokerName都设置好了
      • broker-a.properties和broker-a-s.properties的brokerName都是broker-a
      • broker-b.properties和broker-b-s.properties的brokerName都是broker-b
  • 注意:

    • 集群的节点数 必须小于等于每个topic的队列数(因为负载均衡策略用不了 是每次消息来的时候进行分配 大于队列总数永远无法满足所有节点同时运作)

二.生产者核心知识

1.消息发送状态:

  • FLUSH_DISK_TIMEOUT:只有同步刷盘超时才会报
  • FLUSH_SLAVE_TIMEOUT:同步复制超时
  • SLAVE_NOT_AVAILABLE:同步复制 子节点宕机
  • SEND_OK:发送成功

2.消息重试

  • 生产者:

    • 本身支持重试机制 默认三次如果网络差可以多设置两次
      • 只有同步发送才会重试,异步发送、单向发送都不会重试 只发送一次
      • 设置重试次数:defultMQProducer.setRetryTimesWhenSendFailed()
      • 超过重试次数 抛出异常 扔进死信队列
    • 也可以设置消息的超时时间 但是一旦超时 就不会重试 会抛出异常 扔进死信队列
      • 设置超时时间:defultMQProducer.send(msg,超时时间(单位毫秒))
    • 消息发送失败的概率很低 所以一般不用特别关注 监听死信队列 定期补偿就可以
  • 消费者:

    • excption机制

      • 集群消费(默认):

        • 如果返回的是"RECONSUME_LATER"或者有异常抛出 消息会重回broker进入重试队列再发送 下次接收到该消息的msgId也会改变

        • 用MessageExt.getReconsuTimes()获得重试的次数
          默认是最多16次(messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h)

        • 但一般判断次数大于3次就 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS 然后记录日志然后人工补偿 如果大于16次回进入死信队列

      • 广播消费:

        • consumer.setMessageMode(MessageMode.CLUSTERING)
        • 异常或者RECONSUME_LATER之后不会重回broker重试
    • timeout机制

      • 设置消费超时时间(分钟)
        • consumer.setConsumeTimeout(RocketMQConstant.CONSUMER_TIMEOUT_MINUTES);
      • 如果Send_OK到了消费者 ,但在消费者的消费的超时时间内没有返回状态,这个时候会被认作timeout,rocketmq会进进入重试队列 重新发送该消息 大于16次会进入死信队列

3.异步发送消息

defaultMQProdvider.send(Message,new SendCallback(){
          onSuccess(SendResult){ }、
          onException(){
        		 //根据业务判断是否继续重试(异步不重试 只发一次消息)
          } 
 ))

4.发送方式oneway

  • defaultMQProducter.send():都会有服务器相应发送状态和回调函数 保证消息不会丢失
  • defaultMQProducter.oneway():发送没有服务器返回状态 但是最快不阻塞 用作logserver

5.延迟消息

  • message.setDelayTimeLevel(level)
  • 级别从1开始对应后面的时间:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
  • 使用场景:定时提醒、定时任务

6.MessageQueneSelector

  • 指定一个topic下的指定队列 不指定是随机分配
defaulrMQProducter.send(Message,new MessageQueneSelector(){
           select(List<MessageQueue> mqs, Message msg, Object arg){    
         		  int queueNum = Integer.valueOf(String.valueOf(arg)) % 4;
                  System.out.println("队列id:"+queueNum+" 消息:"+new String(msg.getBody()));
                  return mqs.get(queueNum);  
            }
},args,new SendCallback(){ }  )
  • 其中的args是自己传入的参数 指定的队列的下标(从0开始 不能越界 如果是用程序自动创建的topic默认有4个)
  • 可以再加一个SendCallback()参数来异步发送(该参数非必须)

三.消费者核心知识

1.顺序消息

  • RocketMQ是局部消息满足顺序消息 即指定的topic的每一个消息队列在只能由一个线程处理 不会把一个队列里的消息给多个线程 保证一个线程处理的时候消息的完整与顺序

defaultMQConsumer.registerMessageListner(new MessageListnerOrderly(){ } )

  • 默认的消费方式是并发消费:new MessageListenerConcurrently
    即:为了保证顺序 把所有有顺序的消息放在一个队列中 然后按顺序接收 但是不可以异步发送消息 必须同步

  • 注意:
    不是禁止并发消费 是给每个单独的队列加个锁 把每个队列轮询分配给不同节点处理(分段锁的思想)

2.消息过滤

  • tag标签

defaultMQConsumer.subscribe(topic,“tag1 || tag2 || tag3”)

  • sql92
  1. 在broker开启

主节点编辑 /conf/2m-2s-async/broker-a.properties
从节点编辑 /conf/2m-2s-async/broker-a-s.properties
在配置最后加一行:enablePropertyFilter=true

  1. 重新带配置文件启动

  2. 使用:

给要发送的消息:message.putUserProperty(name,amount)

在消费者订阅的时候:
defaultMQConsumer.subscribee(topic,MessageSelector.bySql(“amount > 5”)

  • 注意:name对应的amount是String类型 然后订阅的时候不一定是比大小 就和普通sql语句一样 (">、<、 =、IS NULL、AND、OR、NOT等都支持)

  • 建议:一个topic单一职责 不用tag和sql92去过滤 直接订阅tag为"*"即可

3.DefaultMQPushConsumer和DefaultMQPullConsumer

  • pushConsumer:
  1. 本质是长轮询(即consumer主动跟broker建立一个链接拉取数据 这个链接在15s内如果有消息就会自动推送到consumer)折中了push的一有消息就推送、pull的每次都要拉取

  2. 收到消息自动处理并改变offset 如果有新的consumer加入会自动负载均衡

  3. 支持优雅的关闭 consumer.shutdown() 会自己保存消息的偏移量offset
    (用SpringBoot的上下文监听器,或者@PostConstruct @PreDestory 来开启和关闭consumer)

  • pullConsumer:
  1. 更加灵活可控 每次自己拉取 编码复杂度高

  2. 需要每次把offset保存在内存或者磁盘 下次再比对

四.Offset和CommitLog

1.offset

  • 当消息的接受为广播模式把消息存储模式设置为本地存储(因为每个节点都要消费topic下所有消息 所以按照各个节点自己的进度消费)

consumer.setMessageMode(MessageMode.BROADCASTING)
consumer.setOffsetStore(OffsetStore.LocalFileOffsetStore)

  • 即:每次每个节点读取消息的时候从本地读取并增加自己的offset 不从broker读取消息的偏移 (同样pushConsumer也会维护本地存储的offset)

2.commitLog

  1. 是消息真正存储的物理文件
    每个消息队列文件的最终持久化地址:

    /root/store/CommitLog/

  2. 每个消息队列存储消息的临时地址:

    /root/store/consumequeue/[topicName]/[queneId]/ 存储的临时的文件也会被持久化到commitLog

    每个文件的名字是偏移量起始地址 最大1G=102410241024 每存满1G 就会先建立一个文件 文件名是102410241024的值

  • 从consumequeue读取索引 然后通过索引到commitLog取数据
  • 集群配置文件的fileReserveTime设置的是消息保存的时间 删了之后offset不会减少

五.分布式消息事务

  • 发送消息到broker之后有一回调 发送发二次确认之后决定Commit还是Rollback

1.发送二次确认的三种状态:

  • LocalTransactionState.COMMIT_MESSAGE:提交消息
  • LocalTransactionState.ROLLBACK_MESSAGE:回滚消息
  • LocalTransactionState.UNKNOW:过一段时间执行回查方法

注意:

  1. 必须同步发送消息
  2. 事务性消息必须单独有一个groupName 不能和其他共享一个groupName

2.使用(只是针对producer):

  • 定义一个内部类:
class TransactionListenerImpl implments TransactionListener{
          @Override
          public LocalTransactionState executeLocalTransaction(Message message,Object arg){
          
                   //message就是那个半发送的消息 arg是在transcationProducter.send(Message,Object)时的另外一个携带参数)

                  //执行本地事务或调用其他为服务

                  if(...) return LocalTransactionState.COMMIT_MESSAGE

                  if(...) return LocalTransactionState.ROLLBACK_MESSAGE

                  //如果在检查事务时数据库出现宕机可以让broker过一段时间回查 和return null 效果相同
                  if(...) return LocalTransactionState.UNKNOW 
        }


       @Override
       public LocalTransactioonState checkLocalTransaction(MessageExt msg){

            //只去返回commit或者rollback
        }
}
  • 定义一个线程池 让broker用来执行回调和回查
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), 
new ThreadFactory() { //给每个线程设置一个名字方便找bug           
          @Override            
          public Thread newThread(Runnable r) {               
                Thread thread = new Thread(r);               
                 thread.setName("client-transaction-msg-check-thread");                
                return thread;            
          }        
});   
  • 新建事务性producer:
new TransactionMQProducer(groupName);
  • 设置groupName、namesrvAdd
  • 在多设置executorService、transacionListener
  • 调用.start()启动(这个init方法应该用@PostStruct注解)
producer.setNamesrvAddr(JmsConfig.NAME_SERVER); 
producer.setExecutorService(executorService); 
producer.setTransactionListener(new transactionListenerImpl()); 
producer.start();
  • 在Producer的类中定义一个getProducer方法 在controller中注入该类之后拿到
transactionMQProducer:
transactionMQProducer.sendMessageTransaction(Message,Object)

六.双主双从配置

  • 推荐同步复制 异步刷盘 同步投递消息
    • 刷盘:每个messageQuene把消息持久化到commitLog
    • 复制:持久到conmmitLog后是否立即同步到从节点
    • producer投递方式:producer的send方法是否异步使用)
  • 配置讲解
    # 注册中心nameserver地址
    namesrvAddr=192.168.56.129:9876;192.168.56.130:9876
    
    # 集群名称(很重要 一个集群的brokerClusterName必须相同)
    brokerClusterName=ItcastCluster 
    
    # broker名称(相同brokerName是主从)
    brokerName=broker01 
    
    # broker的角色(相同brokerName下 0是主 其余1、2、3...是从)
    brokerId=0 
    
    # 什么时候删除过期文件(04指凌晨四点)
    deleteWhen=04
    
    # 文件保存多长时间(小时)
    fileReservedTime=48 
    
    # broker角色
    brokerRole=SYNC_MASTER 
    
    # 刷盘方式
    flushDiskType=ASYNC_FLUSH 
    
    # 本虚拟机ip 用于远程访问broker
    brokerIP1=192.168.56.129
    
    # 本虚拟机ip 用于主从之间访问同步
    brokerIp2=192.168.56.129
    
    # 监听的端口(会占用三个端口 listenPort-2、listenPort、listenPort+1)
    # 如果是用docker部署在一台机子上 那么集群每个节点监听的端口要不同
    # listenPort=10911
    

1.准备四台虚拟机,先按照一配置好一对主从(这对主从的nameserver服务都启动)

brokerRole=SYNC_MASTER(只针对主节点 改为同步复制)

2.在开两个节点(不开启nameserver)

3.分别只配置-b.properties和-b-s.properties

增加:
namesrvAddr=192.168.159.129:9876;192.168.56.130:9876 (相当于注册中心 只要有开启的能够去注册就可以 不用全部开启)

修改:
brokerClusterName=集群名
brokerRole=SYNC_MASTER (只针对主节点 改为同步复制)

4.带配置启动

5.所有的broker的设置都是通过各自集群的文件来设置的

  • 推荐配置:

    defaultTopicQueueNums=4

    #线上应该禁止自动创建topic
    autoCreateTopicEnable=false

    #线上应该禁止自动创建订阅组
    autoCreateSubscriptionGroup=false

    #复制方式改为同步
    brokerRole=SYNC_MASTER

    #刷盘方式改为异步
    flushDiskType=ASYNC_FLUSH

    #每个节点都有从节点

6.过程:

  1. 各个节点在nameserver注册 然后就可以相互访问
  2. 通过brokerClusterName在nameserver的ip中找到一个相同的集群
  3. 找到集群后通过brokerName找到对应的主从节点 然后通过brokerId判断是主还是从

七.常见问题

1.消息重复消费

  • Redis:

    • 每次发送的message都设置唯一的key(message.setKey( ))来标识 在接收到Message后把这个唯一的key放到redis中 每次通过setnx判断是否有接收过此消息
    • 注意:这个消息去重不用考虑原子性问题 但Redis作分布式锁要考虑原子性问题 就要所有在加锁、解锁时 让拿数据和操作数据两个命令一起执行 所以必须使用lua脚本)
  • Mysql:
    新建一个去重表 以Message的key作为表的唯一索引 然后给这张表每次添加添加key 并且捕获异常 如果抛出异常就证明已经消费过了

2.可靠性保证

  • producer:不使用oneway方式发送消息 并且给Message设置唯一的key(为了重试的key唯一)

  • broker:双主双从、并且启动多个nameserver、同步双写 异步刷盘

  • consumer:消息务必保留日志(即消息体和元数据)、做好幂等性(即发送多次相同消息 都只处理一次)

3.消息堆积

  • 新建一个临时topic 把quene的数目增大 把consumer也增加多 然后把堆积的消息都用这个topic消费

4.推荐微服务技术选型:

  • 框架:SpringBoot+SpringCloud/Dubbo+RocketMQ+Redis

  • 部署:nginx+Docker+阿里云

  • 数据库:mysql主从

  • 性能测试:Jmeter

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值