RocketMQ核心源码解读(一)

读源码的方法

  • 带着问题读源码。如果没有⾃⼰的思考,源码不如不读
  • 小步快走。不要觉得⼀两遍就能读懂源码。
  • 分步总结。带上自己的理解,及时总结。

NameServer的启动过程

在这里插入图片描述

  • namesrvConfig, nettyServerConfig, nettyClientConfig这⼏个配置类就可以⽤来指导如何优化Nameserver的配置。
  • 在之前的4.x版本当中,Nameserver中是没有ControllerManager和NettyRemotingClient的,这意味着现在NameServer现在也需要往外发Netty请求了。
  • Nameserver中核⼼组件例如RouteInfoManager的结构,可以发现RocketMQ的整体源码⻛格其实就是典型的MVC思想。 Controller响应⽹络请求,各种Manager和其中包含的Service处理业务,内存中的各种Table保存消息。

Broker服务启动过程

在这里插入图片描述

Broker启动了两个Netty服务,他们的功能基本差不多。可以通过producer.setSendMessageWithVIPChannel(true),让少量⽐较重要的producer⾛VIP的通道。⽽在消费者端,也可以通过consumer.setVipChannelEnabled(true),让消费者⽀持VIP通道的数据。

BrokerStartup.createBrokerController⽅法中可以看到Broker的几个核心配置:

  • BrokerConfig : Broker服务配置
  • MessageStoreConfig : 消息存储配置。 这两个配置参数都可以在broker.conf⽂件中进⾏配置
  • NettyServerConfig :Netty服务端占⽤了10911端⼝。同样也可以在配置⽂件中覆盖。
  • NettyClientConfig : Broker既要作为Netty服务端,向客户端提供核⼼业务能⼒,⼜要作为Netty客户端,向NameServer注册⼼跳。
  • AuthConfig:权限相关的配置。

在BrokerController.start⽅法可以看到启动了⼀⼤堆Broker的核⼼服务

this.messageStore.start();//启动核⼼的消息存储组件
this.timerMessageStore.start(); //时间轮服务,主要是处理指定时间点的延迟消息。
this.remotingServer.start(); //Netty服务端 
this.fastRemotingServer.start(); //启动另⼀个Netty服务端。 
this.brokerOuterAPI.start();//启动客户端,往外发请求 
this.topicRouteInfoManager.start(); //管理Topic路由信息 
BrokerController.this.registerBrokerAll;//向所有依次NameServer注册⼼跳。 
this.brokerStatsManager.start();//服务状态

Netty服务注册框架

RocketMQ实际上是⼀个复杂的分布式系统, NameServer,Broker,Client之间需要有⼤量跨进程的RPC调⽤。

NameServer和Broker的服务内部都是既有RemotingServer和RemotingClient的。
事务消息的Producer也需要响应Broker的事务状态回查,他也是需要NettyServer的。

服务端构建处理链的核⼼代码:

// org.apache.rocketmq.remoting.netty.NettyRemotingServer
 protected ChannelPipeline configChannel(SocketChannel ch) {
         return ch.pipeline()
             .addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, new HandshakeHandler())
             .addLast(defaultEventExecutorGroup,
                 encoder, //请求编码器                 
                 new NettyDecoder(), //请求解码器                
                  distributionHandler, //请求计数器                 
                  new IdleStateHandler(0, 0,
                  	nettyServerConfig.getServerChannelMaxIdleTimeSeconds()), //⼼跳管理器
                  connectionManageHandler, //连接管理器
                  serverHandler //核⼼的业务处理器
                  );
}

addLast类似于添加filter过滤器进行一些操作操作

  1. 请求参数
    RocketMQ的所有RPC请求数据都封装成RemotingCommand对象。
private int code; //响应码,表示请求处理成功还是失败
private int opaque = requestId.getAndIncrement(); //服务端内部会构建唯⼀的请求ID。 
private transient CommandCustomHeader customHeader; //⾃定义的请求头。⽤来区分不的业务请求
private transient byte[] body; //请求参数体
private int flag = 0; //参数类型,默认0表示请求,1表示响应
  1. 处理逻辑
    所有核⼼的业务请求都是通过⼀个NettyServerHandler进⾏统⼀处理。(serverHandler //核⼼的业务处理器)
    @ChannelHandler.Sharable
    public class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
 		//统⼀处理所有业务请求
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) {
            int localPort = RemotingHelper.parseSocketAddressPort(ctx.channel().localAddress());
            NettyRemotingAbstract remotingAbstract = NettyRemotingServer.this.remotingServerTable.get(localPort);
            if (localPort != -1 && remotingAbstract != null) {
                remotingAbstract.processMessageReceived(ctx, msg);//核⼼处理请求的⽅法
                return;
            }
            // The related remoting server has been shutdown, so close the connected channel
            RemotingHelper.closeChannel(ctx.channel());
        }
        
		//调整channel的读写属性
        @Override
        public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            if (channel.isWritable()) {
                if (!channel.config().isAutoRead()) {
                    channel.config().setAutoRead(true);
                    log.info("Channel[{}] turns writable, bytes to buffer before changing channel to un-writable: {}",
                        RemotingHelper.parseChannelRemoteAddr(channel), channel.bytesBeforeUnwritable());
                }
            } else {
                channel.config().setAutoRead(false);
                log.warn("Channel[{}] auto-read is disabled, bytes to drain before it turns writable: {}",
                    RemotingHelper.parseChannelRemoteAddr(channel), channel.bytesBeforeWritable());
            }
            super.channelWritabilityChanged(ctx);
        }
    }

在最核心的处理请求的processMessageReceived⽅法中,会将请求类型分为 REQUEST__COMMAND 和 RESPONSE_COMMAND来处理。为什么会有两种不同类型的请求呢?
因为客户端的业务请求会有两种类型:⼀种是客户端发过来的业务请求,另⼀种是客户上次发过来的业务请求,可能并没有同步给出响应。这时就需要客户端再发⼀个response类型的请求,获取上⼀次请求的响应,来⽀持异步的RPC调⽤。
NettyServer处理完request请求后,会先缓存到responseTable中,等NettyClient下次发送response类型的请求,再来获取。这样就不⽤阻塞Channel,提升请求的吞吐量。

RocketMQ的通信是长连接双向的,使用REQUEST__COMMAND 和 RESPONSE_COMMAND来判断是什么类型的连接,客户端向服务端请求就是REQUEST__COMMAND,服务端接着会发送RESPONSE_COMMAND类型到客户端,客户端做回调(异步操作)

关于RocketMQ的同步结果推送与异步结果推送
RocketMQ的RemotingServer服务端和客户端,会维护⼀个responseTable,这是⼀个线程同步的Map结构。 key为请求的ID,value是异步的消息结果。

实际上,同步也是通过异步实现的。

//org.apache.rocketmq.remoting.netty.ResponseFuture
public class ResponseFuture {
	......
	//发送消息后,通过countDownLatch阻塞当前线程,造成同步等待的效果。
    public RemotingCommand waitResponse(final long timeoutMillis) throws InterruptedException {
        this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
        return this.responseCommand;
    }
	//等待异步获取到消息后,再通过countDownLatch释放当前线程。   
    public void putResponse(final RemotingCommand responseCommand) {
        this.responseCommand = responseCommand;
        this.countDownLatch.countDown();
    }
    ......
}

同步调用不再显式调用 waitResponse(),org.apache.rocketmq.remoting.netty.NettyRemotingAbstract新引入的一种异步+Future 的 invokeSyncImpl 实现方式,与早期版本中通过 ResponseFuture.waitResponse() 同步等待响应的方式不同。可异步链式处理、更优雅的异步模型。

    public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
        final long timeoutMillis)
        throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
        try {
            return invokeImpl(channel, request, timeoutMillis).thenApply(ResponseFuture::getResponseCommand)
                .get(timeoutMillis, TimeUnit.MILLISECONDS);//这段代码
        } catch (ExecutionException e) {
            throw new RemotingSendRequestException(channel.remoteAddress().toString(), e.getCause());
        } catch (TimeoutException e) {
            throw new RemotingTimeoutException(channel.remoteAddress().toString(), timeoutMillis, e.getCause());
        }
    }

在RemotingServer和RemotingClient启动时,会启动⼀个定时的线程任务,不断扫描responseTable,将其中过期的response清除掉。

        TimerTask timerTaskScanResponseTable = new TimerTask() {
            @Override
            public void run(Timeout timeout) {
                try {
                    NettyRemotingClient.this.scanResponseTable();
                } catch (Throwable e) {
                    LOGGER.error("scanResponseTable exception", e);
                } finally {
                    timer.newTimeout(this, 1000, TimeUnit.MILLISECONDS);
                }
            }
        };
         TimerTask timerScanResponseTable = new TimerTask() {
            @Override
            public void run(Timeout timeout) {
                try {
                    NettyRemotingServer.this.scanResponseTable();
                } catch (Throwable e) {
                    log.error("scanResponseTable exception", e);
                } finally {
                    timer.newTimeout(this, 1000, TimeUnit.MILLISECONDS);
                }
            }
        };

只要某一方可能发起请求,就会注册 ResponseFuture,就需要定时清理 responseTable。
所以 RocketMQ 中 Client 和 Server 实际都具备 Client 能力,因此都清理。

在这里插入图片描述

Broker心跳注册管理

Broker启动后会⽴即发起向NameServer注册⼼跳。

//org.apache.rocketmq.broker.BrokerController.start()
        scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) {
           @Override
           public void run0() {
               try {
                   if (System.currentTimeMillis() < shouldStartTime) {
                       BrokerController.LOG.info("Register to namesrv after {}", shouldStartTime);
                       return;
                   }
                   if (isIsolated) {
                       BrokerController.LOG.info("Skip register for broker is isolated");
                       return;
                   }
                   BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());//注册,也是默认namesrv+broker的心跳
               } catch (Throwable e) {
                   BrokerController.LOG.error("registerBrokerAll Exception", e);
               }
           }
       }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS));

NameServer内部会通过RouteInfoManager组件及时维护Broker信息。具体参⻅org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager的registerBroker⽅法

同时在NameServer启动时,会启动定时任务,扫描不活动的Broker。⽅法⼊⼝:NamesrvController.initialize⽅法,往下跟踪到startScheduleService⽅法。scanNotActiveBroker(检查broker最新心跳时间是否超过设定时间,超过则不健康)

在这里插入图片描述

为什么RocketMQ要⾃⼰实现⼀个NameServer,⽽不用Zookeeper、Nacos这样现成的注册中心?

  1. 依赖外部组件会对产品的独⽴性形成侵⼊,不利于自己的版本演进。Kafka要抛弃Zookeeper就是⼀个先例。
  2. NameServer之间不进行信息同步,⽽是依赖Broker端向所有NameServer同时发起注册。
  3. 通用的注册中心设计是所有注册中心数据同步,而RocketMQ不需要。因为客户端从NameServer上获得Broker列表后,只要有⼀个正常运行的Broker就可以了,并不需要完整的Broker列表。

Producer发送消息过程

Producer有两种:

  • DefaultMQProducer。只负责发送消息,发送完消息,就可以停止了。
  • TransactionMQProducer。⽀持事务消息机制。需要在事务消息过程中提供事务状态确认的服务,这就要求事务消息发送者虽然是⼀个客户端,但是也要完成整个事务消息的确认机制后才能退出。
  1. Producer的核心启动流程
    所有Producer的启动过程,最终都会调⽤到DefaultMQProducerImpl#start⽅法。在start方法中的通过⼀个mQClientFactory对象,启动生产者的⼀⼤堆重要服务。
    这个mQClientFactory是最为重要的⼀个对象,负责生产所有的Client,包括Producer和Consumer。
    在这里插入图片描述
  2. 发送消息的核心流程
    在这里插入图片描述

发送消息时,会维护⼀个本地的topicPublishInfoTable缓存,DefaultMQProducer会尽量保证这个缓存数据是最新的。如果NameServer挂了,那么DefaultMQProducer还是会基于这个本地缓存去找Broker。只要能找到Broker,还是可以正常发送消息到Broker的。

可以在生产者示例中,start后打⼀个断点,然后把NameServer停掉,这时,Producer还是可以发送消息的。

生产者如何找MessageQueue: 默认情况下,生产者是按照轮询的⽅式,依次轮询各个MessageQueue。但是如果某⼀次往⼀个Broker发送请求失败后,下⼀次就会跳过这个Broker。

如果在发送消息时传了Selector,那么Producer就不会走这个负载均衡的逻辑,而是会使⽤Selector去寻找⼀个队列。 具体参见org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendSelectImpl 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值