RocketMq源码解读(二)

本章主要介绍的内容有:消息过滤服务filterSrv

一、什么是filterSrv

1、filtersrv

filtersrv是RocketMQ中的消息过滤服务,是介于consumer和broker之间的代理。主要作用是在consumer端定义好filter过滤规则,动态编译成class,根据定义的规则,从broker拉取消息,然后将拉取到的满足过滤条件的消息返回给consumer。所以整体的工作流程有:

2、为什么要单独建立filtersrv服务

  • 在broker端增加消息的过滤:优点是减少对于consumer的无效网络传输,缺点是增加了broker端的复杂度,并且当规则多样时,对broker的负担较大。
  • 在consumer端增加消息的过滤:优点是实现灵活,可以自主的定义灵活规则,缺点是有很多无用的消息传输到consumer,耗费网路带宽和消息的消费速度。

在这种情况下,出现了filtersrv服务,它的思想和nginx类似,做一层简单的代理,这样减少了broker的负担,也减少了consumer接收到的无用消息。不过,这也带来了filtersrv的网络开销。

 二、filterSrv的源码解读

1、先看一段消费端的简单代码

public void subscribe(String topic, String fullClassName, String filterClassSource) throws MQClientException {
    this.defaultMQPushConsumerImpl.subscribe(topic, fullClassName, filterClassSource);
}

在其实现方法中,会向所有的broker发送心跳
   if (this.mQClientFactory != null) {
           this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
    }

发送心跳的时候,会自动上传过滤类到filtersrv, 

this.uploadFilterClassToAllFilterServer(consumerGroup, className, topic, filterClassSource);

所以消费端在消费的时候,需要通过上传的过滤函数过滤。

2、Broker和filtersrv的绑定关系

filtersrv的配置信息放在flitersrvConfig类中,

public class FiltersrvConfig {
    // 通过环境变量确定home dir
    private String rocketmqHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
        System.getenv(MixAll.ROCKETMQ_HOME_ENV));

    // 获取名字空间的地址
    @ImportantField
    private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY,
        System.getenv(MixAll.NAMESRV_ADDR_ENV));
    // 和哪一个broker连接
    private String connectWhichBroker = "127.0.0.1:10911";
    // Filter Server对外服务的IP
    private String filterServerIP = RemotingUtil.getLocalAddress();
    // 消息超过指定大小,开始压缩
    private int compressMsgBodyOverHowmuch = 1024 * 8;
    private int zipCompressLevel = 5;
     // 是否允许client上传过滤类
    private boolean clientUploadFilterClassEnable = true;
    // 过滤类的仓库地址
    private String filterClassRepertoryUrl = "http://fsrep.tbsite.net/filterclass";
    private int fsServerAsyncSemaphoreValue = 2048;
    private int fsServerCallbackExecutorThreads = 64;
    private int fsServerWorkerThreads = 64;
    ...
}

这其中有两个很关键的参数:其中namesrvAddr和broker一样,而connectWhichBroker表示当前的filtersrv是属于哪一个broker。

filter服务在启动的时候,向broker注册,具体的实现方法在在FiltersrvControllerinitialize方法的时候调用了FilterServerOuterAPIregisterFilterServerToBroker方法将自己注册到broker里面去了。并且间隔的周期是10秒中。

  public boolean initialize() {
        // 打印配置信息
        MixAll.printObjectProperties(log, this.filtersrvConfig);
        // 启动远程的netty服务
        this.remotingServer = new NettyRemotingServer(this.nettyServerConfig);
        // 创建消费线程池
        this.remotingExecutor =
            Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(),
                new ThreadFactoryImpl("RemotingExecutorThread_"));
        // 注册处理器
        this.registerProcessor();
        // 固定间隔注册到Broker
        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                FiltersrvController.this.registerFilterServerToBroker();
            }
        }, 15, 10, TimeUnit.SECONDS);
        // 设置最长阻塞时间
        this.defaultMQPullConsumer.setBrokerSuspendMaxTimeMillis(this.defaultMQPullConsumer
            .getBrokerSuspendMaxTimeMillis() - 1000);
        this.defaultMQPullConsumer.setConsumerTimeoutMillisWhenSuspend(this.defaultMQPullConsumer
            .getConsumerTimeoutMillisWhenSuspend() - 1000);
        // 配置名字空间地址
        this.defaultMQPullConsumer.setNamesrvAddr(this.filtersrvConfig.getNamesrvAddr());
        this.defaultMQPullConsumer.setInstanceName(String.valueOf(UtilAll.getPid()));

        return true;
    }

在FilterServerOuterAPI中,会向broker服务发送方注册请求,

public RegisterFilterServerResponseHeader registerFilterServerToBroker(
        final String brokerAddr, final String filterServerAddr
    ) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
        RegisterFilterServerRequestHeader requestHeader = new RegisterFilterServerRequestHeader();
        requestHeader.setFilterServerAddr(filterServerAddr);
        // 发送注册filter的命令
        RemotingCommand request =
            RemotingCommand.createRequestCommand(RequestCode.REGISTER_FILTER_SERVER, requestHeader);

        RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, 3000);
        assert response != null;
        switch (response.getCode()) {
            case ResponseCode.SUCCESS: {
                RegisterFilterServerResponseHeader responseHeader =
                    (RegisterFilterServerResponseHeader) response
                        .decodeCommandCustomHeader(RegisterFilterServerResponseHeader.class);

                return responseHeader;
            }
            default:
                break;
        }
        // 如果注册不成功,会抛出异常
        throw new MQBrokerException(response.getCode(), response.getRemark());
    }

在broker端有接收处理filter注册的处理,

// 在adminBrokerProcessor里,有处理netty请求
case RequestCode.REGISTER_FILTER_SERVER:
    return this.registerFilterServer(ctx, request);

// 可以看到,一个broker可以有多个filtersrv服务,封装到filterServerTable里
 public void registerFilterServer(final Channel channel, final String filterServerAddr) {
        FilterServerInfo filterServerInfo = this.filterServerTable.get(channel);
        if (filterServerInfo != null) {
            filterServerInfo.setLastUpdateTimestamp(System.currentTimeMillis());
        } else {
            filterServerInfo = new FilterServerInfo();
            filterServerInfo.setFilterServerAddr(filterServerAddr);
            filterServerInfo.setLastUpdateTimestamp(System.currentTimeMillis());
            this.filterServerTable.put(channel, filterServerInfo);
            log.info("Receive a New Filter Server<{}>", filterServerAddr);
        }
    }

在broker向namesrv注册自己的时候,会带着filtersrv的地址,

public List<String> buildNewFilterServerList() {
    List<String> addr = new ArrayList<String>();
    Iterator<Entry<Channel, FilterServerInfo>> it = this.filterServerTable.entrySet().iterator();
    while (it.hasNext()) {
        Entry<Channel, FilterServerInfo> next = it.next();
        addr.add(next.getValue().getFilterServerAddr());
    }
    return addr;
}

所以在namesrv里面,会有处理registerBrokerWithFilterServer处理方法,将broker和filterServerList放到filterserverTable的hashmap中。

public RegisterBrokerResult registerBroker(
        final String clusterName,final String brokerAddr,
        final String brokerName, final long brokerId,
        final String haServerAddr, final TopicConfigSerializeWrapper topicConfigWrapper,
        final List<String> filterServerList, final Channel channel){
    ...
     if (filterServerList != null) {
         if (filterServerList.isEmpty()) {
              this.filterServerTable.remove(brokerAddr);
         } else {
              this.filterServerTable.put(brokerAddr, filterServerList);
         }
     }
}

namesrv中通过topic查找对应的broker时,会将信息封装到TopicRouteData返回给client,

public TopicRouteData pickupTopicRouteData(final String topic){
    opicRouteData topicRouteData = new TopicRouteData();
    boolean foundQueueData = false;
    boolean foundBrokerData = false;
    Set<String> brokerNameSet = new HashSet<String>();
    List<BrokerData> brokerDataList = new LinkedList<BrokerData>();
    topicRouteData.setBrokerDatas(brokerDataList);
    HashMap<String, List<String>> filterServerMap = new HashMap<String, List<String>>();
    // 返回数据中带有过滤map
    topicRouteData.setFilterServerTable(filterServerMap);
    ... 
}

3、 filtersrv如何处理来自consumer的MessageFilter

当consumer发送注册filtersrv的心跳包时,filtersrv接收到请求根据不同的code处理响应。请求code分为RequestCode.REGISTER_MESSAGE_FILTER_CLASS和RequestCode.PULL_MESSAGE。处理注册MessageFilter实现类是方法registerMessageFilterClass,最终会在FilterClassManager动态编译来自consumer的java源码生成一个MessageFilter的类对象,然后反射生成对象。具体逻辑如下:

private RemotingCommand registerMessageFilterClass(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
    final RemotingCommand response = RemotingCommand.createResponseCommand(null);
    final RegisterMessageFilterClassRequestHeader requestHeader =
            (RegisterMessageFilterClassRequestHeader) request.decodeCommandCustomHeader(RegisterMessageFilterClassRequestHeader.class);

    try {
        boolean ok = this.filtersrvController.getFilterClassManager().registerFilterClass(requestHeader.getConsumerGroup(),requestHeader.getTopic(),requestHeader.getClassName(),requestHeader.getClassCRC(),request.getBody());
        if (!ok) {
            throw new Exception("registerFilterClass error");
        }
     } catch (Exception e) {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark(RemotingHelper.exceptionSimpleDesc(e));
        return response;
     }
     response.setCode(ResponseCode.SUCCESS);
     response.setRemark(null);
     return response;
}

注册过滤类实现主要是建立一个消费组和topic确定的class map。

public boolean registerFilterClass(final String consumerGroup, final String topic,
        final String className, final int classCRC, final byte[] filterSourceBinary) {
    final String key = buildKey(consumerGroup, topic);
    // 判断是否要注册新的过滤类
    boolean registerNew = false;
    FilterClassInfo filterClassInfoPrev = this.filterClassTable.get(key);
    if (null == filterClassInfoPrev) {
        registerNew = true;
    } else {
        if (this.filtersrvController.getFiltersrvConfig().isClientUploadFilterClassEnable()) {
            if (filterClassInfoPrev.getClassCRC() != classCRC && classCRC != 0) { 
                registerNew = true;
            }
        }
    }
    // 注册新的过滤类
    if (registerNew) {
         synchronized (this.compileLock) {
            ...
            // 放到内存缓存中
            this.filterClassTable.put(key, filterClassInfoNew);
         }
    }
    return true;
}    

在consumer pull消息的时候,会有

private RemotingCommand pullMessageForward(final ChannelHandlerContext ctx, final RemotingCommand request) throws Exception {
    // 构造返回响应
    final RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class);
    final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader();
    final PullMessageRequestHeader requestHeader =
            (PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class);
    // 构造上下文
    final FilterContext filterContext = new FilterContext();
    filterContext.setConsumerGroup(requestHeader.getConsumerGroup());
    response.setOpaque(request.getOpaque());
    DefaultMQPullConsumer pullConsumer = this.filtersrvController.getDefaultMQPullConsumer();
    // 校验Topic过滤类是否完整,如果没有过滤类,返回错误码
    final FilterClassInfo findFilterClass = this.filtersrvController.getFilterClassManager().findFilterClass(requestHeader.getConsumerGroup(), requestHeader.getTopic());
    if (null == findFilterClass) {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark("Find Filter class failed, not registered");
        return response;
    }
    if (null == findFilterClass.getMessageFilter()) {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark("Find Filter class failed, registered but no class");
        return response;
    }

    // 设置下次请求从 Broker主节点。
    responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);
    // 构造从broker拉取小气的参数
    MessageQueue mq = new MessageQueue();
    mq.setTopic(requestHeader.getTopic());
    mq.setQueueId(requestHeader.getQueueId());
    mq.setBrokerName(this.filtersrvController.getBrokerName());
    long offset = requestHeader.getQueueOffset();
    int maxNums = requestHeader.getMaxMsgNums();

    final PullCallback pullCallback = new PullCallback() {
        @Override
        public void onSuccess(PullResult pullResult) {
        responseHeader.setMaxOffset(pullResult.getMaxOffset());
        responseHeader.setMinOffset(pullResult.getMinOffset());
        responseHeader.setNextBeginOffset(pullResult.getNextBeginOffset());
        response.setRemark(null);
        switch (pullResult.getPullStatus()) {
            case FOUND:
                response.setCode(ResponseCode.SUCCESS);
                List<MessageExt> msgListOK = new ArrayList<MessageExt>();
                try {
                    for (MessageExt msg : pullResult.getMsgFoundList()) {
                        // 使用过滤类过滤消息
                        boolean match = findFilterClass.getMessageFilter().match(msg, filterContext);
                        if (match) {
                             msgListOK.add(msg);
                         }
                     }
                     // 消息不为空,返回构造消息
                     if (!msgListOK.isEmpty()) {
                         returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, msgListOK);
                         return;
                     } else {
                         response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                     }
                 } catch (Throwable e) {
                     final String error = String.format("do Message Filter Exception, ConsumerGroup: %s Topic: %s ",
                     requestHeader.getConsumerGroup(), requestHeader.getTopic());
                            log.error(error, e);
                     response.setCode(ResponseCode.SYSTEM_ERROR);
                     response.setRemark(error + RemotingHelper.exceptionSimpleDesc(e));
                     returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);
                     return;
                 }

                 break;
            case NO_MATCHED_MSG:
                 response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);
                 break;
            case NO_NEW_MSG:
                 response.setCode(ResponseCode.PULL_NOT_FOUND);
                 break;
            case OFFSET_ILLEGAL:
                 response.setCode(ResponseCode.PULL_OFFSET_MOVED);
                 break;
            default:
                 break;
       }

       returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);
  }

  @Override
  public void onException(Throwable e) {
      response.setCode(ResponseCode.SYSTEM_ERROR);
      response.setRemark("Pull Callback Exception, " + RemotingHelper.exceptionSimpleDesc(e));
      returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);
      return;
   }
   };

   // 拉取消息
   pullConsumer.pullBlockIfNotFound(mq, null, offset, maxNums, pullCallback);
   return null;
}

在filtersrv中,也会有一个consumer,它的作用是从broker拉取消息,拉取到消息后,会先通过过滤类进行match,如果能够match成功,返回拉取到的消息。如果match失败,会通知consumer立即下一次拉取。

非常重要的一点:filtersrv拉取的消息也属于消费,过滤掉的消息不会返回到consumer,但其他的消费者也无法再次消费到。

4、consumer从filtersrv拉取消息,从broker对应的filtersrv列表中随机选取一个拉取消息,如果选取不到filtersrv,无法拉取到消息。所以,filtersrv一定需要高可用,其方式是一个broker可以对应有多个filtersrv,并且每个filtersrv会定期向broker发送心跳包,注册自己。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值