参考:https://www.cnblogs.com/myseries/p/13153797.html
参考:https://www.cnblogs.com/qdhxhz/p/11109696.html
参考:https://zhuanlan.zhihu.com/p/335216381
基本概念
消息模型
RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。
消息生产者
负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
消息消费者
负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费。
主题(Topic)
表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位。
代理服务器(Broker Server)
消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
名字服务(Name Server)
名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群,但相互独立,没有信息交换。
拉取式消费(Pull Consumer)
Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。
推动式消费(Push Consumer)
Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。
生产者组(Producer Group)
同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。
消费者组(Consumer Group)
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式:集群消费(Clustering)和广播消费(Broadcasting)。
集群消费(Clustering)
集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。
广播消费(Broadcasting)
广播消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息。
普通顺序消息(Normal Ordered Message)
普通顺序消费模式下,消费者通过同一个消费队列收到的消息是有顺序的,不同消息队列收到的消息则可能是无顺序的。
严格顺序消息(Strictly Ordered Message)
严格顺序消息模式下,消费者收到的所有消息均是有顺序的。
消息(Message)
消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。
标签(Tag)
为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性。
DefaultMQPushConsumer
和DefaultMQPullConsumer
:
Push的方式是 Server端接收到消息后,主动把消息推送给 Client端,主动权在Server端,实时性高。用 Push方式主动推送有很多弊 端:首先是加大 Server 端的 工作量,进而影响 Server 的性能;其次,Client 的处理能力各不相同, Client 的状态不受 Server 控制,
Pull方式是 Client端循环地从 Server端拉取消息,主动权在 Client手里, 自己拉取到一定量消息后,处理妥当了再接着取。Pull 方式的问题是循环拉取 消息的间隔不好设定,间隔太短就处在一个 “忙等”的状态,浪费资源; Pull 的时间间隔太长 Server 端有消息到来时 有可能没有被及时处理。
启动rocketmq服务
start /d "D:\RocketMQ\rocketmq-all-4.3.0-bin-release\bin\" mqnamesrv.cmd
timeout /t 7
start /d "D:\RocketMQ\rocketmq-all-4.3.0-bin-release\bin\" mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
启动rocketmq控制台
java -jar rocketmq-console-ng-2.0.0.jar
首先创建一个springboot项目,引入rocketmq的依赖:
<!-- rocketmq依赖 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
我们可以定义一个消息体类,用来封装消息。
@Data
public class MessageBody {
// 消息id
private String messageId;
// body组装时间
private long timestamp;
// 来源 附加信息
private String msgSource;
// overload
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();
}
}
发消息工具类:
@Component
public class MQService {
private final static Logger logger = LoggerFactory.getLogger(MQService.class);
private static enum MSG_TYPE{ ONEWAY, ASYNC, SYNC };
@Autowired
public RocketMQTemplate rocketMQTemplate;
/**
* 发送消息,通用
* @param msg_type
* @param destination
* @param payload
*/
private void sendMsg(MSG_TYPE msg_type, String destination, Object payload, String msgSource){
String msgKey = IdUtils.simpleUUID();
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 (msg_type) {
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 CustomException(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){
// 发送的消息体,消息体必须存在
// 业务主键作为消息key
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){
// 发送的消息体,消息体必须存在
// 业务主键作为消息key
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){
// 发送的消息体,消息体必须存在
// 业务主键作为消息key
String destination = topic + ":" + tag;
oneWaySendMsg(destination, payload,msgSource);
}
}
消费者:
@RocketMQMessageListener(topic = "test-topic",nameServer = "${rocketmq.nameServer}",consumerGroup = "${rocketmq.consumer.group}", selectorExpression = "test-tag")
@Component
@Slf4j
public class ComsumerListener implements RocketMQListener<MessageBody> {
@Autowired
private ItestService testService;
@Override
public void onMessage(MessageBody messageBody) {
System.out.println(messageBody);
}
}
data的结构生产者和消费者约定好就行了。