【Linux系统安装RocketMQ并整合到SpringBoot项目】

一、基本概念

1.1 NameServer

NameServer是一个Broker与Topic路由的注册中心,支持Broker的动态注册与发现。
​ 主要包括两个功能:
1)Broker管理:接受Broker集群的注册信息并且保存下来作为路由信息的基本数据;提供心跳监测机制,检查Broker是否还 存活。
2)路由信息管理:每个NameServer中都保存着Broker集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer可以获取整个Broker集群的路由信息,从而进行消息的投递和消费。
路由信息管理包含以下几个功能:
【1】路由注册:
1.1 NameServer通常也是以集群的方式部署,不过,NameServer是无状态的,即NameServer集群中的各个节点间是无差异的,各节点间相互不进行消息通讯。那各节点中的数据是如何进行数据同步的呢?在Broker节点启动时,轮询NameServer列表,与每个NameServer节点建立长链接,发起注册请求。在NameServer内部维护着一个Broker列表,用来动态存储Broker的信息。
1.2 Broker节点为了证明自己是活着的,为了维护与NameServer的长链接,会将最新的信息以心跳包的方式上报给NameServer,每30秒发送一次心跳。心跳包中包含BrokerId、Broker地址(IP+Port)、Broker名称、Broker所属集群名称等等。NameServer在接收到心跳包后,会更新心跳时间戳,记录这个Broker的最新存活时间。
【2】路由剔除:
2.1 由于Broker关机、宕机或网络抖动等原因,NameServer没有收到Broker的心跳,NameServer可能会将其从Broker列表中剔除。
2.2 NameServer中有一个定时任务,每隔10秒就会扫描一次Broker表,查看每一个Broker的最新心跳时间戳距离当前时间是否超过120秒,如果超过,则会判定Broker失效,然后将其从Broker列表中剔除。
【3】路由发现:
3.1 RocketMQ的路由发现采用的是Pull模型。当Topic路由信息出现变化时,NameServer不会主动推送给客户端,而是客户端定时拉取主题最新的路由。默认客户端每30秒会拉取一次最新的路由。
【4】客户端NameServer选择策略:
4.1 客户端在配置时必须要写上NameServer集群的地址,那么客户端到底连接的是那个NameServer节点呢?客户端首先会产生一个随机数,然后在与NameServer节点数量取模,此时得到的就是所要连接的节点索引,然后会进行连接。如果链接失败,则会采用round-robin策略,逐个尝试去连接其他节点。首先采用的是随机策略进行的选择,失败后采用的是轮询策略。

1.2 Broker

Broker充当着消息中转的角色,负责存储消息、转发消息。Broker在RocketMQ系统中负责接收并存储从生产者发送来的消息,同时为消费者的拉取请求作准备。Broker同时也存储着消息相关的元数据,包括消费者组消费进度偏移offset、主题、队列等。
具体工作流程:
1)启动NameServer,NameServer启动后开始监听端口,等待Broker、Producer、Consumer连接。
2)启动Broker,Broker会与所有的NameServer建立并保持长连接,然后每30秒向NameServer定时发送心跳包
3)发送消息前,可以先创建Topic,创建Topic时需要指定该Topic要存储在哪些Broker上,当然,在创建Topic时也会将Topic与Broker的关系写入到NameServer中。不过,这一步是可选的,也可以在发送消息时自动创建Topic。
4)Producer发送消息,启动时先跟NameServer集群中的一台建立长连接,并从NameServer中获取路由信息,即当前发送的Topic信息的Queue与Broker的地址(IP+Port)的映射关系。然后根据算法策略从队列中选择一个Queue,与队列所在的Broker建立长连接从而向Broker发送消息。当然,在获取到路由信息后,Producer会首先将路由信息缓存到本地,再每30秒从NameServer更新一次路由信息。
5)Consumer与Producer类似,跟其中一台NameServer建立长连接,获取其所订阅Topic的路由信息,然后根据算法策略从路由信息中获取到其所要消费的Queue,然后直接跟Broker建立长连接,开始消费其中的消息。Consumer在获取到路由信息后,同样也会每30秒从NameServer更新一次路由信息。不过不同于Producer的是,Consumer还会向Broker发送心跳,以确保Broker的存活状态。

1.3 Message

消息(Message)是指,消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。

1.3 Topic

主题(Topic)表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主体,是RocketMQ进行消息订阅的基本单位,
一个生产者可以同时发送多种Topic的消息,而一个消费者只对某种特定的Topic感兴趣,即只可以订阅和消费一种Topic的消息。

1.4 Tag

标签(Tag)是指为消息设置的标签,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务的目的在同一主题下设置不同标签。标签能够有效的保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现不同的子主题的不同消费逻辑,实现更好的扩展性。(Topic是消息的一级分类,Tag是消息的二级分类)

1.5 Queue

队列(Queue)是指存储消息的物理实体。
一个Topic中可以包含多个Queue,每个Queue中存放的就是该Topic的消息;
一个Topic的Queue也被称为一个Topic中消息的分区(Partition);
一个Topic的Queue中的消息只能被一个消费者组中的一个消费者消费;
一个Queue中的消息不允许同一个消费者组中的多个消费者同时消费;

1.6 MessageId/Key

消息标识(MessageId/Key):RocketMQ中每个消息拥有唯一的MessageId,且可以携带具有业务标识的Key,以方便对消息的查询。不过需要注意的是,MessageId有两个:在生产者send()消息时会自动生成一个MessageId(msgId),当消息到达Broker后,Broker也会自动生成一个MessageId(offsetMsgId)。msgId、offsetMsgId与key都称为消息标识。
1)msgId:由producer端生成,其生成规则为:producerIp+进程pid+MessageClientIDSetter类的ClassLoader的hashCode+当前时间+AutomicInteger自增计数器
2)offsetMsgId:由broker端生成,其生成规则为:brokerIp+物理分区的offset(Queue中的偏移量)
3)key:由用户指定的业务相关的唯一标识

1.7 Producer

消息生产者(Producer),负责生产消息。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程中支持快速失败并且低延迟。
RocketMQ中的消息生产者都是以生产者组(Producer Group)的形式出现的。生产者组是同一类生产者的集合,这类Producer发送相同Topic类型的消息。一个生产者组可以同时发送过个主题的消息。

1.8 Consumer

消息消费者(Consumer),负责消费消息。一个消息消费者会从Broker服务器中获取到消息,并对消息进行相关业务处理。
RocketMQ中的消息消费者都是以消费者组(Consumer Group)的形式出现的。消费者组是同一类消费者的集合,这类Consumer消费的是同一个Topic类型的消息。消费者组使得在消息消费你方面,实现负载均衡(将一个Topic中的不同Queue平均分配给同一个Consumer Group中的不同Consumer,注意,并不是将消息负载均衡)和容错(一个Consumer挂了,该Consumer Group中的其他Consumer可以接着消费原Consumer消费的Queue)的目标变得非常容易。
消费者组中的Consumer的数量应该小于等于订阅Topic的Queue数量。如果超出Queue数量,则多出的Consumer将不能消费消息,不过,一个Topic类型的消息可以被多个消费者组同时消费。
注意:
1)消费者组只能消费一个Topic的消息,不能同时消费多个Topic消息
2)一个消费者组中的消费者必须订阅完全相同的Topic

二、准备工作

2.1 安装JDK环境

由于RocketMQ运行需要用到JDK环境,如系统未配置JDK环境则需要自行安装后再接着往下进行,本文将跳过该步骤。

2.2 下载RocketMQ安装包

官方下载地址:https://rocketmq.apache.org/zh/download
RockerMQ的安装包有两种:1.源码包需要先编译构建成二进制可执行文件后执行,2.二进制包是已经编译完成可以直接运行的。

在这里插入图片描述

2.3 上传压缩包到Linux系统并解压

#若系统未安装unzip,需要先使用yum安装unzip (已安装的可忽略)
yum install -y unzip
#进入zip包存放的目录(本教程存放在opt目录下)
cd /opt
#解压文件
unzip rocketmq-all-5.1.1-bin-release.zip
#移动并修改文件名到/usr/local/rocketmq
mv rocketmq-all-5.1.1-bin-release /usr/local
cd /usr/local
mv rocketmq-all-5.1.1-bin-release rocketmq

2.4 修改启动配置信息

#修改 runserver.sh
vim /usr/local/rocketmq/bin/runserver.sh

在这里插入图片描述

#修改runbroker.sh
vim /usr/local/rocketmq/bin/runbroker.sh

在这里插入图片描述

三、启动与停止

3.1 启动NameServer

nohup sh mqnamesrv &
tail -f ~/logs/rocketmqlogs/namesrv.log

在这里插入图片描述

3.2 启动Broker

nohup sh mqbroker -n localhost:9876 &
tail -f ~/logs/rocketmqlogs/broker.log

在这里插入图片描述

3.3 使用工具测试发送信息

export NAMESRV_ADDR=localhost:9876
sh tools.sh org.apache.rocketmq.example.quickstart.Producer

3.4 使用工具测试接收信息

sh tools.sh org.apache.rocketmq.example.quickstart.Consumer

3.5 停止Server

#关闭broker
sh mqshutdown broker
#关闭namesrv
sh mqshutdown namesrv

四、安装rocketmq-dashboard控制台

4.1 安装Docker

如果对docker的安装还不熟悉的可以查看我的另一篇文章【Linux安装最新版Docker完整教程】,安装完之后再往下看。

4.2 Docker安装最新版rocketmq-dashboard

#拉取最新镜像
docker pull apacherocketmq/rocketmq-dashboard:latest
#启动dashboard
docker run -d --name rmqdashboard -p 8080:8080 \
 -e "JAVA_OPTS=-Drocketmq.namesrv.addr=192.168.0.132:9876 \
 -Dcom.rocketmq.sendMessageWithVIPChannel=false" \
 apacherocketmq/rocketmq-dashboard:latest

4.3 访问控制台: http://192.168.0.132:8080

在这里插入图片描述

五、整合到SpringBoot项目

5.1 创建项目

在这里插入图片描述

5.2 pom.xml

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

5.3 application.properties

server.port=9999
rocketmq.name-server=192.168.0.132:9876
rocketmq.producer.group=producerGroup

5.4 MessageBody

import lombok.Data;
import java.io.Serializable;

@Data
public class MessageBody implements Serializable {
    
    

    /**
     * 消息id
     */
    private String messageId;
    /**
     * 操作时间
     */
    private long timestamp;
    /**
     * 来源 附加信息
     */
    private String msgSource;
    /**
     * 数据
     */
    private Object data;

    public MessageBody() {
    
    

    }

    public MessageBody(String msgKey, Object data, String msgSource) {
    
    
        this.messageId = msgKey;
        this.data = data ;
        this.msgSource = msgSource;
        this.timestamp = System.currentTimeMillis();
    }

}

5.5 MQService

import com.xiaoankj.rocketmq.entity.MessageBody;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class MQService {
    
    

    private final static Logger logger = LoggerFactory.getLogger(MQService.class);
    private enum MSG_TYPE{
    
     ONEWAY, ASYNC, SYNC };
    @Resource
    public RocketMQTemplate rocketMQTemplate;

    /**
     * 发送消息,通用
     * @param msgType
     * @param destination
     * @param payload
     */
    private void sendMsg(MSG_TYPE msgType, String destination, Object payload, String msgSource){
    
    
        String msgKey = UUID.randomUUID().toString().replace("-", "");
        MessageBody msgBody = new MessageBody(msgKey, payload , msgSource);
        Message<MessageBody> message = MessageBuilder.withPayload(msgBody).setHeader("KEYS", msgKey).build();
        logger.info(String.format("消息发送 MQService 开始: %s %s", destination, message));
        SendResult result = null;
        switch (msgType) {
    
    
            case ONEWAY:
                rocketMQTemplate.sendOneWay(destination, message);
                break;
            case ASYNC:
                rocketMQTemplate.asyncSend(destination, message,new SendCallback() {
    
    
                    @Override
                    public void onSuccess(SendResult sendResult) {
    
    
                    }
                    @Override
                    public void onException(Throwable throwable) {
    
    
                        logger.error("MQService:" + ExceptionUtils.getStackTrace(throwable));
                        throw new RuntimeException(String.format("消息发送失败 topic_tag:%s", destination ));
                    }
                });
                break;
            case SYNC:
                result = rocketMQTemplate.syncSend(destination, message);
                break;
        }
        logger.info(String.format("消息发送 MQService 结束: msgId: %s dest: %s msg: %s",result != null ? result.getMsgId() : "", destination, message));
    }

    /**
     * 同步发送消息,会确认应答
     * @param destination
     * @param payload
     */
    public void syncSendMsg(String destination, Object payload, String msgSource){
    
    
        sendMsg(MSG_TYPE.SYNC,destination, payload,msgSource) ;
    }

    /**
     * 同步发送消息,会确认应答
     * @param topic
     * @param tag
     * @param payload
     */
    public void syncSendMsg(String topic, String tag, Object payload, String msgSource){
    
    
        String destination = topic + ":" + tag;
        syncSendMsg(destination, payload,msgSource);
    }

    /**
     * 异步消息发送,异步日志确认异常
     * @param destination
     * @param payload
     */
    public void asyncSendMsg(String destination, Object payload, String msgSource){
    
    
        sendMsg(MSG_TYPE.ASYNC, destination, payload,msgSource);
    }

    /**
     * 异步消息发送,异步日志确认异常
     * @param topic
     * @param tag
     * @param payload
     * @return
     */
    public void asyncSendMsg(String topic, String tag, Object payload, String msgSource){
    
    
        String destination = topic + ":" + tag;
        asyncSendMsg(destination, payload, msgSource);
    }

    /**
     * 单向发送消息,不关注结果
     * @param destination
     * @param payload
     */
    public void oneWaySendMsg(String destination, Object payload, String msgSource){
    
    
        sendMsg(MSG_TYPE.ONEWAY, destination, payload, msgSource);
    }

    /**
     * 单向发送消息,不关注结果
     * @param topic
     * @param tag
     * @param payload
     */
    public void oneWaySendMsg(String topic, String tag, Object payload, String msgSource){
    
    
        String destination = topic + ":" + tag;
        oneWaySendMsg(destination, payload, msgSource);
    }

}

5.6 ComsumerListener

import com.xiaoankj.rocketmq.entity.MessageBody;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@RocketMQMessageListener(topic = "test-topic",nameServer = "${rocketmq.name-server}",consumerGroup = "${rocketmq.producer.group}", selectorExpression = "test-tag")
@Component
@Slf4j
public class ComsumerListener implements RocketMQListener<MessageBody> {
    
    

    @Override
    public void onMessage(MessageBody messageBody) {
    
    
        System.out.println(messageBody);
    }
}

5.7 ComsumerListener

import com.alibaba.fastjson.JSONObject;
import com.xiaoankj.rocketmq.utils.MQService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("test")
public class TestController {
    
    

    @Resource
    private MQService mQService;

    /**
     * 发送消息
     * @return
     */
    @GetMapping("sendMsg")
    public String sendMsg() {
    
    
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "小明");
        jsonObject.put("sex", "男");
        jsonObject.put("age", 18);
        mQService.syncSendMsg("test-topic", "test-tag", jsonObject, "消息来源~");
        return "发送成功!";
    }

}

六、测试接口

6.1 启动SpringBoot项目

在这里插入图片描述

6.2 浏览器请求测试接口: http://localhost:9999/test/sendMsg

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43755251/article/details/130642657