【二】NameServer

252 阅读7分钟

介绍

nameserver是一个类似zk的服务,用来存储元数据,因为rocketMq不需要强一致性,所以自己实现了nameserver。

一. NameServer服务器角色

元数据

列一下主要的元数据和数据结构。

  1. 主题队列分布map

       {
           "xxx_TOPIC":{           // 某某topic
               "brokerName":"xxx", // 节点名称
               "readQueueNums":0,  // 读队列数
               "writeQueueNums":0, // 写队列数
               "perm":0,           // 权限
               
           }
       }
    
  2. broker地址map

       {
           "brokerName":{           // 节点名称
               "cluster":"xxx",     // 集群名称
               "brokerName":"xxx",  // 节点名称
               "brokerAddrs":{      // 地址。 key为broker+Id. 值为0的机器节点为master,其他是slave
                   "borker0":"192.168",
                   "borker1":"192.168",
                   
               }
           }
       }
    
  3. 集群信息map

    // 集群名称 对应 borkerName集合(Set)
      {"clusterName":["brokerName"]}
    
  4. broker存活状态map

       {
           "192.168.xxx:xxx":{             // ip地址
               "lastUpdateTimestamp":0L,   // 上次心跳时间
               "channel":"Channel",        // 通信通道
               "haServerAddr":"xxx"        // 备份节点地址
           }
       }
    

二. NameServer启动源码分析

  1. 入口 NameServerStartup.main0()
  2. 创建nameSrv控制器createNameServerController(args)
    1. 初始化NameSrv配置,new NamesrvConfig()
    2. 初始化NettySrv配置,new NettyServerConfig()
    3. 创建NameServer控制器 new NamesrvController(namesrvConfig, nettyServerConfig)
  3. 启动逻辑start(controller);
    1. 初始化NameServer控制启器 controller.initialize();
      1. 加载本地kv配置 this.kvConfigManage.load()
      2. 创建网络服务对象【重要】 new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
      3. 创建业务线程池,默认线程数8 remotingExecutor
      4. 注册协议处理器 registerProcessor()
      5. 定时任务1, 每10秒检查一下broker存活状态,将idle状态的broker移除。 6 定时任务2,每10分钟,打印一遍kv配置
    2. 注册jvm hook ,处理平滑关机逻辑
    3. 启动NameServer控制器 controller.start();
      • 网络层服务启动 this.remotingServer.start(); // remotingServer 后面单独列

NameSrvConfig存储的是业务相关的数据 image.png NettyServerConfig存储的是网络相关的数据 image.png 控制器
image.png

三. RocketMq服务端网络通信层设计【经典模型】RemotingServer -> NettyRemotingServer

四. RemotingServer源码分析

配置项


父类的 NettyRemotingAbstract

// 单程请求和异步请求的信号量
protected final Semaphore semaphoreOneway;
protected final Semaphore semaphoreAsync;

// 请求映射表。 opaque 和 ResponseFuture
// 会有定时任务来扫描这个表,把超时的任务移除
// NettyRemotingAbstract#scanResponseTable() 扫描的具体代码
// NettyRemotingServer#start() 配置定时任务的地方
protected final ConcurrentMap<Integer /* opaque */, ResponseFuture> responseTable =
            new ConcurrentHashMap<Integer, ResponseFuture>(256);

//  业务代码(Code) 和  pair 映射 。 其实就是获取 处理器 和 线程池资源。
protected final HashMap<Integer/* request code */, Pair<NettyRequestProcessor, ExecutorService>> processorTable =
            new HashMap<Integer, Pair<NettyRequestProcessor, ExecutorService>>(64);
// 还有一些,没往出列

### NettyRemotingServer 类下的。
// netty服务端启动对象
private final ServerBootstrap serverBootstrap;
// netty worker组线程池
private final EventLoopGroup eventLoopGroupSelector;
// netty boss组线程池, 一般只有一个线程
private final EventLoopGroup eventLoopGroupBoss;

// netty 服务端网络配置
private final NettyServerConfig nettyServerConfig;

// 公共线程池
private final ExecutorService publicExecutor;

// HouseKeepingService。[这块没学好 ]TODO
private final ChannelEventListener channelEventListener;

// 定时器, 执行scanResponseTable 任务
private final Timer timer = new Timer("ServerHouseKeepingService", true);

// 当向channel pipeline 添加handler 时,指定了group时,网络事件传播到 当前handler 时,由 分配给 handler 的线程执行
private DefaultEventExecutorGroup defaultEventExecutorGroup;
// 服务端绑定的端口
private int port = 0;
private static final String HANDSHAKE_HANDLER_NAME = "handshakeHandler";
private static final String TLS_HANDLER_NAME = "sslHandler";
private static final String FILE_REGION_ENCODER_NAME = "fileRegionEncoder";

// 共享的处理器,多个channel 都使用同一个对象。
// sharable handlers
private HandshakeHandler handshakeHandler;
private NettyEncoder encoder;
private NettyConnectManageHandler connectionManageHandler;
private NettyServerHandler serverHandler;

重点方法

  1. 构造方法上面可以看到,在NameServerController创建时被调用,进行初始化

    1. 调用父类,初始化异步和单次请求的信号量。semaphoreAsync:64,semaphoreOneway:256
    2. 初始化netty的一些配置和类。
    3. 创建公共线程池 publicExecutor 并且将线程数由0改为4
    4. 创建netty的线程组 eventLoopGroupBoss boss组,eventLoopGroupSelector worker组
  2. 启动方法上面可以看到,在NameServerController启动时被调用,进行启动操作

    1. 创建事件处理器,这块涉及到netty,我没有看明白。
      // 当向channel pipeline 添加handler,并且指定了group时,网络事件传播到 当前handler 时,事件处理由 分配给 handler的线程执行。
      this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
              nettyServerConfig.getServerWorkerThreads(),
              new ThreadFactory() {
      
                  private AtomicInteger threadIndex = new AtomicInteger(0);
      
                  @Override
                  public Thread newThread(Runnable r) {
                      return new Thread(r, "NettyServerCodecThread_" + this.threadIndex.incrementAndGet());
                  }
              });
      
    2. 创建共享的 处理器handler prepareSharableHandlers();
    3. 初始化ServerBootstrap
    4. 使用 PooledByteBufAllocator.DEFAULT初始化内存池,如果在配置文件中开启内存池的话。
    5. 启动,绑定端口。 this.serverBootstrap.bind().sync();
    6. 开启 网络异常事件处理器,
    7. 设定定时任务, 扫描 responseTable表,将过期的responseFuture 移除。每秒执行一次。

1. 发送网络请求代码

1. 同步请求

```java 
 /**
 * 方法介绍
 * - 执行同步请求
 * 服务器 主动向客户端发起请求时,使用的方法,
 * 同步调用=> 服务器业务线程 需要在这里等待client 返回结果之后,整个调用才完毕。
 *
 * @param channel       客户端channel
 * @param request       网络请求对象,remotingCommand
 * @param timeoutMillis 超时时长
 * @return
 * @throws InterruptedException
 * @throws RemotingSendRequestException
 * @throws RemotingTimeoutException
 */
@Override
public RemotingCommand invokeSync(final Channel channel, final RemotingCommand request, final long timeoutMillis)
        throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    // 【重点】
    return this.invokeSyncImpl(channel, request, timeoutMillis);
}

 /**
 * 具体代码
 *
 * @param channel       客户端channel
 * @param request       网络请求对象,remotingCommand
 * @param timeoutMillis 超时时长
 * @return
 * @throws InterruptedException
 * @throws RemotingSendRequestException
 * @throws RemotingTimeoutException
 */
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis) throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
    // 请求id
    final int opaque = request.getOpaque();

    try {

        /**
         * 因为要等客户端回应,封装了一个future。 用来处理同步和异步请求。
         * 是网络请求的核心
         *
         * @param channel        客户端 channel
         * @param opaque         请求id
         * @param timeoutMillis  超时时间
         */
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
        // 放入 映射表
        this.responseTable.put(opaque, responseFuture);

        // 获取客户端的地址信息
        final SocketAddress addr = channel.remoteAddress();

        /**
         * 将数据写入 客户端 channel
         * 注意, 这里  writeAndFlush(request) 相当于发送请求。
         *            addListener(xxx)       相当于设置回调。
         * 然后下面的 responseFuture.waitResponse(timeoutMillis); 进行阻塞,等待
         *
         */
        channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture f) throws Exception {
                if (f.isSuccess()) {
                    // 成功就返回
                    responseFuture.setSendRequestOK(true);
                    return;
                } else {
                    // 失败
                    responseFuture.setSendRequestOK(false);
                }


                // 下面都都是失败逻辑
                // 将当前请求的 responseFuture 从映射表 移除。
                responseTable.remove(opaque);
                // 设置失败原因
                responseFuture.setCause(f.cause());
                // 返回null, 调用者会处理 为null的情况。
                // 这个方法执行完毕后,会调用  this.countDownLatch.countDown(); ,唤醒下面业务线程
                responseFuture.putResponse(null);
                log.warn("send a request command to channel <" + addr + "> failed.");
            }
        });

        // 业务线程 在这里进入阻塞,挂起状态。
        // 内部有个countDownLatch。
        RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);

        // 线程执行到这里,有两种情况。
        // 1. 成功,客户端返回数据了,IO线程 将业务线程唤醒、
        // 2. 超时,
        if (null == responseCommand) {
            // 执行到这里,说明 超时。。 或者其他异常情况。 这里可能是超时
            if (responseFuture.isSendRequestOK()) {
                throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
                        responseFuture.getCause());
            } else {
                // 这里就是纯纯的请求失败了。会给出失败原因
                throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
            }
        }
        // 返回 客户端 的 结果
        return responseCommand;
    } finally {
        // 请求结束,移除 映射表
        this.responseTable.remove(opaque);
    }
}


```

2. 异步请求

```java
  /**
 * 方法介绍
 * - 执行异步请求
 * 服务器主动向客户端发起请求时,使用的方法。
 *
 * @param channel        客户单channel
 * @param request        网络请求对象 RemotingCommand
 * @param timeoutMillis  超时时长
 * @param invokeCallback 请求结果回调处理对象。
 * @throws InterruptedException
 * @throws RemotingTooMuchRequestException
 * @throws RemotingTimeoutException
 * @throws RemotingSendRequestException
 */
@Override
public void invokeAsync(Channel channel, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback)
        throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    this.invokeAsyncImpl(channel, request, timeoutMillis, invokeCallback);
} 


/**
 * /**
 * 异步请求具体代码
 *
 * @param channel        客户单channel
 * @param request        网络请求对象 RemotingCommand
 * @param timeoutMillis  超时时长
 * @param invokeCallback 请求结果回调处理对象。
 * @throws InterruptedException
 * @throws RemotingTooMuchRequestException
 * @throws RemotingTimeoutException
 * @throws RemotingSendRequestException
 */
public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
                            final InvokeCallback invokeCallback)
        throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    // 开始时间
    long beginStartTime = System.currentTimeMillis();
    // 请求 ID
    final int opaque = request.getOpaque();
    /*
     * 信号量,如果获取失败,说明当前并发大,失败就报错了,放弃了
     *
     *     // 服务端 单向访问 客户端时的并发控制、
     *     private int serverOnewaySemaphoreValue = 256;
     *
     *     // 服务端 异步访问 客户端时的并发限制。
     *     private int serverAsyncSemaphoreValue = 64;
     */
    boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
    if (acquired) {
        // 执行到这里,说明当前线程可以发起请求,服务器请求客户端的 并发 没有到达上限。
        // once 对象 封装了 释放信号量的操作
        final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
        // 计算 执行到这一步 为止。 耗费的时间。
        long costTime = System.currentTimeMillis() - beginStartTime;

        // 条件成立的话, 说明已经超时了,就不用发起rpc了。
        if (timeoutMillis < costTime) {
            once.release();
            throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
        }


        /**
         *和 invokeSyncImpl 一样的。不过多穿了参数
         *
         * @param channel        客户端 channel
         * @param opaque         请求id
         * @param timeoutMillis  超时时间
         * @param invokeCallback 回调处理对象
         * @param once           信号量释放操作封装对象
         */
        final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
        // 将 responseFuture 加入 响应 映射表 。 key其实是 RequestId
        this.responseTable.put(opaque, responseFuture);
        try {
            // 1. 业务线程 将数据 交给netty, netty io 线程 接管 写 和刷 数据的操作
            // 2. 注册 写刷 操作监听器, 监听器由 IO线程回调。
            channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    if (f.isSuccess()) {
                        // 写刷成功,设置 responseFuture 发生状态为 true
                        responseFuture.setSendRequestOK(true);
                        return;
                    }
                    // 执行到这里,就是发送失败..
                    requestFail(opaque);
                    log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
                }
            });
        } catch (Exception e) {
            responseFuture.release();
            log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
            throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
        }
        // 没有返回结果。 因为最终会回调  invokeCallback

    } else {
        // 执行到这里,说明获取 信号量失败,说明 当前服务器 并发比较高...
        if (timeoutMillis <= 0) {
            throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
        } else {
            String info =
                    String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
                            timeoutMillis,
                            this.semaphoreAsync.getQueueLength(),
                            this.semaphoreAsync.availablePermits()
                    );
            log.warn(info);
            throw new RemotingTimeoutException(info);
        }
    }
}

/**
 * 请求失败的处理逻辑
 *
 * @param opaque
 */
private void requestFail(final int opaque) {
    ResponseFuture responseFuture = responseTable.remove(opaque);
    if (responseFuture != null) {
        responseFuture.setSendRequestOK(false);
        responseFuture.putResponse(null);
        try {
            // 调用 回调处理器对象,异步 使用 CallbackExecutor, 公共线程池
            executeInvokeCallback(responseFuture);
        } catch (Throwable e) {
            log.warn("execute callback in requestFail, and callback throw", e);
        } finally {
            responseFuture.release();
        }
    }
}


```

3. 单次请求 // 就是不需要考虑返回信息

```java 

 /**
 * 方法介绍
 * - 单行请求 不关注返回结果
 * 服务器主动向客户端 发起请求时,使用的方法。
 *
 * @param channel       客户端channel
 * @param request       网络请求对象 RemotingCommand
 * @param timeoutMillis 超时时长
 * @throws InterruptedException
 * @throws RemotingTooMuchRequestException
 * @throws RemotingTimeoutException
 * @throws RemotingSendRequestException
 */
@Override
public void invokeOneway(Channel channel, RemotingCommand request, long timeoutMillis) throws InterruptedException,
        RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    this.invokeOnewayImpl(channel, request, timeoutMillis);
}

/**
 * 单行请求 不关注返回结果
 *
 * @param channel       客户端channel
 * @param request       网络请求对象 RemotingCommand
 * @param timeoutMillis 超时时长
 * @throws InterruptedException
 * @throws RemotingTooMuchRequestException
 * @throws RemotingTimeoutException
 * @throws RemotingSendRequestException
 */
public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)
        throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
    // 设别标记, 对端 检查标记, 就可以知道这个请求,是单向请求
    request.markOnewayRPC();
    // 获取信号量,同上
    boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
    if (acquired) {
        // 释放信号量逻辑封装对象。
        final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway);
        try {
            // 1. 将数据 交给 channel, 这里数据发送的逻辑,是由netty线程完成的。
            // 2. 添加 写 刷 数据操作的 监听器 由netty IO 线程回调。

            channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture f) throws Exception {
                    // 请求发送后,直接释放
                    once.release();
                    if (!f.isSuccess()) {
                        log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed.");
                    }
                }
            });
        } catch (Exception e) {
            once.release();
            log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed.");
            throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
        }
    } else {
        if (timeoutMillis <= 0) {
            throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast");
        } else {
            String info = String.format(
                    "invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
                    timeoutMillis,
                    this.semaphoreOneway.getQueueLength(),
                    this.semaphoreOneway.availablePermits()
            );
            log.warn(info);
            throw new RemotingTimeoutException(info);
        }
    }
}

```

2. 接收网络请求代码

入口在 NettyRemotingServer


/**
 * 客户端发送的请求。最终会到这个类的方法里。是netty 处理后的数据。
 */
@ChannelHandler.Sharable
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
        processMessageReceived(ctx, msg);
    }
}

重点代码

org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor#processRequest 主要业务处理都在这里类里,想看业务就进去翻一下


    /**
     * 处理请求
     * 根据retCode 调用不同的方法。
     *
     * @param ctx
     * @param request
     * @return
     * @throws RemotingCommandException
     */
    @Override
    public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {

        if (ctx != null) {
            log.debug("receive request, {} {} {}",
                    request.getCode(),
                    RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                    request);
        }


        switch (request.getCode()) {
            case RequestCode.PUT_KV_CONFIG:
                return this.putKVConfig(ctx, request);
            case RequestCode.GET_KV_CONFIG:
                return this.getKVConfig(ctx, request);
            case RequestCode.DELETE_KV_CONFIG:
                return this.deleteKVConfig(ctx, request);
            case RequestCode.QUERY_DATA_VERSION:
                return queryBrokerTopicConfig(ctx, request);
            case RequestCode.REGISTER_BROKER:
                // 路由注册

                Version brokerVersion = MQVersion.value2Version(request.getVersion());
                if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
                    return this.registerBrokerWithFilterServer(ctx, request);
                } else {
                    // 4.8 版本后
                    // 路由注册 入口
                    return this.registerBroker(ctx, request);
                }
            case RequestCode.UNREGISTER_BROKER:
                // 服务下线
                return this.unregisterBroker(ctx, request);
            case RequestCode.GET_ROUTEINFO_BY_TOPIC:
                // 查询路由信息
                return this.getRouteInfoByTopic(ctx, request);
            case RequestCode.GET_BROKER_CLUSTER_INFO:
                return this.getBrokerClusterInfo(ctx, request);
            case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
                return this.wipeWritePermOfBroker(ctx, request);
            case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:
                return getAllTopicListFromNameserver(ctx, request);
            case RequestCode.DELETE_TOPIC_IN_NAMESRV:
                return deleteTopicInNamesrv(ctx, request);
            case RequestCode.GET_KVLIST_BY_NAMESPACE:
                return this.getKVListByNamespace(ctx, request);
            case RequestCode.GET_TOPICS_BY_CLUSTER:
                return this.getTopicsByCluster(ctx, request);
            case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:
                return this.getSystemTopicListFromNs(ctx, request);
            case RequestCode.GET_UNIT_TOPIC_LIST:
                return this.getUnitTopicList(ctx, request);
            case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:
                return this.getHasUnitSubTopicList(ctx, request);
            case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:
                return this.getHasUnitSubUnUnitTopicList(ctx, request);
            case RequestCode.UPDATE_NAMESRV_CONFIG:
                return this.updateConfig(ctx, request);
            case RequestCode.GET_NAMESRV_CONFIG:
                return this.getConfig(ctx, request);
            default:
                break;
        }
        return null;
    }

3. 业务处理

业务处理相关的 org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager