30分钟搞懂 RocketMQ原理

概述

消息队列作用
应用解耦:对系统之间的交互使用消息队列,降低系统之间的耦合.
流量消峰:利用消息队列进行缓存,使短时高并发的任务,可以分散在一段时间内进行处理.
消息分发:将数据写入消息队列,供个个系统之间进行订阅.
保证最终一致性:通过消息队列的事物功能,保证两个系统的事物要么都成功要么都失败.

RocketMQ优点

1.基于java开发,易于优化和维护
2.相较于rabbitMq吞吐量更好
3.相较于kafka消息失败可以支持重试.
4.经过阿里双十一的洗礼,对万亿级消息支持良好

消息队列的选择

1.Kafka
Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,适合产生大量数据的互联网服务的数据收集业务。大型公司建议可以选用,如果有日志采集功能,肯定是首选kafka了。
2.RocketMQ
天生为金融互联网领域而生,对于可靠性要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况。RoketMQ在稳定性上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验,如果你的业务有上述并发场景,建议可以选择RocketMQ。
3.RabbitMQ
结合erlang语言本身的并发优势,性能较好,社区活跃度也比较高,但是不利于做二次开发和维护。不过,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug。
如果你的数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ。

RocketMQ角色介绍

在这里插入图片描述

Customer消费者用于接受消息
Producter生产者用于生产消息
NameServer消息队列的协调者
Broker用于暂存传输消息

配置两台互为主备的rocketmq服务

配置步骤

1.设两台服务器的地址为192.168.100.131和192.168.100.132
分别在上述的机器上启动NameServer(nosup sh bin/mqnamesrv &)
得到NameServer服务地址:192.168.100.131:9876和192.168.100.132:9876

2.配置Master Broker(配置文件在conf/2m-2s-sync目录下):

192.168.100.131的Master Broker配置:

namesrvAddr=192.168.100.131:9876;192.168.100.132:9876
brokerClusterName=DefalutCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=SYNC_MASTER
flushDiskType=ASYNC_FLUSH
listenPort=10911
storePathRootDir=/home/rocketmq/store-a

192.168.100.132的Master Broker配置:

namesrvAddr=192.168.100.131:9876;192.168.100.132:9876
brokerClusterName=DefalutCluster
brokerName=broker-b
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=SYNC_MASTER
flushDiskType=ASYNC_FLUSH
listenPort=10911
storePathRootDir=/home/rocketmq/store-b

192.168.100.131的Slave Broker配置(131备份132,132备份131从而保证当一台机器宕机时消息不丢失):

namesrvAddr=192.168.100.131:9876;192.168.100.132:9876
brokerClusterName=DefalutCluster
brokerName=broker-b
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
listenPort=10911
storePathRootDir=/home/rocketmq/store-b

192.168.100.132的Slave Broker配置:

namesrvAddr=192.168.100.131:9876;192.168.100.132:9876
brokerClusterName=DefalutCluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
listenPort=10911
storePathRootDir=/home/rocketmq/store-a

3.这样一个由两台机器搭建的rocketmq就配置好了.接下来只需分别启动上述四个broker就好了,启动方式: nohup sh ./bin/mqbroker -c config_file &
如果想要可视化化查看rocketmq的集群状态,可以在一台机器上启动Rocket-console,比如192.168.100.131,然后输入192.168.100.131:8080,机可实现可视化查看集群状态

配置参数介绍

1.namesrvAddr=192.168.100.131:9876;192.168.100.132:9876 //namesever地址,可以配置多个
2.brokerClusterName=DefalutCluster //集群的名称
3.brokerName=broke-a //broker名称,master和slave通过相同的broker名称关联
4.brokerId=0 //一个master可以拥有多个slave,0表示master,大于0表示slave
5.fileReservedTime=48 //在磁盘上保存消息的时长,单位是一小时,自动删除超时消息
6.deleteWhen=04 //表示在04点做删除操作
7.brokerRole=SYNC_MASTER //brokerRole有SYNC_MASTER,ASYNC_MASTER,SLAVE三种角色,SYNC与ASYNC表示主从同步机制
8.flushDiskType=ASYNC_FLUSH //表示刷盘策略,有ASYNC_FLUSH和SYNC_FLUSH两种.
9.listenPort=10911 //broker的监听端口号
10.storePathRootDir=/home/rockmq/store-a //存储消息及一些配置信息的根目录

消费者Customer

rocketmq的消费者可以分为DefaultMQPushConsumer和DefaultMQPullConsumer两类.
DefaultMQPushConsumer由系统控制读取操作.系统收到消息时,自动保存offset,新加入的消费者会自动做负载均衡.
DefaultMQPullConsumer读取操作大部分功能由开发者自主控制.

DefaultMQPushConsumer使用

DefaultMQPushConsumer使用需要设置三个参数:customer的groupname,nameserver,topic
其中Groupname把多个customer组织到一起.Cluster集群模式时,每个消费者消费一部分内容,达到负载均衡的目的;broadcasting广播模式时,每个消费者都订阅全部的消息.
Topic用于标记消息类型,需要提前创建,如果不需要topic下的所有消息,可以通过指定tag进行过滤

DefaultMQPushConsumer处理流程

DefaultMQPushConsumer通过长轮询方式实现服务端与客户端的配合.
服务端接受到消息后不立即返回,通过一个循环,默认每五秒查看一次.利用这种方式hold住客户端的请求一段时间,如果有消息到达结果就立刻返回给customer.如果超过BrokerSuspendMaxTimeMillis(默认15秒)的时间没有消息到达,就返回空.

DefaultMQPushConsumer 流量控制

Rocketmq 为每个message mq生成一个processQueue.
processQueue主要内容是一个TreeMap和读写锁.TreeMap的key是offset,value为未被读取的消息.读写锁控制着多个线程对TreeMap对象的并发访问.
DefaultMQPushConsumer会判断获取但未处理的消息数量,消息总大小,offset的跨度.任何一个值超过设定的大小就会隔一段时间再去拉取,从而达到控制流量的目的.

DefaultMQPullConsumer

DefaultMQPullConsumer采用pull的方式读取信息,每隔一段时间拉取信息.
处理流程:
1.根据topic得到多个message queue,已读取其中的信息
2.随着读取offset不断增长,需要自己保存offset
3.根据不同的消息状态做处理,拉取信息后一共会返回 FOUND(找到消息),NO_MATCHED_MSG(没有对应消息),NO_NEW_MSG(没有新消息),OFFSET_ILLEGAL(offset不合法)
DefaultMQPullConsumer与DefaultMQPushConsumer的差别在于,DefaultMQPullConsumer需要自己实现遍历message,并保存offset.有更多的灵活性和自主性.

Consumer的关闭

1.DefaultMQPullConsumer在关闭或者发生异常时要自己记录offset
2.DefaultMQPushConsumer退出时要调用shutdown保存offset

生产者Producter

生产负责把消息写入队列,写入策略有同步发送,异步发送,延迟发送,发送事物等.
Producter可以分为DefaultMQProducter和TransactionMQProducter两种模式.

DefaultMQProducter消息发送步骤

1.设置DefaultMQProducter的groupname
2.设置instanceName
3.设置发送失败次数,如果网络出问题,会按照这个次数进行重发
4.设置nameserver地址
5.组装消息并发送
6.根据消息返回状态进行处理,消息返回状态有:
FLUSH_DISK_TIMEOUT:没有在规定时间内完成刷盘
FLUSH_SLAVE_TIMEOUT:主备模式下broker没有在设定时间内完成主从同步
SLAVE_NOT_AVALIBLE:主备模式下没有找到从broker
SEND_OK:发送成功

发送延迟消息

Broker收到消息后,延迟一段时间再处理,使消息在规定的时间内再生效.

延迟发送消息通过调用setDelayTimeLevel(int level)实现.延迟时间长度有(1s/5s/10s/30s/1m/2m/…/10m/30m/1h/2h),比如setDelayTimeLevel(3)表示延迟10秒

把消息发送到指定的messagequeue

一个topic有多个message queue,消息会均匀的负载到每个message queue中.如果想发送到指定messagequeue中,可以使用Producter.send(Message msg,MessageQueueSelector seletor,object arg)方法.自己实现MessageQueueSelector 接口,复写MessageQueue select(Message msg,List mqs,object arg)方法实现,接口返回的MessageQueue 就是消息指定发送的的messagequeue

对事物的支持

通过TransactionMQProducter实现,实现流程为:
1.发送待确认消息
2.Rocketmq回复待确认消息发送成功
3.发送方执行业务
4.根据业务执行结果向rocketmq发送二次确认消息,如果发送Commit订阅方将接收到消息如果是Rollback消息将取消
5.如果4失败,服务器经过固定时间发送回查请求(回查监听通过TransactionCheckListener实现)
6.如果Producer不能工作通过本地同一个group发送给其他的producer
7.收到回查请求后根据4去处理
RocketMQ事务消息代码样例:https://my.oschina.net/u/3768341/blog/1616193

消息队列协调者NameServer

NameServer是整个消息队列中的状态服务器,集群的各个组件通过它来了解全局的信息.同时各个角色的机器都要定期向nameserver上报自己的状态,超时不上报的话,nameserver会认为机器出故障不可用,其他的组件会把这个机器从可用列表中移除.
Nameserver可以部署多个,从而达到热备份的目的.

集群状态的存储结构

HashMap<String,List<QueueData>> topicQueueTable //Key为消息队列的topic,queueData存储着broker的名称,queue的数量,同步标识等.
HashMap<String,BrokerData> brokerAddrTable //Key为brokername,value为brokername对应的broker信息包括所属集群的名称,一个master broker和多个slave broker

HashMap<String,set<String>> clusterAddrTable //Key为集群名称,value为集群对应的brokerName的集合
HashMap<String,BrokerLiveInfo> brokerLiveTable //Key为broker的地址,value为broker的状态,如上次更新的时间戳
HashMap<String,List<String>> filterServerTable //key为broker地址,value为与broker关联的多个filter Server地址

nameserver主要就是通过上述的五个变量实现对broker组件信息的维护

NameServer对broker的状态维护过程

1.其他组件主动向nameserver上报状态
2.Nameserver根据上报信息更新信息.包括断开事件也会触发状态更新
3.Broker向nameserver发送心跳检验,每10秒一次.nameserver接受到后更新broker的时间戳.如果超过2分钟nameserver没有接收到,会判断broker下线

为何不用Zookeeper

因为rocketmq当有一个机器宕机后,新消息会发送给集群中其他的broker,而宕机那台的master会被其对应的slave机器继续支持其读原来的消息.
所以rocketmq不需要进行master节点选举,因此也就不用Zookeeper.

消息队列的核心broker

大部分重量级的工作都有broker完成,包括接受producer的信息,处理consumer的信息和文件的存储,消息HA机制以及服务端过滤功能

消息的存储

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

Rocketmq消息存储由customerqueue和commitLog配合完成.
每个topic下的每个messagequeue都有一个customerqueue文件,地址在:${storeRoot}\consumerqueue${topicName}${queueId}${fileName}
CommitLog以物理文件存储,每台机器上的CommitLog被这台机器的所有customerqueue共享.文件地址在:${user.home}${store}${CommitLog}${fileName}

以上存储机制的好处:
1.CommitLog顺序写,可以提高写入速率
2.随机读,利用操作系统的pagecache机制,可以批量从磁盘读取,作为cache存到内存中,加速后续的读取速度
3.Consumerqueue只保存了offset,大部分Consumerqueue都能写入内存,保证了读取速度.CommitLog保存了Consumer queues,message key,tag等信息,即时Consumerqueue丢失也能恢复

高可用机制

消费端高可用:在Consumer的配置文件中,并不需要设置从master还是slave读,当master繁忙或者不可用时,会自动条到slave读.

发送端高可用:把多个topic的多个message queue创建在多个broker组上(相同brokename的broker),当一个broker的master不可用后,会发送到其他master上,保证producer仍可以发送消息.

刷盘方式

1.异步刷盘:在返回写成功状态时,消息可能只是被写入了内存的pagecache.
2.同步刷盘:在返回写成功状态时,消息不仅被写入了内存的pagecache,写入后也完成了刷盘写入硬盘.

主从复制方式

1.异步复制:master写入成功即返回
2.同步复制:master和slave都写入成功再返回

通常把主从复制方式设置为异步复制,刷盘方式设置为同步方式.

Offset的存储

Offset指某个topic下的一条消息在某个message queue中位置.

DefaultMQPushConsumer的offset由broker存储.DefaultMQPushConsumer只要正常关闭都能从最近的offset位置开始获取消息,所以一般DefaultMQPushConsumer收消息时如果自己设置offset的位置,通常只在第一次链接时生效.
DefaultMQPullConsumer需要自己存储offset的位置,建议通过数据库或者文件等对offset做持久化.

RocketMQ日志输出

Rocketmq日志的默认存储位置为${home}/Logs/rocketmqLogs.
日志级别通过:rocketmq.Client.Loglevel设置如启动时设置System.setProperty(“rocketmq.Client.Loglevel”,”WARN”)
Rocketmq日志支持自定义设置,首先需要设置System.setProperty(“rocketmq.Client.Log.loadcofig”,”false”),然后把Logback.xml放到maven项目的resource下.Logback.xml配置见:https://Logback.qos.ch/manual/configuration.html

消息文件清理

消息存储在CommitLog之后,会被删除,删除的任一条件如下:
1.消息文件过期(fileReservedTime默认72小时),且到达清理时点(deleteWhen默认是凌晨4点),删除过期文件。
2.消息文件过期,且磁盘空间达到了水位线(默认75%),删除过期文件。
3.磁盘已经达到必须释放的上限(85%水位线)的时候,则开始批量清理文件(无论是否过期),直到空间充足。(若磁盘空间达到危险水位线(默认90%),出于保护自身的目的,broker会拒绝写入服务)

RocketMq对高可用场景的支持

重点介绍在满足业务需求的要求上,怎么实现稳定,高可用.

部分顺序消息实现

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

如上图一般情况下生产者采用轮询方式向队列中发消息,消费者根据负载均匀的读取消息.
所以在实现部分顺序消息时需要:
1.让生产者把同一个业务的消息发送到指定的messagequeue(上述章节”生产者Producer”中所诉)
2.在消费者保证同一时间,同一个messagequeue的消息不被并发消费,实现方式可通过增加MessageListenerOrderly 监听实现.如:

public class MyMessageListener implements  MessageListenerOrderly {

    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, 	  		ConsumeOrderlyContext context) {
        // 设置自动提交
        context.setAutoCommit(true);
        for (MessageExt msg : msgs) {
            System.out.println(msg + ",内容:" + new String(msg.getBody()));
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
}

然后在customer中加上:

MyMessageListener myMessageListener = new MyMessageListener();
consumer.registerMessageListener(myMessageListener);

来实现.

它的实现原理是:
为每个consumer queue 加个锁,消费每个消费前,需要先获得这个消息对应的consumerqueue的锁.这样就保证了同一时间,同一个messagequeue的消息不被并发消费.从而实现消费端的顺序消费.

消息重复问题

消息重复问题如:producer有个函数setRetryTimesWhenSendFailed为同步方式下的自动重试次数,默认为2次.broker在消息量大,网络波动的情况下,可能在收到消息后没有返回成功的状态.造成了消息重复.
消息重复解决方式:1.把消费端的消费逻辑设计为幂等2.自己维护一个已消费状态

动态增减broker

在新增的broker上分配topic,假设broker的地址为 192.168.0.1:10911 nameserver地址为192.168.0.100.9876,执行命令:
sh ./bin/mqadmin updateTopic -b 192.168.0.1:10911 -t TestTopic -n 192.168.0.100.9876
这样就在新的broker上动态的完成了为 TestTopic 分配8个读写队列的操作.

在producer还在运行时停掉一台master broker:
1.如果是同步发送的方式,不会丢失消息,因为会根据setRetryTimesWhenSendFailed重试,然后向其他broker发送消息.
2.DefaultMQProducter默认每30秒到nameServer请求最新的路由消息,producer如果获取不到已停止的队列消息后,就不会向这个队列发送消息.
3.在消费者方面此时会读取slave因此也不会存在消息丢失的问题
4.关闭broker要有shutdown或者kill pid的方式,不能用kill -9 ,这样可以保证broker安全退出

各种故障对消息的影响

1.Master broker正常关闭重启:在master broker关闭后consumer会自动去读slave,master重启后又会自动去读master,不会有任何影响.但是此时如果把consumer也关闭,然后重启master后再重启consumer,consumer回去读master上滞后的offset造成消息重复
2.机器断电,rocketmq异常后重启:如果是主从都是同步刷盘策略可以不会有消息重复和丢失的风险
3.机器设备损坏:主从复制如果采用同步复制方式,也可以达到消息不丢失的效果.

消息优先级解决方案

1.设置多个topic,把消息量大的类型消息单独配置一个topic
2.把消息量大的消息和其他消息分别写入topic下不同的messageQueue,其中topic下messageQueue的队列数量可以用 -r 设置如
sh ./bin/mqadmin updateTopic -t TestTopic -r 100
3.用PullConsumer实现,自主控制对messageQueue的遍历

RocketMq对提高吞吐量的支持

Broker端进行消息过滤

通过broker端对消息的过滤,减少无效消息发送到customer,从而达到提高吞吐量的作用

1.通过Tag过滤:创建message增加一个tag.tag过滤的方式为对比tag的hashcode,如果相同再对比tag字符,然后再从CommitLog中读出消息.
2.用SQL过滤:
在创建message对象msg后为msg增加过滤条件,如msg.setUserProperty(“a”,2);
然后在消费者中加入consumer.subsribe(“topicTest”,MessageSelector.bySql(“a between 0 and 3”));实现sql过滤
3.FilterServer过滤:
实现MessageFilter接口如:

Public class MyFilterImpl implements MessageFilter{
	@override
	Public boolean match(MessageExt msg){
		//获取property后,写过滤逻辑
		String property = msg.getUserProperty(“id”);
		If(property !=null && property .equals(1)){
			return true;
		}
		return false
	}
}

然后上传MyFilterImpl 到服务器
最后在consumer中加入

String filterCode = MaxAll.file2String(“MyFilterImpl 文件所在地址”);
consumer.subscribe(“testTopic”,com.alibaba.rocketmq.example.filter.MyFilterImpl”,filterCode );  

即可.

提高consumer处理能力

1.增加同一topic下consumer的数量,提高消费并行度,consumer数量要小于读队列数,否则超过的consumer将接受不到消息
2.以批量方式进行消费,设置consumeMessageBathMaxSize,默认为1,如果为n,将收到长度为n的消息列表
3.检测延时情况,跳过非重要消息,如:

Public ConConcurrentlyStatus consumeMessage(List<MessageExt> 			 mgs,ConsumeCurrentlyContext context){
	Long offset = mgs.get(0).getQueueOffset();
	String maxOffset = mgs.get(0).getProperty(message.PROPERTY_MAX_OFFSET);
	Long diff = Long.parseLong(maxOffset ) - offset;
	If(diff >10000){//消息堆积10000条以上直接返回,抛弃消息
		retrun ConsumeCurrentlyStatus.CONSUME_SUCCESS;
	}
	//正常消费消息
	retrun ConsumeCurrentlyStatus.CONSUME_SUCCESS;
}

Consumer负载均衡

Rocketmq的负载均衡在consumer端的代码中完成,consumer从broker中得到全局消息然后只处理分给自己的那部分消息.

1.DefaultMQPushConsumer:分配粒度只到messagequeue.如在均匀分配的策略下,当同一个topic的messagequeue为2个,consumer为3个时,第一个consumer分到1个messagequeue,第二个分到1个messagequeue.第三个无法分配;当messagequeue为3个时,每个consumer分配到1个;当为4个时,第一个messagequeue分配到2个,其余分配到1个.因此messagequeue不能太少,避免分配不均匀.

2.DefaultMQPullConsumer:DefaultMQPullConsumer需要自己完成负载均衡,如:

public class Test1 {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        String namesrvAddr = "";
        //消费组
        DefaultMQPullConsumer pullConsumer = new DefaultMQPullConsumer("GroupName1");
        //MQ NameService地址
        pullConsumer.setNamesrvAddr(namesrvAddr);
        //负载均衡模式
        pullConsumer.setMessageModel(MessageModel.CLUSTERING);

        //需要处理的消息topic
        pullConsumer.start();


        while (true) {
            boolean waiting = true;
            Set<MessageQueue> mqs = pullConsumer.fetchMessageQueuesInBalance("TopicTest1");
            //未获取到负载均衡的时候,等待1S重新获取
            if (!CollectionUtils.isEmpty(mqs)) {
                waiting = false;
                Thread.sleep(1000L);
            }
            for (MessageQueue mq : mqs) {
                System.out.printf("Consume from the queue: " + mq + "%n");
                SINGLE_MQ:
                while (true) {
                    long offset = pullConsumer.fetchConsumeOffset(mq, false);
                    try {
                        PullResult pullResult =
                                pullConsumer.pullBlockIfNotFound(mq, null, offset, 32);  //遍历所有queue,挨个调用pull

                        System.out.printf("%s%n", pullResult);
                        switch (pullResult.getPullStatus()) {
                            case FOUND:
                                offset = pullResult.getNextBeginOffset();
                                pullConsumer.updateConsumeOffset(mq, offset);
                                break;
                            case NO_MATCHED_MSG:
                                break;
                            case NO_NEW_MSG:
                                break SINGLE_MQ;
                            case OFFSET_ILLEGAL:
                                break;
                            default:
                                break;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            if(waiting){
                Thread.sleep(100L);
            }
        }
    }
}

也可以通过MQPullConsumerScheduleService来实现,在MQPullConsumer这个类里面,有一个MessageQueueListener,它的目的就是当queue发生变化的时候,通知Consumer。也正是这个借口,帮助我们在Pull模式里面,实现负载均衡。

void registerMessageQueueListener(final String topic, final MessageQueueListener listener);

public interface MessageQueueListener {
    void messageQueueChanged(final String topic, final Set<MessageQueue> mqAll,
                             final Set<MessageQueue> mqDivided);
}

有了这个Listener,我们就可以动态的知道当前的Consumer分摊到了几个MessageQueue。然后对这些MessageQueue,我们可以开个线程池来消费。

public static void main(String[] args) throws MQClientException {
       //消费组
        final MQPullConsumerScheduleService scheduleService = new MQPullConsumerScheduleService("GroupName1");
       //MQ NameService地址
        scheduleService.getDefaultMQPullConsumer().setNamesrvAddr(namesrvAddr);
       //负载均衡模式
        scheduleService.setMessageModel(MessageModel.CLUSTERING);
       //需要处理的消息topic
        scheduleService.registerPullTaskCallback("TopicTest1", new PullTaskCallback() {

            @Override
            public void doPullTask(MessageQueue mq, PullTaskContext context) {
                MQPullConsumer consumer = context.getPullConsumer();
                try {

                    long offset = consumer.fetchConsumeOffset(mq, false);
                    if (offset < 0)
                        offset = 0;

                    PullResult pullResult = consumer.pull(mq, "*", offset, 32);
                    System.out.printf("%s%n", offset + "\t" + mq + "\t" + pullResult);
                    switch (pullResult.getPullStatus()) {
                        case FOUND:
                            break;
                        case NO_MATCHED_MSG:
                            break;
                        case NO_NEW_MSG:
                        case OFFSET_ILLEGAL:
                            break;
                        default:
                            break;
                    }
                    consumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());


                    context.setPullNextDelayTimeMillis(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        scheduleService.start();
}

提高producer发送速度

1.通过oneway发送消息,只发送不等待应答.写入客户端Socket就返回.可用于可靠度要求低的场景.如收集日志.
2.增加producer的并发量
3.Linux系统操作调忧:推荐使用EXT4文件系统,IO调度算法采用deadline

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值