rocketMQ學習筆記——nameServer
rocketMQ學習筆記——nameServer
nameServer
nameServer是一個服務中心, 用於broker的註冊, 然後consumer和producer通過連線namesrv獲取broker的資訊, namesrv是無狀態的節點, 這意味著它不會有主從之分
以下是官方文件對namesrv的概念說明
名稱服務充當路由訊息的提供者。生產者或消費者能夠通過名字服務查詢各主題相應的Broker IP列表。多個Namesrv例項組成叢集,但相互獨立,沒有資訊交換。
namesrv的啟動需要設定啟動引數
-c /home/rocketmq/conf/namesrv.properties 設定配置檔案
在配置檔案中可以設定rocketmqHome, 它的作用會在後面namesrv原始碼中有所體現
public static void main(String[] args) {
main0(args);
}
public static NamesrvController main0(String[] args) {
try {
// 建立namesrv的控制器
NamesrvController controller = createNamesrvController(args);
// 啟動控制器, 啟動namesrv服務
start(controller);
String tip = "The Name Server boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
log.info(tip);
System.out.printf("%s%n", tip);
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
main方法很簡單, 他僅僅建立了一個nameSrv的控制器, 然後啟動控制器而已
這裡只給出重要的部分
public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
// 構建命令列選項nameServer地址和help
Options options = ServerUtil.buildCommandlineOptions(new Options());
// 如果options中是help則列印help資訊然後退出
// 給options中新增c:{配置檔案}和p:{列印配置資訊}引數
commandLine = ServerUtil.parseCmdLine("mqnamesrv", args, buildCommandlineOptions(options), new PosixParser());
if (null == commandLine) {
System.exit(-1);
return null;
}
final NamesrvConfig namesrvConfig = new NamesrvConfig();
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
// 設定了namesrv的埠資訊
nettyServerConfig.setListenPort(9876);
if (commandLine.hasOption('c')) {
// 獲取配置檔案地址
String file = commandLine.getOptionValue('c');
if (file != null) {
// 建立輸入流讀取配置檔案
InputStream in = new BufferedInputStream(new FileInputStream(file));
properties = new Properties();
properties.load(in);
// 通過反射設定物件引數
MixAll.properties2Object(properties, namesrvConfig);
MixAll.properties2Object(properties, nettyServerConfig);
namesrvConfig.setConfigStorePath(file);
System.out.printf("load config properties file OK, %s%n", file);
in.close();
}
}
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);
// rocketmqHome的值是從args中獲取的, 啟動時需要配置
if (null == namesrvConfig.getRocketmqHome()) {
System.out.printf("Please set the %s variable in your environment to match the location of the RocketMQ installation%n", MixAll.ROCKETMQ_HOME_ENV);
System.exit(-2);
}
final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
return controller;
}
controller的建立就是
- 設定rocketmq的版本
- 設定namesrv埠
- 讀取預設的配置檔案
接下去就是controller的啟動
public static NamesrvController start(final NamesrvController controller) throws Exception {
if (null == controller) {
throw new IllegalArgumentException("NamesrvController is null");
}
// 做controller的初始化
boolean initResult = controller.initialize();
if (!initResult) {
controller.shutdown();
System.exit(-3);
}
// 新增結束的鉤子函式
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread(log, new Callable<Void>() {
@Override
public Void call() throws Exception {
controller.shutdown();
return null;
}
}));
//啟動
controller.start();
return controller;
}
在controller的初始化中做了以下幾件事情
- 將namesrv的配置檔案中設定的kvConfig讀取出來
- 開始初始化netty, 根據作業系統(linux是epoll)設定了bossLoop和selector
- 根據配置中設定的執行緒數量設定並初始化了netty使用到的執行緒池
- 註冊了處理器(處理器用於netty的業務邏輯處理), 由於rocketmq的所有模組使用的netty程式都是相同的一段, 所以這裡使用到了享元模式將netty中處理業務邏輯的部分抽離出來
- 啟動一個服務, 每十秒掃描一次停止活動的broker, 並且移除
- 啟動一個服務, 每十秒列印一次所有配置
以下是部分原始碼
public boolean initialize() {
this.kvConfigManager.load();
this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
this.remotingExecutor =
Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
// 註冊處理器, 這裡註冊了存放了註冊broker的處理器
this.registerProcessor();
// 掃描停止活動的broker 10秒執行一次
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.routeInfoManager.scanNotActiveBroker();
}
}, 5, 10, TimeUnit.SECONDS);
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
NamesrvController.this.kvConfigManager.printAllPeriodically();
}
}, 1, 10, TimeUnit.MINUTES);
return true;
}
在controller的啟動流程中, 僅僅是做了兩件事情
- 啟動netty服務
- 如果檔案監聽服務存在則啟動
namesrv提供的是服務, 所以只需要啟動netty的服務端
ServerBootstrap childHandler =
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
.channel(useEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_KEEPALIVE, false)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSndBufSize())
.childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketRcvBufSize())
.localAddress(new InetSocketAddress(this.nettyServerConfig.getListenPort()))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(defaultEventExecutorGroup, HANDSHAKE_HANDLER_NAME, handshakeHandler)
.addLast(defaultEventExecutorGroup,
encoder,
new NettyDecoder(),
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
connectionManageHandler,
serverHandler
);
}
});
可以看見這裡設定了serverHandler這個處理器來處理事件
而serverHandler中的channelRead0方法呼叫了抽象類NettyRemotingAbstract的processMessageReceived方法
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
final RemotingCommand cmd = msg;
if (cmd != null) {
switch (cmd.getType()) {
case REQUEST_COMMAND:
processRequestCommand(ctx, cmd);
break;
case RESPONSE_COMMAND:
processResponseCommand(ctx, cmd);
break;
default:
break;
}
}
}
由於該方法被client和server都會呼叫, 所以需要判斷訊息是請求還是響應型別, 顯然傳送給server的訊息都是請求型別
processRequestCommand該方法會判斷訊息中的code來判斷使用何種處理器, 這裡的處理器就是在初始化的時候註冊的
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
// 根據RequestCode獲取具體的處理器
final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
// 沒有獲取到處理器就用預先設定的預設處理器
final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
final int opaque = cmd.getOpaque();
if (pair != null) {
Runnable run = new Runnable() {
@Override
public void run() {
try {
doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
final RemotingResponseCallback callback = new RemotingResponseCallback() {
@Override
public void callback(RemotingCommand response) {
doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
if (!cmd.isOnewayRPC()) {
if (response != null) {
response.setOpaque(opaque);
response.markResponseType();
try {
ctx.writeAndFlush(response);
} catch (Throwable e) {
log.error("process request over, but response failed", e);
log.error(cmd.toString());
log.error(response.toString());
}
} else {
}
}
}
};
// 根據不同的處理器使用不同方法, 非同步請求則非同步處理
if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
processor.asyncProcessRequest(ctx, cmd, callback);
} else {
NettyRequestProcessor processor = pair.getObject1();
RemotingCommand response = processor.processRequest(ctx, cmd);
callback.callback(response);
}
} catch (Throwable e) {
log.error("process request exception", e);
log.error(cmd.toString());
if (!cmd.isOnewayRPC()) {
final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
RemotingHelper.exceptionSimpleDesc(e));
response.setOpaque(opaque);
ctx.writeAndFlush(response);
}
}
}
};
}
broker的註冊
預設請求處理器中的switch存在如下程式碼
case RequestCode.REGISTER_BROKER: // 註冊broker
Version brokerVersion = MQVersion.value2Version(request.getVersion());
if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
return this.registerBrokerWithFilterServer(ctx, request);
} else {
return this.registerBroker(ctx, request);
}
broker註冊的時候, code會被設定為REGISTER_BROKER
namesrv識別到這一code就會進入註冊方法, 在3.0.11版本後加入了請求過濾
RocketMQ的消費者可以根據Tag進行訊息過濾,也支援自定義屬性過濾。訊息過濾目前是在Broker端實現的,優點是減少了對於Consumer無用訊息的網路傳輸,缺點是增加了Broker的負擔、而且實現相對複雜。
訊息過濾的實現本文暫時不細究
在registerBrokerWithFilterServer方法中設定了響應頭, 呼叫了broker的註冊方法
響應頭資訊尤為重要, 該響應頭中帶有高可用服務地址和master的地址, 該資訊對於broker的高可用服務的通訊有影響
下面看看registerBroker方法是如何註冊的
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) {
RegisterBrokerResult result = new RegisterBrokerResult();
try {
try {
this.lock.writeLock().lockInterruptibly();
// 獲取該broker叢集下面的所有broker名稱
Set<String> brokerNames = this.clusterAddrTable.get(clusterName);
if (null == brokerNames) {
brokerNames = new HashSet<String>();
this.clusterAddrTable.put(clusterName, brokerNames);
}
// 新增當前註冊的broker
brokerNames.add(brokerName);
boolean registerFirst = false;
// 此前如果有註冊過就直接獲取, 沒註冊過就註冊
BrokerData brokerData = this.brokerAddrTable.get(brokerName);
if (null == brokerData) {
registerFirst = true;
brokerData = new BrokerData(clusterName, brokerName, new HashMap<Long, String>());
this.brokerAddrTable.put(brokerName, brokerData);
}
Map<Long, String> brokerAddrsMap = brokerData.getBrokerAddrs();
//Switch slave to master: first remove <1, IP:PORT> in namesrv, then add <0, IP:PORT>
//The same IP:PORT must only have one record in brokerAddrTable
// 主從broker的brokername是一樣的
// 這裡用於從服務升級為主服務
Iterator<Entry<Long, String>> it = brokerAddrsMap.entrySet().iterator();
while (it.hasNext()) {
Entry<Long, String> item = it.next();
// 如果原先該地址已經註冊過, 並且brokerId與此前不同則從map中移除該元素
if (null != brokerAddr && brokerAddr.equals(item.getValue()) && brokerId != item.getKey()) {
it.remove();
}
}
String oldAddr = brokerData.getBrokerAddrs().put(brokerId, brokerAddr);
registerFirst = registerFirst || (null == oldAddr);
if (null != topicConfigWrapper
&& MixAll.MASTER_ID == brokerId) {
if (this.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion())
|| registerFirst) {
ConcurrentMap<String, TopicConfig> tcTable =
topicConfigWrapper.getTopicConfigTable();
if (tcTable != null) {
for (Map.Entry<String, TopicConfig> entry : tcTable.entrySet()) {
// topicQueueTable中存入broker資訊
this.createAndUpdateQueueData(brokerName, entry.getValue());
}
}
}
}
// 設定broker的實時資訊
BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr,
new BrokerLiveInfo(
System.currentTimeMillis(),
topicConfigWrapper.getDataVersion(),
channel,
haServerAddr));
if (null == prevBrokerLiveInfo) {
log.info("new broker registered, {} HAServer: {}", brokerAddr, haServerAddr);
}
if (filterServerList != null) {
if (filterServerList.isEmpty()) {
this.filterServerTable.remove(brokerAddr);
} else {
this.filterServerTable.put(brokerAddr, filterServerList);
}
}
// 如果是slave註冊, 則會設定一個master地址
if (MixAll.MASTER_ID != brokerId) {
String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);
if (masterAddr != null) {
BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr);
if (brokerLiveInfo != null) {
result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());
result.setMasterAddr(masterAddr);
}
}
}
} finally {
this.lock.writeLock().unlock();
}
} catch (Exception e) {
log.error("registerBroker Exception", e);
}
return result;
}
註冊broker的時候在namesrv中用0, 1 來標識了broker的主從, 並且會給從服務設定主服務的地址
consumer根據topic獲取broker資訊
consumer獲取broker資訊的時候code會設定為GET_ROUTEINFO_BY_TOPIC
然後去namesrv獲取該topic下的broker資訊
public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
final GetRouteInfoRequestHeader requestHeader =
(GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
// 這裡根據topic從topicQueueTable獲取brokerName
// 而後根據brokerName從brokerAddrTable獲取broker資訊
TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
if (topicRouteData != null) {
if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
String orderTopicConf =
this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
requestHeader.getTopic());
topicRouteData.setOrderTopicConf(orderTopicConf);
}
byte[] content = topicRouteData.encode();
response.setBody(content);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}
response.setCode(ResponseCode.TOPIC_NOT_EXIST);
response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
+ FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
return response;
}
獲取broker資訊的過程很簡單, 僅僅就是根據topic去獲取之前註冊broker的時候存入的資訊就行了
後記
個人感覺namesrv是整個rocketmq中最簡單的模組, 畢竟之前是使用zookeeper做註冊中心的, namesrv主要的功能就是接受broker的註冊, 然後處理consumer和producer的獲取broker資訊請求
相關文章
- RocketMQ學習筆記 2MQ筆記
- 5-RocketMQ-NameServerMQServer
- 【RocketMQ】路由中心 NameServerMQ路由Server
- RocketMQ中NameServer的啟動MQServer
- 深入剖析RocketMQ原始碼-NameServerMQ原始碼Server
- 一張圖進階 RocketMQ - NameServerMQServer
- RocketMQ系列:使用systemd管理nameserver和brokerMQServer
- 聊一聊RocketMQ的註冊中心NameServerMQServer
- RocketMQ(六):nameserver與佇列儲存定位解析MQServer佇列
- numpy的學習筆記\pandas學習筆記筆記
- 學習筆記筆記
- 【學習筆記】數學筆記
- 《JAVA學習指南》學習筆記Java筆記
- 機器學習學習筆記機器學習筆記
- 學習筆記-粉筆980筆記
- 學習筆記(3.29)筆記
- 學習筆記(4.1)筆記
- 學習筆記(3.25)筆記
- 學習筆記(3.26)筆記
- JavaWeb 學習筆記JavaWeb筆記
- golang 學習筆記Golang筆記
- Nginx 學習筆記Nginx筆記
- spring學習筆記Spring筆記
- gPRC學習筆記筆記
- GDB學習筆記筆記
- 學習筆記(4.2)筆記
- 學習筆記(4.3)筆記
- 學習筆記(4.4)筆記
- Servlet學習筆記Servlet筆記
- 學習筆記(3.27)筆記
- jest 學習筆記筆記
- NodeJS學習筆記NodeJS筆記
- WebSocket 學習筆記Web筆記
- mount 學習筆記筆記
- mapGetters學習筆記筆記
- jQuery學習筆記jQuery筆記
- 學習筆記:DDPG筆記
- flex學習筆記Flex筆記