安装搭建前置条件(参照官网https://rocketmq.apache.org/docs/quick-start/)
安装步骤
> unzip rocketmq-all-4.4.0-source-release.zip
> cd rocketmq-all-4.4.0/
> mvn -Prelease-all -DskipTests clean install -U
> cd distribution/target/apache-rocketmq
> nohup sh bin/mqnamesrv &
> tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
> nohup sh bin/mqbroker -n localhost:9876 &
> tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success...
> sh bin/mqshutdown broker
The mqbroker(36695) is running...
Send shutdown request to mqbroker(36695) OK
> sh bin/mqshutdown namesrv
The mqnamesrv(36664) is running...
Send shutdown request to mqnamesrv(36664) OK
中间产生的问题
问题1:Please set the JAVA_HOME variable in your environment, We need java(x64)
解决:需要本地配置JAVA_HOME
JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Con
export JAVA_HOME
CLASS_PATH="$JAVA_HOME/lib"
PATH=".$PATH:$JAVA_HOME/bin"
测试
安装步骤
> rocketmq.config.namesrvAddr=127.0.0.1:9876
> cd rocketmq-externals/rocketmq-console
> mvn clean package -Dmaven.test.skip=true
中间产生的问题
问题1:打包失败
解决:检查pom文件
将4.4.0-SNAPSHOT
调整为4.4.0
问题2:启动失败
解决:检查pom文件Springboot版本
将 1.4.3.RELEASE
调整为1.5.4.RELEASE
然后重新打包,并启动
问题3:控制台报 连接10909错误
原因:Broker 默认开启了vip通道,端口为10911-2=10909(源码可查看)
解决:防火墙开启端口10909
源码地址
前置条件
开发步骤
pom.xml添加RocketMq依赖
org.apache.rocketmq
rocketmq-client
4.4.0
yml增加配置信息
server:
port: 8888
mouse:
rocketmq:
producer:
groupName: pay_producer_group #发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
namesrvAddr: localhost:9876 #mq的nameserver地址
maxMessageSize: 4096 #消息最大长度 默认1024*4(4M)
sendMsgTimeout: 3000 #发送消息超时时间,默认3000
retryTimesWhenSendFailed: 2 #发送消息失败重试次数,默认2
consumer:
isOnOff: on #on==true 踩过的坑
groupName: pay_producer_group
namesrvAddr: localhost:9876 #mq的nameserver地址
topics: TopicTest~* #该消费者订阅的主题和tags("*"号表示订阅该主题下所有的tags),格式:topic~tag1||tag2||tag3;topic2~*;
consumeThreadMin: 20
consumeThreadMax: 64
consumeMessageBatchMaxSize: 1 #设置一次消费消息的条数,默认为1条
增加生产者bean
@SpringBootConfiguration
public class MQProducerConfiguration {
public static final Logger LOGGER = LoggerFactory.getLogger(MQProducerConfiguration.class);
public static final String PREFIX = "mouse";
/**
* 发送同一类消息的设置为同一个group,保证唯一,默认不需要设置,rocketmq会使用ip@pid(pid代表jvm名字)作为唯一标示
*/
@Value("${mouse.rocketmq.producer.groupName}")
private String groupName;
@Value("${mouse.rocketmq.producer.namesrvAddr}")
private String namesrvAddr;
/**
* 消息最大大小,默认4M
*/
@Value("${mouse.rocketmq.producer.maxMessageSize}")
private Integer maxMessageSize ;
/**
* 消息发送超时时间,默认3秒
*/
@Value("${mouse.rocketmq.producer.sendMsgTimeout}")
private Integer sendMsgTimeout;
/**
* 消息发送失败重试次数,默认2次
*/
@Value("${mouse.rocketmq.producer.retryTimesWhenSendFailed}")
private Integer retryTimesWhenSendFailed;
@Bean
public DefaultMQProducer getRocketMQProducer()throws RocketMQException {
if (StringUtils.isEmpty(this.groupName)) {
throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"groupName is blank",false);
}
if (StringUtils.isEmpty(this.namesrvAddr)) {
throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"nameServerAddr is blank",false);
}
DefaultMQProducer producer;
producer = new DefaultMQProducer(this.groupName);
producer.setNamesrvAddr(this.namesrvAddr);
//如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
//producer.setInstanceName(instanceName);
if(this.maxMessageSize!=null){
producer.setMaxMessageSize(this.maxMessageSize);
}
if(this.sendMsgTimeout!=null){
producer.setSendMsgTimeout(this.sendMsgTimeout);
}
//如果发送消息失败,设置重试次数,默认为2次
if(this.retryTimesWhenSendFailed!=null){
producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);
}
try {
producer.start();
LOGGER.info(String.format("producer is start ! groupName:[%s],namesrvAddr:[%s]"
, this.groupName, this.namesrvAddr));
} catch (MQClientException e) {
LOGGER.error(String.format("producer is error {}"
, e.getMessage(),e));
throw new RocketMQException(e);
}
return producer;
}
}
增加消费者监听
@Component
public class MQConsumeMsgListenerProcessor implements MessageListenerConcurrently{
private static final Logger logger = LoggerFactory.getLogger(MQConsumeMsgListenerProcessor.class);
/**
* 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息
* 不要抛异常,如果没有return CONSUME_SUCCESS ,consumer会重新消费该消息,直到return CONSUME_SUCCESS
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
if(CollectionUtils.isEmpty(msgs)){
logger.info("接受到的消息为空,不处理,直接返回成功");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
MessageExt messageExt = msgs.get(0);
try {
String body = new String(messageExt.getBody(), "utf-8");
logger.info("接受到的消息body为:"+body);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
logger.info("接受到的消息为:"+messageExt.toString());
if(messageExt.getTopic().equals("你的Topic")){
if(messageExt.getTags().equals("你的Tag")){
//TODO 判断该消息是否重复消费(RocketMQ不保证消息不重复,如果你的业务需要保证严格的不重复消息,需要你自己在业务端去重)
//TODO 获取该消息重试次数
int reconsume = messageExt.getReconsumeTimes();
if(reconsume ==3){//消息已经重试了3次,如果不需要再次消费,则返回成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
//TODO 处理对应的业务逻辑
}
}
// 如果没有return success ,consumer会重新消费该消息,直到return success
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
增加消费者bean
@Configuration
@ConditionalOnProperty(name = "mouse.rocketmq.consumer.isOnOff", havingValue = "true")
public class MQConsumerConfiguration {
public static final Logger LOGGER = LoggerFactory.getLogger(MQConsumerConfiguration.class);
@Value("${mouse.rocketmq.consumer.namesrvAddr}")
private String namesrvAddr;
@Value("${mouse.rocketmq.consumer.groupName}")
private String groupName;
@Value("${mouse.rocketmq.consumer.consumeThreadMin}")
private int consumeThreadMin;
@Value("${mouse.rocketmq.consumer.consumeThreadMax}")
private int consumeThreadMax;
@Value("${mouse.rocketmq.consumer.topics}")
private String topics;
@Value("${mouse.rocketmq.consumer.consumeMessageBatchMaxSize}")
private int consumeMessageBatchMaxSize;
@Autowired
private MQConsumeMsgListenerProcessor mqMessageListenerProcessor;
@Bean
public DefaultMQPushConsumer getRocketMQConsumer() throws RocketMQException {
if (StringUtils.isEmpty(groupName)){
throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"groupName is null !!!",false);
}
if (StringUtils.isEmpty(namesrvAddr)){
throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"namesrvAddr is null !!!",false);
}
if(StringUtils.isEmpty(topics)){
throw new RocketMQException(RocketMQErrorEnum.PARAMM_NULL,"topics is null !!!",false);
}
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr(namesrvAddr);
consumer.setConsumeThreadMin(consumeThreadMin);
consumer.setConsumeThreadMax(consumeThreadMax);
consumer.registerMessageListener(mqMessageListenerProcessor);
/**
* 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
* 如果非第一次启动,那么按照上次消费的位置继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
/**
* 设置消费模型,集群还是广播,默认为集群
*/
//consumer.setMessageModel(MessageModel.CLUSTERING);
/**
* 设置一次消费消息的条数,默认为1条
*/
consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);
try {
/**
* 设置该消费者订阅的主题和tag,如果是订阅该主题下的所有tag,则tag使用*;如果需要指定订阅该主题下的某些tag,则使用||分割,例如tag1||tag2||tag3
*/
String[] topicTagsArr = topics.split(";");
for (String topicTags : topicTagsArr) {
String[] topicTag = topicTags.split("~");
consumer.subscribe(topicTag[0],topicTag[1]);
}
consumer.start();
LOGGER.info("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr);
}catch (MQClientException e){
LOGGER.error("consumer is start !!! groupName:{},topics:{},namesrvAddr:{}",groupName,topics,namesrvAddr,e);
throw new RocketMQException(e);
}
return consumer;
}
}
中间产生的问题
问题1: org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException:
sendDefaultImpl call timeout
原因:因为服务器存在多个网卡需要指定,客户端可访问的ip地址
解决: conf/broker.conf 增加 brokerIP1=你的客户端访问的ip
问题2:MQClientException: No route info of this topic, TopicTest
原因:Broker 禁止自动创建topic,且用户没有手动创建topic,或者是broker和Nameserver网络不通,以及客户端版本与服务端版本不一致
解决: sh bin/mqbroker -m 查看配置,autoCreateTopicEnable=true 则是自动创建(生产环境建议false);防火墙查看;控制中心创建topic;版本查看比较
其他错误:
https://blog.csdn.net/qq_14853889/article/details/81053145
https://blog.csdn.net/wangmx1993328/article/details/81588217
https://www.jianshu.com/p/bfd6d849f156
https://blog.csdn.net/wangmx1993328/article/details/81588217
背景
随着业务发展,越来越多的应用接入了同一个中间件,耦合性太强。也觉得越来越不安心,不稳心。怕哪天宕了后,所有业务也跟着受到牵连影响。为了组件高可用,决定新搭建一套集群,业务后续逐步迁移过来。
1.单节点
指单个Master节点
优点:配置简单,同步刷盘不会丢失消息
缺点:不可靠,宕机会导致整个服务不可用
2.主从
指一个Master节点和一个Slave节点(异步,同步双写)
优点:同步双写消息不会丢失,异步复杂会存在少量丢失情况。主节点宕机,从节点可以对外提供消息的消费,但不支持写入。
缺点:主从会有短暂的消息延迟,毫秒级,目前版本不支持自动切换
3.双主
指两个或两个以上个Master节点
优点:配置简单,单个Master 宕机或重启维护对应用无影响,在磁盘配置为RAID10 时,即使机器宕机不可恢复情况下,由与 RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢)。性能最高。
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到受到影响。
4.多主多从(异步复制)
每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟,毫秒级。
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为Master宕机后,消费者仍然可以从Slave消费,此过程对应用透明。不需要人工干预。性能同多Master模式几乎一样。
缺点:Master宕机,磁盘损坏情况,会丢失少量消息。
5.多主多从(同步复制)
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,主备都写成功,向应用返回成功。
优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
缺点:性能比异步复制模式略低,大约低10%左右,发送单个消息的RT会略高。目前主宕机后,备机不能自动切换为主机,后续会支持自动切换功能。
同步复制:数据安全性高,性能低
异步复杂:数据可能丢失,性能高
1.同步双写,异步刷盘(SYNC_MASTER + ASYNC_FLUSH)
对于消息丢失容忍度很低的应用,官方建议采用SYNC_MASTER + ASYNC_FLUSH的方式,这样只有master和slave在刷盘前同时挂掉,且都没有刷到磁盘,消息才会丢失。这样是一个兼顾性能和可靠性的较好平衡
2.异步双写,异步刷盘(ASYNC_MASTER+ASYNC_FLUSH)
对消息丢失容忍度较高的应用,数据实时性更高
ROCKETMQ集群安装-多Master多Slave模式,异步复制(2m-2s-async)
前期准备
server1 ssh [email protected]
server1 ssh [email protected]
1.修改RocketMQ 内存大小(内存够大可忽略)
[root@localhost bin]# pwd
/usr/local/software/rocketmq/apache-rocketmq/bin
[root@localhost bin]# vim runserver.sh
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
[root@localhost bin]# pwd
/usr/local/software/rocketmq/apache-rocketmq/bin
[root@localhost bin]# vim runbroker.sh
JAVA_OPT="${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m"
2.两台服务器分别启动nameserver
nohup sh bin/mqnamesrv &
[root@localhost apache-rocketmq]# jps
27764 Jps
27701 NamesrvStartup
3.修改配置文件
Master节点修改conf/2m-2s-async/broker-a.properties
namesrvAddr=10.37.129.3:9876;10.37.129.4:9876
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
Slave节点修改conf/2m-2s-async/broker-a-s.properties
namesrvAddr=10.37.129.3:9876;10.37.129.4:9876
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
4.指定配置文件启动broker
Master 节点启动
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a.properties &
Slave 节点启动
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties &
2019-04-09 16:07:19 INFO brokerOutApi_thread_1 - register broker to name server 10.37.129.3:9876 OK
2019-04-09 16:07:19 INFO brokerOutApi_thread_2 - register broker to name server 10.37.129.4:9876 OK
至此服务端已经搭建完成
5.mq管控台
搭建步骤参照第二章
修改application.properties里面nameserver地址,并重新打包启动
#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=10.37.129.3:9876;10.37.129.4:9876
问题1: ERROR Unexpected error occurred in scheduled task.
java.lang.RuntimeException: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.0.1:10909> failed
at com.google.common.base.Throwables.propagate(Throwables.java:160)
原因:因为服务器存在多个网卡需要指定,客户端可访问的ip地址
解决: conf/2m-2s-async/broker-a.properties 增加 brokerIP1=你的客户端访问的ip