Linux下用RocketMQ实现服务消息总线

Linux下用RocketMQ实现服务消息总线

前言

产品越来越复杂,微服务越来越繁杂。
如何控制好各服务节点的业务同步,就成了一个必须要面对的问题。
比如:人员离职,那在各个业务系统下的权限清理,组织关系,甚至内部IM通讯的状态、群组的清退等等,以往要拿一张单子在各个部门走一遍,除了财务和运维,其他是没有实物交接的,就造成了人力无力的浪费。
于是,便需要引入一个机制,实现一站式服务,我们称其为:“一键byebye“。
考虑以后架构中对于边缘硬件的接入,这个机制便要考虑推而广之,不能因为一个硬件问题的协调不及时,影响整个系统。
经过考虑和权衡,总线的设计思想基本占据了优势。


一、选型

基于系统的套接字和封装,消息总线的底层采用RocketMQ。
基于未来业务的考虑,消息协议使用MQTT 5.0标准封装,QoS等级为2。

二、准备工作

1.部署rocketMQ及踩过的坑

官方文档写的很详细,不做赘述。链接直达:https://rocketmq.apache.org/docs/quick-start/
下文以CentOS 7为背景,Windows环境大同小异。

再说一下可能会跳的坑:

1.1 防火墙:

部署启动后,可能出现外网无法访问的情况,防火墙的端口除了默认可见的如:9876和10911以外,还要根据服务判断一下其他开放的端口,比如10909,10912等。
基本命令如下:

放开端口10911:

firewall-cmd --zone=public --add-port=10911/tcp --permanent

重启防火墙:

systemctl restart firewalld.service

查看已生效端口:

firewall-cmd --list-ports

1.2 Docker

Docker没有官方镜像,网上的镜像也都是基于旧版封装的,坑很多。有动手能力可以自己封装docker镜像,初探的话,还是老老实实在本地先跑通。

1.3 项目依赖

rocketMQ脱胎于阿里巴巴,所以springCloud官方有造好的轮子,maven直接引用:

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-rocketmq</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

1.4 RocketMQ配置

./bin/runbroker.sh和./bin/runserver.sh里面的默认配置很占内存。
修改里面-Xms8g、 -Xmx8g、 -Xmn2g这些配置,改为合适的内存初始化大小。

1.5 RocketMQ服务启停

rocketMQ broker启动时会检查系统环境参数配置是否有NAMESRV_ADDR,有与没有都会引发一些问题,为避免环境的配置问题,可以更新一下系统环境参数:

export NAMESRV_ADDR=localhost:9876

2.通讯

设计实现的效果基本如下:
在这里插入图片描述
如何更好地利用rocketMQ的机制来实现呢?
将rocketMQ的特点结合微服务的节点信息,就可以很好的实现分服务,分节点的分发消息,并且控制好消息的消费。
RocketMQ有两种消费模式:BROADCASTING广播模式,CLUSTERING集群模式,默认的是 集群消费模式。
模式的设定在消费端完成,MessageModel.BROADCASTING和MessageModel.CLUSTERING,后文会给出简单的例子。
RocketMQ中的topic、group、tag的概念,网上讲的不多。
结合消费模式,总的来说:

  • 广播模式下,同一个topic里的每一个消费者都能完整接收所有消息;
  • 集群模式下,同一个topic里,
    如果消费者的group一样,消息被各消费节点分掉,即消息1被A消费,那消息1就不会到B,B可能收到的只能是消息2及以后的消息;
    如果消费者的group不一样,那消息会被每个消费者完整的消费一遍。

基于以上特性,topic就是上图中各种颜色的管道,group就是一个管道上的不同龙头,也就是同一个业务的不同节点。所以,开工!

2.1、创建Topic

命令行:

./bin/mqadmin updateTopic -b localhost:10911 -t TopicTest

2.2、 代码如下:

消费者1:
public class Consumer {
	public static void main(String[] args) throws MQClientException {  
         //声明并初始化一个consumer  
         //需要一个consumer group名字作为构造方法的参数,这里为consumer1  
         DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer1");  
  
         //同样也要设置NameServer地址  
         consumer.setNamesrvAddr("192.168.11.128:9876");  
         consumer.setMessageModel(MessageModel.CLUSTERING); 
         consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);  
  
         //设置consumer所订阅的Topic和Tag,*代表全部的Tag  
         consumer.subscribe("TopicTest", "*");  
  
         //设置一个Listener,主要进行消息的逻辑处理  
         consumer.registerMessageListener(new MessageListenerConcurrently() {  
             @Override  
             public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {  
                 for (MessageExt messageExt : msgs) {    
                    String messageBody = new String(messageExt.getBody());   
                     System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
                    		 new Date())+"消费响应:msgId : " + messageExt.getMsgId() + ",  msgBody : " + messageBody);//输出消息内容    
                 }   
                 //返回消费状态  
                 //CONSUME_SUCCESS 消费成功  
                 //RECONSUME_LATER 消费失败,需要稍后重新消费  
                 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;  
             }  
         });  
  
         //调用start()方法启动consumer  
         consumer.start();  
         System.out.println("Consumer1 Started.");  
     }  
}
消费者2:
public class Consumer2 {
	public static void main(String[] args) throws MQClientException {
		// 声明并初始化一个consumer
		// 需要一个consumer group名字作为构造方法的参数,这里为consumer2
		DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer2");

		// 同样也要设置NameServer地址
		consumer.setNamesrvAddr("192.168.11.128:9876");
		consumer.setMessageModel(MessageModel.CLUSTERING);
		consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
		// 设置consumer所订阅的Topic和Tag,*代表全部的Tag
		consumer.subscribe("TopicTest", "*");
		// 设置一个Listener,主要进行消息的逻辑处理
		consumer.registerMessageListener(new MessageListenerConcurrently() {
			@Override
			public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				for (MessageExt messageExt : msgs) {
					String messageBody = new String(messageExt.getBody());
					System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
							+ "2--------消费响应:msgId : " + messageExt.getMsgId() + 
							",  msgBody : " + messageBody);// 输出消息内容
				}
				// 返回消费状态
				// CONSUME_SUCCESS 消费成功
				// RECONSUME_LATER 消费失败,需要稍后重新消费
				return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
			}
		});

		// 调用start()方法启动consumer
		consumer.start();
		System.out.println("Consumer2 Started.");
	}
}
生产者:
public class Producer {
	public static void main(String[] args) throws MQClientException, InterruptedException {  
        //声明并初始化一个producer  
        //需要一个producer group名字作为构造方法的参数,这里为producer1  
        DefaultMQProducer producer = new DefaultMQProducer("producer1");  
          
        //设置NameServer地址,此处应改为实际NameServer地址,多个地址之间用;分隔  
        //NameServer的地址必须有,但是也可以通过环境变量的方式设置,不一定非得写死在代码里  
        producer.setNamesrvAddr("192.168.11.128:9876");  
        //调用start()方法启动一个producer实例  
        producer.start();  
  
        //发送10条消息到Topic为TopicTest,tag为TagA,消息内容为“Hello RocketMQ”拼接上i的值  
        for (int i = 0; i < 100; i++) {  
            try {  
                Message msg = new Message("TopicTest",// topic  
                        "TagA",// tag  
                        ("Hello RocketMQ " + i).getBytes("utf-8")// body  
                );  
                  
                //调用producer的send()方法发送消息  
                //这里调用的是同步的方式,所以会有返回结果  
                SendResult sendResult = producer.send(msg);  
                System.out.println(sendResult.getSendStatus()); //发送结果状态  
                //打印返回结果,可以看到消息发送的状态以及一些相关信息  
                System.out.println(sendResult);  
            } catch (Exception e) {  
                e.printStackTrace();  
                Thread.sleep(1000);  
            }  
        }  
  
        //发送完消息之后,调用shutdown()方法关闭producer  
        producer.shutdown();  
    }  
}

2.3、结果

先运行两个消费者,再运行生产者,结果如下:
在这里插入图片描述
至此,最初的想法已基本验证完毕。

三、中间件的封装

需要思考的问题:
如何保证RocketMQ消息的可靠性?
如何保证服务节点的执行结果?

既然是队列,就离不开解耦、削峰和数据分发的功能;
既然是中间件,就要考虑独立、可靠地完成消息传递的工作。

1、监控

引入rocketmq-console服务,项目地址 https://github.com/SummerUnfair/rocketmq-externals.git
具体使用方法见git文档。

2、架构

  • 引入SpringCloud Stream,设计生产者集群、消费者集群、消息的防灾、过期消息清理;
  • 设计路由中心,多路复用、负载均衡;
  • 数据一致性校验;
  • 消息的同步、异步、单向、顺序、批量、过滤的处理;
  • 事务消息及事务的管理
    ………………

这是消息总线最终应该有的样子:
在这里插入图片描述


总结

以上就是最近一个阶段的简单记录。

– 为了理想肝脑涂地

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值