介绍
nameserver是一个类似zk的服务,用来存储元数据,因为rocketMq不需要强一致性,所以自己实现了nameserver。
一. NameServer服务器角色
元数据
列一下主要的元数据和数据结构。
-
主题队列分布
map{ "xxx_TOPIC":{ // 某某topic "brokerName":"xxx", // 节点名称 "readQueueNums":0, // 读队列数 "writeQueueNums":0, // 写队列数 "perm":0, // 权限 } } -
broker地址
map{ "brokerName":{ // 节点名称 "cluster":"xxx", // 集群名称 "brokerName":"xxx", // 节点名称 "brokerAddrs":{ // 地址。 key为broker+Id. 值为0的机器节点为master,其他是slave "borker0":"192.168", "borker1":"192.168", } } } -
集群信息
map// 集群名称 对应 borkerName集合(Set) {"clusterName":["brokerName"]} -
broker存活状态
map{ "192.168.xxx:xxx":{ // ip地址 "lastUpdateTimestamp":0L, // 上次心跳时间 "channel":"Channel", // 通信通道 "haServerAddr":"xxx" // 备份节点地址 } }
二. NameServer启动源码分析
- 入口
NameServerStartup.main0() - 创建nameSrv控制器
createNameServerController(args)- 初始化NameSrv配置,
new NamesrvConfig() - 初始化NettySrv配置,
new NettyServerConfig() - 创建NameServer控制器
new NamesrvController(namesrvConfig, nettyServerConfig)
- 初始化NameSrv配置,
- 启动逻辑
start(controller);- 初始化NameServer控制启器
controller.initialize();- 加载本地kv配置
this.kvConfigManage.load() - 创建网络服务对象【重要】
new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService); - 创建业务线程池,默认线程数8
remotingExecutor - 注册协议处理器
registerProcessor() - 定时任务1, 每10秒检查一下broker存活状态,将idle状态的broker移除。 6 定时任务2,每10分钟,打印一遍kv配置
- 加载本地kv配置
- 注册jvm hook ,处理平滑关机逻辑
- 启动NameServer控制器
controller.start();- 网络层服务启动
this.remotingServer.start(); // remotingServer 后面单独列
- 网络层服务启动
- 初始化NameServer控制启器
NameSrvConfig存储的是业务相关的数据
NettyServerConfig存储的是网络相关的数据
控制器
三. 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;
重点方法
-
构造方法
上面可以看到,在NameServerController创建时被调用,进行初始化- 调用父类,初始化异步和单次请求的信号量。semaphoreAsync:64,semaphoreOneway:256
- 初始化netty的一些配置和类。
- 创建公共线程池
publicExecutor并且将线程数由0改为4 - 创建netty的线程组
eventLoopGroupBoss boss组,eventLoopGroupSelector worker组
-
启动方法
上面可以看到,在NameServerController启动时被调用,进行启动操作- 创建事件处理器,这块涉及到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()); } }); - 创建共享的 处理器handler
prepareSharableHandlers(); - 初始化
ServerBootstrap - 使用
PooledByteBufAllocator.DEFAULT初始化内存池,如果在配置文件中开启内存池的话。 - 启动,绑定端口。
this.serverBootstrap.bind().sync(); - 开启 网络异常事件处理器,
- 设定定时任务, 扫描 responseTable表,将过期的responseFuture 移除。每秒执行一次。
- 创建事件处理器,这块涉及到netty,我没有看明白。
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